详解最实用的几种dll注入方式
目錄
概念
注入方式
鉤子注入
原理
示例代碼
代碼注解
運行截圖
遠線程注入
原理
示例代碼?
突破Session 0 隔離的遠線程注入?
示例代碼?
*入口點注入
原理
參考代碼
?APC注入
一、APC注入
二、API
三、實現
四、示例代碼
概念
要談dll注入,首先則要了解dll ,對dll的概念和使用不熟悉的讀者可移步?dll概念和使用方式詳解
所謂DLL注入就是將一個DLL放進某個進程的地址空間里,讓它成為那個進程的一部分。這樣該進程和dll共享同一內存空間,這樣dll可以使用該進程的所有資源,隨時監控程序運行。通常,我們將需要實現的功能封裝生成dll文件,然后將其注入到某一進程中,從而在該進程中添加或擴展我們需要的功能。因此dll注入技術被廣泛運用在惡意攻擊、游戲外掛、木馬等程序中。
注入方式
鉤子注入
原理
利用函數SetWindowsHookEx來實現注入。該函數將一個應用程序定義的掛鉤處理函數安裝到掛鉤鏈中去,可以通過安裝掛鉤處理過程來對系統的某些類型事件進行監控,這些事件與某個特定的線程或系統中的所有事件相關。如果我們需要向某一進程注入dll,則首先獲取該進程中某一線程ID,在dll文件中封裝我們的掛鉤處理函數,對該線程設置鉤子,當觸發類型事件時,線程則調用dll文件中的掛鉤函數。此時,如果線程想要成功地調用該掛鉤函數,必須先加載該dll文件,這樣也就實現了我們的dll注入了。
函數原型
HHOOK SetWindowsHookExA(
?? ?[in] int ? ? ? idHook,
?? ?[in] HOOKPROC ?lpfn,
?? ?[in] HINSTANCE hmod,
?? ?[in] DWORD ? ? dwThreadId
);
[in] lpfn
指向掛接過程的指針。如果?dwThreadId?參數為零或指定由其他進程創建的線程的標識符,則?lpfn?參數必須指向 DLL 中的掛接過程。否則,lpfn?可以指向與當前進程關聯的代碼中的掛接過程。
[in] hmod
DLL 的句柄,其中包含?lpfn?參數所指向的掛鉤過程。如果?dwThreadId?參數指定由當前進程創建的線程,并且掛接過程位于與當前進程關聯的代碼中,則必須將?hMod?參數設置為?NULL。
[in] dwThreadId
掛接過程要與之關聯的線程的標識符。對于桌面應用程序,如果此參數為零,則掛鉤過程將與在與調用線程相同的桌面上運行的所有現有線程相關聯
示例代碼
dll文件
#include "pch.h" #include<windows.h> BOOL APIENTRY DllMain( HMODULE hModule,DWORD ul_reason_for_call,LPVOID lpReserved) {switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE; } HHOOK hhk = 0;LRESULT CALLBACK KeyboardProc(int code,WPARAM wParam,LPARAM lParam ) {if (code == HC_ACTION){if (wParam == VK_HOME && (lParam & (1<<30)))MessageBoxA(NULL, "您按下了HOME鍵", "HOME",MB_OK);}return CallNextHookEx(hhk, code, wParam, lParam);}extern "C" _declspec(dllexport) HOOKPROC getDllPoint(void) {return KeyboardProc; }設置鉤子
typedef HOOKPROC(*func)(void); void CInjectTestDlg::OnBnClickedButton1() {HWND hwnd = ::FindWindowA(NULL, "陳子青注入工具2.0");DWORD pid;DWORD thread_id = ::GetWindowThreadProcessId(hwnd, &pid);HMODULE mod = LoadLibraryA("myDll.dll");func proc = (func)::GetProcAddress(mod, "getDllPoint");HOOKPROC fp=proc();HHOOK hhk= ::SetWindowsHookEx(WH_KEYBOARD, fp, mod, thread_id); }代碼注解
首先獲取窗口線程,加載dll文件獲取getDllPoint函數,該函數返回鉤子處理函數的地址,然后給窗口線程設置鍵盤鉤子,當觸發鍵盤事件時,該線程調用myDll.dll文件中的處理函數,那么該線程必須先加載該dll文件,這時也就等同于我們將myDll.dll注入到該程序中了。
運行截圖
點擊注入,當按下HOME鍵時,彈出信息框。
遠線程注入
原理
遠線程注入比較好理解,通過CreateRemoteThread函數在需要注入的進程中創建一個自己的線程,在該線程中調用系統的LoadLibraryA加載dll文件即可。
LoadLibraryA函數Kernel32.dll中,因此可以通過加載Kernel32.dll以來獲取LoadLibraryA函數地址,其函數的參數就是dll文件的路徑。此時可以通過VirtualAllocEx函數在該進程中創建一塊內存用來保存需要被注入dll文件路徑。
CreateRemoteThread函數原型
HANDLE CreateRemoteThread(
? [in] ?HANDLE ? ? ? ? ? ? ? ? hProcess,
? [in] ?LPSECURITY_ATTRIBUTES ?lpThreadAttributes,
? [in] ?SIZE_T ? ? ? ? ? ? ? ? dwStackSize,
? [in] ?LPTHREAD_START_ROUTINE lpStartAddress,
? [in] ?LPVOID ? ? ? ? ? ? ? ? lpParameter,
? [in] ?DWORD ? ? ? ? ? ? ? ? ?dwCreationFlags,
? [out] LPDWORD ? ? ? ? ? ? ? ?lpThreadId
);
[in] hProcess
要在其中創建線程的進程的句柄。句柄必須具有PROCESS_CREATE_THREAD、PROCESS_QUERY_INFORMATION、PROCESS_VM_OPERATION、PROCESS_VM_WRITE和PROCESS_VM_READ訪問權限,并且在某些平臺上沒有這些權限可能會失敗。有關詳細信息,請參閱進程安全性和訪問權限。
[in] lpThreadAttributes
指向SECURITY_ATTRIBUTES結構的指針,該結構指定新線程的安全描述符,并確定子進程是否可以繼承返回的句柄。如果?lpThreadAttributes?為 NULL,則線程將獲得默認的安全描述符,并且句柄無法繼承。
[in] lpStartAddress
指向類型為 LPTHREAD_START_ROUTINE的應用程序定義函數的指針將由線程執行,并表示遠程進程中線程的起始地址。該函數必須存在于遠程進程中。有關詳細信息,請參閱線程過程。
[in] lpParameter
指向要傳遞給線程函數的變量的指針。
[in] dwCreationFlags
控制線程創建的標志。
| 0 | 線程在創建后立即運行。 |
| CREATE_SUSPENDED 0x00000004 | 線程是在掛起狀態下創建的,并且在調用?ResumeThread?函數之前不會運行。 |
| STACK_SIZE_PARAM_IS_A_RESERVATION 0x00010000 | dwStackSize?參數指定堆棧的初始保留大小。如果未指定此標志,則 dwStackSize?指定提交大小。 |
[out] lpThreadId
指向接收線程標識符的變量的指針。
如果此參數為?NULL,則不返回線程標識符。
VirtualAllocEx函數原型
LPVOID VirtualAllocEx(
? [in] ? ? ? ? ? HANDLE hProcess,
? [in, optional] LPVOID lpAddress,
? [in] ? ? ? ? ? SIZE_T dwSize,
? [in] ? ? ? ? ? DWORD ?flAllocationType,
? [in] ? ? ? ? ? DWORD ?flProtect
);?
[in] hProcess
進程的句柄。該函數在此進程的虛擬地址空間內分配內存。
句柄必須具有PROCESS_VM_OPERATION訪問權限。
[in, optional] lpAddress
為要分配的頁面區域指定所需起始地址的指針。
如果要預留內存,該函數會將此地址向下舍入到分配粒度的最接近的倍數。
如果要提交已預留的內存,則該函數會將此地址向下舍入到最近的頁邊界。若要確定主計算機上的頁面大小和分配粒度,請使用?GetSystemInfo?函數。
[in] dwSize
要分配的內存區域的大小,以字節為單位。
如果?lpAddress?為?NULL,則該函數將?dwSize?向上舍入到下一頁邊界。
如果?lpAddress?不是?NULL,則該函數將分配包含從?lpAddress 到 lpAddress+dwSize?范圍內的一個或多個字節的所有頁面。這意味著,例如,跨越頁面邊界的 2 字節范圍會導致函數同時分配這兩個頁面。
[in] flAllocationType
內存分配的類型。此參數必須包含以下值之一。
| MEM_COMMIT 0x00001000 | 為指定的保留內存頁分配內存費用(從內存的總大小和磁盤上的分頁文件)。該函數還保證當調用方稍后最初訪問內存時,內容將為零。除非/直到實際訪問虛擬地址,否則不會分配實際的物理頁。 要一步到位地保留和提交頁面,請使用 調用?VirtualAllocEx。MEM_COMMIT | MEM_RESERVE 嘗試通過指定不MEM_RESERVE和非?NULL?lpAddress?的MEM_COMMIT來提交特定地址范圍將失敗,除非已保留整個范圍。生成的錯誤代碼為?ERROR_INVALID_ADDRESS。 嘗試提交已提交的頁不會導致函數失敗。這意味著您可以提交頁面,而無需首先確定每個頁面的當前提交狀態。 如果?lpAddress?指定安全區內的地址,則必須MEM_COMMIT?flAllocationType。 |
| MEM_RESERVE 0x00002000 | 保留進程的一系列虛擬地址空間,而不在內存或磁盤上的分頁文件中分配任何實際的物理存儲。 您可以通過使用MEM_COMMIT再次調用VirtualAllocEx來提交保留頁面。要一步到位地保留和提交頁面,請使用 調用?VirtualAllocEx。MEM_COMMIT | MEM_RESERVE 其他內存分配函數(如?malloc?和?LocalAlloc)在釋放保留內存之前無法使用保留內存。 |
| MEM_RESET 0x00080000 | 指示不再對?lpAddress?和?dwSize?指定的內存范圍中的數據感興趣。不應從分頁文件中讀取或寫入分頁文件。但是,稍后將再次使用內存塊,因此不應將其取消提交。此值不能與任何其他值一起使用。 使用此值并不能保證使用MEM_RESET操作的范圍將包含零。如果希望范圍包含零,請取消提交內存,然后重新提交。 當您使用MEM_RESET時,VirtualAllocEx?函數將忽略?fProtect?的值。但是,您仍必須將?fProtect?設置為有效的保護值,如PAGE_NOACCESS。 如果您使用MEM_RESET并且內存范圍映射到文件,則VirtualAllocEx將返回錯誤。共享視圖只有在映射到分頁文件時才可接受。 |
| MEM_RESET_UNDO 0x1000000 | MEM_RESET_UNDO應僅在之前已成功應用MEM_RESET的地址范圍上調用。它指示調用方對?lpAddress?和?dwSize?指定的指定內存范圍內的數據感興趣,并嘗試反轉MEM_RESET的影響。如果函數成功,則意味著指定地址范圍內的所有數據都完好無損。如果函數失敗,則地址范圍內至少有一些數據已替換為零。 此值不能與任何其他值一起使用。如果在之前未MEM_RESET的地址范圍上調用MEM_RESET_UNDO,則行為未定義。指定MEM_RESET時,VirtualAllocEx?函數將忽略?flProtect 的值。但是,您仍必須將?flProtect?設置為有效的保護值,如PAGE_NOACCESS。 Windows Server 2008 R2、Windows 7、Windows Server 2008、Windows Vista、Windows Server 2003 和 Windows XP:?MEM_RESET_UNDO標志在 Windows 8 和 Windows Server 2012 之前不受支持。 |
此參數還可以指定以下值,如所示。
| MEM_LARGE_PAGES 0x20000000 | 使用大頁面支持分配內存。 大小和對齊方式必須是大頁最小值的倍數。若要獲取此值,請使用?GetLargePageMinimum?函數。 如果指定此值,則還必須指定MEM_RESERVE和MEM_COMMIT。 |
| MEM_PHYSICAL 0x00400000 | 保留可用于映射地址窗口化擴展插件 (AWE) 頁的地址范圍。 此值必須與MEM_RESERVE一起使用,不得與其他值一起使用。 |
| MEM_TOP_DOWN 0x00100000 | 在可能的最高地址分配內存。這可能比常規分配慢,尤其是在分配很多時。 |
[in] flProtect
要分配的頁面區域的內存保護。如果要提交頁,則可以指定任何一個內存保護常量。
如果?lpAddress?指定安全區內的地址,則 flProtect?不能是以下任何值:
- PAGE_NOACCESS
- PAGE_GUARD
- PAGE_NOCACHE
- PAGE_WRITECOMBINE
為安全區分配動態內存時,必須PAGE_READWRITE或PAGE_EXECUTE_READWRITE?flProtect?參數。
示例代碼?
void CInjectTestDlg::OnBnClickedButton2() {const char* path = "E:\\工程文件\\myDll\\Debug\\mydll2.dll";//要注入的dll路徑HWND hwnd = ::FindWindowA(NULL, "陳子青注入工具2.0");DWORD pid;GetWindowThreadProcessId(hwnd, &pid);HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);LPVOID lpAddr = VirtualAllocEx(hProc,NULL, strlen(path)+1, MEM_COMMIT, PAGE_READWRITE);WriteProcessMemory(hProc, lpAddr, path,strlen(path), NULL);HMODULE sysMod = LoadLibraryA("Kernel32.dll");LPTHREAD_START_ROUTINE fp =(LPTHREAD_START_ROUTINE) GetProcAddress(sysMod, "LoadLibraryA");CreateRemoteThread(hProc, NULL, NULL,(LPTHREAD_START_ROUTINE)fp, lpAddr, 0, NULL);}突破Session 0 隔離的遠線程注入?
使用ZwCreateThreadEx函數,突破SESSION 0隔離,向system等權限的系統服務器進程中入時,由于系統的SESSION 0隔離機制,導致注入失敗;我們可使用ZwCreateThreadEx函數進行遠程線程注入。ZwCreateThreadEx函數在ntdll.dll中沒有被聲明,所以需要使用GetProcAddress從ntdll.dll中獲取函數導出地址。該函數在官網文檔里是查詢不到的。其函數原型如下
X64
DWORD ZwCreateThreadEx(
?? ?PHANDLE?? ??? ??? ??? ??? ?ThreadHandle,
? ? ACCESS_MASK?? ??? ??? ??? ?DesiredAccess,
? ? LPVOID?? ??? ??? ??? ??? ?ObjectAttributes,
? ? HANDLE?? ??? ??? ??? ??? ?ProcessHandle,
? ? LPTHREAD_START_ROUTINE?? ?lpStartAddress,
? ? LPVOID?? ??? ??? ??? ??? ?lpParameter,
? ? ULONG?? ??? ??? ??? ??? ?CreateThreadFlags,
? ? SIZE_T?? ??? ??? ??? ??? ?ZeroBits,
? ? SIZE_T?? ??? ??? ??? ??? ?StackSize,
? ? SIZE_T?? ??? ??? ??? ??? ?MaximumStackSize,
? ? LPVOID?? ??? ??? ??? ??? ?pUnkown
)
X32
DWORD ZwCreateThreadEx(
? ? PHANDLE ThreadHandle,
? ? ACCESS_MASK DesiredAccess,
? ? LPVOID ObjectAttributes,
? ? HANDLE ProcessHandle,
? ? LPTHREAD_START_ROUTINE lpStartAddress,
? ? LPVOID lpParameter,
? ? BOOL CreateSuspended,
? ? DWORD dwStackSize,
? ? DWORD dw1,
? ? DWORD dw2,
? ? LPVOID pUnkown);
示例代碼?
typedef_ZwCreateThreadEx ZwCreateThreadEx = (typedef_ZwCreateThreadEx)GetProcAddress(hNtdll, "ZwCreateThreadEx");if (NULL == ZwCreateThreadEx){printf("獲取ZwCreateThreadEx函數地址失敗!");CloseHandle(hProcess);return FALSE;}//在目標進程中創建線程HANDLE hRemoteThread = NULL;//pfnStartAddress 為loadlibrary函數地址, pBuf 傳參DWORD dwStatus = ZwCreateThreadEx(&hRemoteThread, PROCESS_ALL_ACCESS, NULL, hProcess,pfnStartAddress, pBuf, 0, 0, 0, 0, NULL);if (NULL == hRemoteThread){printf("目標進程中創建線程失敗!");CloseHandle(hProcess);return FALSE;}*入口點注入
原理
在以CREATE_SUSPENDED的方式啟動程序,進程啟動時被掛起,此時該進程無法拿到任何權限和數據,此時修改PE文件的入口點代碼,在進程入口點跳轉到實現注入的代碼段,并在此恢復入口點代碼再跳轉回入口點,讓程序正常運行。
這樣做的好處是:第一我們注入的代碼時運行在進程的主線程中的;第二進程在拉起時就被暫定完成注入之后才開始運行,實現“神不知鬼不覺”的效果
實現過程中涉及如下關鍵技術點
① 獲取程序入口點??C++獲取程序入口點
② 進程的創建掛起??進程的創建與使用
③? jmp 遠跳 對應機器碼 0xE9
④ 如何定位寫入數據 技巧 :通過 0xCCCCCCCC 來轉換
⑤ 關于寫入函數的問題,函數中涉及調用都是通過地址尋址來調用的,如果直接寫入,該地址在對方內存空間中是無效的 。
⑥ Jmp調轉的相對地址計算 = 跳轉的目標地址 - 當前地址 - 0x5
該方法需要對注入進程的內存進行反復改寫和恢復,修正過程中要在對方進程中進行Debug,因此需要一定的逆向基礎真正完善,否則程序常常是容易崩潰的,
下面提供一段入口點注入的代碼,僅供參考
參考代碼
#include<stdio.h> #include<cstring> #include<Windows.h> using namespace std;char dllPath[MAX_PATH] = "E:\\工程文件\\EntryPointDemo\\Debug\\Dll1.dll"; const char exePath[MAX_PATH] = "C:\\Users\\Administrator\\Desktop\\game.exe";typedef HMODULE(*_loadLibrayA)(LPCSTR); typedef HMODULE (*_getModuleHandle)(LPCSTR); typedef BOOL (*_virtualProtect)(LPVOID ,SIZE_T,DWORD,PDWORD);typedef struct ROMOTE_DATA {char path[MAX_PATH];//dll地址_loadLibrayA loadLibrary;DWORD entryPoint;_getModuleHandle getModuleHande;_virtualProtect virtualProtect;char code[0x5];LPVOID injectAddr;}*PROMOTE_DATA;PROMOTE_DATA rotData;DWORD GetEntryPointAddr(); void initRotData() {//strcpy_s(rotData->path,dllPath);memcpy(rotData->path,dllPath,sizeof(dllPath));HMODULE hMod = LoadLibraryA("Kernel32.dll");rotData->loadLibrary = (_loadLibrayA)GetProcAddress(hMod, "LoadLibraryA");//printf("loadLibrary :%x\n", rotData->loadLibrary);rotData->entryPoint = GetEntryPointAddr();rotData->getModuleHande = (_getModuleHandle)GetProcAddress(hMod, "GetModuleHandleA");//printf("getModuleHande:%x\n", rotData->getModuleHande);rotData->virtualProtect = (_virtualProtect)GetProcAddress(hMod, "VirtualProtect");//printf("virtualProtect: %x\n", rotData->virtualProtect);}DWORD GetEntryPointAddr() {HANDLE hFile = CreateFileA(exePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);int szFile = GetFileSize(hFile, NULL);LPBYTE lpFile = new BYTE[szFile];int ret = ReadFile(hFile, lpFile, szFile, NULL, NULL);if (ret == 0){printf("read file error:%d", GetLastError());return NULL;}PIMAGE_DOS_HEADER imgDosHeader = (PIMAGE_DOS_HEADER)lpFile;PIMAGE_NT_HEADERS imgNtHeader = (PIMAGE_NT_HEADERS)(lpFile + imgDosHeader->e_lfanew);PIMAGE_OPTIONAL_HEADER imgOptionHeader = &(imgNtHeader->OptionalHeader);DWORD bRet = imgOptionHeader->AddressOfEntryPoint;if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);if (lpFile != NULL) delete[] lpFile;return bRet; }void InjectFunc() {PROMOTE_DATA pData = (PROMOTE_DATA)0xCCCCCCCC;pData->loadLibrary(pData->path);char* prevcode = (char*)pData->entryPoint;for (int i = 0; i < 0x5; i++) prevcode[i] = pData->code[i];unsigned point = pData->entryPoint;__asm{mov eax, pointjmp eax} }void modifyEntryPoint(PROMOTE_DATA p) {DWORD baseAddr= (DWORD)p->getModuleHande(0);p->entryPoint += baseAddr;DWORD lwt{};p->virtualProtect((LPVOID)p->entryPoint, 0x5, PAGE_EXECUTE_READWRITE, &lwt);char *prevcode = (char*)p->entryPoint;for (int i = 0; i < 5; ++i) p->code[i] = prevcode[i];prevcode[0] = 0xE9;//jmpunsigned distance = (unsigned)( (DWORD)p->injectAddr - p->entryPoint - 0x5);unsigned *_code = (unsigned*)(p->entryPoint+1);_code[0] = distance;} int main() {STARTUPINFOA startUp;ZeroMemory(&startUp, sizeof(STARTUPINFOA));PROCESS_INFORMATION proInfo;ZeroMemory(&proInfo, sizeof(PROCESS_INFORMATION));int bRet = CreateProcessA(exePath, NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL,"C:\\Users\\Administrator\\Desktop\\",&startUp, &proInfo);if (bRet == 0){printf("createprocess error:%d\n", GetLastError());return 0;}//進行入口點注入rotData = new ROMOTE_DATA;DWORD rotAddr=(DWORD)VirtualAllocEx(proInfo.hProcess, NULL, 0x3000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);DWORD rotDataAddr = rotAddr + 0x200;initRotData();char modifyCode[0x200];memcpy(modifyCode, InjectFunc, sizeof(modifyCode));for (int i = 0; i < 0x200; i++){ unsigned* pcode = (unsigned*)(&modifyCode[i]);if(pcode[0] == 0xCCCCCCCC){pcode[0] = (unsigned)rotDataAddr;break;}}DWORD injectAddr = rotAddr + 0x1000; //注入dll的函數rotData->injectAddr = (LPVOID)injectAddr;WriteProcessMemory(proInfo.hProcess, (LPVOID)rotDataAddr, rotData, 0x200, NULL);WriteProcessMemory(proInfo.hProcess, (LPVOID)injectAddr, modifyCode, 0x200, NULL);DWORD rotFuncAddr = rotAddr + 0x2000; //寫入遠程函數WriteProcessMemory(proInfo.hProcess, (LPVOID)rotFuncAddr, modifyEntryPoint, 0x500, NULL);HANDLE hProcHnd = CreateRemoteThread(proInfo.hProcess, NULL, 0,(LPTHREAD_START_ROUTINE)rotFuncAddr, (LPVOID)rotDataAddr, 0, NULL);if (hProcHnd != INVALID_HANDLE_VALUE) WaitForSingleObject(hProcHnd, INFINITE);else printf("CreateRemoteThread error : %d", GetLastError());//注入完成后恢復進程運行ResumeThread(proInfo.hThread);return 0; }?APC注入
一、APC注入
什么是APC?
[原創]小Win,點一份APC(Apc機制詳解)(一)-編程技術-看雪論壇-安全社區|安全招聘|bbs.pediy.com
每一個線程都有自己的APC隊列,使用QueueUserAPC函數把一個APC函數壓入APC隊列中。當處于處于用戶模式的APC壓入線程APC隊列后,該線程并不直接調用APC函數,除非該線程處于可通知狀態,調用的順序為先入先出(FIFO)。
二、API
QueueUserAPC函數
QueueUserAPC function (processthreadsapi.h) - Win32 apps | Microsoft Docs
三、實現
一個進程包含多個線程,為了確保能夠執行插入的APC,應向目標進程的所有線程都插入相同的APC,實現加載DLL的操作
實現APC注入的具體流程如下:
首先,通過OpenProcess函數打開目標進程,獲取目標進程的句柄。
然后,通過調用WIN32 API函數CreateToolhelp32Snapshot、Thread32First以及Thread32Next遍歷線程快照,獲取目標進程的所有ID。
接著,調用VirtualAllocEx函數在目標進程中申請內存,并通過WriteProcessMemory函數向內存中寫入DLL的注入路徑。
最后,遍歷獲取的線程ID,并調用OpenThread函數以THREAD_ALL_ACCESS訪問權限打開線程,獲取線程句柄。并調用QueueUserAPC函數向線程插入APC函數,設置APC函數的地址為LoadLibraryA函數的地址,并設置APC函數參數為上述DLL路徑地址。
四、示例代碼
#include "stdafx.h" #include "ApcInject.h"void ShowError(char *pszText) {char szErr[MAX_PATH] = { 0 };::wsprintf(szErr, "%s Error[%d]\n", pszText);::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR); }// 根據進程名稱獲取PID DWORD GetProcessIdByProcessName(char *pszProcessName) {DWORD dwProcessId = 0;PROCESSENTRY32 pe32 = { 0 };HANDLE hSnapshot = NULL;BOOL bRet = FALSE;::RtlZeroMemory(&pe32, sizeof(pe32));pe32.dwSize = sizeof(pe32);// 獲取進程快照hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);if (NULL == hSnapshot){ShowError("CreateToolhelp32Snapshot");return dwProcessId;}// 獲取第一條進程快照信息bRet = ::Process32First(hSnapshot, &pe32);while (bRet){// 獲取快照信息if (0 == ::lstrcmpi(pe32.szExeFile, pszProcessName)){dwProcessId = pe32.th32ProcessID;break;}// 遍歷下一個進程快照信息bRet = ::Process32Next(hSnapshot, &pe32);}return dwProcessId; }// 根據PID獲取所有的相應線程ID BOOL GetAllThreadIdByProcessId(DWORD dwProcessId, DWORD **ppThreadId, DWORD *pdwThreadIdLength) {DWORD *pThreadId = NULL;DWORD dwThreadIdLength = 0;DWORD dwBufferLength = 1000;THREADENTRY32 te32 = { 0 };HANDLE hSnapshot = NULL;BOOL bRet = TRUE;do{// 申請內存pThreadId = new DWORD[dwBufferLength];if (NULL == pThreadId){ShowError("new");bRet = FALSE;break;}::RtlZeroMemory(pThreadId, (dwBufferLength * sizeof(DWORD)));// 獲取線程快照::RtlZeroMemory(&te32, sizeof(te32));te32.dwSize = sizeof(te32);hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);if (NULL == hSnapshot){ShowError("CreateToolhelp32Snapshot");bRet = FALSE;break;}// 獲取第一條線程快照信息bRet = ::Thread32First(hSnapshot, &te32);while (bRet){// 獲取進程對應的線程IDif (te32.th32OwnerProcessID == dwProcessId){pThreadId[dwThreadIdLength] = te32.th32ThreadID;dwThreadIdLength++;}// 遍歷下一個線程快照信息bRet = ::Thread32Next(hSnapshot, &te32);}// 返回*ppThreadId = pThreadId;*pdwThreadIdLength = dwThreadIdLength;bRet = TRUE;} while (FALSE);if (FALSE == bRet){if (pThreadId){delete[]pThreadId;pThreadId = NULL;}}return bRet; }// APC注入 BOOL ApcInjectDll(char *pszProcessName, char *pszDllName) {BOOL bRet = FALSE;DWORD dwProcessId = 0;DWORD *pThreadId = NULL;DWORD dwThreadIdLength = 0;HANDLE hProcess = NULL, hThread = NULL;PVOID pBaseAddress = NULL;PVOID pLoadLibraryAFunc = NULL;SIZE_T dwRet = 0, dwDllPathLen = 1 + ::lstrlen(pszDllName);DWORD i = 0;do{// 根據進程名稱獲取PIDdwProcessId = GetProcessIdByProcessName(pszProcessName);if (0 >= dwProcessId){bRet = FALSE;break;}// 根據PID獲取所有的相應線程IDbRet = GetAllThreadIdByProcessId(dwProcessId, &pThreadId, &dwThreadIdLength);if (FALSE == bRet){bRet = FALSE;break;}// 打開注入進程hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);if (NULL == hProcess){ShowError("OpenProcess");bRet = FALSE;break;}// 在注入進程空間申請內存pBaseAddress = ::VirtualAllocEx(hProcess, NULL, dwDllPathLen, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);if (NULL == pBaseAddress){ShowError("VirtualAllocEx");bRet = FALSE;break;}// 向申請的空間中寫入DLL路徑數據 ::WriteProcessMemory(hProcess, pBaseAddress, pszDllName, dwDllPathLen, &dwRet);if (dwRet != dwDllPathLen){ShowError("WriteProcessMemory");bRet = FALSE;break;}// 獲取 LoadLibrary 地址pLoadLibraryAFunc = ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "LoadLibraryA");if (NULL == pLoadLibraryAFunc){ShowError("GetProcessAddress");bRet = FALSE;break;}// 遍歷線程, 插入APCfor (i = 0; i < dwThreadIdLength; i++){// 打開線程hThread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, pThreadId[i]);if (hThread){// 插入APC::QueueUserAPC((PAPCFUNC)pLoadLibraryAFunc, hThread, (ULONG_PTR)pBaseAddress);// 關閉線程句柄::CloseHandle(hThread);hThread = NULL;}}bRet = TRUE;} while (FALSE);// 釋放內存if (hProcess){::CloseHandle(hProcess);hProcess = NULL;}if (pThreadId){delete[]pThreadId;pThreadId = NULL;}return bRet; }總結
以上是生活随笔為你收集整理的详解最实用的几种dll注入方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: welearn随行课堂刷题教程
- 下一篇: 吴恩达深度学习第三周