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注入技術原理介紹