本人的第一篇随笔,简单介绍一下经典的inline hook技术。
钩取(Hooking)是一种截取信息、更改程序流向、添加新功能的技术。钩取技术多种多样,其中钩取Win32 API的技术被称为API钩取。它与消息钩取共同广泛应用于用户模式(ring3)。这里我以MessageBoxW这个简单的API为例,实现简单的程序自身inline hook。
MessageBoxW函数原型:
1 int WINAPI MessageBoxW( 2 _In_opt_ HWND hWnd, // 父窗口句柄 3 _In_opt_ LPCWSTR lpText, // 文本 4 _In_opt_ LPCWSTR lpCaption, // 标题 5 _In_ UINT uType); // 按键类型组合
所谓inline hook,就是将API代码的前5个字节修改为JMP XXXXXXXX指令来钩取API。调用执行被钩取的API时,(修改后的)JMP XXXXXXXX指令就会被执行,转向控制hooking函数。
hook之前的API代码开头
hook之后的API代码开头
可以看出,在hook之后,当用户调用API时,会直接跳转到hook处理函数,在hook处理函数中,可以进行其他操作,但需要保证函数原型及参数一致,以保证堆栈平衡。如果在hooking函数内部要调用原API函数,则在调用前需要进行“脱钩”处理,否则如果直接调用,API开头又是一个跳转指令,将陷入死循环。在调用之后再进行hook,方便下次钩取,以上就是hook的大致流程。
详细代码如下:
#include <windows.h> #include <stdio.h> BYTE g_pOrgMSGBOXW[5] = { 0, }; // 用于存储API开头的5字节 // hooking函数原型,保证与原API(MessageBoxW)一致 int WINAPI NewMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType); // 函数指针 typedef int(WINAPI *PFMESSAGEBOXW)( HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType ); BOOL inlinehook(LPCSTR szDllName, LPCSTR szFuncName, PROC pfnNew, PBYTE pOrgBytes) { HMODULE hModule = NULL; FARPROC pFunc = NULL; // API函数指针 BYTE pBuf[5] = { 0, }; // 形成跳转指令的5字节代码 PBYTE pByte = NULL; DWORD dwOldProtect = 0; DWORD dwJmpOffSet = 0; // 跳转的偏移值(= 新函数地址 - (原函数地址 + 5)) // 获取目标模块句柄(user32.dll) if (!(hModule = GetModuleHandleA(szDllName))) { printf("GetModuleHandleA error: %d\n", GetLastError()); return FALSE; } // 获取要钩取的 API 地址 if (!(pFunc = GetProcAddress(hModule, szFuncName))) { printf("GetProcAddress error: %d\n", GetLastError()); return FALSE; } // 修改内存属性,因为这边要修改内存中的代码数据 VirtualProtect((LPVOID)pFunc, 5, PAGE_READWRITE, &dwOldProtect); // 如果已经被 hook, 则失败 pByte = (PBYTE)pFunc; if (pByte[0] == 0xE9) { return FALSE; } memcpy(pOrgBytes, pFunc, 5); // 保存原代码 pBuf[0] = 0xE9; // JMP XXXXXXXX指令的第一个字节是0xE9 dwJmpOffSet = (DWORD)pfnNew - ((DWORD)pFunc + 5); // 计算跳转的偏移值 memcpy(&pBuf[1], &dwJmpOffSet, 4); memcpy(pFunc, pBuf, 5); // 修改原API地址处的代码 VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect); return TRUE; } BOOL unhook(LPCSTR szDllName, LPCSTR szFuncName, PBYTE pOrgBytes) { HMODULE hModule = NULL; FARPROC pFunc = NULL; PBYTE pByte = NULL; DWORD dwOldProtect = 0; if (!(hModule = GetModuleHandleA(szDllName))) { printf("GetModuleHandleA error: %d\n", GetLastError()); return FALSE; } if (!(pFunc = GetProcAddress(hModule, szFuncName))) { printf("GetProcAddress error: %d\n", GetLastError()); return FALSE; } VirtualProtect((LPVOID)pFunc, 5, PAGE_READWRITE, &dwOldProtect); // 如果未被 hook, 则失败 pByte = (PBYTE)pFunc; if (pByte[0] != 0xE9) { return FALSE; } memcpy(pFunc, pOrgBytes, 5); // 将原API的代码还原 VirtualProtect((LPVOID)pFunc, 5, dwOldProtect, &dwOldProtect); return TRUE; } int WINAPI NewMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType) { int iRet = 0; FARPROC pFunc = NULL; // 为调用原API,这里需要进行“脱钩” unhook("user32.dll", "MessageBoxW", g_pOrgMSGBOXW); if (!(pFunc = GetProcAddress(GetModuleHandleA("user32.dll"), "MessageBoxW"))) { printf("GetProcAddress error: %d\n", GetLastError()); return FALSE; } // 调用原API iRet = ((PFMESSAGEBOXW)pFunc)(hWnd, L"你被 hook 了!", lpCaption, uType); // 再进行hook,方便下次钩取 inlinehook("user32.dll", "MessageBoxW", (PROC)NewMessageBoxW, g_pOrgMSGBOXW); return iRet; } int main() { inlinehook("user32.dll", "MessageBoxW", (PROC)NewMessageBoxW, g_pOrgMSGBOXW); MessageBoxW(NULL, L"这是正常的", L"提示", MB_OK); unhook("user32.dll", "MessageBoxW", g_pOrgMSGBOXW); MessageBoxW(NULL, L"这是正常的", L"提示", MB_OK); return 0; }
运行结果:
这里只是程序自身自行hook,没有用到dll注入等技术,以后会写上。