查看: 288|回复: 1

[转载图文] Hook技术:Ring3层下的Inline Hook详解【附源码】

[复制链接]

29

主题

38

帖子

1

精华

VIP

Rank: 16

学币
365
荣耀
0
rank
200
违规
0

鬼斧神工

发表于 2020-5-21 20:38:15 | 显示全部楼层 |阅读模式

  Ring3层下的Inline Hook是最常用的Hook手段之一,是一种通过修改机器码的方式来实现hook的技术。
  探究API调用正常执行流程

我们知道正常函数的执行流程是call 0xxxxxxxx,然后跳转到调用的函数去执行。
  
  0x1000地址的call指令执行后跳转到0x3000地址处执行,执行完毕后再返回执行call指令的下一条指令。
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】
  
  我们以MessageBox为例:
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】
  
  正常调用如下:
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

函数执行流程  
首先是call MessageBox:
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】
  
  可以看到MessageBox的地址为77101370:
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】
  跳转到Message的程序开头。
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】
  
  我们在hook的时候,可能会读取或者修改call指令执行之前所压入栈的内容。
  
  那么,我们可以将call指令替换成jmp指令,jmp到我们自己编写的函数,在函数里call原来的函数,函数结束后再jmp回到原先call指令的下一条指令。如图:
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】
  探究Inline Hook中的函数调用过程
  实现思路比较简单,主要思想:
1、构造跳转指令。
2、在内存中找到欲HOOK函数地址,并保存欲HOOK位置处的前5个字节。(但其实并不一定就是5个字节,像是MessageBox就是6个字节)
3、将构造的跳转指令写入需HOOK的位置处。
4、当被HOOK位置被执行时会转到我们的流程执行。
5、如果要执行原来的流程,那么恢复HOOK,也就是还原被修改的字节。
6、执行函数原来的流程。
  在这过程中,主要涉及到几个问题:
1、如何定位自己要hook的地址?
2、如何处理函数开头要替换的字节?
3、如何跳转以及跳转地址如何计算?
4、自己的函数执行完成后,如何跳转回来?
  那么带着这几个问题,我们开始尝试实现Inline hook。
  
  我们还是以MessageBox为例子,来探究一下上面的问题。
  

第一步:构造跳转指令这里我们使用近距离地址跳转的jmp指令,它的机器码为E9,这种类型的jmp指令需要一个地址参数,为当前jmp指令地址距离目标函数地址的字节数。
  
  一、正常的跳转计算公式如下所示”:
  
  计算公式:jmp跳转的地址=自己实现的函数地址-(jmp所在的地址+5) (这里的5是 jmp xxxxxxxx的五个字节。)
  
  也就是:
*((ULONG*)(__HookCode + 1)) = (ULONG)FakeFuncAddress - ((ULONG)OriginalFuncAddress + 5);
  


  二、mov eax xxxxxxxx
  jmp eax
  
  在这里我们使用了mov eax,地址;jmp eax的方式。一共是七个字节。
  
// 构造新头部代码
  __NewCode[0] = 0xB8;
  memcpy(&__NewCode[1], &JmpAddress, 4);    // mov eax, _JmpAddr
  __NewCode[5] = 0xFF;            //
  __NewCode [6] = 0xE0;           // jmp eax
  



第二步:找到API函数地址,并保存前7字节  
ReadProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __OldCode, 7, NULL);
  

  
第三步:将构造的跳转指令写入需HOOK的位置处  
首先要修改内存属性为可读可写,然后再进行跳转指令的写入。
  
DWORD dwOldProtect = 0; //旧保护属性
  // 去内存保护
  ::VirtualProtect(__MessageBoxAddress, 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);  //写入跳转,开始Hook
  WriteProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __NewCode, 7, NULL);  // 写内存保护
  ::VirtualProtect(__MessageBoxAddress, 7, dwOldProtect, &dwOldProtect);
  



第四步:当被HOOK位置被执行时会转到我们的流程执行  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】
  进入MessageBoxA之后,会发现前7个字节已经改变为我们的跳板指令。
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】
  
  然后就会转入我们实现的函数,进行执行。
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

第五步:如果要执行原来的流程,那么恢复HOOK,也就是还原被修改的字节  
我们对原函数前七字节进行修改。
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

第六步:执行函数原来的流程  
之后再对MessageBoxA()调用,就恢复正常了。
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】
  
  综上我们Inline Hook就已经实现了,现在我们来解决之前的问题:
  
  1、如何定位自己要hook的地址?
  调用API所在Dll,GetProAddress。
  
  2、如何处理函数开头要替换的字节?
  一般来说,是对前五个字节保存替换,但要视具体情况而定。
  
  例如这里我们使用的就不是jmp xxxxxxxx,而是mov eax xxxxxxxx;
         jmp eax
  这时候就需要保存替换前7个字节了。
  

