本文為看雪論壇優秀文章
看雪論壇作者ID:hhkqqs
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數組:新的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數組的索引,同時該索引也參與到取內核結構體地址的運算。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檢索方法發生了變化,如:15063下tagHOOKObject=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的調用,如圖所示:至此,枚舉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;}本人水平有限,代碼寫的很粗糙,如有不妥之處歡迎大家批評指正。看雪ID:hhkqqs
https://bbs.pediy.com/user-home-805252.htm
*本文由看雪論壇 hhkqqs 原創,轉載請註明來自看雪社區。