在Windows上实现远程IAT hook
一、概述
IAT hook,就是我们要hook的目标函数在导入表IT中,通过修改目标函数所在IAT中项的地址为我们自定义函数的地址,从而实现函数hook。
在这里我们要hook的目标是一个远程进程,采用之前博客《Windows上的进程注入》一文中用到的反射式dll注入这一技术,在其中添加额外的IAT hook代码来实现hook。
二、远程进程 IAT hook 原理
hook的步骤:
- 将dll文件注入到目标进程中并完成加载;
- 在DllMain函数中调用hook处理函数;
- 遍历导入表寻找目标函数所在IAT中的位置;
- 保存原目标函数地址;
- 修改对应IAT中的项,将存储的地址修改为我们定义的函数。
我们可以通过调用GetModuleHandle(NULL)来获取该进程的映像基址,这样我们就可以访问该映像的PE头了,其中可选头就有我们需要的导入表描述符的地址信息。经过几次的成员访问,指针跳转(内容有点啰嗦,就略过了。不懂的同学可以参考PE头相关内容),找到导入表第一个dll成员描述符的地址。每一个dll成员描述符(IMAGE_IMPORT_DESCRIPTOR结构体)都有两个数组OriginalFirstThunk和FirstThunk,它们分别指向该dll的INT(导入名称表)和IAT(导入地址表)。IMAGE_IMPORT_DESCRIPTOR结构体如下:
typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; //该项指向INT }; DWORD TimeDateStamp; //时间戳 DWORD ForwarderChain; //指向前一个IMAGE_IMPORT_DESCRIPTOR DWORD Name; //dll的名字 DWORD FirstThunk; //该项指向IAT } IMAGE_IMPORT_DESCRIPTOR;
INT和IAT的类型皆为_IMAGE_THUNK_DATA32,如下:
typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE DWORD Function; // PDWORD DWORD Ordinal; DWORD AddressOfData; //RVA 指向_IMAGE_IMPORT_BY_NAME } u1; } IMAGE_THUNK_DATA32;
它们每一项都按照顺序还是一一对应的。我们通过比较INT中对应项记录的函数名判断其是否为我们的目标函数,若是则修改对应IAT中的项记录的函数地址,并保存一份原始数据。
因为IAT所在的内存空间默认是不可写的,所以我们还需要修改一下权限,通过使用VirtualProtect函数添加一个写入权限即可。为了进程安全和稳定,完事后记得改回来。
最好,我们编写一个自定义的函数用于替换目标函数,这里我选取了MessageBoxA函数为目标。
int hook_MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { char lpTextBak[100] = "Successfuly Hooked!"; MESSAGEBOXA originalMessageBoxA = (MESSAGEBOXA)savedAddress; return originalMessageBoxA(hWnd, lpTextBak, lpCaption, uType); }
三、代码实现
IAT hook 代码如下:
void hookIATFunctionByName(char *functionName, ULONG_PTR hookAddress) { ULONG_PTR pBaseAddress; ULONG_PTR pFuncAddress; ULONG_PTR pNameArray; ULONG_PTR pExportDir; ULONG_PTR pNameOrdinals; DWORD dwCounter = 0; savedAddress = 0; pBaseAddress = (ULONG_PTR)GetModuleHandle(NULL); ULONG_PTR pTmp1 = pBaseAddress + ((PIMAGE_DOS_HEADER)pBaseAddress)->e_lfanew; ULONG_PTR pTmp2 = (ULONG_PTR)&((PIMAGE_NT_HEADERS)pTmp1)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; // pTmp3 = 导入表第一个dll成员描述符的地址 ULONG_PTR pTmp3 = (pBaseAddress + ((PIMAGE_DATA_DIRECTORY)pTmp2)->VirtualAddress); ULONG_PTR pTmp4 = 0; //遍历所有dll的导入表 while ( ((PIMAGE_IMPORT_DESCRIPTOR)pTmp3)->Name ) { // pTmp4 = VA of the OriginalFirstThunk------ INT pTmp4 = (pBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)pTmp3)->OriginalFirstThunk); // pTmp1 = VA of the IAT (via first thunk not origionalfirstthunk) pTmp1 = (pBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)pTmp3)->FirstThunk); while (DEREF(pTmp4)) { pTmp2 = (pBaseAddress + DEREF(pTmp4)); if (strcmp((char*)((PIMAGE_IMPORT_BY_NAME)pTmp2)->Name, functionName) == 0) { MEMORY_BASIC_INFORMATION mbi; DWORD dwOldProtect; VirtualQuery(pTmp1, &mbi, sizeof(mbi)); BOOL bRetn = VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &dwOldProtect); if (bRetn) { savedAddress = DEREF(pTmp1); DEREF(pTmp1) = (ULONG_PTR)hookAddress; VirtualProtect(mbi.BaseAddress, mbi.RegionSize, dwOldProtect, 0); } dwCounter = 1; break; } pTmp1 += sizeof(ULONG_PTR); if (pTmp4) pTmp4 += sizeof(ULONG_PTR); } if (dwCounter == 1) break; pTmp3 += sizeof(IMAGE_IMPORT_DESCRIPTOR); } }
关于DllMain函数的代码如下:
BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved ) { BOOL bReturnValue = TRUE; switch( dwReason ) { case DLL_QUERY_HMODULE: if( lpReserved != NULL ) *(HMODULE *)lpReserved = hAppInstance; break; case DLL_PROCESS_ATTACH: hAppInstance = hinstDLL; hookIATFunctionByName("MessageBoxA", (DWORD)hook_MessageBoxA); MessageBoxA( NULL, "Hello from DllMain!", "Reflective Dll Injection", MB_OK ); break; case DLL_PROCESS_DETACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; } return bReturnValue; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构