心念旧安,夙夜忧叹。

Windows下Hook API技术

上一篇 / 下一篇  2007-04-29 11:12:01 / 个人分类:转贴好文

查看( 4304 ) / 评论( 3 )
什么叫Hook API?所谓Hook就是钩子的意思,而API是指Windows开放给程序员的编程接口,使得在用户级别下可以对操作系统进行控制,也就是一般的应用程序都需要调用API来完成某些功能,Hook API的意思就是在这些应用程序调用真正的系统API前可以先被截获,从而进行一些处理再调用真正的API来完成功能。在讲Hook API之前先来看一下如何Hook消息,例如Hook全局键盘消息,从而可以知道用户按了哪些键,这种Hook消息的功能可以由以下函数来完成,该函数将一个新的Hook加入到原来的Hook链中,当某一消息到达后会依次经过它的Hook链再交给应用程序。 HHOOK SetWindowsHookEx(
7n"f-Qe0C5l ]0     int idHook,                     //Hook类型,例如WH_KEYBOARD,WH_MOUSE
4Vt3R W5Z o0     HOOKPROC lpfn,             //Hook处理过程函数的地址
laq1\+G3ll0     HINSTANCE hMod,          //包含Hook处理过程函数的dll句柄(若在本进程可以为NULL)
e/F7Y x*Cpi#AV0     DWORD dwThreadId,      //要Hook的线程ID,若为0,表示全局Hook所有
,w JO%R;f?;cnFr4p0V0 );     这里需要提一下的就是如果是Hook全局的而不是某个特定的进程则需要将Hook过程编写为一个DLL,以便让任何程序都可以加载它来获取Hook过程函数。    而对于Hook API微软并没有提供直接的接口函数,也许它并不想让我们这样做,不过有2种方法可以完成该功能。第一种,修改可执行文件的IAT表(即输入表),因为在该表中记录了所有调用API的函数地址,则只需将这些地址改为自己函数的地址即可,但是这样有一个局限,因为有的程序会加壳,这样会隐藏真实的IAT表,从而使该方法失效。第二种方法是直接跳转,改变API函数的头几个字节,使程序跳转到自己的函数,然后恢复API开头的几个字节,在调用AP完成功能后再改回来又能继续Hook了,但是这种方法也有一个问题就是同步的问题,当然这是可以克服的,并且该方法不受程序加壳的限制。    下面将以一个Hook指定程序send函数的例子来详细描述如何Hook API,以达到监视程序发送的每个封包的目的。采用的是第二种方法,编写为一个dll。首先是一些全局声明, //本dll的handle
RvZ4{x*z y(hO7vJ0 HANDLE g_hInstance = NULL;
MD k^&sC5s0 //修改API入口为 mov eax, 00400000;jmp eax是程序能跳转到自己的函数
j$lB1^)V4lN0 BYTE g_btNewBytes[8] = { 0xB8, 0x0, 0x0, 0x40, 0x0, 0xFF, 0xE0, 0x0 };
6XlnAHg[0 //保存原API入口的8个字节
-b7Z4U I)rm"]0 DWORD g_dwOldBytes[2][2] = { 0x0, 0x0, 0x0, 0x0 };51Testing软件测试网V6q { {F0n
//钩子句柄51Testing软件测试网,r\.z1FKS"s)W
HHOOK   g_hOldHook = NULL;
TgO*?Ga0 //API中send函数的地址51Testing软件测试网EC8e1b0R"r9p yP$R
DWORD g_pSend = 0;
a&j iF*o4LB`0 //事务,解决同步问题51Testing软件测试网\(v\9EL1b
HANDLE g_hSendEvent = NULL;//自己的send函数地址,参数必须与API的send函数地址相同int _stdcall hook_send( SOCKET s, const char *buf, int len, int flags );//要Hook的进程和主线程ID号DWORD g_dwProcessID = 0;
)l qk#v#_0 DWORD g_dwThreadID = 0;     从声明可以看出,我们会把API函数的首8个字节改为 mov eax, 00400000;jmp eax ,使程序能够跳转,只需获取我们自己的函数地址填充掉00400000即可实现跳转。而g_dwOldBytes是用来保存API开头原始的8个字节,在真正执行API函数是需要写回。还有一点,在声明新的函数时,该例中为hook_send,除了保正参数与API的一致外,还需要声明为__stdcall类型,表示函数在退出前自己来清理堆栈,因为这里是直接跳转到新函数处,所以必须自己清理堆栈。下面看主函数, BOOL APIENTRY DllMain( HANDLE hModule, 51Testing软件测试网A(H)I+gI3]3O
                                   DWORD  ul_reason_for_call, 51Testing软件测试网Q.WxlCiNya
                                   LPVOID lpReserved
&Xuc)I9PP0                                  )
l'Ey6c2?ai5e0 {51Testing软件测试网H5F/z-}+P {!W6N#f.ak
    if(ul_reason_for_call == DLL_PROCESS_ATTACH)51Testing软件测试网O"| Qb:w'Ih
   {      //获取本dll句柄
;]z b9u A0sd:|U3o0       g_hInstance = hModule;            //创建事务51Testing软件测试网$Sg Vu\Pc
      g_hSendEvent = CreateEvent( NULL, FALSE, TRUE, NULL );
A|G:Q2v$Q0       
+^J'K:C7JD0       //重写API开头的8字节
8QP5r-L3])|AG0       HMODULE hWsock = LoadLibrary( "wsock32.dll" );
]G5k9v'I.x%b%b0       g_pSend = ( DWORD )GetProcAddress( hWsock, "send" );       //保存原始字节      ReadProcessMemory( INVALID_HANDLE_VALUE, ( void * )g_pSend, 51Testing软件测试网 Hck9r%]4?
          ( void * )g_dwOldBytes[0], sizeof( DWORD )*2, NULL );      //将00400000改写为我们函数的地址
%X:^4SN`!z@0       *( DWORD* )( g_btNewBytes + 1 ) = ( DWORD )hook_send;51Testing软件测试网1J"c wI1_"g(MA'Z
      WriteProcessMemory( INVALID_HANDLE_VALUE, ( void * )g_pSend,
3tX{6Nr](DMQ0           ( void * )g_btNewBytes, sizeof( DWORD )*2, NULL );
GF&T;KL-QNb K0     }51Testing软件测试网 x:~:O q$M5Z$|
    return TRUE;51Testing软件测试网){7x+Y"T},F/E
}     以上是dll的main函数,在被指定的程序加载的时候会自动运行dll的main函数来完成初始化,这里就是改写API的首地址来完成跳转。当然本程序是对于指定程序进行Hook,如果要进行全局Hook,可以在main函数中用GetModuleFileName函数来获取exe文件完整路径,判断当前进程是否是想要Hook的进程。写函数中使用INVALID_HANDLE_VALUE,表示写本进程。 int _stdcall hook_send( SOCKET s, const char *buf, int len, int flags )51Testing软件测试网cuUJ c*KOt
{
9}D[0CeR0d T w-]0    int nRet;   WaitForSingleObject( g_hSendEvent, INFINITE );     //恢复API头8个字节51Testing软件测试网-E _nv CrjO z
   WriteProcessMemory( INVALID_HANDLE_VALUE, ( void* )g_pSend, 51Testing软件测试网#aG&P J f.S
      ( void* )g_dwOldBytes[0], sizeof( DWORD )*2, NULL );    /*   这里可以添加想要进行的处理过程   */    //真正执行API函数51Testing软件测试网Y RxH5J#c'V
   nRet = send( s, buf, len, flags );    //写入跳转语句,继续Hook
WrW|,q'x6E6q0    WriteProcessMemory( INVALID_HANDLE_VALUE, ( void* )g_pSend,
5P6og1k[%J j0       ( void* )g_btNewBytes, sizeof( DWORD )*2, NULL );    SetEvent( g_hSendEvent );    return nRet;51Testing软件测试网N t*|Q B|-[)y
} HOOK_API BOOL StartHook(HWND hWnd)51Testing软件测试网vL:M0Q%{'wU
{    //通过传入的窗口句柄获取线程句柄51Testing软件测试网7Z?M:N+y*Rey
    g_dwThreadID = GetWindowThreadProcessId( hWnd, &g_dwProcessID );     //WH_CALLWNDPROC类型的Hook
g d&](^j/Oyy-G;i0     g_hOldHook = SetWindowsHookEx( WH_CALLWNDPROC,  HookProc,          ( HINSTANCE ) g_hInstance, g_dwThreadID );
*bUK9l$oG0     if( g_hOldHook == NULL )51Testing软件测试网r"V?(k ta7B`
        return FALSE;51Testing软件测试网(e$JL;X0@!M
    return TRUE;
7c:`$_;K.dW!Lh6SKQ0 } static LRESULT WINAPI HookProc( int nCode, WPARAM wParam, LPARAM lParam )
Ok!o&Y.c8j7_ [#Wh0 {
/xw3@lOEiv\0 return CallNextHookEx( g_hOldHook, nCode, wParam, lParam ); 51Testing软件测试网zw1E%t2F4N%t1}
} HOOK_API void StopHook(void)51Testing软件测试网_/Q%Xc)^
{51Testing软件测试网zV'crk5W/U)t yZ
   if(g_hOldHook != NULL)
XZoqF Cc0    {51Testing软件测试网"MJqCP|$H
       WaitForSingleObject( g_hSendEvent, INFINITE );
k L.M1o"p0        HANDLE hProcess = NULL;
+`}i T/d T,z0        hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, g_dwProcessID);51Testing软件测试网/}d h'}_I0e$bS*P$b
       DWORD dwOldProc;
