————————————————————————————————————————————————————————————————————————————————數組
在前一篇博文中,咱們已經處理完最棘手的部分:殺掉 QQFrmMgr.sys 建立的系統線程。剩餘的工做就輕鬆多了——移除 QQFrmMgr.sys 和 QQProtect.sys 數據結構
安裝的 SSDT(系統服務調度表)鉤子與 SSDT Shadow 鉤子、銷燬它們註冊的事件通知 callback,從而將系統恢復至乾淨狀態。函數
在此以前,按照慣例,仍是先來檢查一下這兩個 QQ 驅動是否 attach 到了其它設備棧中的設備上,由於 rootkit 或惡意軟件一般會掛載到其它合法驅動建立的測試
設備上,以便攔截或修改途經的 IRP(I/O 請求包)中攜帶的敏感數據,好比用戶的擊鍵數據。spa
若是發現了任何掛載跡象,則能夠經過前一篇介紹的 APC 機制結合 IoDetachDevice() 例程,把惡意設備從設備棧中清理掉。
因爲這兩個 QQ 驅動會向 Windows 對象管理器維護的全局名稱空間中,註冊相應的設備對象名,以下圖所示:線程
————————————————————————————————————————————————————————————————3d
Windows I/O 管理器導出的兩個例程 IoCreateDevice() 與 IoCreateSymbolicLink() 廣泛被驅動程序用來向 NT 名稱空間中註冊設備對象名,以及相應的符指針
號連接。用戶態進程使用符號連接訪問該對象;而在內核空間中,能夠直接經過對象名進行訪問,因此咱們先用內核調試器的「!devstack」擴展命令,後接這調試
兩個 QQ 設備對象的名稱,查詢它們是否掛載到了任何系統現存的設備棧上:對象
如你所見,這兩個設備對象各自所在的設備棧中,都只有它們自身——若是它們掛載到了任何其它設備上,「!devstack」的輸出中就會含有那些「受害」的
設備。其中,「!DevObj」欄位下的 4 字節 16 進制數是該設備對象的「nt!_DEVICE_OBJECT」結構地址;「!DrvObj」欄位下的則是建立它們的驅動對象名
稱。其實這兩個 QQ 設備對象還算是「良性」的——某些 rootkit 建立設備對象時,根本不註冊名字到 NT 名稱空間(經過向 IoCreateDevice() 的第三個參
數傳入 NULL,就能夠作到這一點),對於此類「惡性」的匿名設備,須要獲悉它的「nt!_DEVICE_OBJECT」結構地址,而後才能用「!devstack」遍歷設備
棧,這個難度就不小了。
言歸正傳,接下來先檢查系統的 SSDT,尋找有無被掛鉤的系統服務,以下圖,SSDT 的起始地址爲 0x83c80f7c,一共有 0x191(401)個系統服務,其中一
部分已經被替換成 QQFrmMgr.sys 的鉤子函數:
原本能夠利用「!chkimg」擴展命令執行自動化檢查,將 nt 模塊(ntoskrnl.exe)的內存映像與磁盤文件比較,從而找出那些被修改了的部分,但不知爲什麼我
的宿主機上 WinDbg 沒法對 nt 模塊實施檢查,老是提示 ntkrpamp.exe/ntoskrnl.exe 的版本不匹配。(——還請成功執行「!chkimg」命令檢查 nt 模塊的
各位提供經驗——)
一種最原始的方法就是先記錄下受感染機器上 QQFrmMgr.sys 的鉤子函數在 SSDT 中的位置,而後把它與另外一個乾淨系統上的 SSDT 對比,以得知被 hook
的是哪一個系統服務——前面那張截圖就是用這種髒累的體力活實現的。
注意,系統每次初始化時,SSDT 的基地址,以及其中的系統服務入口點地址都是隨機變化的,於是咱們不能記錄它們的內核地址,而是要記錄函數名,在復
原前用反彙編指令「u」,便可強制解析原始系統服務在本次啓動時分配到的內核地址,而後以「eb」指令編輯還原被 hook 的表項——言詞蒼白無力,仍是
看圖有真相吧:
注意,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」以外。。。。。
擊敗「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(您能夠看到
描述該虛擬地址的頁表條目是非法的):
若是當前線程的「nt!_KTHREAD」結構的「Win32Thread」字段爲「NULL」,那麼它就不是一個 GUI 線程,從而它的頁表中,描述
SSDT Shadow 虛擬地址的那一項 PTE 就是無效的,無法用來轉譯物理地址:
爲了「看見」Win32k.sys 實現的 SSDT Shadow,咱們能夠在絕大多數用戶、內核模式線程都頻繁調用的例程—— nt!NtWaitForSingleObject()
下一個條件斷點,僅在調用線程的「Win32Thread」字段不爲空時,才斷入內核調試器,這樣咱們就可以窺視 SSDT Shadow 了!
以下一系列截圖所示:
綜上截圖所述,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 驅動註冊的事件通知回調函數——將放在下一篇博文中介紹,有任何疑問或者建議請在下方評論處反饋。
—————————————————————————————————————————————————————————————————————————————————————————