首页
社区
课程
招聘
[原创]注入学习总结
2020-4-11 12:02 5580

[原创]注入学习总结

2020-4-11 12:02
5580

Ring3-注入-学习总结

常见的注入方式:

0x1.DLL注入


DLL注入:

       通常的方法是调用CreateRemoteThread等函数在目标进程中执行LoadLibrary函数,使目标进程加载要的注入DLL。DLL注入也可以用下下面所示APC注入,注册表AppInit_DLLs注入 和 SetWindowsHookEx注入等方式。这里不做赘诉。


DLL卸载:
      在当前进程 卸载 DLL的方法一般是 FreeLibrary(modBaseAddr), 所以最简单的方法就是在目标进程执行 FreeLibrary(modBaseAddr)实现DLL卸载。[可以看出来,这种方法为简单的代码注入的方法]
具体过程:
      a.找到 modBaseAddr ,通过 枚举 目标进程模块获得我们要卸载的DLL信息
      b.获得 FreeLibrary函数指针
      c.调用CreateRemoteThread(),在目标进程执行 FreeLibrary(modBaseAddr)

/*
参数:
DWORD dwPID:注入进程的进程ID
LPCTSTR szDllName:注入到进程的DLL
*/
BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)  //《逆向工程核心原理》截取部分源码
{
	BOOL bMore = FALSE, bFound = FALSE;
	HANDLE hSnapshot, hProcess, hThread;
	HMODULE hModule = NULL;
	MODULEENTRY32 me = { sizeof(me) };
	LPTHREAD_START_ROUTINE pThreadProc;
	//dwPID=notepad进程ID
	//使用TH32CS_SNAPMODULE参数,获取加载到notepad进程的DLL名称
	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID);

	//此函数检索与进程相关联的第一个模块的信息
	bMore = Module32First(hSnapshot, &me);

	for (; bMore; bMore = Module32Next(hSnapshot, &me))
	{
		if (!_tcsicmp((LPCTSTR)me.szModule, szDllName) || !_tcsicmp((LPCTSTR)me.szExePath, szDllName))
		{
			bFound = TRUE;
			break;
		}
	}
	if (!bFound)
	{
		CloseHandle(hSnapshot);
		return FALSE;
	}

	if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
	{
		_tprintf(L"OpenProcess(%d) failed!!![%d]\n", dwPID, GetLastError);
		return FALSE;
	}

	hModule = GetModuleHandle(L"kernel32.dll");
	pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hModule, "FreeLibrary");
	//modBaseAddr模块基址
	hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
	WaitForSingleObject(hThread, INFINITE);
	CloseHandle(hThread);
	CloseHandle(hProcess);
	CloseHandle(hSnapshot);
	return TRUE;
}

0x2.代码注入
      结合上面DLL卸载的过程,如果使用远程线程的方式,设想要是我们不得不设置很多参数,而且要执行我们自定义的函数。我们可以将参数集合成结构体,将结构体和我们要执行的自定义函数事先保存在目标进程的内存中。在通过 调用CreateRemoteThread 相关参数分别设置为自定义函数写入地址,结构体数据 写入地址指针。


