驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)

Editor發表於2018-05-30


在Part I 中,我們已經處理完最棘手的部分:殺掉 QQFrmMgr.sys 建立的系統執行緒。剩餘的工作就輕鬆多了——移除 QQFrmMgr.sys 和 QQProtect.sys

安裝的 SSDT(系統服務排程表)鉤子與 SSDT Shadow 鉤子、銷燬它們註冊的事件通知callback,從而將系統恢復至乾淨狀態。


在此之前,按照慣例,還是先來檢查一下這兩個 QQ 驅動是否attach到了其它裝置棧中的裝置上,因為 rootkit 或惡意軟體通常會掛載到其它合法驅動建立的裝置上,以便攔截或修改途經的 IRP(I/O 請求包)中攜帶的敏感資料,比如使用者的擊鍵資料。


如果發現了任何掛載跡象,則可以通過前一篇介紹的 APC 機制結合 IoDetachDevice() 例程,把惡意裝置從裝置棧中清理掉。


由於這兩個 QQ 驅動會向 Windows 物件管理器維護的全域性名稱空間中,註冊相應的裝置物件名,如下圖所示:


驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)


Windows I/O 管理器匯出的兩個例程 IoCreateDevice() 與 IoCreateSymbolicLink() 普遍被驅動程式用來向 NT 名稱空間中註冊裝置物件名,以及相應的符號連結。


使用者態程式使用符號連結訪問該物件;而在核心空間中,可以直接通過物件名進行訪問,所以我們先用核心偵錯程式的 “!devstack” 擴充套件命令,後接這兩個 QQ 裝置物件的名稱,查詢它們是否掛載到了任何系統現存的裝置棧上:


驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)


如你所見,這兩個裝置物件各自所在的裝置棧中,都只有它們自身——如果它們掛載到了任何其它裝置上,“!devstack” 的輸出中就會含有那些 “受害” 的裝置。


其中,“!DevObj” 欄位下的 4 位元組 16 進位制數是該裝置物件的 “nt!_DEVICE_OBJECT” 結構地址;“!DrvObj” 欄位下的則是建立它們的驅動物件名稱。


其實這兩個 QQ 裝置物件還算是 “良性” 的——某些 rootkit 建立裝置物件時,根本不註冊名字到 NT 名稱空間(通過向 IoCreateDevice() 的第三個引數傳入 NULL,就可以做到這一點),對於此類 “惡性” 的匿名裝置,需要獲悉它的 “nt!_DEVICE_OBJECT” 結構地址,然後才能用 “!devstack” 遍歷裝置棧,這個難度就不小了。


言歸正傳,接下來先檢查系統的 SSDT,尋找有無被掛鉤的系統服務,如下圖,SSDT 的起始地址為 0x83c80f7c,一共有 0x191(401)個系統服務,其中一部分已經被替換成 QQFrmMgr.sys 的鉤子函式:


驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)

驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)


本來可以利用 “!chkimg” 擴充套件命令執行自動化檢查,將 nt 模組(ntoskrnl.exe)的記憶體映像與磁碟檔案比較,從而找出那些被修改了的部分,但不知為何我的宿主機上 WinDbg 無法對 nt 模組實施檢查,總是提示 ntkrpamp.exe/ntoskrnl.exe 的版本不匹配。(還請成功執行 “!chkimg” 命令檢查 nt 模組的各位提供經驗)


一種最原始的方法就是先記錄下受感染機器上 QQFrmMgr.sys 的鉤子函式在 SSDT 中的位置,然後把它與另一個乾淨系統上的 SSDT 對比,以得知被 hook的是哪個系統服務——前面那張截圖就是用這種髒累的體力活實現的。


注意,系統每次初始化時,SSDT 的基地址,以及其中的系統服務入口點地址都是隨機變化的,因而我們不能記錄它們的核心地址,而是要記錄函式名,在復原前用反彙編指令 “u” ,即可強制解析原始系統服務在本次啟動時分配到的核心地址,然後以 “eb” 指令編輯還原被 hook 的表項——言詞蒼白無力,還是看圖有真相吧:


驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)


驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)


注意,Intel x86/x64 體系結構的微處理器採用“小端位元組序”在記憶體中放置資料,換言之,一個 “雙字” (DWORD)資料的最低有效位元組位於連續的四位元組儲存空間中的最小地址處;最高有效位則存放在最大地址處。以上圖為例,系統服務 “nt!NtCreateSection” 的入口點地址——83e5583b,其中最低有效位元組是 “3b”,所以我們在編輯時把它放在最前面(最小地址處)。


這個遊戲規則是處理器硬體廠商規定的,如果不遵守它來辦事就無法正確地恢復被掛鉤的系統函式!