3、如何跳转以及跳转地址如何计算?具体参考上面实现。
  
  根据《加密与解密》中,一共有五种跳转方式。
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

4、自己的函数执行完成后,如何跳转回来?看上面的实现流程。
  
  
  源码
  源码已上传我的资源,无需积分,兄弟们需要自取。
  
#include <windows.h>
  #include <stdio.h>
  #include <iostream>
  #include <tchar.h>
  //修改API入口为 mov eax, 00400000;jmp eax是程序能跳转到自己的函数
  BYTE __NewCode[7] = { 0xE9, 0x0, 0x0, 0x0, 0x0, 0x0 };
  BYTE __OldCode[7] = { 0 };
  FARPROC __MessageBoxAddress;
  int WINAPI MyMessageBoxA(
  HWND hWnd,          // handle to owner window
  LPCTSTR lpText,     // text in message box
  LPCTSTR lpCaption,  // message box title
  UINT uType          // message box style
  );
  void InlineHook();
  void main()
  {
  InlineHook();
  //调用MessageBoxA测试一下。
  MessageBoxA(NULL, "Hello World", "Title", MB_OK);
  }
  void InlineHook()
  {
  HMODULE hModule_User32 = LoadLibrary("user32.dll");
  __MessageBoxAddress = GetProcAddress(hModule_User32, "MessageBoxA");
  //打印MessageBoxA的地址
  printf("__MessageBoxAddress is %x\n", __MessageBoxAddress);
  //MyMessageBoxA的地址
  printf("MyMessageBoxA Addr is %x\n", MyMessageBoxA);
  //读MessageBoxA函数的前6个字节
  if (ReadProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __OldCode, 7, NULL) == 0)
  {
  printf("ReadProcessMemory error\n");
  return;
  }
  //MessAgeBoxA()API 原6字节
  printf("__OldCode is %x%x%x%x%x%x%x\n", __OldCode[0], __OldCode[1], __OldCode[2],
  __OldCode[3], __OldCode[4], __OldCode[5], __OldCode[6]);
  DWORD JmpAddress = (DWORD)MyMessageBoxA;
  // 计算自定义函数的地址.
  // 构造新头部代码
  __NewCode[0] = 0xB8;            //
  memcpy(&__NewCode[1], &JmpAddress, 4);    // mov eax, _JmpAddr
  __NewCode[5] = 0xFF;            //
  __NewCode [6] = 0xE0;            // jmp eax
  DWORD dwOldProtect = 0;
  printf("NewBytes is %x%x%x%x%x\n", __NewCode[0], __NewCode[1], __NewCode[2], __NewCode[3],
  __NewCode[4], __NewCode[5], __NewCode[6]);
  //DWORD dwOldProtect = 0; //旧保护属性
  // 去内存保护
  ::VirtualProtect(__MessageBoxAddress, 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);
  //写入跳转,开始Hook
  WriteProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __NewCode, 7, NULL);
  // 写内存保护
  ::VirtualProtect(__MessageBoxAddress, 7, dwOldProtect, &dwOldProtect);
  }
  int WINAPI MyMessageBoxA(
  HWND hWnd,          // handle to owner window
  LPCTSTR lpText,     // text in message box
  LPCTSTR lpCaption,  // message box title
  UINT uType          // message box style
  )
  {
  printf("MessageBoxA 已经被Hook\r\n");
  //恢复API头7个字节
  WriteProcessMemory(INVALID_HANDLE_VALUE, (void*)__MessageBoxAddress,
  (void*)__OldCode, 7, NULL);
  //调用正确的函数
  int ret = MessageBoxA(NULL, "Hello World", "Title", MB_OK);
  //写入跳转语句,继续Hook
  WriteProcessMemory(INVALID_HANDLE_VALUE, (void*)__MessageBoxAddress,
  (void*)__NewCode, 7, NULL);
  return ret;
  }
  


  运行结果

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】
  
  
  
  参考资料

《加密与解密(第四版)》——第十三章
  小弟在学习Inline hook过程中,遇到了很多问题,作为初学者虽然解决了不少,但一定还有遗漏或不对的地方,请大佬们指点和讨论。
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】

  文件下载网址:https://github.com/Powerful99/Hook
  
  

Hook技术:Ring3层下的Inline Hook详解【附源码】

Hook技术:Ring3层下的Inline Hook详解【附源码】
  -End -

温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的最好奖励,还可以获得学币奖励,请尊重作者的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。
嘿嘿

0

主题

25

帖子

0

精华

初级会员

Rank: 4

学币
30
荣耀
0
rank
0
违规
0

    发表于 2020-7-1 01:05:21 | 显示全部楼层
    看了LZ的帖子,我只想说一句很好很强大!
    学逆向论坛-免费的逆向学习论坛
    微信公众号
    快速回复 返回顶部 返回列表