/*
参数:
DWORD dwProcessID:要注入进程的进程ID
*/
BOOL InjectCode(DWORD dwProcessID)  //《逆向工程核心原理》截取部分源码
{
	HMODULE         hMod = NULL;
	THREAD_PARAM    param = { 0, };
	HANDLE          hProcess = NULL;
	HANDLE          hThread = NULL;
	LPVOID          pRemoteBuf[2] = { 0, };
	DWORD           dwSize = 0;
	hMod = GetModuleHandleA("kernel32.dll");
	// 设置结构体数据
	param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
	param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
	strcpy_s(param.szBuf[0], "user32.dll");
	strcpy_s(param.szBuf[1], "MessageBoxA");
	strcpy_s(param.szBuf[2], "www.reversecore.com");
	strcpy_s(param.szBuf[3], "ReverseCore");

	// 打开进程
	if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS,   
		FALSE,                
		dwProcessID)))           
	{
		printf("OpenProcess() fail : err_code = %d\n", GetLastError());
		return FALSE;
	}
	//申请第一块内存
	dwSize = sizeof(THREAD_PARAM);
	if (!(pRemoteBuf[0] = VirtualAllocEx(hProcess,       
		NULL,               
		dwSize,               
		MEM_COMMIT,           
		PAGE_READWRITE)))    
	{
		printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
		return FALSE;
	}
	//向第一块内存在写入结构体数据 pRemoteBuf[0] 为结构体指针
	if (!WriteProcessMemory(hProcess,                     
		pRemoteBuf[0],                  
		(LPVOID)&param,                
		dwSize,                         
		NULL))                      
	{
		printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
		return FALSE;
	}

	//申请第二块内存
	dwSize = (DWORD)InjectCode - (DWORD)ThreadProc; //计算注入ThreadProc的大小
	if (!(pRemoteBuf[1] = VirtualAllocEx(hProcess,      
		NULL,                
		dwSize,              
		MEM_COMMIT,         
		PAGE_EXECUTE_READWRITE)))  
	{
		printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
		return FALSE;
	}
          //向第二块内存中写入要执行回调函数
	if (!WriteProcessMemory(hProcess,                     
		pRemoteBuf[1],                
		(LPVOID)ThreadProc,            
		dwSize,                        
		NULL))                       
	{
		printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
		return FALSE;
	}
	if (!(hThread = CreateRemoteThread(hProcess,           
		NULL,               
		0,                  
		(LPTHREAD_START_ROUTINE)pRemoteBuf[1],    
		pRemoteBuf[0],      
		0,                   
		NULL)))             
	{
		printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError());
		return FALSE;
	}
	WaitForSingleObject(hThread, INFINITE);
	CloseHandle(hThread);
	CloseHandle(hProcess);
	return TRUE;
}

DWORD WINAPI ThreadProc(LPVOID lParam)
{
	PTHREAD_PARAM   pParam = (PTHREAD_PARAM)lParam;
	HMODULE         hMod = NULL;
	FARPROC         pFunc = NULL;
	// LoadLibrary()
	hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]);    // "user32.dll"
	if (!hMod)
		return 1;
	// GetProcAddress()
	pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]);  // "MessageBoxA"
	if (!pFunc)
		return 1;
	// MessageBoxA()(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);
	((PFMESSAGEBOXA)pFunc)
	return 0;
}

常见的注入方法:

0x1.APC注入

APC注入,我先了解一下APC队列。线程 APC 队列:

       每个线程可以通过调用 QueueUserAPC 函数,明确的创建一个"异步调用队列", 其实就是为线程在线程函数调用栈之外再安排一组函数去执行。 默认情况下,创建线程时不会创建这个队列,当调用该函数时,就会为这个线程创建这个队列。创建 APC 队列的函数,一般使用 Wait 函数族或者 SleepEx 函数等带有 bAlertable 参数的函数进入一种假"暂停"的状态, 进入 Alertable 状态的线程,系统调度器会在线程函数本身处于"暂停"(等待状态)时,一次调用线程 APC 队列中的函数。关于 Alertable 状态,可以去了解 SleepEx和Sleep函数的区别。

       注意 APC 队列中的函数不要执行事件过长,以免影响线程函数本身的执行。需要注意的是,有些函数虽然也会使线程进入等待状态,但不能进入可警告状态,也即不能调用异步的函数,比如:GetMessage 函数等(这些函数也没有 bAlterable 参数)。 最后也需要注意的是,不要在 APC 函数中再调用让线程进入Alterable 状态的 API,这会引起一个递归,而导致线程栈溢出。

原理:  
       在一个进程中,当一个执行到SleepEx()或者WaitForSingleObjectEx()时,系统就会产生一个软中断,当线程再次被唤醒时,此线程会首先执行APC队列中的被注册的函数,利用QueueUserAPC()这个API,并以此去执行我们的DLL加载代码,进而完成DLL注入的目的。