此外,通過分析我們還得知:QQFrmMgr.sys 利用 “inline hook” 技術硬掛鉤了KeUserModeCallback() 核心例程中的正常函式呼叫,由於我機器上的 “!chkimg” 不能工作,無法依賴它檢測處掛鉤前的原始函式呼叫,但是我們可以用 IDA PRO 逆向 ntkrpamp.exe/ntoskrnl.exe 的磁碟檔案,定位到 KeUserModeCallback() 中的原始函式呼叫——這種不修改函式指標陣列(如 SSDT,一般位於 .data section),而是修改特定函式(一般位於.text section)中的呼叫邏輯,就稱為“inline hook”。


如下兩張截圖,手工比較 “ntkrnlpa.exe” 的磁碟檔案與記憶體映像,兩者的KeUserModeCallback() 初始邏輯(機器指令序列)基本相同,除了記憶體映像中的首條 call 指令目標被替換成了 “QQFrmMgr+0x503e” 之外......


驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)

驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)


擊敗 “inline hook” 的方法也是用前面介紹過的 “eb” 記憶體編輯指令,根據上圖 IDA PRO 中的原始資訊來還原。同樣需要留心位元組序的問題!


(事實上,“!chkimg”有一個開關選項為“-f”,能夠把二進位制檔案的記憶體映像中所有被篡改或損壞之處還原成與磁碟檔案上的一致,“一鍵還原所有 hook”,無需前面介紹的繁雜手工操作;但既然該指令用在 nt 模組上如此令人蛋疼,這裡也就無法演示了,請各位自行測試!)


清理完 SSDT 中的病毒後,讓我們來關注 SSDT Shadow——核心全域性變數 “KeServiceDescriptorTableShadow” 持有該表的基地址,該表由 Win32 子系統的核心模式部分—— Win32k.sys 填寫,負責實現 GUI 執行緒請求的所有涉及視窗繪圖,滑鼠指標,以及其它圖形操作。


由於多數正規應用程式為了與使用者互動,都會請求圖形操作,因此該表也成了 rootkit 們重點 hook 的系統資料結構之一。


如下圖所示,我們在檢查 SSDT Shadow 中的函式時遇到了無效的記憶體地址(顯示為問號),這是因為斷入偵錯程式的當前執行緒不是一個 “GUI 執行緒” ,所以它的 SSDT Shadow 起始虛擬地址(圖中的 9a94c000)沒有對映到實體記憶體中的實際 SSDT Shadow(您可以看到描述該虛擬地址的頁表條目是非法的):


驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)


如果當前執行緒的 “nt!_KTHREAD” 結構的 “Win32Thread” 欄位為“NULL”,那麼它就不是一個 GUI 執行緒,從而它的頁表中,描述 SSDT Shadow 虛擬地址的那一項 PTE 就是無效的,沒法用來轉譯實體地址:


驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)


為了“看見 ”Win32k.sys 實現的 SSDT Shadow,我們可以在絕大多數使用者、核心模式執行緒都頻繁呼叫的例程——nt!NtWaitForSingleObject()

下一個條件斷點,僅在呼叫執行緒的 “Win32Thread” 欄位不為空時,才斷入核心偵錯程式,這樣我們就能夠窺視 SSDT Shadow 了!


如下一系列截圖所示:


驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)

驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)

驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)

驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)


綜上截圖所述,QQFrmMgr.sys 在 SSDT Shadow 中僅安裝了一枚鉤子,我們計算出該鉤子例程在整個 Win32k.sys 記憶體映像中的偏移量為0x20c630——該資訊相當關鍵,它用於在 Win32k.sys 磁碟檔案中定位原始的 SSDT Shadow 函式入口地址。


再次以 IDA PRO 開啟 Win32k.sys,從該模組的描述資訊可知,它的基地址為0xBF800000,把這個值加上 0x20c630 得出0xBFA0C630——也就是持有 “受害” 函式入口點的位置,於是我們在 IDA PRO 中執行 “Jump to address” 選單選項,跳轉到該地址,真相大白,被 hook 的就是 “win32k!NtUserFindWindowEx” ,請注意它的前後兩個例程與記憶體映像中鉤子例程的前後兩個完全一致。


反彙編 “win32k!NtUserFindWindowEx” 來獲取它在本次引導例項中的核心地址,然後用 “eb” 移除掉可惡的 QQ 鉤子函式,至此大功告成!


驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)

驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)

驅動除錯—還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)

小結


本文演示瞭如何通過除錯手段,將被惡意修改的關鍵系統設施還原成初始狀態。

限於篇幅,最後一部分內容——銷燬 QQ 驅動註冊的事件通知回撥函式——將放在 Part III 中介紹。


原文連結:[原創]---驅動除錯-還原 QQ 過濾驅動對關鍵核心設施所做的修改(Part II)


本文由看雪論壇 shayi  原創

轉載請註明來自看雪社群

相關文章