Hook :HOOK API 原理深入剖析2 - Inline(hot-patching)

来源:互联网 发布:scopus数据库多少钱 编辑:程序博客网 时间:2024/05/08 17:11

利用windows为多数API预留的前五个字节的nop空间和mov edi, edi占用的两个字节来实现inline hook。即所谓的hot-patching技术,运行时修改函数的行为,同时不破坏函数的主体逻辑,因此免去了hook与unhook不断切换,在线程安全上也有很好的效果。

 

本文还是以LoadLibraryA函数为例,先看LoadLibraryA的反汇编:

7602285F  nop              
76022860  nop              
76022861  nop              
76022862  nop              
76022863  nop              
76022864  mov         edi,edi 
76022866  push        ebp  
76022867  mov         ebp,esp 
76022869  cmp         dword ptr [ebp+8],0 
7602286D  push        ebx  
7602286E  push        esi  
7602286F  push        edi  
76022870  je          7602288A 
76022872  push        760228A0h 
76022877  push        dword ptr [ebp+8] 
7602287A  call        dword ptr ds:[75FD12E4h] 
76022880  pop         ecx  
76022881  pop         ecx  
76022882  test        eax,eax 
76022884  je          7603F39F 
7602288A  push        0    
7602288C  push        0    
7602288E  push        dword ptr [ebp+8] 
76022891  call        76022859 
76022896  pop         edi  
76022897  pop         esi  
76022898  pop         ebx  
76022899  pop         ebp  
7602289A  ret         4  

 

如上,前面红色的部分即是可以灵活操作的部分,mov edi,edi两个字节可以替换成一个short jmp,5个nop字节可以替换成一个长跳。

蓝色的0x76022864地址是LoadLibraryA的入口,如果将这里改成short jmp后,要调用正常的LoadLibraryA,则只需要在此基础上加2个字节的偏移进行call即可。清楚了原理,先动手吧,用C++实现,先写一个类。

[cpp] view plaincopyprint?
  1. class InlineHookHolder  
  2. {  
  3. public:  
  4.     class _ReadWriteVPHolder  
  5.     {  
  6.     public:  
  7.         _ReadWriteVPHolder( void* addr, DWORD size ) : pAddr( addr ), dwSize( size )  
  8.         {   
  9.             VirtualProtect( pAddr, dwSize, PAGE_EXECUTE_READWRITE, &dwFlag );   
  10.         }  
  11.   
  12.         ~_ReadWriteVPHolder( void )  
  13.         {  
  14.             VirtualProtect( pAddr, dwSize, dwFlag, &dwFlag );  
  15.         }  
  16.   
  17.     private:  
  18.         void* pAddr;  
  19.         DWORD dwFlag;  
  20.         DWORD dwSize;  
  21.     };  
  22.   
  23.     InlineHookHolder( int dst_jmp_func, int src_hook_func ) :  
  24.         pSrcFunc( ( BYTE* )src_hook_func - 5 )  
  25.     {   
  26.         // raii vp Holder  
  27.         _ReadWriteVPHolder holder( pSrcFunc, 7 );  
  28.   
  29.         // jmp offset, contain 5 byte of itself  
  30.         ( int& )pSrcFunc[1] = ( int )dst_jmp_func - ( int )pSrcFunc - 5;  
  31.         pSrcFunc[ 0 ]       = 0xE9;     // far jmp  
  32.   
  33.         // mov edi, edi  
  34.         pSrcFunc[ 5 ]       = 0xEB;     // short jmp  
  35.         pSrcFunc[ 6 ]       = 0xF9;     // short jmp offset: -7  
  36.     }  
  37.   
  38.     ~InlineHookHolder( void )   
  39.     {  
  40.         // raii vp holder  
  41.         _ReadWriteVPHolder holder( pSrcFunc, 7 );  
  42.   
  43.         // 5 nop  
  44.         memset( pSrcFunc, 0x90, 5 );  
  45.   
  46.         // unhook mov edi, edi  
  47.         pSrcFunc[ 5 ] = 0x8B;  
  48.         pSrcFunc[ 6 ] = 0xFF;  
  49.     }  
  50.   
  51. private:  
  52.     BYTE* pSrcFunc;  
  53. };  

 