/*
参数:
HANDLE ProcessHandle: 要注入进程的句柄
THREAD_LIST *pThreadIdList: 要注入进程线程ID列表
*/
DWORD APC_Inject(HANDLE ProcessHandle, THREAD_LIST *pThreadIdList)
{
	THREAD_LIST *pCurrentThreadId = pThreadIdList;
	CHAR path[MAX_PATH] = { 0 };
	//获得当前目录路径
        GetCurrentDirectoryA(MAX_PATH, path);
	//获得Dll的全路径
        strcat(path, ("\\Dll.dll"));
	DWORD ModuleNameLength = strlen(path) + 1;
        //申请内存
	PVOID param = VirtualAllocEx(ProcessHandle,
		NULL, ModuleNameLength, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
	UINT_PTR LoadLibraryAAddress = (UINT_PTR)GetProcAddress(GetModuleHandle("Kernel32.dll"), "LoadLibraryA");

	if (param != NULL)
	{
		SIZE_T ReturnLength;
                //向在目标进程申请的内存中写入DLL地址
		if (WriteProcessMemory(ProcessHandle, param, (LPVOID)path, ModuleNameLength, &ReturnLength))
		{
			while (pCurrentThreadId)
			{
                                //获得线程句柄,同时看看线程存不存在
				HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, pCurrentThreadId->dwThreadId);
				if (hThread != NULL)
				{
                                       // QueueUserAPC解析看上方原理
					int a= QueueUserAPC((PAPCFUNC)LoadLibraryAAddress, hThread, (ULONG_PTR)param);
					printf(" %d\r\n",a);
				}
				pCurrentThreadId = pCurrentThreadId->pNext;
			}
		}
		SleepEx(10000, TRUE);
	}
	return 0;
}

0x2.注册表AppInit_DLLs

原理:
        User32.dll被加载到进程时, 会读取AppInit_DLLs注册表项, 若有值, 则调用LoadAppInit()API加载用户DLL. 所以严格来说, 相应DLL不会被加载到所有进程, 只是加载至加载user32.dll的进程 。
        在注册表编辑器中, 将要注入的DLL的路径字符串写入AppInit_DLLs项目, 然后把LoadAppInit_DLLs的项目值设置为1. 重启后, 指定DLL会注入所有运行进程。
