高版本64位Win10(RS2或更高)下枚舉消息鉤子的一種思路

2021-03-02 看雪學院

本文為看雪論壇優秀文章

看雪論壇作者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數組:新的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 原創,轉載請註明來自看雪社區。

相關焦點

  • win10 1803 64位系統下眾多單機老遊戲兼容性測試(一)
    win10 1803 64位系統下眾多單機老遊戲兼容性測試(一) 2018-08-25 22:58  漢宮隋城長安殿
  • win10系統32位和64位區別詳解
    現在使用最多的系統是64位win10系統,但是我們在重新安裝系統時經常看到32位win10系統。許多小夥伴問win10系統的哪個位數更適合他們自己的計算機。接下來,讓我們了解下把。win10系統中32位和64位有什麼區別?
  • win10系統裝哪個版本的CAD?
    一些win10系統電腦的用戶想安裝cad這一款製圖軟體,但是,不知道選擇cad的哪一個版本來安裝比較好。那麼,win10系統裝哪個版本的CAD?在今天的win10系統教程,我們就來了解一下這個問題!
  • win10最高版本是企業版嗎?win7系統各個版本有什麼區別
    1、Starter(初級版)功能最少的一個版本,連很多基本的常用功能都沒有,比如只支持32位處理器,不支持64位處理器;沒有win7最好用的windows媒體中心和移動中心;就連基本桌面背景更換,都有限制。那麼這個版本,有什麼用呢?
  • 放棄32位!微軟Win10 2004版OEM預裝只有64位版本
    IT之家5月14日消息微軟將開始一個漫長的過程,逐漸不再支持Windows10系統32位版本。 從Windows10版本2004開始,它已經提供給OEM和開發人員,微軟不再提供新的PCOEM系統的32位版本。 此更改顯示在最低硬體需求文檔中。
  • ARM版Win10要支持64位應用,這意味著什麼?
    事實上,微軟之前曾推出過ARM版的Windwos10,但是不堪重用,因為它只運行32位應用。目前,市面上大部分的軟體不再推出32位版本的應用,基本上都是只有64位版本,例如谷歌的Chrome瀏覽器、Adobe的Creative Suite等。簡單點來講,就是32位應用正在被時代所淘汰。
  • win10 64位系統無法安裝Net framework3.5的詳解
    一些用戶因為工作需求,想要在windows10_64位系統電腦上安裝Net framework3.5,可是在操作時總是遇到失敗的情況。嘗試使用「啟用於關閉windows功能」、控制面板在線安裝、使用下載的離線安裝包離線安裝、使用安裝介質或者iso鏡像文件裡面\sources\sxs直接安裝 。。。。等一系列的方法,都沒有解決。這該怎麼辦呢?
  • Win10激活密鑰key(可激活所有版本)
    ,一般激活Win10系統有兩種方式,一是使用Win10產品密鑰,二是使用HWIDGen 數字激活工具(親測有效)官方有提供win10專業版等多種版本的激活碼key,這邊系統天地小編為大家整理最新windows10序列號激活密匙。
  • Win10重磅更新正式倒計時!Win7死亡讀秒
    今天微軟也為手機用戶推送了Windows 10 Mobile Build 15031系統更新,宣告切換到rs2-release分支。這意味著Windows 10 Creators Update(創作者更新)正式版進入真正意義上的倒計時階段,很快就會交付(預計4月)。
  • win10系統各版本功能對比介紹和安裝方法
    PC端和移動端,不同win10版本的功能都有區別,在升級win10系統時也要根據win7和win8.1的版本選擇對應的版本,否則無法免費激活,那麼win10系統都有哪些版本呢?win10系統幾個版本都有什麼不同?接下來就跟大家帶來win10系統各個版本的功能詳細對比。
  • 自己用的電腦要裝系統,XP, Win7, Win10到底選哪個?解救小白篇
    或更高內存: 128MB  顯卡: Super VGA(800 x 600)或更高解析度  硬碟剩餘空間: 1.5GB以上  其他設備:CD-ROM以上  Windows7最低配置要求:主要硬體要求如下:  處理器(cpu)最好是主頻1GHZ以上,32位或64位處理器,不過目前的處理器幾乎都是64位的了,一般雙核處理器都在1GHZ以上。
  • Windows10 64位版本的最佳頁面文件大小是多少?
    Windows和Windows Server的64位版本比32位版本支持更多的物理內存(內存),但是,配置或增加頁面文件大小的原因沒有改變。它始終是在必要時支持系統故障轉儲,或者在需要時擴展系統提交限制。
  • Win10 預覽版 10147 英文 64 位 ISO 鏡像及語言包下載洩露
    IT之家訊 6月18日消息,俄羅斯爆料大神WZor在其Twitter上公布了Win10預覽版10147英文64位版ISO鏡像和語言包下載洩露的消息,而這次洩露地址卻來自中國的網盤。
  • Win10 預覽版 10135 英文版 32 位鏡像下載洩露
    IT之家訊 6月9日消息,今天上午Win10預覽版10135英文版32位鏡像也被洩露到了網上,有需要這個版本的朋友可以下載使用。Win10預覽版10135英文版32位ISO鏡像下載:CRC32: 0038FAD7 MD5: 14606C2DD47D31F987543FDF769BD463 SHA-1: EC1DEDAAAD3B418BEF4F8DBC83E9B572F0845848國外網盤下:
  • Win7大戰Win10流暢性測試:你猜誰贏了?
    分別給它們安裝64位的Win7 sp1,版本號為677408,和64位Win10 1607版本也就是周年更新版。同時按下開機鍵,測試三次後取平均值,在win10系統下,這臺電腦的開機速度為39秒,win7系統下為64秒, 可以看出win10的開機速度要遠遠快於win7。
  • Win10專業工作站2020版本最新官方原版下載地址和激活密鑰
    Win10專業工作站版本2020版本64位下載地址:ed2k://|file|cn_windows_10_business_editions_version
  • 老電腦運行Win7與Win10對比 差距最大居然是瀏覽器
    分別給它們安裝64位的Win7 sp1,版本號為677408,和64位Win10 1607版本也就是周年更新版。在安裝時發現,Win7的安裝速度比win10要快一些,不過安裝完系統後有7個驅動要裝,比如網卡驅動、顯卡驅動等,最終的花費時間是安裝速度的好幾倍,因為Win7系統年代久遠,所以驅動安裝完畢後還需要安裝140個安全補丁。
  • win10激活工具aact win10aact激活工具下載
    激活win10的aact分享給大家,aact win10激活工具不需要。net框架支持,直接就能使用,絕對綠色版。Win10激活工具aact不僅可以支持win10所有版本,還能激活office辦公軟體。下面小編給大家分享win10激活aact下載地址以及激活win10步驟。
  • 全能記事本 Notepad++ 更新:新增 64 位版本
    IT之家訊 9月22日消息,免費全能代碼文本編輯器Notepad++更新至7.0版,本次更新增加了64位版本,同時修復了大量bug。編輯器可以運行在微軟的windows系統環境下,它的使用是受GPL許可證保護的,支持的語言:C、C++、Java、C#、XML、HTML、PHP、Javascript。基於強大的編輯組件Scintilla,Notepad++也是使用C++代碼開發而成,並且使用了win32的api接口和STL標準模板庫,因此確保軟體有更高效的執行效率和更小的程序體積。
  • 詳解win10各版本的區別
    它安全性高,兼容性強,深受大家的喜愛。那麼win10系統有這麼多版本,這些版本之間有什麼區別呢?哪個版本更好?接下來,我將介紹win10版本的不同之處win10版本有什麼區別?隨著win10系統的不斷完善,越來越多的朋友開始選擇win10系統。但看到這麼多版本的win10系統,糾結就會發生。