这个InlineHookHolder构造时,便将目标函数进行hook操作,这个操作将mov edi,edi替换成jmp XX,这里是相对偏移,偏移量为7字节(5个nop字节加上本身2个字节),所以有:
pSrcFunc[ 5 ]       = 0xEB;     // short jmp
pSrcFunc[ 6 ]       = 0xF9;     // short jmp offset: -7

0xEB即是short jmp的机器码,0xF9即偏移,这里是负数,向前偏移7个字节。

 

至于5个nop即替换成了jmp 0x???????。因此有:

( int& )pSrcFunc[1] = ( int )dst_jmp_func - ( int )pSrcFunc - 5; // 跳转的偏移,目标地址与当前地址的差值+jmp本身的5个字节
pSrcFunc[ 0 ]       = 0xE9;   // 长jmp 的机器码

 

在构造函数修改之后,析构函数负责unhook,在其间的过程中都不需要操作。

写好了类,然后再写跳转到的自定义函数,代码如下:

 

[cpp] view plaincopyprint?
  1. typedef WINBASEAPI HMODULE ( WINAPI *PLOADLIBA )( LPCSTR lpFileName );  
  2. typedef WINBASEAPI HMODULE ( WINAPI *PLOADLIBW )( LPCWSTR lpFileName );  
  3.   
  4. #define LOADER_CAST( T, ptr ) reinterpret_cast< T >( ( ( int )ptr ) + 2 );  
  5. #define WINAPI_FUNC( _ret, _name ) _ret WINAPI _name  
  6. #define DECLARE_HOOK_HOLDER( _T, holder, jmp_, src_ ) /  
  7.         _T jmp_##holder( ( int )jmp_, ( int )src_ );  
  8.   
  9. WINAPI_FUNC( HMODULE, MyLoadLibraryA )( LPCSTR lpFileName )  
  10. {  
  11.     if ( lpFileName == NULL )  
  12.         return NULL;  
  13.   
  14.     PLOADLIBA pLoader = LOADER_CAST( PLOADLIBA, LoadLibraryA );  
  15.     HMODULE   hMod    = pLoader( lpFileName );  
  16.     // code....  
  17.     return hMod;  
  18. }  

 

如上面代码,LOADER_CAST宏用于将API的入口地址加上2,避免又跳转到自定义hook函数里面了。

 

由于LoadLibraryA是__stdcall(WINAPI宏),会在内部ret时保持堆栈平衡,因此我们自定义的函数也保持这个规则,不然会导致堆栈不平衡。WINAPI_FUNC宏就是为了遵循这个规则而定义的,免得粗心忘了加__stdcall了。

 

DECLARE_HOOK_HOLDER宏是声明定义一个InlineHookHolder对象,将原函数和目标函数传入构造函数进行hook操作。我们可以将这个对象声明成全局的,这样在程序退出时调用析构进行unhook,_ReadWriteVPHolder类也是为了RAII的机制。

 

最后调用代码可以简单如下:

[cpp] view plaincopyprint?
  1. DECLARE_HOOK_HOLDER( InlineHookHolder, _inline_hook_a, MyLoadLibraryA, LoadLibraryA );  
  2. int main( void )  
  3. {  
  4.     LoadLibraryA( "d3d9.dll" ); // 假如load这个dll  
  5.     return 0;  
  6. }  

 

当然在MyLoadLibraryA里就可以做一些我们想做的事情了,比如检测你加载的DLL是否合法等,这对于比较简单的游戏反外挂上有一定的作用。本文就只抛砖引玉介绍下原理吧。

 

 

与之前的版本比较,这种方法只能应用于预留了5个nop和mov edi,edi这7个字节的API函数,不建议强制使用这种方式,视情况而定。再者,在本文中没有涉及手工编写内嵌汇编代码,逻辑简单且更清晰,在效率上也较高一些,安全性方面也要好一些。不过之前的方法对于追究堆栈调用模型很有好处。

原创粉丝点击