void RegistryInject()
{
	NTSTATUS ntStatus;
	wchar_t wDllPath[MAX_PATH] = { 0 };
	HKEY  hKey = NULL;
	GetCurrentDirectory(MAX_PATH, wDllPath);
#ifdef _WIN64
	wcscat(wDllPath, L"\\X64.dll");
	WCHAR*	KeyFullPath = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows";
#else
	wcscat(wDllPath, L"\\X86.dll");
	WCHAR*	KeyFullPath = L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows NT\\CurrentVersion\\Windows";
#endif

	ntStatus = RegOpenKeyExW(HKEY_LOCAL_MACHINE, KeyFullPath,0,KEY_ALL_ACCESS,&hKey);
	if (ntStatus != ERROR_SUCCESS)
		return;

	WCHAR*	wcAppInit_DLLs = L"AppInit_DLLs";
	DWORD	dAppInit_DLLsType = 0;
	UINT8	u8AppInit_DLLsData[MAX_PATH] = { 0 };
	DWORD	dAppInit_DLLsLength = 0;

	//保留原先的数据
	ntStatus = RegQueryValueExW(hKey, wcAppInit_DLLs,NULL,&dAppInit_DLLsType, u8AppInit_DLLsData,&dAppInit_DLLsLength);
	if (ntStatus != ERROR_SUCCESS)
	{
		if (ntStatus !=ERROR_MORE_DATA)
			goto Exit;
	}
	// 设置Dll的完整路径键值
	ntStatus = RegSetValueExW(hKey, wcAppInit_DLLs,NULL, dAppInit_DLLsType,(CONST BYTE*)wDllPath,(lstrlen(wDllPath) + 1) * sizeof(WCHAR));
	if (ntStatus != ERROR_SUCCESS)
		goto Exit;
	
	WCHAR*	wcLoadAppInit_DLLs = L"LoadAppInit_DLLs";
	DWORD	dLoadAppInit_DLLsType = 0;
	UINT8	u8LoadAppInit_DLLsData[MAX_PATH] = { 0 };
	DWORD	dLoadAppInit_DLLsLength = 0;
	//保留原先的数据
	ntStatus = RegQueryValueExW(hKey, wcLoadAppInit_DLLs,NULL,&dLoadAppInit_DLLsType, u8LoadAppInit_DLLsData,&dLoadAppInit_DLLsLength);
	if (ntStatus != ERROR_SUCCESS)
	{
		if (ntStatus != ERROR_MORE_DATA)
			goto Exit;	
	}
	DWORD  v2 = 1;
	ntStatus = RegSetValueExW(hKey, wcLoadAppInit_DLLs,NULL,dLoadAppInit_DLLsType,(CONST BYTE*)&v2,sizeof(DWORD));
	if (ntStatus != ERROR_SUCCESS)
		goto Exit;
	
	printf("Input AnyKey To Resume\r\n");
	getchar();
Exit:
	if (dAppInit_DLLsLength!=0)
		ntStatus = RegSetValueExW(hKey, wcLoadAppInit_DLLs,NULL,dAppInit_DLLsType,
               (CONST BYTE*)u8AppInit_DLLsData,(lstrlenA((char*)u8AppInit_DLLsData) + 1) * sizeof(WCHAR));
	
	if (dLoadAppInit_DLLsLength!=0)
		ntStatus = RegSetValueExW(hKey, wcLoadAppInit_DLLs,NULL,dLoadAppInit_DLLsType,
                (CONST BYTE*)u8LoadAppInit_DLLsData,sizeof(DWORD));
	
	if (hKey !=NULL)
	{
		RegCloseKey(hKey);
		hKey = NULL;
	}
}


0x3.SetWindowsHookEx

        注入A.dll文件是一个含有钩子过程(XXXXXXXProc)的DLL文件. B.exe注入程序是最先加载注入dll并安装键盘钩子的程序. B.exe加载A.dll文件后使用SetWindowsHookEx()安装键盘钩子( XXXXXXXProc ). 若其他进程中发生键盘输入事件, OS会将强制将A.dll加载到相应进程的内存, 然后调用 XXXXXXXProc ()函数。

注入:

/*
参数:
HANDLE hThreadID: 指定要与钩子子程关联的线程的标识符。如果该参数为零,则钩子子程与与调用线程在同一桌面中运行的所有现有线程相关联。
OUT HHOOK* hHook: 如果函数成功,则返回钩子子程的句柄。可以用于钩子卸载
WCHAR* wDllPath:注入Dll的完整路径。
*/
BOOL SetWindowsHookEx_Inject(HANDLE hThreadID, OUT HHOOK* hHook,WCHAR* wDllPath)
{
	HMODULE	hModuleBase = LoadLibrary(wDllPath);
	FARPROC dllFunctionAddr = GetProcAddress(hModuleBase, "dllFunction");
	*hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)dllFunctionAddr, hModuleBase,(DWORD)hThreadID);
	if (*hHook == NULL)
		return FALSE;
	return TRUE;
}
卸载:
UnhookWindowsHookEx(hHook);

0x4.创建线程函数:CreateRemoteThread [NtCreateThreadEx、RtlCreateUserThread]

在MSDN中查看CreateRemoteThread等函数参数说明,了解用法,这里就不赘述了。

上面创建线程函数windows XP下查看远程线程函数调用关系:

CreateThread-->RtlCreateUserThread-->ZwCreateThread--> NtCreateThread-->PspCreateThread 
CreateRemoteThread -->NtCreateThread 