WXa8F$S'f#aJG4eM*v|0        DWORD dwNewProc;        //改变页面属性为读写51Testing软件测试网L E!RC6j*dew
       VirtualProtectEx( hProcess, ( void* )g_pSend, 8, PAGE_READWRITE, &dwOldProc );        //恢复API的首8个字节
_9WQ,s1?3GYR0{0        WriteProcessMemory( hProcess, ( void* )g_pSend, 51Testing软件测试网"eS.l)Y5y5P MnB
            ( void* )g_dwOldBytes[0], sizeof( DWORD )*2, NULL );        //恢复页面文件的属性51Testing软件测试网"`)y4D0H(_@.Y#V3[eF$p
       VirtualProtectEx( hProcess, ( void* )g_pSend, 8, dwOldProc, &dwNewProc );51Testing软件测试网|a({ da8S [
  51Testing软件测试网i*qDXa.n{-jM
       CloseHandle(g_hSendEvent);
uTq7D:s7y,NYh0   51Testing软件测试网aMEjp4\"b
       UnhookWindowsHookEx( g_hOldHook );
)x![4y v9W!l j?0     }
*zPU)Ys!UR0 }    可以看出,我们创建的Hook类型是WH_CALLWNDPROC类型,该类型的Hook在进程与系统一通信时就会被加载到进程空间,从而调用dll的main函数完成真正的Hook,而在SetWindowsHookEx函数中指定的HookProc函数将不作任何处理,只是调用CallNextHookEx将消息交给Hook链中下一个环节处理,因为这里SetWindowsHookEx的唯一作用就是让进程加载我们的dll。    以上就是一个最简单的Hook API的例子,该种技术可以完成许多功能。例如网游外挂制作过程中截取发送的与收到的封包即可使用该方法,或者也可以在Hook到API后加入木马功能,反向连接指定的主机或者监听某一端口,还有许多加壳也是用该原理来隐藏IAT表,填入自己的函数地址。

TAG:

mstiunicon的个人空间 mstiunicon 发布于2007-04-30 17:35:22
好贴。保存下来,准备好好的研究一下
阿理SoftwareTestingBlog becomegreat 发布于2008-11-09 19:19:48
对我来说还是有难度的啊
浆果儿发布于2011-10-17 12:51:59
以前的工作中在相关问题上遇到过麻烦,现在看到LZ的帖子觉得很亲切再次学习,感谢分享。
我来说两句

(可选)

日历

« 2024-04-20  
 123456
78910111213
14151617181920
21222324252627
282930    

数据统计

  • 访问量: 453670
  • 日志数: 138
  • 图片数: 4
  • 建立时间: 2006-11-26
  • 更新时间: 2013-08-30

RSS订阅

Open Toolbar