Win32Thread是指向_W32THREAD结构体的指针,通过这个结构体可以获得钩子所在进程ID和线程ID.该结构体定义如下。
typedef struct _W32THREAD { PVOID pEThread ; //该指针用以获得进程ID和线程ID ULONG RefCount ;ULONG ptlW32 ;ULONG pgdiDcattr ;ULONG pgdiBrushAttr ;ULONG pUMPDObjs ;ULONG pUMPDHeap ;ULONG dwEngAcquireCount ;ULONG pSemTable ;ULONG pUMPDObj ;PVOID ptl;PVOID ppi; //该指针用以获得模块基址}W32THREAD, *PW32THREAD;_W32THREAD结构体第一个参数pEThread指向的内存偏移0x01EC处分别保存着进程ID和线程ID.注意pEThread指针指向的内存是内核内存。
_W32THREAD结构体最后一个参数ppi指向的内存偏移0xA8处是所有模块基址的地址表, _HOOK_INFO结构体的iMod成员就标识了本钩子所属模块基址在此地址表中的位置。(每个地址占4个字节)所以通常使用ppi+0xa8+iMod*4定位模块基址的地址。注意ppi指向的内存是内核内存。
2、实现细节首先编写程序枚举消息钩子句柄,需要得到GUI TABLE,它的地址实际上存储于User32.dll的一个全局变量中,该模块导出的函数UserRegisterWowHandlers将返回该全局变量的值。所以我们只要调用这个函数就能够得到GUI TABLE.然而UserRegisterWowHandlers是一个未公开的函数,不确定它的函数原型,需要反汇编猜出它的原型。笔者反汇编后得到的原型如下。
typedef PSHAREDINFO (__stdcall *USERREGISTERWOWHANDLERS) (PBYTE ,PBYTE );仅知道它两个参数是两个指针,但是不知道它的两个参数的含义,所以我们无法构造出合理的参数。如果随便构造参数传进去又会导致user32.dll模块发生错误。所以通过调用这个函数接收其返回值的方法就不能用了。再次反汇编该函数的实现可以看出,在不同操作系统下该函数的最后三行代码如下。
2K系统:(5.0.2195.7032)
:77E3565D B880D2E477 mov eax, 77E4D280:77E35662 C20800 ret 0008 XP系统:(5.1.2600.2180)
:77D535F5 B88000D777 mov eax, 77D70080:77D535FA 5D pop ebp:77D535FB C20800 ret 0008 2003系统:(5.2.3790.1830)
:77E514D9 B8C024E777 mov eax, 77E724C0:77E514DE C9 leave:77E514DF C2080000 ret 0008可以看到共同点,该函数的倒数第三行代码就是将保存GUI TABLE指针的全局变量值赋值给寄存器EAX,只要我们想办法搜索到这个值即可。能够看出无论是哪个版本的函数实现中,都有 C20800代码,含义是ret 0008.我们可以自UserRegisterWowHandlers函数的入口地址开始一直搜索到C20800,找到它以后再向前搜索B8指令,搜到以后B8指令后面的四个字节数据就是我们需要的数据。代码如下。
//获得UserRegisterWowHandlers函数的入口地址DWORD UserRegisterWowHandlers = (DWORD) GetProcAddress(LoadLibrary("user32.dll"), "UserRegisterWowHandlers");PSHAREDINFO pGUITable; //保存GUITable地址的指针for(DWORD i=UserRegisterWowHandlers; i
{ if((*(USHORT*)i==0x08c2)&&*(BYTE *)(i+2)== 0x00)
{ //已找到ret 0008指令,然后往回搜索B8 for (int j=i; j>UserRegisterWowHandlers; j——)
{ //找到B8它后面四个字节保存的数值即为GUITable地址if (*(BYTE *)j == 0xB8)
{ pGUITable = (PSHAREDINFO)*(DWORD *)(j+1);break;} }break;}得到SHAREDINFO结构指针后,它的成员pServerInfo的成员cHandleEntries就是句柄的总个数,然后循环遍历每一个句柄,找到属于指定模块的消息钩子句柄。代码如下。
int iHandleCount = pGUITable->pServerInfo->cHandleEntries;HOOK_INFO HookInfo;DWORD dwModuleBase;struct TINFO { DWORD dwProcessID;DWORD dwThreadID;};char cModuleName[256] = {0};for (i=0; i
{ //判断句柄类型是否为消息钩子句柄if (pGUITable->pHandleEntry[i].bType == TYPE_HOOK)
{ DWORD dwValue = (DWORD)pGUITable->pHandleEntry[i].pObject;//获得消息钩子内核对象数据GetKernelMemory(pGUITable->pHandleEntry[i].pObject, (BYTE *)&HookInfo, sizeof(HookInfo));W32THREAD w32thd;if( GetKernelMemory(HookInfo.pWin32Thread,(BYTE *)&w32thd , sizeof(w32thd)) )
{ //获取钩子函数所在模块的基址if (!GetKernelMemory((PVOID)((ULONG)w32thd.ppi+0xA8+4*HookInfo.iMod),(BYTE *)&dwModuleBase, sizeof(dwModuleBase)))
{ continue;} TINFO tInfo;//获取钩子所属进程ID和线程ID if (!GetKernelMemory((PVOID)((ULONG)w32thd.pEThread+0x1ec),(BYTE *)&tInfo, sizeof(tInfo)))
{ continue;} HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, tInfo.dwProcessID);if (hProcess == INVALID_HANDLE_VALUE)
{ continue;} //根据模块基址,获取钩子函数所属模块的名称if (GetModuleFileNameEx(hProcess, (HMODULE)dwModuleBase, cModuleName, 256))
{ OutputDebugString(cModuleName);OutputDebugString("\r\n");}
利用上面的代码就可以找到所属病毒DLL的消息钩子句柄,然后调用UnhookWindowsHookEx函数卸载这个消息钩子就OK了。