0x5.反射式DLL注入:

这个方法是我这几天逆向螃蟹时学的,可能是 “我的爱是你” 坛友 所述的方法,下面简单说明一下其原理:


1️⃣ 首先注入程序要将DLL文件拷贝到在被注入程序的申请的内存中(一定是整个文件)

//在目标进程分配内存(RWX)把整个DLL写进去
lpRemoteLibraryBuffer = VirtualAllocEx(hProcess, NULL, 
	dwLength, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!lpRemoteLibraryBuffer)
{
	break;
}
if (!WriteProcessMemory(hProcess, lpRemoteLibraryBuffer,
	lpBuffer, dwLength, NULL))
{
	break;
}


2️⃣ 注入程序要找到 DLL文件中加载器 申(ReflectiveLoader[导出函数]) 执行代码的文件偏移,文件偏移加上我们请的内存地址获得在目标进程下加载器的代码位置,这个时候我们就可CreateRemoteThread或其他方法,在目标进程执行 DLL 加载器( ReflectiveLoader )就行了。


//线程函数的地址=基地址+文件偏移
LPTHREAD_START_ROUTINE lpReflectiveLoader = (LPTHREAD_START_ROUTINE)((ULONG_PTR)lpRemoteLibraryBuffer + dwReflectiveLoaderOffset);
//拖后腿的CreateRemoteThread
hThread = CreateRemoteThread(hProcess, NULL, 1024 * 1024, lpReflectiveLoader, lpParameter, (DWORD)NULL, &dwThreadId);

3️⃣ ReflectiveLoader 的工作步骤:

      STEP 0: 计算当前映像基址。就是获得DLL的文件加载地址,可以用lpParameter传参进来。不传的情况下,我们需要用一些巧妙的方法来获得到 DLL的文件加载地址。就是调用_ReturnAddress()函数,这个函数不用我们编写, _ReturnAddress函数就是几句汇编指令。

     
ebp+4:存着这个函数执行完后的返回地址,就是在程序中调用这个函数我们就会得到 Call _ReturnAddress指令的下一条指令地址,然后我们可以从这一条指令地址处向上遍历,找到PE头的标志,即文件PE头部位置。


      STEP 1:首先获得我们Loader需要的一些函数地址有:VirtualAlloc(用来为镜像要加载的地址分配空间)、LoadLibraryA(处理导入表)、GetProcAddress(同上)、NtFlushInstructionCache(刷新数据,让CPU执行新指令)。获取PEB的方法:FS:[0x30]和GS:[0x60],前者为32位系统,后者为64位系统。PEB ---->_PEB_LDR_DATA数据结构,存储着当前进程所加载的模块信息,我们需要遍历已经加载的模块,从中找到我们需要的模块,获得以上几个函数的地址。


获得PEB代码
#ifdef _WIN64
	uiBaseAddress = __readgsqword(0x60);
#else 
	uiBaseAddress = __readfsdword(0x30);
