高版本64位Win10(RS2或更高)下枚举消息钩子的一种思路

  • A+
所属分类:逆向工程
高版本64位Win10(RS2或更高)下枚举消息钩子的一种思路

本文为看雪论坛优秀文章

看雪论坛作者ID:hhkqqs



先是参考文章:

https://bbs.pediy.com/thread-14600.htm (作者:一块三毛钱)
https://bbs.pediy.com/thread-55453.htm (作者:炉子)

这里先简单回顾一下前人的枚举思路:一块三毛钱采用的是枚举所有线程并取ETHREAD的THREADINFO,同时枚举16种钩子类型iHookType,调用Win32k.sys中未导出函数PVOID PhkFirstValid(PTHREADINFO pti, int iHookType)获得保存钩子信息的结构体tagHOOK的首地址,随后循环调用PVOID PhkNextValid(tagHOOK*)以枚举所有的消息钩子。

炉子的思路是获取user32.dll中与Win32k.sys的共享变量gSharedInfo,遍历gSharedInfo->aheList的数组成员_HANDLEENTRY,总数为gSharedInfo->psi->cHandleEntries,若成员_HANDLEENTRY的bType为5,则其为消息钩子,_HANDLEENTRY的phead成员即为钩子对应的内核tagHOOK结构体首地址。

鉴于炉子的方法相对简便,本文的研究思路以炉子的方法作为基础展开。

首先介绍枚举方案中涉及的关键结构体:tagHOOK和_HANDLEENTRY。

tagHOOK结构体成员在64位Win10下没有变化,其简要结构为:

