实现32位和64位系统的Inline Hook API

Krismile

发布日期: 2018-12-03 08:51:07 浏览量: 1172
评分:
star star star star star star star star star_border star_border
*转载请注明来自write-bug.com

背景

API HOOK技术是一种用于改变API执行结果的技术,Microsoft 自身也在 Windows 操作系统里面使用了这个技术,如Windows兼容模式等。 API HOOK 技术并不是计算机病毒专有技术,但是计算机病毒经常使用这个技术来达到隐藏自己的目的。

本文就是向大家讲解在 32 位系统和 64 位系统下的 Inline Hook Api 技术的具体实现,现在,我就把实现思路和原理整理成文档,分享给大家。

实现原理

我们程序的 Inline Hook API 实现原理不难理解,核心原理就是获取进程中,指定 API 函数的地址,然后修改该函数入口地址的前几个字节,修改为跳转到我们的新 API 函数,执行我们自己的操作。

要注意区分 32 位系统和 64 位系统,因为 32 位和 64 位系统的指针长度是不同的,导致地址长度也不同。32 位下用 4 字节表示地址,而 64 位下使用 8 字节来表示地址。

在 32 位下,汇编跳转语句为:

  1. jmp _dwNewAddress

机器码为:

  1. e9 _dwOffset(跳转偏移)

其中,要注意理解跳转偏移的计算方法:

  1. addr1 --> jmp _dwNewAddress指令的下一条指令的地址,即 eip 的值
  2. addr2 --> 跳转地址的值,即 _dwNewAddress 的值
  3. 跳转偏移 _dwOffset = addr2 - addr1

在 64 位下,汇编跳转语句为:

  1. mov rax, _dwNewAddress(0x1122334455667788)
  2. jmp rax

机器码为:

  1. 48 b8 _dwNewAddress(0x1122334455667788)
  2. ff e0

所以,32 位下要更改前 5 字节数据; 64 位下要更改前 12 字节数据。

那么 HOOK 的流程为:

  • 首先,我们从进程中获取 HOOK API 对应的模块基址,这样,就可以使用 GetProcAddress 函数获取 API 函数的在进程中的地址。

  • 然后,我们根据 32 位和 64 位版本,计算 HOOK API 函数的前几字节数据。

  • 接着,修改 API 函数的前几字节数据的页面保护属性,更改为可读、可写、可执行,然后,我们便获取原来前几字节数据后,再写入新的跳转数据。

  • 最后,还原页面保护属性。

UNHHOK 的流程基本上和 HOOK 的流程是一样的,只不过这次写入的数据是之前保存的前几字节数据。这样,API函数又恢复正常了。

编码实现

Hook API

  1. // Hook Api
  2. BOOL HookApi_MessageBoxA()
  3. {
  4. // 获取 user32.dll 模块加载基址
  5. HMODULE hDll = ::GetModuleHandle("user32.dll");
  6. if (NULL == hDll)
  7. {
  8. return FALSE;
  9. }
  10. // 获取 MessageBoxA 函数的导出地址
  11. PVOID OldMessageBoxA = ::GetProcAddress(hDll, "MessageBoxA");
  12. if (NULL == OldMessageBoxA)
  13. {
  14. return FALSE;
  15. }
  16. // 计算写入的前几字节数据, 32位下5字节, 64位下12字节
  17. #ifndef _WIN64
  18. // 32位
  19. // 汇编代码:jmp _dwNewAddress
  20. // 机器码位:e9 _dwOffset(跳转偏移)
  21. // addr1 --> jmp _dwNewAddress指令的下一条指令的地址,即eip的值
  22. // addr2 --> 跳转地址的值,即_dwNewAddress的值
  23. // 跳转偏移 _dwOffset = addr2 - addr1
  24. BYTE pNewData[5] = {0xe9, 0, 0, 0, 0};
  25. DWORD dwNewDataSize = 5;
  26. DWORD dwOffset = 0;
  27. // 计算跳转偏移
  28. dwOffset = (DWORD)NewMessageBoxA - ((DWORD)OldMessageBoxA + 5);
  29. ::RtlCopyMemory(&pNewData[1], &dwOffset, sizeof(dwOffset));
  30. #else
  31. // 64位
  32. // 汇编代码:mov rax, _dwNewAddress(0x1122334455667788)
  33. // jmp rax
  34. // 机器码是:
  35. // 48 b8 _dwNewAddress(0x1122334455667788)
  36. // ff e0
  37. BYTE pNewData[12] = { 0x48, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xe0};
  38. DWORD dwNewDataSize = 12;
  39. ULONGLONG ullNewFuncAddr = (ULONGLONG)NewMessageBoxA;
  40. ::RtlCopyMemory(&pNewData[2], &ullNewFuncAddr, sizeof(ullNewFuncAddr));
  41. #endif
  42. // 设置页面的保护属性为 可读、可写、可执行
  43. DWORD dwOldProtect = 0;
  44. ::VirtualProtect(OldMessageBoxA, dwNewDataSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);
  45. // 保存原始数据
  46. ::RtlCopyMemory(g_pOldData, OldMessageBoxA, dwNewDataSize);
  47. // 开始修改 MessageBoxA 函数的前几字节数据, 实现 Inline Hook API
  48. ::RtlCopyMemory(OldMessageBoxA, pNewData, dwNewDataSize);
  49. // 还原页面保护属性
  50. ::VirtualProtect(OldMessageBoxA, dwNewDataSize, dwOldProtect, &dwOldProtect);
  51. return TRUE;
  52. }

Unhook Api

  1. // Unhook Api
  2. BOOL UnhookApi_MessageBoxA()
  3. {
  4. // 获取 user32.dll 模块加载基址
  5. HMODULE hDll = ::GetModuleHandle("user32.dll");
  6. if (NULL == hDll)
  7. {
  8. return FALSE;
  9. }
  10. // 获取 MessageBoxA 函数的导出地址
  11. PVOID OldMessageBoxA = ::GetProcAddress(hDll, "MessageBoxA");
  12. if (NULL == OldMessageBoxA)
  13. {
  14. return FALSE;
  15. }
  16. // 计算写入的前几字节数据, 32位下5字节, 64位下12字节
  17. #ifndef _WIN64
  18. DWORD dwNewDataSize = 5;
  19. #else
  20. DWORD dwNewDataSize = 12;
  21. #endif
  22. // 设置页面的保护属性为 可读、可写、可执行
  23. DWORD dwOldProtect = 0;
  24. ::VirtualProtect(OldMessageBoxA, dwNewDataSize, PAGE_EXECUTE_READWRITE, &dwOldProtect);
  25. // 恢复数据
  26. ::RtlCopyMemory(OldMessageBoxA, g_pOldData, dwNewDataSize);
  27. // 还原页面保护属性
  28. ::VirtualProtect(OldMessageBoxA, dwNewDataSize, dwOldProtect, &dwOldProtect);
  29. return TRUE;
  30. }

新 API 函数

  1. // 新的 API
  2. int WINAPI NewMessageBoxA(
  3. HWND hWnd, // handle to owner window
  4. LPCTSTR lpText, // text in message box
  5. LPCTSTR lpCaption, // message box title
  6. UINT uType // message box style
  7. )
  8. {
  9. // Unhook
  10. UnhookApi_MessageBoxA();
  11. // 获取 user32.dll 模块加载基址
  12. HMODULE hDll = ::GetModuleHandle("user32.dll");
  13. if (NULL == hDll)
  14. {
  15. return FALSE;
  16. }
  17. // 获取 MessageBoxA 函数的导出地址
  18. typedef_MessageBoxA OldMessageBoxA = (typedef_MessageBoxA)::GetProcAddress(hDll, "MessageBoxA");
  19. if (NULL == OldMessageBoxA)
  20. {
  21. return FALSE;
  22. }
  23. int iRet = OldMessageBoxA(hWnd, "I am Nobody!!!", "Who Am I", MB_YESNO);
  24. // Hook
  25. HookApi_MessageBoxA();
  26. return iRet;
  27. }

程序测试

我们直接运行一个加载程序,首先,先调用 MessageBoxA 函数,这是还没有执行 API HOOK,正常弹窗:

然后,使程序加载 InlineHookApi_Test.dll ,这样,DLL 便会在入口点函数 DllMain 中就开始 Hook 进程中的 MessageBoxA 函数。在调用 MessageBoxA 函数进行测试,数据成功更改,HOOK API 成功:

测试了 32 位版本系统成功,测试了 64 位版本系统也成功。

总结

要注意两个地方:

  • 一是在修改导出函数地址前几字节数据的时候,建议先对页面属性保护重新设置一下,可以调用 VirtualProtect 函数将页面属性保护设置成 可读、可写、可执行的属性 PAGE_EXECUTE_READWRITE。这样,我们在对这块内存操作的时候,就不会报错。

  • 二是我们在创建新的API来替代就的API函数的时候,新API函数声明一定要加上 WINAPI(__stdcall)的函数调用约定,否则新函数会默认使用 C 语言的调用约定。否则,会在函数返回,堆栈平衡操作的时候会报错的。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download InlineHookApi_Test.7z ( 188.47kb, 9次下载 )

发送私信

真正的强者不是要压倒一切,而是不被一切压倒

16
文章数
12
评论数
最近文章
eject