#endif


       STEP 2: 将我们的映像加载到内存中的新位置,拷贝头部数据和所有区段到内存中的新位置,要经过对齐:



	 // STEP 2:	将我们的映像加载到内存中的新位置.. 拷贝所有区段

	 //NT头的虚拟地址,原DLL文件中
	uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;

	// 申请一块内存,我们将DLL加载到内存中
	// 重新定位。
	// 同时将所有内存清零并将其标记为READ,WRITE和EXECUTE,以避免出现任何问题。
	uiBaseAddress = (ULONG_PTR)pVirtualAlloc(NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

	// 我们需要拷贝所有节表和所有头部
	// 拷贝大小
	uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;
	//起始拷贝地址,即缓冲区的起始地址
	uiValueB = uiLibraryAddress;
	//dll将被加载的地址的起始地址
	uiValueC = uiBaseAddress;
	
	//复制头和节表的数据到新开辟的缓冲区
	while (uiValueA--)
	{
		*(BYTE *)uiValueC++ = *(BYTE *)uiValueB++;
	}
	
	
	//节表的第一项
	uiValueA = ((ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader);

	// 遍历所有部分,并将它们加载到内存中。 要对齐!!
	//pe中节的数量
	uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;
	while (uiValueE--)
	{
		//区段的虚拟地址
		uiValueB = (uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress);
		//区段的文件偏移
		uiValueC = (uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData);
		//经过对齐的物理大小
		uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData;
		//拷贝数据
		while (uiValueD--)
		{
			*(BYTE *)uiValueB++ = *(BYTE *)uiValueC++;
		}
		//到下一个区段表 继续拷贝
		uiValueA += sizeof(IMAGE_SECTION_HEADER);
	}

      STEP 3: 处理导入表,手动加载DLL要 修复导入表IAT。

      

// STEP 4: 修复导入表...
	// 获得导入表数据目录
	uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
	// uiValueC是导入表中的第一项
	uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress);	//基地址+RVA即导入表描述符的地址VA
	//遍历所有导入 外层循环[模块] 内存循环[函数]
	//链接库名字
	while (((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name)
	{
		//使用LoadLibraryA将需要的模块加载到内存
		uiLibraryAddress = (ULONG_PTR)pLoadLibraryA((LPCSTR)(uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name));
		//指向INT的IMAGE_THUNK_DATA的VA  
		uiValueD = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk);
		// uiValueA = IAT 的虚拟地址
		//要导入IAT的IMAGE_THUNK_DATA结构体
		uiValueA = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk);
		//遍历所有导入的函数,如果不存在名称,则按顺序导入
		while (DEREF(uiValueA))
		{
			// uiValueD为不为空,是不是遍历完了,因为某些编译器仅由FirstThunk导入
			if (uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG) //是不是序号导入?
			{
				// 获取模块NT标头的VA
				uiNtheader = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
				// 导出表数据目录
				uiExportDir = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiNtheader)->
					OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
				// 获取导出表的VA
				uiExportDirVA = (uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiExportDir)->VirtualAddress);
				// 获取导出地址数组的VA
				uiAddressArray = (uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDirVA)->AddressOfFunctions);
				// use the import ordinal (- export ordinal base) as an index into the array of addresses
				//使用导入序数 (-导出序数基数) 作为地址数组的索引
				//函数地址(RVA)
				uiAddressArray += ((IMAGE_ORDINAL(((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal)
					- ((PIMAGE_EXPORT_DIRECTORY)uiExportDirVA)->Base) * sizeof(DWORD));
				//函数地址VA
				// 修补此导入功能的地址
				DEREF(uiValueA) = (uiLibraryAddress + DEREF_32(uiAddressArray));
			}
			else 
			{
				//通过名称struct获取此函数的VA
				uiValueB = (uiBaseAddress + DEREF(uiValueA));
				// 使用GetProcAddress并在此导入函数的地址中打补丁
				DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress((HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name);
			}
			// 获取下一个导入的功能函数
			uiValueA += sizeof(ULONG_PTR);
			if (uiValueD) //遍历完没
				uiValueD += sizeof(ULONG_PTR);//没完
		}
		//获取下一个导入模块
		uiValueC += sizeof(IMAGE_IMPORT_DESCRIPTOR);
	}

           STEP 4: 处理我们所有的Images的重定位表。因为实际装载和建议装载的偏移不一样,原重定位表中的值是以程序建议的装载地址为基址,我们要修复一下。

   

//实际装载和建议装载的偏移,原重定位表中的值是以程序建议的装载地址为基址
	ULONG_PTR Offset = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
	//offset(偏移offset) =  uiBaseAddress(实际)-ImageBase(程序建议的装载地址) 

	// uiValueB = relocation 数据目录地址																						
	uiValueB = (ULONG_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];

	// 检查它们是否存在任何重定位
	if (((PIMAGE_DATA_DIRECTORY)uiValueB)->Size)//重定位表大小不为0
	{
		
		//重定位表的地址 uiValueC = 重定位表的地址(第一个IMAGE_BASE_RELOCATION表)
		uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress);

		//然后我们遍历所有块...[]
		while (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock) //不为空,继续循环
		{
			// (重定位内存页的起始RVA)
			uiValueA = (uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress);//
			//重定位块中的项数(整个块的大小减去结构体的大小 - 得到重定位项的总大小,除以每个重定位项的大小)
			// uiValueB = 此重定位块中的条目数
			uiValueB = (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC);
			
			//重定位块的第一项(过信息PIMAGE_BASE_RELOCATION)
			uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION);
			//遍历重定位项
			while (uiValueB--)
			{
				//执行重定位,并根据需要跳过IMAGE_REL_BASED_ABSOLUTE。
				//我们不使用switch语句来避免编译器建立跳转表

				/*
				值      常量符号                          含义               
				0       IMAGE_REL_BASED_ABSOLUTE          无意义,仅做对齐作用
				1       IMAGE_REL_BASED_HIGH              双字节中,仅高十六位被修正
				2       IMAGE_REL_BASED_LOW               双字节中,仅低十六位被修正
				3       IMAGE_REL_BASED_HIGHLOW           双字32位都需要修正
				4       IMAGE_REL_BASED_HIGHADJ           进行基地址重定位时将差值的高十六位加到指定偏移处的
				5       IMAGE_REL_BASED_MIPS_JMPADDR      对MIPS平台的跳转指令进行基地址重定位
				6                                        保留,必须为0   
				7                                        保留,必须为0
				9       IMAGE_REL_BASED_MIPS_JMPADDR16    对MIPS16平台的跳转指令进行基地址重定位
				10      IMAGE_REL_BASED_DIR64              进行基地址重定位时将差值加到指定偏移处的一个64位域上
				*/

				//重定位项的高四位代表此重定位项的类型
				if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64)
					*(ULONG_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += Offset;
				else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW)
					*(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)Offset;

				else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH)
					*(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(Offset);
				else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW)
					*(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(Offset);
				//下一个重定位项
				uiValueD += sizeof(IMAGE_RELOC);
			}
			//下一个重定位块
			uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;
		}
	}

           STEP 5: 调用入口点

