条目1、使用注册表来注入DLL。(P575)
原理:当User32.dll被映射到一个新的进程时,它的DLL_PROCESS_ATTACH通知会取得注册表HKEY_LOCAL_MACHINE/Software/Microsoft/Windows NT/CurrentVersion/Windows/AppInit_DLLs中的DLL列表,逐个加载。这个DLL列表的第一个DLL文件可以包含路径,其他DLL包含的路径则将被忽略,根据“随笔记录19中条目8”中的搜索算法对DLL的文件映像进行定位。为了使系统可以使用这个注册表项,我们还必须在与注册表项AppInit_DLLs同一级别下创建一个名为LoadAppInit_DLLs,类型为DWORD的注册表项,并将其值设置1。由于被注入的DLL在进程生命期的早期被载入,因此我们要慎重调用一些函数。另外,User32.dll不会检查每个DLL的载入或初始化是否成功。
示例代码:
(1) 创建用于注入的测试DLL,将其放在windows/system32目录下。
//InjLib.c //compile:cl /c InjLib.c //link: link /DLL InjLib.obj //说明:用于注入的DLL,将其放在Windows/system32目录下 #include <windows.h> BOOL WINAPI DllMain(HINSTANCE hInstDLL,DWORD fdwReason,LPVOID lpvReserved) { switch(fdwReason) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hInstDLL); } return TRUE; }
(2) 修改注册表信息,向AppInit_Dlls添加DLL名称:InjLib.dll (逗号隔开)
(3) 创建会使用User32.dll的客户端程序。
//RegInjDllDemo.c //compile:cl RegInjDllDemo.c #include <windows.h> #pragma comment(lib,"user32") int main(void) { MessageBox(NULL,"Register Inject DLL","RegInjDllDemo",MB_OK); }
条目2、使用WINDOWS挂钩来注入DLL。(P576)
原理:我们可以通过SetWindowsHookEx函数将DLL注入到进程的地址空间中,最后一个参数dwThreadId指向的是被注入进程内的某个线程ID。这里以WH_GETMESSAGE挂钩来描述其执行步骤。(请先通过MSDN了解SetWindowsHookEx函数)
(1) 进程A调用SetWindowsHookEx(WH_GETMESSAGE,lpfn,hMod,dwThreadId)对线程dwThread执行WH_GETMESSAGE挂钩
(2) 线程dwThreadId准备提取消息进行处理,这时它会被系统监控到
(3) 系统检查SetWindowsHookEx中hMod指向的DLL是否已被载入到线程dwThreadId所在的进程(假设进程B)地址空间中,若否,则载入。这时,假设DLL被载入到进程B的hMod2位置。
(4) 系统计算lpfn在进程B中的位置,计算方式:lpfn + hMod2 - hMod1。
(5) 系统在进程B中递增该DLL(hMod2)的锁计数
(6) 系统在进程B的地址空间中调用lpfn函数(其位置在:lpfn + hMod2 - hMod1)
(7) lpfn函数返回时候,系统递减该DLL(hMod2)在进程B中的锁计数
示例代码:
(1) 创建用于注入的测试DLL。
//InjLib.c //compile:cl /c InjLib.c //link:link /DLL InjLib.obj #include <windows.h> #pragma comment(lib,"User32") #pragma data_seg("Shared") DWORD ds_dwClientThreadId = 0; HHOOK ds_hHook = NULL; #pragma data_seg() HINSTANCE g_hInstDLL = NULL; __declspec(dllexport) LRESULT CALLBACK GetMsgProc(int code,WPARAM wParam,LPARAM lParam) { static BOOL s_fLoad = FALSE; if(!s_fLoad) { //告诉客户端该DLL已经注入 PostThreadMessage(ds_dwClientThreadId,WM_NULL,0,0); s_fLoad = TRUE; } return CallNextHookEx(ds_hHook,code,wParam,lParam); } __declspec(dllexport) BOOL InstallInjLib(DWORD dwThread) { ds_hHook = SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,g_hInstDLL,dwThread); ds_dwClientThreadId = GetCurrentThreadId(); if(NULL == ds_hHook) return FALSE; return TRUE; } __declspec(dllexport) BOOL UninstallInjLib() { return UnhookWindowsHookEx(ds_hHook); } BOOL WINAPI DllMain(HINSTANCE hInstDLL,DWORD fdwReason,LPVOID lpvReserved) { switch(fdwReason) { case DLL_PROCESS_ATTACH: g_hInstDLL = hInstDLL; DisableThreadLibraryCalls(hInstDLL); } return TRUE; }
(2) 创建客户端用于将DLL注入到目标进程
//HookInjDllClient.c //compile:cl HookInjDllClient.c #include <windows.h> #pragma comment(lib,"InjLib") #pragma comment(lib,"user32") int main(void) { MSG msg; DWORD dwThread = 0; //获取某个窗体应用程序创建线程 dwThread = GetWindowThreadProcessId(FindWindow("ipmsg_class",NULL),NULL); //安装HOOK if(!InstallInjLib(dwThread)) { printf("hook failure!"); return 0; } //提醒目标线程要注入DLL。 PostThreadMessage(dwThread,WM_NULL,0,0); //等待其通知! GetMessage(&msg,NULL,0,0); printf("hook success!/nplease enter any key to unhook and exit."); _getch(); UninstallInjLib(); PostThreadMessage(dwThread,WM_NULL,0,0); }
条目3、使用远程线程注入DLL。(P587)
原理:在目标进程中开启一个线程以执行LoadLibrary(Ex)操作。
步骤:
(1) 用VirtualAllocEx函数在远程进程的地址空间中分配一块内存
(2) 用WriteProcessMemory函数把DLL的路径名复制到(1)步骤分配的内存中
(3) 用GetProcAddress获取LoadLibrary(Ex)函数的实际地址
(4) 用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用正确的LoadLibrary(Ex)函数并在参数中传入(1)步骤分配的内存地址
(5) 用VirtualFreeEx来释放(1)步骤分配的内存
(6) 用GetProcAddress获取FreeLibrary函数的实际地址
(7) 用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用正确的FreeLibrary函数并在参数中传入远程DLL的HMODULE
示例代码:
(1) 创建用于注入的DLL
//InjLib.c //cl /c InjLib.c //link /DLL InjLib.obj #include <windows.h> BOOL WINAPI DllMain(HINSTANCE hInstDLL,DWORD fdwReason,LPVOID lpvReserved) { switch(fdwReason) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hInstDLL); } return TRUE; }
(2) 创建客户端用于将DLL注入到目标进程
//RemoteThreadInj.c //编译:cl RemoteThreadInj.c //使用:RemoteThreadInj <目标进程的ID> <要注入的DLL全路径> #include <windows.h> #include <Tlhelp32.h> int main(int argc,char **argv) { MODULEENTRY32 me32 = {sizeof(MODULEENTRY32)}; LPTHREAD_START_ROUTINE lpStartAddress = NULL; HANDLE hProcess = NULL,hThread = NULL,hProcessSnap = NULL; LPVOID lpBaseAddress = NULL; DWORD dwProcessId = 0; char *endptr = "/0"; if(argc != 3) { printf("Usage:InjDebuggee.exe <PID> <Be Inject Dll Path>"); return 0; } dwProcessId = strtol(argv[1],&endptr,10); //1.打开目标进程 hProcess = OpenProcess(PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE|PROCESS_CREATE_THREAD, FALSE,dwProcessId); if(NULL == hProcess) { printf("Error:0x%08X,can not open process.",GetLastError()); return 0; } //2.为目标进程分配内存 lpBaseAddress = VirtualAllocEx(hProcess,NULL,lstrlen(argv[2])+1, MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE); if(NULL == lpBaseAddress) { CloseHandle(hProcess); printf("Error:0x%08X,can not alloc memory.",GetLastError()); return 0; } //3.将要注入的DLL路径写入到目标进程 if(!WriteProcessMemory(hProcess,lpBaseAddress,argv[2],lstrlen(argv[2]),NULL)) { printf("Error:0x%08X,can not write process.",GetLastError()); VirtualFreeEx(hProcess,lpBaseAddress,0,MEM_RELEASE); CloseHandle(hProcess); return 0; } //4.获取LoadLibrary的地址 lpStartAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("kernel32"),"LoadLibraryA"); //5.创建远程线程执行注入DLL操作 hThread = CreateRemoteThread(hProcess,NULL,0,lpStartAddress,lpBaseAddress,0,NULL); if(NULL == hThread) { printf("Error:0x%08X,can not create remote thread.",GetLastError()); VirtualFreeEx(hProcess,lpBaseAddress,0,MEM_RELEASE); CloseHandle(hProcess); return 0; } //6.等待其加载完毕 WaitForSingleObject(hThread,INFINITE); CloseHandle(hThread); printf("inject success!/nplease type any key to eject and exit."); _getch(); //卸载DLL,为了更清楚的描述流程,以下不再做错误判断处理! hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,dwProcessId); Module32First(hProcessSnap,&me32); do { if(!lstrcmp(me32.szExePath,argv[2])) break; } while(Module32Next(hProcessSnap,&me32)); //7.获取FreeLibrary的地址 lpStartAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("kernel32"),"FreeLibrary"); //8.创建远程线程执行卸载DLL操作 hThread = CreateRemoteThread(hProcess,NULL,0,lpStartAddress,me32.hModule,0,NULL); //9.等待其卸载完毕 WaitForSingleObject(hThread,INFINITE); CloseHandle(hThread); //10.释放资源 VirtualFreeEx(hProcess,lpBaseAddress,0,MEM_RELEASE); CloseHandle(hProcess); }
条目4、使用木马DLL来注入DLL
原理:将进程必然会载入的DLL替换掉。我们可以采取以下两种方式进行替换
(1) 创建自己的DLL,把它取名为进程要载入的DLL文件名,把原DLL改成别的名称。
(2) 搜索进程文件的导入段,更改其导入段中的DLL名称,使之指向我们创建的DLL。
示例代码(1),我们采用“《WINDOWS核心编程第5版》随笔记录20”中的条目20,对目标程序强制执行DLL重定向。为了不影响目标程序原先的代码执行,凡涉及到调用原DLL的函数,我们采用“《WINDOWS核心编程第5版》随笔记录20”中的条目18,函数转发器修改新DLL。步骤如下:
(1) 创建目标程序使用的DLL,名为:OrgLib
//文件:OrgLib.c //编译:cl /c OrgLib.c //链接:link /DLL OrgLib.obj #include <windows.h> __declspec(dllexport) int fun(int a,int b) { return a + b; }
(2) 创建目标程序,让其包含OrgLib!fun数。
//文件:TrojanClient.c //编译:cl TrojanClient.c #include <windows.h> #pragma comment(lib,"OrgLib") int main(void) { printf("fun(1,2)=%d/n",fun(1,2)); system("pause"); }
(3) 创建木马DLL,让其和OrgLib同名,把原来的OrgLib.dll改名为OrgLibR.dll
//文件:OrgLib.c //编译:cl /c OrgLib.c //链接:link /DLL OrgLib.obj #include <windows.h> #pragma comment(linker,"/EXPORT:fun=OrgLibR.fun")
(4) 创建Trojan文件夹,将目标程序TrojanClient.exe和木马DLL(OrgLib.dll)放在里面,并在该文件夹下创建TrojanClient.exe.local文件(夹)。把OrgLibR.dll的路径放入PATH环境变量中(或用set path命令行设置)。
(5) 测试运行,我们会发现TrojanClient.exe加载的是木马DLL,并运行无误。
示例代码(2),我们通过修改目标程序的PE段,修改其加载的DLL名称,这里要注意的是,我们的木马DLL名称的长度必须和被修改DLL名称的长度相同。
(1) 创建目标程序,让其包含User32!MessageBoxA函数。
//文件:Client.c //编译:cl Client.c #include <windows.h> #pragma comment(lib,"user32") int main(void) { MessageBoxA(NULL,"Patch PE Import DLL","Client",MB_OK); }
通过dumpbin -imports Client.exe 查看User32.dll已在导入段中。
Dump of file client.exe
File Type: EXECUTABLE IMAGE
Section contains the following imports:
USER32.dll <--- 这里!
4080F0 Import Address Table
409620 Import Name Table
... ...
(2) 创建注入DLL,它将用来替换目标程序中的User32.DLL
//文件:InjLib.c //编译:cl /c InjLib.c //链接:link /DLL InjLib.obj #include <windows.h> #pragma comment(linker,"/EXPORT:MessageBoxA=User32.MessageBoxA")
(3) 创建补丁程序,修改目标程序中的DLL导入名称。
//文件:RepairImportDll.c //编译:cl RepairImportDll.c //使用:RepairImportDll <目标程序(.exe)> <原DLL名称(.dll)> <新DLL名称(.dll)> #include <windows.h> #pragma comment(lib,"User32") int main(int argc,char **argv) { HANDLE hFile = NULL,hFileMapping = NULL; LPVOID lpBaseAddress = NULL; PCHAR szDllName = NULL; PIMAGE_DOS_HEADER pDosHeader = NULL; PIMAGE_NT_HEADERS pNTHeaders = NULL; //PIMAGE_OPTIONAL_HEADER optHeader = NULL; PIMAGE_DATA_DIRECTORY pImpDataDir = NULL; PIMAGE_IMPORT_DESCRIPTOR pImpDescrip = NULL; PIMAGE_THUNK_DATA pOrgFirstThunk = NULL,pFirstThunk = NULL; PIMAGE_IMPORT_BY_NAME pImpByName = NULL; if(argc != 4) { printf("Usage:RepairImportDll <目标程序(.exe)> <原DLL名称(.dll)> <新DLL名称(.dll)>"); return 0; } if(lstrlen(argv[2]) != lstrlen(argv[3])) { printf("%s和%s的长度不相同!",argv[2],argv[3]); return 0; } //转换为大写DLL CharUpperBuff(argv[2],lstrlen(argv[2])); CharUpperBuff(argv[3],lstrlen(argv[3])); //为使主干代码更为清晰,以下不考虑任何错误. //1.打开目标程序的文件,将其映射到本进程的地址空间中 hFile = CreateFile(argv[1],GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE == hFile) { printf("can not open file."); return 0; } hFileMapping = CreateFileMapping(hFile,NULL,PAGE_READWRITE,0,0,NULL); if(NULL == hFileMapping) { printf("can not create file map."); CloseHandle(hFile); return 0; } lpBaseAddress = MapViewOfFile(hFileMapping,FILE_MAP_READ|FILE_MAP_WRITE,0,0,0); if(NULL == lpBaseAddress) { printf("can not map view of file."); CloseHandle(hFile); CloseHandle(hFileMapping); return 0; } //2.从PE结构中定位到导入段 pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress; pNTHeaders = (PIMAGE_NT_HEADERS)((PCHAR)pDosHeader + pDosHeader->e_lfanew); //optHeader = pNTHeaders->OptionalHeader; pImpDataDir= pNTHeaders->OptionalHeader.DataDirectory + 1; pImpDescrip= (PIMAGE_IMPORT_DESCRIPTOR)(pImpDataDir->VirtualAddress + (PCHAR)pDosHeader); printf("Import table address : 0x%p/n",pImpDescrip); //输出导入模块的基本信息(模块名/函数名) while(pImpDescrip->Characteristics) { szDllName = pImpDescrip->Name + (PCHAR)pDosHeader; CharUpperBuff(szDllName,lstrlen(szDllName)); if(!lstrcmp(szDllName,argv[2])) { printf("found the target module : %s/n",szDllName); lstrcpy(szDllName,argv[3]); printf("the target module rename: %s/n",argv[3]); //lstrcpy(szDllName,argv[3]); break; } pImpDescrip ++; } }
运行RepairImportDll.exe Client.exe User32.dll InjLib.dll 后我们查看Client.exe的导入段:
Dump of file Client.exe
File Type: EXECUTABLE IMAGE
Section contains the following imports:
INJLIB.DLL <-------这里!已经被改变。
4080F0 Import Address Table
409620 Import Name Table
0 time date stamp
0 Index of first forwarder reference
1DF MessageBoxA
KERNEL32.dll
... ...
(4) 测试运行,Client.exe运行无误。
条目5、把DLL作为调试器来注入(P598)
原理:调试器在载入被调试程序的时候,被调试程序会在地址空间准备完毕但主线程会尚未开始执行之前通知调试器。我们可以在这个时候给被调试程序注入一段加载DLL的代码。然后把被调试程序的EIP设置为这段加载代码的起始位置使之执行。调试API的知识请Google:Win32调试API,有三部分。
步骤:
(1) 通过DebugActiveProcess将目标进程附加到调试器上。
(2) 调用DebugSetProcessKillOnExit避免调试器退出时,目标进程也跟着退出。
(3) 通过WaitForDebugEvent建立调试循环体,一旦接收到通知,调试信息便会被填充到DEBUG_EVENT结构中。
(4) 初次附加时会产生CREATE_PROCESS_DEBUG_EVENT通知,在这里我们把一段加载DLL的代码写入到目标进程的地址空间A中。接着,我们通过GetThreadContext获取目标进程的当前CONTEXT信息,保存一份CONTEXT副本,然后更改其中EIP值为A。最后,通过SetThreadContext保存回去,让目标程序按新的EIP继续执行。
(5) 目标程序执行我们注入的代码,执行到INT3时,会产生EXCEPTION_BREAKPOINT(在EXCEPTION_DEBUG_EVENT中)通知(记住,在执行被调试程序的第一条指令前windows将发送一个EXCEPTION_BREAKPOINT通知)。在这里我们把先前保存下来的CONTEXT通过SetThreadContext还原回去。让目标程序按原先的EIP继续执行。
示例代码:
(1) 创建用于注入的DLL
//文件:InjLib.c //编译:cl /c InjLib.c //链接:link /DLL InjLib.obj #include <windows.h> BOOL WINAPI DllMain(HINSTANCE hInstDLL,DWORD fdwReason,LPVOID lpvReserved) { switch(fdwReason) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hInstDLL); } return TRUE; }
(2) 创建调试器,用于将DLL注入到目标进程
//文件:InjDebuggee.c //编译:cl InjDebuggee.c //使用:InjDebuggee <目标进程的ID> <被注入DLL的全路径> #include <windows.h> #include <strsafe.h> #include <stddef.h> #pragma comment(lib,"strsafe") //B8 00000000 mov eax, &szDllPath //50 push eax //B9 00000000 mov ecx, &LoadLibrary //FFD1 call ecx //CC int3 //结构必须字节对齐! #pragma pack(1) typedef struct _INJECT_CODE { BYTE byMOV_EAX; DWORD dwMOV_EAX_VALUE; BYTE byPUSH_EAX; BYTE byMOV_ECX; DWORD dwMOV_ECX_VALUE; WORD wCALL_ECX; BYTE byINT3; CHAR szDllPath[MAX_PATH]; }INJECT_CODE,*PINJECT_CODE; #pragma pack() //代码注入函数 BOOL InjectDebuggeeCode(HANDLE hProcess,LPVOID lpBaseAddress,PCHAR szDllPath) { BOOL fSuccess = FALSE; INJECT_CODE ic = {sizeof(ic)}; ic.byMOV_EAX = 0xB8; ic.dwMOV_EAX_VALUE = (DWORD)lpBaseAddress + offsetof(INJECT_CODE,szDllPath); ic.byPUSH_EAX = 0x50; ic.byMOV_ECX = 0xB9; ic.dwMOV_ECX_VALUE = (DWORD)&LoadLibrary; ic.wCALL_ECX = 0xD1FF; ic.byINT3 = 0xCC; StringCbCopy(ic.szDllPath,MAX_PATH,szDllPath); fSuccess = WriteProcessMemory(hProcess,lpBaseAddress,&ic,sizeof(ic),NULL); if(!fSuccess)printf("Error:%d,Inject Debuggee Code Failed.",GetLastError()); fSuccess = FlushInstructionCache(hProcess,lpBaseAddress,sizeof(ic)); if(!fSuccess)printf("Error:%d,Flush Debuggee Code Failed.",GetLastError()); return fSuccess; } int main(int argc,char **argv) { DWORD dwProcessId = 0; char *endptr = "/0"; BOOL fSuccess = FALSE,fFirst = TRUE; LPVOID lpBaseAddress = NULL; HANDLE hThread = NULL, hProcess = NULL; DEBUG_EVENT dbgEvent = {0}; CONTEXT ctxOld = {CONTEXT_FULL},ctxNew = {CONTEXT_FULL}; INJECT_CODE ic = {0}; STARTUPINFO si = {sizeof(si)}; PROCESS_INFORMATION pi = {0}; if(argc != 3) { printf("Usage:InjDebuggee.exe <PID> <Be Inject Dll Path>"); return 0; } dwProcessId = strtol(argv[1],&endptr,10); //1.通过DebugActiveProcess将目标进程附加到调试器上 fSuccess = DebugActiveProcess(dwProcessId); if(!fSuccess){printf("Debug Process [%d] Failed.",dwProcessId);return 0;} //CreateProcess(NULL,"IpMsg.exe",NULL,NULL,FALSE,DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&si,&pi); //2.本程序退出时,防止被调试进程也一并退出 DebugSetProcessKillOnExit(FALSE); //3.建立调试循环体,一旦接收到通知,调试信息便会被填充到DEBUG_EVENT结构中 while(WaitForDebugEvent(&dbgEvent,INFINITE)) { switch(dbgEvent.dwDebugEventCode) { case CREATE_PROCESS_DEBUG_EVENT : hProcess = dbgEvent.u.CreateProcessInfo.hProcess; hThread = dbgEvent.u.CreateProcessInfo.hThread; //分配内存,填充注入指令 lpBaseAddress = VirtualAllocEx(hProcess, NULL, sizeof(INJECT_CODE), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if(NULL == lpBaseAddress) { printf("Error:%d,Can not alloc enough memory",GetLastError()); return 0; } //将加载DLL指令写入到目标进程中 fSuccess = InjectDebuggeeCode(hProcess,lpBaseAddress,argv[2]); if(!fSuccess) return 0; //获取当前线程上下文 GetThreadContext(hThread,&ctxOld); ctxNew = ctxOld; ctxNew.Eip = (DWORD)lpBaseAddress; printf("Old Eip = 0x%08X/n",ctxOld.Eip); printf("New Eip = 0x%08X/n",ctxNew.Eip); //4.设置新的线程上下文 SetThreadContext(hThread,&ctxNew); break; case EXCEPTION_DEBUG_EVENT : if(dbgEvent.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) { GetThreadContext(hThread,&ctxNew); /*第一次捕获断点则跳过不执行!查阅MSDN中DebugActiveProcess的最后一段话 After all of this is done, the system resumes all threads in the process. When the first thread in the process resumes, it executes a breakpoint instruction that causes an EXCEPTION_DEBUG_EVENT debugging event to be sent to the debugger. All future debugging events are sent to the debugger by using the normal mechanism and rules. */ if(ctxNew.Eip == (DWORD)lpBaseAddress) break; printf("Second Exception Eip = 0x%08X/n",ctxNew.Eip); //释放内存 VirtualFreeEx(hProcess, lpBaseAddress, sizeof(INJECT_CODE), MEM_RELEASE); //5.设置原先的线程上下文 SetThreadContext(hThread,&ctxOld); //执行原EIP指向的指令 ContinueDebugEvent(dbgEvent.dwProcessId,dbgEvent.dwThreadId,DBG_CONTINUE); return 0; //退出调试程序! } break; } ContinueDebugEvent(dbgEvent.dwProcessId,dbgEvent.dwThreadId,DBG_EXCEPTION_NOT_HANDLED); } }
(3) 测试运行,执行InjDebuggee 7572 f:/injdebuggee/injlib.dll,目标进程成功被注入DLL。
条目6、使用CreateProcess创建子进程的方式注入代码
原理:父进程在创建子进程的时候将子进程挂起,这时候父进程可以通过子进程的主线程句柄对线程执行的代码进行修改。
步骤:
(1) 让进程生成一个被挂起的子进程。
(2) 从.exe模块(子进程)的头文件中取得主线程的起始内存地址。
(3) 将位于该内存地址处的机器指令保存起来。
(4) 强制将一些手工编写的机器指令写入到该内存地址处。这些指令应该调用LoadLibrary来载入一个DLL。
(5) 让子进程的主线程恢复运行,从而让这些指令得到执行。
(6) 把保存起来的原始指令恢复到起始地址处。
(7) 让进程从起始地址继续执行,就好像什么也没发生过一样。
示例代码:
(1) 创建用于注入的DLL
//文件:InjLib.c //编译:cl /c InjLib.c //链接:link /DLL InjLib.obj #include <windows.h> BOOL WINAPI DllMain(HINSTANCE hInstDLL,DWORD fdwReason,LPVOID lpvReserved) { switch(fdwReason) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hInstDLL); } return TRUE; }
将其放在任意目录下,比如:F:/InjLib.dll。
(2) 创建用于注入DLL的代理DLL,此代理DLL用于执行真正DLL的注入工作。
//文件:InjLibProxy.c //编译:cl /c InjLibProxy.c //链接:link /DLL InjLibProxy.obj #include <windows.h> #include <strsafe.h> #include <stddef.h> #include <Imagehlp.h> #pragma comment(lib,"user32") #pragma comment(lib,"strsafe") #pragma comment(lib,"Imagehlp") #pragma pack(push,1) typedef struct _INJECT_CODE { BYTE byPUSHAD; BYTE byMOV_EAX; DWORD dwMOV_EAX_VALUE; BYTE byPUSH_EAX; BYTE byMOV_ECX; DWORD dwMOV_ECX_VALUE; WORD wCALL_ECX; BYTE byMOV_EDX; DWORD dwMOV_EDX_VALUE; WORD wJMP_EDX; BYTE szTmpPath[16]; BYTE szDllPath[130]; }INJECT_CODE,*PINJECT_CODE; #pragma pack(pop) INJECT_CODE g_ic = {0}; PBYTE g_pbyOEP = NULL; VOID GetOrgCodesAndOep() { FILE *file = NULL; fopen_s(&file,"injorgcode.dat","rb"); fread(&g_ic,sizeof(g_ic),1,file); fread(&g_pbyOEP,sizeof(g_pbyOEP),1,file); fclose(file); } VOID RestoreExeEntryCode() { DWORD flNewProtect = PAGE_READWRITE,flOldProtect = 0; VirtualProtect(g_pbyOEP,sizeof(INJECT_CODE),flNewProtect,&flOldProtect); MoveMemory(g_pbyOEP,&g_ic,sizeof(INJECT_CODE)); VirtualProtect(g_pbyOEP,sizeof(INJECT_CODE),flOldProtect,&flNewProtect); } VOID JmpInstructionImpl() { //1.执行真正的加载操作 LoadLibrary((g_pbyOEP + offsetof(INJECT_CODE,szDllPath))); //2.恢复子进程的起始指令码 RestoreExeEntryCode(); //3.跳转到子进程的起始指令地址 __asm { popad mov eax,g_pbyOEP jmp g_pbyOEP; } } BOOL SetJmpInstruction() { PINJECT_CODE pInjCode = NULL; DWORD flNewProtect = PAGE_READWRITE,flOldProtect = 0; DWORD dwMOV_EDX_VALUE = (DWORD)&JmpInstructionImpl; __asm { call dels dels:pop eax sub eax,dels add dwMOV_EDX_VALUE,eax; } pInjCode = (PINJECT_CODE)g_pbyOEP; VirtualProtect(g_pbyOEP,sizeof(INJECT_CODE),flNewProtect,&flOldProtect); pInjCode->dwMOV_EDX_VALUE = dwMOV_EDX_VALUE; VirtualProtect(g_pbyOEP,sizeof(INJECT_CODE),flOldProtect,&flNewProtect); } BOOL WINAPI DllMain(HINSTANCE hInstDLL,DWORD fdwReason,LPVOID lpvReserved) { switch(fdwReason) { case DLL_PROCESS_ATTACH: DisableThreadLibraryCalls(hInstDLL); GetOrgCodesAndOep(); SetJmpInstruction(); } return TRUE; }
(3) 创建客户程序,用于将DLL注入到目标程序中。
//文件:InjSubProcess.c //编译:cl InjSubProcess.c //使用:InjSubProcess <目标程序(.exe)> <用于注入的DLL全路径> #include <windows.h> #include <strsafe.h> #include <stddef.h> #include <Imagehlp.h> #pragma comment(lib,"strsafe") #pragma comment(lib,"Imagehlp") #pragma pack(push,1) typedef struct _INJECT_CODE { BYTE byPUSHAD; BYTE byMOV_EAX; DWORD dwMOV_EAX_VALUE; BYTE byPUSH_EAX; BYTE byMOV_ECX; DWORD dwMOV_ECX_VALUE; WORD wCALL_ECX; BYTE byMOV_EDX; DWORD dwMOV_EDX_VALUE; WORD wJMP_EDX; BYTE szTmpPath[16]; BYTE szDllPath[130]; }INJECT_CODE,*PINJECT_CODE; #pragma pack(pop) //获取子进程的起始指令执行地址 PBYTE GetExeEntryPoint(char* file) { PLOADED_IMAGE pli = NULL; PBYTE pbyOEP = NULL; pli = ImageLoad(file,NULL); if(pli == NULL) return NULL; pbyOEP = (PBYTE)(pli->FileHeader->OptionalHeader.ImageBase + pli->FileHeader->OptionalHeader.AddressOfEntryPoint); ImageUnload(pli); return pbyOEP; } //保存子进程的起始指令码 BOOL SaveExeEntryCode(HANDLE hProcess,PBYTE pbyOEP) { FILE * file = NULL; INJECT_CODE ic = {0}; DWORD flNewProtect = PAGE_READWRITE,flOldProtect = 0; VirtualProtectEx(hProcess,pbyOEP,sizeof(ic),flNewProtect,&flOldProtect); ReadProcessMemory(hProcess,pbyOEP,&ic,sizeof(ic),NULL); VirtualProtectEx(hProcess,pbyOEP,sizeof(ic),flOldProtect,&flNewProtect); fopen_s(&file,"injorgcode.dat","wb"); fwrite(&ic,sizeof(ic),1,file); fwrite(&pbyOEP,sizeof(PBYTE),1,file); fclose(file); return TRUE; } //替换子进程的起始指令码 BOOL ReplaceExeEntryCode(HANDLE hProcess,PBYTE pbyOEP,char* dllfile) { INJECT_CODE ic = {0}; DWORD flNewProtect = PAGE_READWRITE,flOldProtect = 0; //00401000 > 60 pushad //00401001 B8 00000000 mov eax, &szDllPath //00401006 50 push eax //00401007 B9 01000000 mov ecx, &LoadLibraryA //0040100C FFD1 call ecx //0040100E BA 02000000 mov edx, 2 //00401013 FFE2 jmp edx ic.byPUSHAD = 0x60; ic.byMOV_EAX = 0xB8; ic.dwMOV_EAX_VALUE = (DWORD)(pbyOEP + offsetof(INJECT_CODE,szTmpPath)); ic.byPUSH_EAX = 0x50; ic.byMOV_ECX = 0xB9; ic.dwMOV_ECX_VALUE = (DWORD)(&LoadLibrary); ic.wCALL_ECX = 0xD1FF; ic.byMOV_EDX = 0xBA; ic.dwMOV_EDX_VALUE = 0x12345678; ic.wJMP_EDX = 0xE2FF; StringCbCopy(ic.szDllPath,130,dllfile); StringCbCopy(ic.szTmpPath,16,"InjLibProxy"); VirtualProtectEx(hProcess,pbyOEP,sizeof(ic),flNewProtect,&flOldProtect); WriteProcessMemory(hProcess,pbyOEP,&ic,sizeof(ic),NULL); VirtualProtectEx(hProcess,pbyOEP,sizeof(ic),flOldProtect,&flNewProtect); return TRUE; } int main(int argc,char **argv) { STARTUPINFO si = {sizeof(si)}; PROCESS_INFORMATION pi = {0}; BOOL fCreate = FALSE; PBYTE pbyOEP = NULL; if(argc != 3) { printf("usage:InjSubProcess <Exe file> <Dll File>"); return 0; } //1.创建子进程 fCreate = CreateProcess(NULL, argv[1], NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi); if(!fCreate) { printf("Create Process %s Failed./n",argv[1]); return 0; } //2.获取子进程的起始指令地址 pbyOEP = GetExeEntryPoint(argv[1]); if(NULL == pbyOEP) { printf("can not get entry point of [%s]./n",argv[1]); return 0; } //3.保存子进程的起始指令码 SaveExeEntryCode(pi.hProcess,pbyOEP); //4.替换子进程的起始指令码 ReplaceExeEntryCode(pi.hProcess,pbyOEP,argv[2]); //5.子进程继续执行 ResumeThread(pi.hThread); //6.主进程暂停5秒钟,等待子进程加载完毕 Sleep(5000); //7.关闭所有子进程的句柄 CloseHandle(pi.hThread); CloseHandle(pi.hProcess); }
(4) 测试运行,将InjSubProcess.exe和InjLibProx.dll放在同一目录下,运行:InjSubProcess F:/bak/ipmsg.exe F:/InjLib.dll,目标进程被成功注入InjLib.dll。
条目7、通过覆盖代码来拦截API
原理:在内存中对要拦截的函数进行定位,覆盖其头几个字节使之跳转到我们自身的函数内。
不足:首先,对CPU有依赖性:x86、x64、IA-64以及其他CPU的jmp指令各不相同。其次,在抢占式、多线程环境下易出错!
示例代码:
//文件:HookAPIDemo.c //编译: cl HookAPIDemo.c #include <windows.h> #pragma comment(lib,"user32") //00401000 > B8 01000000 mov eax, 1 //00401005 FFE0 jmp eax #pragma pack(push,1) typedef struct _INJECT_CODE { BYTE byMOV_EAX; DWORD dwMOV_EAX_VALUE; WORD wJMP_EAX; }INJECT_CODE,*PINJECT_CODE; #pragma pack(pop) INJECT_CODE g_orgic = {0}; //声明格式和挂接的API一样 int WINAPI MyMessageBox(HWND hWnd,LPCTSTR lpText,LPCTSTR lpCaption,UINT uType) { printf("MessageBox has been hook!"); return 0; } void UnhookMessageBox() { DWORD flNewProtect = PAGE_READWRITE,flOldProtect = 0; PINJECT_CODE pMsgBox = (PINJECT_CODE)MessageBox; VirtualProtect(pMsgBox,sizeof(DWORD),flNewProtect,&flOldProtect); //恢复起始指令 *pMsgBox = g_orgic; VirtualProtect(pMsgBox,sizeof(DWORD),flOldProtect,&flNewProtect); } void HookMessageBox() { DWORD dwMyMsgBoxOffset = (DWORD)&MyMessageBox; DWORD flNewProtect = PAGE_READWRITE,flOldProtect = 0; PINJECT_CODE pMsgBox = (PINJECT_CODE)MessageBox; VirtualProtect(pMsgBox,sizeof(DWORD),flNewProtect,&flOldProtect); //!.保存起始指令 g_orgic = *pMsgBox; __asm { push eax call dels dels:pop eax sub eax,dels add dwMyMsgBoxOffset,eax pop eax } //2.改写起始指令 pMsgBox->byMOV_EAX = 0xB8; pMsgBox->dwMOV_EAX_VALUE = dwMyMsgBoxOffset; pMsgBox->wJMP_EAX = 0xE0FF; VirtualProtect(pMsgBox,sizeof(DWORD),flOldProtect,&flNewProtect); } int main(void) { MessageBox(NULL,"1.no be hook.","normal",MB_OK); HookMessageBox(); MessageBox(NULL,"2.no be hook.","normal",MB_OK); UnhookMessageBox(); MessageBox(NULL,"3.no be hook.","normal",MB_OK); }
条目8、通过修改模块的导入段来拦截API
原理:通过修改目标进程的导入段中函数来达到拦截API的目的。
示例代码:
//文件:PatchImpFun.c //编译:cl PatchImpFun.c //说明:本程序对ExitProcess进行拦截 #include <windows.h> #pragma comment(lib,"user32") typedef VOID (WINAPI *FNEXITPROCESS)(UINT uExitCode); FNEXITPROCESS g_fnOrgExitProcess = NULL; VOID WINAPI MyExitProcess(UINT uExitCode) { MessageBox(NULL,"进程调用了ExitProcess退出!","MyExitProcess",MB_OK); g_fnOrgExitProcess(uExitCode); } void HookExitProcess() { PCHAR szDllName = NULL; HMODULE hModule = NULL; PIMAGE_DOS_HEADER pDosHeader = NULL; PIMAGE_NT_HEADERS pNtHeaders = NULL; PIMAGE_DATA_DIRECTORY pImpDataDirectory = NULL; PIMAGE_IMPORT_DESCRIPTOR pImpDescriptor = NULL; PIMAGE_THUNK_DATA pImpThunkData = NULL; DWORD flNewProtect = PAGE_READWRITE, flOldProtect = 0; hModule = GetModuleHandle(NULL); pDosHeader = (PIMAGE_DOS_HEADER)hModule; pNtHeaders = (PIMAGE_NT_HEADERS)((PCHAR)pDosHeader + pDosHeader->e_lfanew); pImpDataDirectory = (PIMAGE_DATA_DIRECTORY)(pNtHeaders->OptionalHeader.DataDirectory + 1); pImpDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)((PCHAR)pDosHeader + pImpDataDirectory->VirtualAddress); VirtualProtect(pImpDescriptor,pImpDataDirectory->Size,flNewProtect,&flOldProtect); while(pImpDescriptor->Characteristics) { szDllName = (PCHAR)pDosHeader + pImpDescriptor->Name; //printf("DLL Module:%s/n",szDllName); CharUpperBuff(szDllName,lstrlen(szDllName)); if(!lstrcmp(szDllName,"KERNEL32.DLL")) { pImpThunkData = (PIMAGE_THUNK_DATA)((PCHAR)pDosHeader+pImpDescriptor->FirstThunk); while(pImpThunkData->u1.Function) { //1.找到导入表中要替换的API if(pImpThunkData->u1.Function == (DWORD)&ExitProcess) { printf("Found ExitProcess=%08X",(DWORD)&ExitProcess); //2.保存原先的ExitProcess地址 g_fnOrgExitProcess = &ExitProcess; //3.将其替换为自己实现的一个函数上 VirtualProtect(&(pImpThunkData->u1.Function),sizeof(DWORD),flNewProtect,&flOldProtect); *(&(pImpThunkData->u1.Function)) = (DWORD)&MyExitProcess; break; } pImpThunkData ++; } if(pImpThunkData->u1.Function)break; } pImpDescriptor ++; } } int main(void) { //修复IAT,使ExitProcess函数指向自定义函数MyExitProcess. HookExitProcess(); //当程序退出时候,会调用MyExitProcess! return 0; }
测试运行:当程序退出时,系统弹跳出对话话提示我们!
以上代码基于XP SP2系统所做的测试,不保证在其他版本的WINDOWS系统上运行正确。