上节内容是使用的调试器Hook,通过修改目标代码第一个字节为0xCC
来设置调试事件,随后当相关API被调用,由于此时第一个字节被设置为断点异常调试事件
,可以被调试器捕获到,此时获取程序执行上下文的CONTEXT结构
可以获取到对应API的参数地址,做相应的修改后,恢复API第一个字节,之后在正常调用,由于参数被修改了,借此达到Hook的目的。
本节采取的是另外一种方法,Hook IAT
中的函数。
展示书中Hook的实例,钩取的目标API是user32.SetWindowTextW
,函数原型:
1 2 3 4 BOOL SetWindowTextW ( [in] HWND hWnd, [in, optional] LPCWSTR lpString ) ;
钩取后修改第二个参数内容,可以达到修改Windows窗口即将显示的内容,比如修改计算器的显示字符为中文:(由于计算器可能因OS版本存在差异,这里笔者使用的为书的作者的计算器)
PE格式回顾 明确本节的内容目的在于Hook IAT中的API,需要从程序中读取IAT中的目标API。
这里回顾下PE文件格式的内容,建议读者和我一样,尽可能按照手动查找的方式,不过于依赖工具,假以时日PE格式解读会更上一层路。话不多说,我们开始。
在HxD下,找到相关字段:
读取出IAT的位置为0x00012B80(RVA)
,查看区段表,发现该地址位于节区.text
节区.text
的节区头信息如下:
地址转换,RVA -> RAW : 0x00012B80 - 0x00001000 + 0x00000400 = 0x00011F80
,文件中跟随该地址:
以0x14 byte为单位进行读取(这是由于IAT表项指向单元为IMAGE_IMPORT_DESCRIPTOR
,大小占0x14 bytes),上图已经划分好,可以看到,calc.exe载入6个DLL文件,最后一个是结束标志,以全0结构体IMAGE_IMPORT_DESCRIPTOR
为标识。
1 2 3 4 5 6 7 8 9 10 11 typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; } DUMMYUNIONNAME; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR;
一次查看,发现了我们的目标函数SetWindowsText
所在的DLL文件—user32.dll
,对应倒数第二个结构项。
1 B0 2C 01 00 FF FF FF FF FF FF FF FF A4 36 01 00 A4 10 00 00
对应的存放着IAT地址为RVA = 0x00012CB0 -> RAW = 0x00120B0
,[0x00120B0]是IAT的地址 = 0x0001335C -> 0x0001275C
查看到了目标API是存在与IAT中的。于是开始下边的实验部分。
实验部分 从源码入手分析,该Hook方法的实施细节,这里贴一下完整代码(DLL),需要配合先前的DLL注入器使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 #include "stdio.h" #include "wchar.h" #include "windows.h" typedef BOOL (WINAPI *PFSETWINDOWTEXTW) (HWND hWnd, LPWSTR lpString) ;FARPROC g_pOrgFunc = NULL ; BOOL WINAPI MySetWindowTextW (HWND hWnd, LPWSTR lpString) { wchar_t * pNum = L"¿µÀÏÀÌ»ï»ç¿ÀÀ°Ä¥Æȱ¸" ; wchar_t temp[2 ] = {0 ,}; int i = 0 , nLen = 0 , nIndex = 0 ; nLen = wcslen(lpString); for (i = 0 ; i < nLen; i++) { if ( L'0' <= lpString[i] && lpString[i] <= L'9' ) { temp[0 ] = lpString[i]; nIndex = _wtoi(temp); lpString[i] = pNum[nIndex]; } } return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString); } BOOL hook_iat (LPCSTR szDllName, PROC pfnOrg, PROC pfnNew) { HMODULE hMod; LPCSTR szLibName; PIMAGE_IMPORT_DESCRIPTOR pImportDesc; PIMAGE_THUNK_DATA pThunk; DWORD dwOldProtect, dwRVA; PBYTE pAddr; hMod = GetModuleHandle(NULL ); pAddr = (PBYTE)hMod; pAddr += *((DWORD*)&pAddr[0x3C ]); dwRVA = *((DWORD*)&pAddr[0x80 ]); pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA); for ( ; pImportDesc->Name; pImportDesc++ ) { szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name); if ( !_stricmp(szLibName, szDllName) ) { pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod + pImportDesc->FirstThunk); for ( ; pThunk->u1.Function; pThunk++ ) { if ( pThunk->u1.Function == (DWORD)pfnOrg ) { VirtualProtect((LPVOID)&pThunk->u1.Function, 4 , PAGE_EXECUTE_READWRITE, &dwOldProtect); pThunk->u1.Function = (DWORD)pfnNew; VirtualProtect((LPVOID)&pThunk->u1.Function, 4 , dwOldProtect, &dwOldProtect); return TRUE; } } } } return FALSE; } BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch ( fdwReason ) { case DLL_PROCESS_ATTACH : g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll" ), "SetWindowTextW" ); hook_iat("user32.dll" , g_pOrgFunc, (PROC)MySetWindowTextW); break ; case DLL_PROCESS_DETACH : hook_iat("user32.dll" , (PROC)MySetWindowTextW, g_pOrgFunc); break ; } return TRUE; }
DllMain 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { switch ( fdwReason ) { case DLL_PROCESS_ATTACH : g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll" ), "SetWindowTextW" ); hook_iat("user32.dll" , g_pOrgFunc, (PROC)MySetWindowTextW); break ; case DLL_PROCESS_DETACH : hook_iat("user32.dll" , (PROC)MySetWindowTextW, g_pOrgFunc); break ; }
第7、8行,获取目标API的地址,存放在g_pOrgFunc
中
调用hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
,根据下边的函数原型,可以看到传入的参数大致情况:
第一个参数,目标API所在的DLL文件
第二个参数,目标API所在DLL中的地址
第三个参数,钩子函数地址。
hook_iat 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 BOOL hook_iat (LPCSTR szDllName, PROC pfnOrg, PROC pfnNew) { HMODULE hMod; LPCSTR szLibName; PIMAGE_IMPORT_DESCRIPTOR pImportDesc; PIMAGE_THUNK_DATA pThunk; DWORD dwOldProtect, dwRVA; PBYTE pAddr; hMod = GetModuleHandle(NULL ); pAddr = (PBYTE)hMod; pAddr += *((DWORD*)&pAddr[0x3C ]); dwRVA = *((DWORD*)&pAddr[0x80 ]); pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA); for ( ; pImportDesc->Name; pImportDesc++ ) { szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name); if ( !_stricmp(szLibName, szDllName) ) { pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod + pImportDesc->FirstThunk); for ( ; pThunk->u1.Function; pThunk++ ) { if ( pThunk->u1.Function == (DWORD)pfnOrg ) { VirtualProtect((LPVOID)&pThunk->u1.Function, 4 , PAGE_EXECUTE_READWRITE, &dwOldProtect); pThunk->u1.Function = (DWORD)pfnNew; VirtualProtect((LPVOID)&pThunk->u1.Function, 4 , dwOldProtect, &dwOldProtect); return TRUE; } } } } return FALSE; }
第10行,获取本进程的句柄(因为GetModuleHandle
的参数是NULL)句柄存放在hMod
变量中
第11行,将hMod
转为PBYTE类型付给pAddr
,这使得pAddr指向文件的A to MZ signature (IMAGE_DOS_HEADER)
后续应该是根据这里,查找SetWindowsText
API的调用地址(文件IAT)
第14-20行,从PE文件开头定位到PE文件中导入表结构体IMAGE_IMPORT_DESCRIPTOR
第22-57行,遍历结构体IMAGE_IMPORT_DESCRIPTOR
,找到目标DLL–user32.dll
对应的IMAGE_IMPORT_DESCRIPTOR
结构体
里层遍历user32.dll
导入的函数,匹配目标API–SetWindowsText
pThunk->u1.Function == (DWORD)pfnOrg
ps:之所以能这样是由于user32.dll是系统级DLL,系统中所有进程共享一份,因此能够这样跨进程(Injectdll.exe -> calc.exe)匹配。找到后实施Hook
VirtualProtect
修改相应内存的保护权限,需要写入,执行权限。
pThunk->u1.Function = (DWORD)pfnNew
覆盖IAT中目标API地址为钩子函数,这样当进程调用它以为是SetWindowsText
时,其实调用的是Hook Function
覆盖完IAT后,需要使用VirtualProtect
恢复相应内存的旧权限,这是因为可能PE有相关字段对应着,不恢复可能PE装载器装载时可能出现错误
hook function 钩子函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 BOOL WINAPI MySetWindowTextW (HWND hWnd, LPWSTR lpString) { wchar_t * pNum = L"一二三四五六七八九" ; wchar_t temp[2 ] = {0 ,}; int i = 0 , nLen = 0 , nIndex = 0 ; nLen = wcslen(lpString); for (i = 0 ; i < nLen; i++) { if ( L'0' <= lpString[i] && lpString[i] <= L'9' ) { temp[0 ] = lpString[i]; nIndex = _wtoi(temp); lpString[i] = pNum[nIndex]; } } return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString); }
回顾
定位到PE文件中的OptionalHeader.DataDirectory[1]
记录的第一个IMAGE_IMPORT_DESCRIPTOR
结构体
遍历所有IMAGE_IMPORT_DESCRIPTOR
中的name字段,判断是否是目标DLL
确定目标DLL后,获取目标函数相应在DLL中的位置
修改相应的内存权限,覆盖目标函数的的地址
下钩成功
下钩成功后,程序正常调用被Hook的API时,实际调用的是我们覆写进去的钩子函数的地址,在钩子函数相应的参数修改,由于此时API的参数被修改了,放行程序即可完成一次完整的API Hook