typedef struct tagHOOK{       PVOID            Handle;       PVOID            cLockObj;       PW32THREAD       Win32Thread;       ULONGLONG        Patch1;       tagHOOK*         pSelf;                                //指向结构体的首地址       tagHOOK*         pNext;                                //指向下一个结构体       ULONG            hType;                                //钩子的类型       ULONG            Patch2;       PVOID            offPfn;                               //ihmod为-1时,表示钩子函数绝对地址,否则为函数相对所在模块的偏移       ULONG            flags;                                //取值1或3为全局钩子       int                        ihmod;                      //钩子所在模块的索引       PTHREADINFO      ptiHooked;                            //被hook线程的THREADINFO} HOOK, *PHOOK;

在Win10 RS1(14393)及之前的64位Windows系统,_HANDLEENTRY结构为:

typedef struct _HANDLEENTRY{       PVOID phead;       PVOID pOwner;       UCHAR bType;       UCHAR bFlags;       USHORT wUniq;} HANDLEENTRY, *PHANDLEENTRY;

按照炉子的方法,只要取出其中的phead成员即为内核tagHOOK结构体的首地址,然而Win10 RS2(15063)中,微软为了修复GDI Object Leak,将_HANDLEENTRY中的phead和pOwner成员移除了,以下分别是14393和15063下gSharedInfo->aheList的_HANDLEENTRY数组:

高版本64位Win10(RS2或更高)下枚举消息钩子的一种思路

高版本64位Win10(RS2或更高)下枚举消息钩子的一种思路
    
新的HANDLEENTRY结构也没有写进符号文件,笔者水平有限,只能得到新的HANDLEENTRY大概长这样:

typedef struct _HANDLEENTRY_RS2{       PVOID Unknown1;       PVOID UniqueThreadId;       PVOID Unknown2;       UCHAR bType;       UCHAR bFlags;       USHORT wUniq;} HANDLEENTRY, *PHANDLEENTRY;

第一眼看过去,似乎新版HANDLEENTRY没有提供多少查找tagHOOK结构体的线索。笔者决定跟一下gSharedInfo在内核的变动,值得一提的是,Windows 10下gSharedInfo从Win32k.sys移到了Win32kbase.sys中。

经过一番查找,笔者发现Win32kbase!HMValidateHandle引用了gSharedInfo->aheList的成员,传入的句柄参数取低16位即为aheList数组的索引,同时该索引也参与到取内核结构体地址的运算。

在Win10 RS2中伪代码如下:

enum HANDLE_TYPE {       TYPE_FREE = 0,       TYPE_WINDOW = 1,       TYPE_MENU = 2,       TYPE_CURSOR = 3,       TYPE_SETWINDOWPOS = 4,       TYPE_HOOK = 5,       TYPE_CLIPDATA = 6,       TYPE_CALLPROC = 7,       TYPE_ACCELTABLE = 8,       TYPE_DDEACCESS = 9,       TYPE_DDECONV = 10,       TYPE_DDEXACT = 11,       TYPE_MONITOR = 12,       TYPE_KBDLAYOUT = 13,       TYPE_KBDFILE = 14,       TYPE_WINEVENTHOOK = 15,       TYPE_TIMER = 16,       TYPE_INPUTCONTEXT = 17,       TYPE_HIDDATA = 18,       TYPE_DEVICEINFO = 19,       TYPE_TOUCHINPUT = 20,       TYPE_GESTUREINFO = 21,       TYPE_CTYPES = 22,       TYPE_GENERIC = 255};
PVOID HMValidateHandle(HANDLE Handle, HANDLE_TYPE hType){ PVOID Object = 0; if ((USHORT)Handle < gSharedInfo->psi->cHandleEntries) { PHANDLEENTRY hEntry = &gSharedInfo->aheList[(USHORT)Handle]; PVOID *pObject = &gpKernelHandleTable[2 * (USHORT)Handle]; USHORT Handle_HIWORD = (USHORT)(Handle >> 0x10); if ((Handle_HIWORD == hEntry->wUniq || Handle_HIWORD == -1 || !Handle_HIWORD && PsGetCurrentProcessWow64Process()) && !(hEntry->bFlags & 1) && hEntry->bType == hType ) { Object = *pObject; } } if (W32GetThreadWin32Thread(PsGetCurrentThread())->TIF_flags & 0x20000000) { if (!ValidateHandleSecure(Handle, 3)) Object = 0; } else { if (!ValidateHandleSecure(Handle, 2)) Object = 0; } if (!Object) { DWORD dwErrorCode; switch (hType) { case TYPE_WINDOW: dwErrorCode = 1400; break; case TYPE_MENU: dwErrorCode = 1401; break; case TYPE_CURSOR: dwErrorCode = 1402; break; case TYPE_SETWINDOWPOS: dwErrorCode = 1405; break; case TYPE_HOOK: dwErrorCode = 1404; break; case TYPE_ACCELTABLE: dwErrorCode = 1403; break; default: dwErrorCode = 6; break; } UserSetLastError(dwErrorCode); } return Object;}

从伪代码来看,似乎只需要遍历gSharedInfo->aheList并取出HANDLEENTRY.bType为TYPE_HOOK的索引,即可从gpKernelHandleTable中找到我们想要的tagHOOK。

但是,在15063以上的版本的Win10中,gpKernelHandleTable检索方法发生了变化,如:

15063tagHOOKObject=gpKernelHandleTable[2 * Index];

18362下则变成了tagHOOKObject=gpKernelHandleTable[3 * Index]。

出于兼容性的考虑,笔者决定通过wUniq来生成钩子的句柄hHOOK = Index | (gSharedInfo->aheList[Index].wUniq << 0x10),并调用HmValidateHandle来获取tagHOOK。

虽然Win32kbase.sys下HmValidateHandle没有导出且没有导出函数调用它,但Win32kfull.sys里也有与其功能相同的HmValidateHandle。

而且笔者发现user32!UnhookWindowsHookEx的底层调用Win32kfull!NtUserUnhookWindowsHookEx中出现了对HmValidateHandle的调用,如图所示:

高版本64位Win10(RS2或更高)下枚举消息钩子的一种思路

至此,枚举tagHOOK结构体的思路已经完善,伪代码如下:

tagSHAREDINFO* gSharedInfo = 0;typedef PVOID(*HMValidateHandleFn)(HANDLE Handle, HANDLE_TYPE hType);HMValidateHandleFn HMValidateHandle = 0;void GetWinHook_15063orHigher(PKPROCESS GUIProcess, PHOOK OutputBuffer){       _KAPC_STATE ApcState;       KeStackAttachProcess(GUIProcess, &ApcState);       if (!gSharedInfo)              gSharedInfo = (tagSHAREDINFO*)GetKernelProcAddress(GetKernelModuleHandle(L"win32kbase.sys"), "gSharedInfo");       if (!HMValidateHandle)       {              ULONGLONG p = GetKernelProcAddress(GetKernelModuleHandle(L"win32kfull.sys"), "NtUserUnhookWindowsHookEx");              //Pattern B2 05 ?? ?? ?? E8 ?? ?? ?? ??              while ((*(PULONGLONG)p & 0xFF000000FFFF) != 0xE800000005B2) p++;              HmValidateHandle = (HMValidateHandleFn)((p & 0xFFFFFFFF00000000) + (DWORD)(p + *(PDWORD)(p + 6) + 10));       }       PHOOK Hook = OutputBuffer;       ULONG HandleEntryCount = (ULONG)gSharedInfo->psi->cHandleEntries;       for (ULONG i = 0; i < HandleEntryCount; i++)       {              if (gSharedInfo->aheList[i].bType == TYPE_HOOK)              {                     HANDLE ObjectHandle = (HANDLE)(i | (gSharedInfo->aheList[i].wUniq << 0x10));                     PHOOK HookObject = HmValidateHandle(ObjectHandle, TYPE_HOOK);                     if (HookObject)                     {                            memcpy(Hook, HookObject, sizeof(HANDLEENTRY));                            Hook++;                     }              }       }       KeUnstackDetachProcess(&ApcState);}

作为题外话,这里再谈一谈tagHOOK中ihmod的用法,按一块三毛钱的说法,若tagHOOK.ihmod!=-1,直接取tagHOOK.ptiHooked->ppi->ahmodLibLoaded [tagHOOK.ihmod]即可获取钩子函数所在模块的基地址,其中ppi是指向_PROCESSINFO的指针。

但是从Win10开始,微软把THREADINFO和PROCESSINFO这两结构体从符号文件移除了,目前可行的检索模块信息的方法是使用Win32kfull!aatomSysLoaded。

这是一个ATOM类型的数组,ATOM所记录的名称即为模块路径,遗憾的是该数组没有导出,只能通过dbghelp.dll定位符号或特征码定位。

获取钩子所在模块的文件路径的伪代码如下:

typedef USHORT ATOM;typedef NTSTATUS(*RtlQueryAtomInAtomTableFn)(PVOID AtomTable, ATOM Atom, PULONG RefCount, PULONG PinCount, PWSTR AtomName, PULONG NameLength);ATOM* aatomSysLoaded = 0;PVOID UserAtomTableHandle = 0;RtlQueryAtomInAtomTableFn RtlQueryAtomInAtomTable = 0;//使用前先挂靠GUI进程NTSTATUS QueryModuleImageFileNameFromHookObject(IN PHOOK HookObject, OUT LPWSTR NameBuffer){       if (!aatomSysLoaded) aatomSysLoaded = (ATOM*)DBGLocateSymbol(L"win32kfull.sys", "aatomSysLoaded");       if (!UserAtomTableHandle) UserAtomTableHandle = (PVOID)GetKernelProcAddress(GetKernelModuleHandle(L"win32kbase.sys"), "UserAtomTableHandle");       if (!RtlQueryAtomInAtomTable) RtlQueryAtomInAtomTable = (RtlQueryAtomInAtomTableFn)GetKernelProcAddress(GetKernelModuleHandle(L"ntoskrnl.exe"), "RtlQueryAtomInAtomTable");       ULONG MaxLength = sizeof(WCHAR) * MAX_PATH;       return RtlQueryAtomInAtomTable(UserAtomTableHandle, aatomSysLoaded[HookObject->ihmod], 0, 0, NameBuffer, &MaxLength);}

获取模块文件路径后,可以通过遍历VAD树或PEB->Ldr来获取模块基址,加上tagHOOK.offPfn即为钩子函数的地址,这里以非Wow64进程为例贴出查询钩子函数地址的伪代码:

PVOID GetWinHookFunctionAddress(IN PHOOK HookObject, IN LPWSTR szDllPath){       if (HookObject->ihmod == -1) return HookObject->offPfn;       PVOID HookFunctionAddress = 0;       _KAPC_STATE ApcState;       PKPROCESS Process = IoThreadToProcess(HookObject->ptiHooked->Win32Thread.pEThread);       PPEB Peb = (PPEB)PsGetProcessPeb(Process);       KeStackAttachProcess(Process, &ApcState);       PLIST_ENTRY pFirstLoadOrderFlink = &Peb->Ldr->InLoadOrderModuleList;       for (PLIST_ENTRY pListEntry = pFirstLoadOrderFlink->Flink; pListEntry != pFirstLoadOrderFlink; pListEntry = pListEntry->Flink)       {              LPWSTR FullDllNameBuffer = ((PLDR_DATA_TABLE_ENTRY)pListEntry)->FullDllName.Buffer;              if (FullDllNameBuffer && !wcsicmp(FullDllNameBuffer, szDllPath))              {                     HookFunctionAddress = (PVOID)((ULONGLONG)((PLDR_DATA_TABLE_ENTRY)pListEntry)->DllBase + (ULONGLONG)HookObject->offPfn);                     break;              }       }       KeUnstackDetachProcess(&ApcState);       return HookFunctionAddress;}

本人水平有限,代码写的很粗糙,如有不妥之处欢迎大家批评指正。


高版本64位Win10(RS2或更高)下枚举消息钩子的一种思路
- End -


高版本64位Win10(RS2或更高)下枚举消息钩子的一种思路


看雪ID:hhkqqs

https://bbs.pediy.com/user-home-805252.htm

  *本文由看雪论坛 hhkqqs 原创,转载请注明来自看雪社区。


高版本64位Win10(RS2或更高)下枚举消息钩子的一种思路



推荐文章++++

高版本64位Win10(RS2或更高)下枚举消息钩子的一种思路

* Linux Kernel Pwn 学习笔记 (UAF)

* Linux-5.6.6 内核引导

* 崩溃回溯分析

加密壳之ACProtect系列通杀

CVE-2016-0165 Win32k漏洞分析笔记







高版本64位Win10(RS2或更高)下枚举消息钩子的一种思路
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]



求分享

求点赞

高版本64位Win10(RS2或更高)下枚举消息钩子的一种思路

求在看


高版本64位Win10(RS2或更高)下枚举消息钩子的一种思路
“阅读原文一起来充电吧!

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: