Windows系统大量使用dll做为组件复用,应用程序也会经过dll实现功能模块的拆分。DLL注入技术是向一个正在运行的进程插入自有DLL的过程。安全
常见的Windows代码注入方法以下:函数
注册表注入
编译注册表中的AppInit_DLLs选项,凡是使用GUI的进程,都会读取AppInit_DLLs内容,加载这些Dll。工具
Windows Hook注入
使用 SetWindowsHookEx、UnHkkkWindowsHookEx 来进行,为目标进程安装钩子,在注入dll中监听目标进程消息。编码
远程线程注入.net
使用 CreateRemoteThread 函数在目标进程中建立线程,在该线程中加载注入dll。线程
DLL函数转发调试
使用伪造的dll来替换目标dll,两个dll的导出符号彻底相同,在自定义DLL中,先利用函数转发器将请求转发到真实dll中,而后进行本身的一些处理。code
在本篇文章中,主要介绍 Windows Hook注入 这一种方式。在具体介绍以前,先介绍下Dll的加载顺序、加载过程。blog
系统在搜索加载指定DLL以前,按照以下顺序作检查:递归
系统已知DLL列表配置位于注册表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
选项中,本机上的内容以下:
标准的DLL搜索顺序取决于系统安全DLL搜索模式,该模式默认使能。按照以下顺序搜索DLL:
DLL加载分为隐式加载和显示加载。
隐式加载既为在编译连接选项中增长导入库,在程序运行目录存放待加载的dll。双击程序启动时,由系统加载程序根据exe中的导入表加载对应dll到进程空间,若dll有依赖其余dll的,会递归加载直到加载完成全部必需的dll,而后进行exe的导入函数地址重定位,使得可以调用dll的导出函数。
显示加载指的是由应用程序按需加载,具体为调用 LoadLibrary
、 FreeLibrary
和 GetProcAddress
这三个API函数,加载并获取dll的导出函数地址。
使用CreateRemoteThread可以使得目前进程建立线程,但要加载注入dll,须要在目标线程的虚拟地址空间申请内存,用于保存目标dll的名称,经过 GetProcAddress 函数在远程线程中 kernel32.dll 模块的LoadLibrary函数地址,用于远程线程的入口函数,主要流程以下:
执行完本身的函数后,就要远程卸载dll,思路与注入相似,函数变为FreeLibrary,传入参数对对应dll的句柄。
获取已加载的模块句柄(经过EnumProcessModule实现)
注入代码示例:
// 得到指定进程名称的进程PID int GetProcessId(const char* pName) { PROCESSENTRY32 pe; DWORD id = 0; HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); pe.dwSize = sizeof(PROCESSENTRY32); if (!Process32First(hSnapshot, &pe)) return 0; while (1) { pe.dwSize = sizeof(PROCESSENTRY32); if (Process32Next(hSnapshot, &pe) == FALSE) break; if (strcmp(pe.szExeFile, pName) == 0) { id = pe.th32ProcessID; break; } } CloseHandle(hSnapshot); return id; } //利用远程线程来注入dll bool RemoteThreadDllInject() { ////提权代码,在Windows Vista 及以上的版本须要将进程的权限提高,不然打开进程会失败 if (!SetDebugPrivilege(TRUE)) { printf("提高权限失败\n"); return false; } int nPid = GetProcessId(INJECT_EXE_NAME); // 打开目标进程 HANDLE hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, nPid); if (NULL == hRemoteProcess) { printf("OpenProcess failed! %d", GetLastError()); return false; } // 得到 LoadLibraryW 在 kernel32.dll 中的地址 typedef HMODULE(WINAPI *pfnLoadLibrary)(LPCWSTR); // 这里注意要载入宽字节版本仍是普通版本的 LoadLibrary pfnLoadLibrary pfnThreadRtn2 = (pfnLoadLibrary)GetProcAddress(GetModuleHandle(TEXT("kernel32")), "LoadLibraryA"); // 在远程进程中申请内存空间,用于保存远程线程的参数 LPVOID lpRemoteMemory = VirtualAllocEx(hRemoteProcess, 0, MAX_PATH, MEM_COMMIT, PAGE_READWRITE); string strInjectDllName(INJECT_DLL_NAME); DWORD nWritten = 0; BOOL bRet = WriteProcessMemory(hRemoteProcess, lpRemoteMemory, strInjectDllName.c_str(), strInjectDllName.length() + 1, &nWritten); HANDLE hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pfnThreadRtn2, lpRemoteMemory, 0, NULL); WaitForSingleObject(hRemoteThread, INFINITE); VirtualFreeEx(hRemoteProcess, lpRemoteMemory, 0, MEM_RELEASE); CloseHandle(hRemoteThread); CloseHandle(hRemoteProcess); return true; }
远程卸载dll示例代码:
// 得到指定进程内指定模块信息 bool GetProcessModule(DWORD dwPid, string strModuleName, LPMODULEENTRY32 lpMe32, DWORD cbMe32) { HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid); if (INVALID_HANDLE_VALUE == hSnapshot) { printf("CreateToolhelp32Snapshot Error"); return false; } bool bFind = false; MODULEENTRY32 moduleInfo = {0}; moduleInfo.dwSize = sizeof(MODULEENTRY32); if (Module32First(hSnapshot, &moduleInfo)) { do { if (strModuleName == string(moduleInfo.szModule)) { memcpy(lpMe32, &moduleInfo, cbMe32); bFind = true; break; } } while (!bFind && Module32Next(hSnapshot, &moduleInfo)); } CloseHandle(hSnapshot); return bFind; } bool RemoteDllUnLoad() { //在远程线程执行结束后,注入的 dll 仍然存在与目标进程中,咱们须要再次使用 CreateRemoteThread,执行 FreeLibrary ,将以前注入的 dll 卸载掉 // 实现思路:枚举进程的模块,根据模块名称找到对应模块的句柄。 int nPid = GetProcessId(INJECT_EXE_NAME); ////提权代码,在Windows Vista 及以上的版本须要将进程的权限提高,不然打开进程会失败 if (!SetDebugPrivilege(TRUE)) { printf("提高权限失败\n"); return false; } MODULEENTRY32 moduleInfo = {0}; if (!GetProcessModule(nPid, INJECT_DLL_NAME, &moduleInfo, sizeof(moduleInfo))) { printf("can't find %s dll in %s", INJECT_DLL_NAME, INJECT_EXE_NAME); return false; } typedef BOOL(*pfnFreeLibrary)(HMODULE); pfnFreeLibrary pFreeLibrary = (pfnFreeLibrary)GetProcAddress(GetModuleHandle("kernel32.dll"), "FreeLibrary"); HANDLE hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, nPid); if (hRemoteProcess == NULL) { printf("OpenProcess Error"); return false; } HANDLE hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFreeLibrary, moduleInfo.hModule, 0, NULL); WaitForSingleObject(hRemoteThread, INFINITE); CloseHandle(hRemoteThread); CloseHandle(hRemoteProcess); return true; }
注意事项:
通常来讲,输入法(搜狗输入法的皮肤组件PicFace.dll、资源组件Resource.dll等)、监控软件的dll会自动加载到全部进程的dll中去。
每一个模块(exe和dll)在编译输出时,都有一个首选基址,它指示加载器将模块映射到进程地址空间中的首选位置,通常exe的基址设定为 0x00400000,dll模块为 0x10000000.当一个exe依赖于多个dll时,第一个dll被正确的加载到 0x10000000上,随后的dll就不能在加载到0x10000000上,加载程序会对随后的dll进行基址重定位,把它放到别的地方。基址重定位会增长程序初始化时间,所以,若是将多个模块载入同一进程空间,能够给不一样模块指定不一样的基址。在VS开发环境中,基址配置方式:DLL工程的配置属性-->连接器-->高级-->基址。
在全部dll编译完成后,使用 Rebase.exe
工具,对须要载入进程地址空间的全部模块进行基址重定位,将结果写回到dll文件中。
一旦一个dll模块的基址已知,那么可直接推算出exe在使用该DLL的导出函数的真实地址。这种预先将exe和所依赖的dll绑定在一块儿的作法,能够提升应用程序的启动速度。
VS提供 Bind.exe
程序提供了绑定可执行文件与dll的功能。工做原理为,读取全部dll的基址和导出符号的RVA,计算后填充到可执行文件的导入表中。
参考文档:多种DLL注入技术原理介绍