// uiValueA = 是我们新加载的DLL / EXE入口点的 VA
	uiValueA = (uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint);
	// 我们必须刷新指令高速缓存,以避免使用过时的代码,该代码已由我们的重定位处理更新。
	pNtFlushInstructionCache((HANDLE)-1, NULL, 0);
	((DLLMAIN)uiValueA)((HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL);
反射式DLL注入就完成了!

0x6. SetThreadContext 注入: 
           这个是“hekes"坛友所提到的方法,本来想在Hook集合中总结为EIP Hook,还是先简单说一下:
主要过程:

//伪代码
CONTEXT Context;                                  //定义一个CONTEXT结构      
SuspendThread(hThread);                                  //挂起线程  
ThreadContext.ContextFlags = CONTEXT_ALL;  //修改对Context操作的权限
GetThreadContext(hThread, &Context);                  //获得Context信息
BufferData = VirtualAllocEx()                               //在目标线程申请内存 来执行我们的shellcode
//编写shellcode 
WriteProcessMemory(ProcessHandle, BufferData, ShellCode, sizeof(ShellCode), NULL) // 写入shellcode
ThreadContext.Eip = (UINT32)BufferData                //修改EIP指向
Context.ContextFlags = CONTEXT_CONTROL;
SetThreadContext(hThread, &Context);              //重新设置线程上下文
ResumeThread(hThread);                                        //恢复线程,现在线程开始从BufferData这个地方开始执行指令

SetThreadContext 注入的重点就是shellcode实现Loadlibrary:

UINT8 __ShellCode[0x100] = {

0x60, // [+0] pusha   //其入栈顺序是:EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI

0x9c, // [+1] pushf

0x68, // [+2] push

0x00,0x00,0x00,0x00,         // [+3] DLL路径

0xff,0x15, // [+7] call

0x00,0x00,0x00,0x00,         // [+9] LoadLibrary 

0x9d, // [+13] popf

0x61, // [+14] popa

0xff,0x25, // [+15] jmp

0x00,0x00,0x00,0x00,         // [+17] eip 

// eip 地址

0x00,0x00,0x00,0x00,         // [+21] 保eip地址

// LoadLibrary 地址

0x00,0x00,0x00,0x00,         // [+25]    填写得到 LoadLibrary函数指针 

// DllFullPath 

0x00,0x00,0x00,0x00 // [+29]    填写要加载DLL的路径指针   


        填空: RemoteBufferData为shellcode起始地址

        PUINT8 v1 = __ShellCode + 29;

//将Dll完整路径存入目标进程空间中   

memcpy(v1, __DllFullPath, (wcslen(__DllFullPath) + 1) * sizeof(WCHAR)); 

//Push Address 

*(PULONG)(__ShellCode + 3) = (ULONG)RemoteBufferData + 29;

//当前exe模块中的导入函数

*(PULONG)(__ShellCode + 25) = (ULONG)__LoadLibraryW;   

        *(PULONG_PTR)(__ShellCode + 9) = (ULONG_PTR)RemoteBufferData + 25;

         .......

         *(PULONG_PTR)(__ShellCode + 21) = ThreadContext.Eip;

*(PULONG_PTR)(__ShellCode + 17) = (ULONG_PTR)RemoteBufferData + 21;


总结的不全,大佬们还有啥方法,都可以砸给我,感激不尽!  我后面一起加上去  [可爱]

参考:

《逆向工程核心原理》

《加密与解密》

《恶意代码分析实战》


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

最后于 2020-4-19 20:07 被Dascolee编辑 ,原因:
收藏
点赞5
打赏
分享
最新回复 (10)
雪    币: 4612
活跃值: (2029)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hekes 2020-4-11 12:14
2
1
还有SetThreadContext
雪    币: 4612
活跃值: (2029)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
hekes 2020-4-11 13:22
3
0
还有一种方法:
获取目标进程kernel32.dll模块的LoadLibrary地址,然后采用远程线程的方式调用目标进程的LoadLibrary函数,这种方式可以显示32注入64,不然也不会这么蛋疼的方法
雪    币: 9934
活跃值: (2554)
能力值: ( LV6,RANK:87 )
在线值:
发帖
回帖
粉丝
Lixinist 1 2020-4-11 14:54
4
0
期待完善
雪    币: 9934
活跃值: (2554)
能力值: ( LV6,RANK:87 )
在线值:
发帖
回帖
粉丝
Lixinist 1 2020-4-11 15:04
5
1
用AppInit_DLLs进行注入,某些情况下不用重启也能生效(暂时不知道什么情况,之前测试的时候时灵时不灵)
雪    币: 46
活跃值: (12)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
我的爱是你 2020-4-12 19:36
6
1
貌似好像还有一种方法,申请一片内存把dll整个写入目标进程内存中?
雪    币: 1484
活跃值: (1135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
冰雄 2020-4-12 21:22
7
0
楼主漏了一张无痕注入。NtMapViewOfSection
雪    币: 1484
活跃值: (1135)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
冰雄 2020-4-12 21:22
8
1
楼主漏了一种无痕注入。NtMapViewOfSection
雪    币: 33
活跃值: (318)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
Dascolee 2020-4-13 14:27
9
0
多谢大佬们指点,过几天逐一加上去
最后于 2020-4-13 14:29 被Dascolee编辑 ,原因:
雪    币: 83
活跃值: (1037)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
killpy 2 2020-4-13 22:04
10
0
注入方式太多了
雪    币: 545
活跃值: (237)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
ggsuper 2020-5-2 00:00
11
0
mark
游客
登录 | 注册 方可回帖
返回