前面介紹 Kernel Debugging 時,我們已經開始接觸 Windows Kernel,以及如何使用 WinDbg 分析惡意的 Kernel Driver。
而當惡意程式進入 Kernel Mode 之後,除了能取得相當高的權限之外,還有一件非常重要的事情可以做:
修改作業系統原本的行為。
這也帶出了今天的主題——Rootkit。
Rootkit 最麻煩的地方不一定在於它執行了什麼惡意功能,而是它可以修改作業系統內部的功能,進一步隱藏自己的存在。
例如隱藏:
這代表即使我們使用一般程式去查看系統狀態,看到的結果也不一定是真的。
今天會從教材中的經典案例 SSDT Hooking 開始,看看 Rootkit 如何攔截 Windows 的 System Call,以及分析人員如何透過 WinDbg 與 IDA Pro 找出這些 Hook。
【聲明】由於時間問題本日文章由 AI 生成QQ
Rootkit 的核心概念,可以簡單理解成:
修改作業系統內部功能,讓系統替惡意程式隱藏某些資訊。
一般惡意程式可能會建立檔案、Process 或 Network Connection,而防毒軟體與分析人員可以透過各種工具尋找這些跡象。
但是,如果 Rootkit 直接修改了 OS 回傳資訊的方式,情況就不一樣了。
假設系統裡其實存在:
malware.sys
正常情況下,我們向 Windows 查詢目錄內容,Windows 應該會告訴我們這個檔案存在。
但 Rootkit 可以在 Kernel 中攔截這些操作,把 malware.sys 從結果中移除。
如此一來,檔案實際上仍然存在,只是 User Mode 程式所取得的結果已經被 Rootkit 動過手腳。
也因此,Rootkit 能讓 Antivirus、Administrator,甚至 Malware Analyst 更難發現惡意活動。
教材提到,大部分 Rootkit 都會透過某種方式修改 Kernel,而其中一個非常經典的技術就是:
System Service Descriptor Table Hooking,也就是 SSDT Hooking。
SSDT Hooking 是一種相對老舊,而且比其他 Rootkit 技術容易被偵測的方法。
不過它的原理簡單、彈性高,也很好理解,因此很適合拿來學習 Rootkit 到底如何修改 Windows Kernel 的行為。
在理解 SSDT Hooking 以前,要先知道 SSDT 是什麼。
SSDT 全名是:
System Service Descriptor Table
有時也會被稱為:
System Service Dispatch Table
Windows 會在內部使用 SSDT 尋找對應的 Kernel Function。
我們前面學過,User Mode 的程式不能直接隨意執行 Kernel Mode Code。
User Space 要進入 Kernel Space,需要透過特定機制,例如:
SYSCALL
SYSENTER
INT 0x2E
教材使用的系統中則是透過 SYSENTER 進入 Kernel。
例如,當程式需要呼叫:
NtCreateFile
ntdll.dll 中可以看到類似下面的程式碼:
mov eax, 25h
mov edx, 7FFE0300h
call dword ptr [edx]
retn 2Ch
接著執行:
mov edx, esp
sysenter
這裡最值得注意的是:
mov eax, 25h
也就是把:
0x25
放進 EAX。
這個數值代表 NtCreateFile 對應的 Function Number。
進入 Kernel 之後,Windows 就能使用這個數字作為 Index,到 SSDT 中尋找真正需要執行的 Kernel Function。
可以把 SSDT 簡化想成:
SSDT
0x22 → NtCreateDirectoryObject
0x23 → NtCreateEvent
0x24 → NtCreateEventPair
0x25 → NtCreateFile
0x26 → NtCreateIoCompletion
0x27 → NtCreateJobObject
所以:
EAX = 0x25
就代表要執行 SSDT 中 Index 0x25 所指向的 Function。
正常情況下,這個位置會指向真正的:
NtCreateFile
知道 SSDT 的用途之後,Rootkit 要做的事情其實就很好理解了。
假設原本:
SSDT[0x25] → NtCreateFile
Rootkit 可以直接修改 SSDT 裡面的 Function Address,讓它變成:
SSDT[0x25] → Rootkit Function
如此一來,User Mode 程式雖然還是正常呼叫 NtCreateFile,但是進入 Kernel 並查詢 SSDT 之後,真正被執行的卻變成 Rootkit 自己的程式碼。
流程原本是:
Application
↓
NtCreateFile
↓
SYSENTER
↓
SSDT[0x25]
↓
真正的 NtCreateFile
被 Hook 之後則變成:
Application
↓
NtCreateFile
↓
SYSENTER
↓
SSDT[0x25]
↓
Rootkit Hook
Rootkit 取得控制權之後,就能決定接下來要怎麼處理這個 Request。
不過 Rootkit 通常不會把原本的 NtCreateFile 完全丟掉。
比較常見的作法是:
Request
↓
Rootkit Hook
↓
檢查 Request
↓
要隱藏?
┌───────┴───────┐
Yes No
↓ ↓
阻止 Request Original NtCreateFile
也就是只針對 Rootkit 想隱藏的內容進行 Filter,其他正常操作仍然交給原本的 NtCreateFile。
例如有人試圖開啟 Rootkit 自己的檔案,就回傳「檔案不存在」。
但如果使用者開啟:
C:\Windows\notepad.exe
Rootkit 根本不需要干涉,直接讓原本的 NtCreateFile 繼續執行即可。
要注意的是,教材中的例子只 Hook NtCreateFile,因此主要能阻止特定檔案被開啟,不代表檔案就不會出現在 Directory Listing 中。
如果要完整隱藏檔案,還需要處理其他相關操作。
知道 Rootkit 怎麼 Hook SSDT 之後,接下來就是實際分析。
假設現在拿到一台疑似遭到感染的電腦,我們懷疑系統裡安裝了惡意 Driver。
既然懷疑的是 SSDT Hooking,最直接的方法自然就是:
檢查 SSDT。
在 WinDbg 中,可以透過:
nt!KeServiceDescriptorTable
找到 SSDT。
正常情況下,SSDT 裡面的 Function Address 應該要指向 Windows Kernel,也就是 ntoskrnl.exe 所在的 Address Range。
教材中的 ntoskrnl.exe 範圍為:
804d7000
~
806cd580
因此如果 SSDT 裡突然出現:
f7ad94a4
就非常可疑。
因為:
f7ad94a4
根本不在 ntoskrnl.exe 的範圍裡。
進一步檢查之後,可以發現異常位置剛好就是:
SSDT[0x25]
也就是原本應該對應:
NtCreateFile
的位置。
換句話說,我們已經找到一個很重要的跡象:
NtCreateFile
↓
SSDT[0x25]
↓
不再指向 ntoskrnl.exe
很可能代表這個 Function 已經被 Hook。
知道:
f7ad94a4
很可疑之後,下一個問題自然是:
這個 Address 到底屬於誰?
這時候可以使用 WinDbg:
lm
列出目前載入的 Module。
在 Kernel Debugging 中,這裡看到的 Module 主要就是各種 Driver。
教材中的結果可以看到:
f7ad9000 f7ada680 Rootkit
而剛才找到的 Hook Address:
f7ad94a4
剛好落在:
f7ad9000 ~ f7ada680
之間。
所以我們就知道:
SSDT[0x25]
↓
0xf7ad94a4
↓
Rootkit Driver
到這裡已經可以建立出相當完整的關係:
NtCreateFile
↓
SSDT[0x25]
↓
被修改
↓
Rootkit Driver
接下來就可以開始 Reverse Engineering 這個 Driver。
找到惡意 Driver 之後,教材接著使用 IDA Pro 進行分析。
這時主要要找兩個部分:
安裝 Hook 的程式碼
Hook 被觸發之後實際執行的 Function
其中尋找 Hook 安裝程式碼的一種簡單方法,就是在 IDA Pro 中尋找對 Hook Function 的 Data Reference。
教材中的 Rootkit 會先處理兩個重要名稱:
NtCreateFile
KeServiceDescriptorTable
接著使用:
MmGetSystemRoutineAddress
取得它們的 Address。
如果之前分析過 User Mode Malware,可能很熟悉:
GetProcAddress
它可以用來取得 Exported Function 的 Address。
但現在是在 Kernel Mode。
Kernel Mode 不能直接使用 GetProcAddress,因此這裡使用的是:
MmGetSystemRoutineAddress
可以先簡單把它理解成 Kernel Mode 中類似 GetProcAddress 的存在。
不過兩者並不完全相同,教材特別指出 MmGetSystemRoutineAddress 只能取得 hal 與 ntoskrnl Kernel Module 所 Export 的 Address。
這個 Rootkit 第一次呼叫:
MmGetSystemRoutineAddress
取得:
NtCreateFile
真正的 Address。
第二次則取得:
KeServiceDescriptorTable
的 Address。
因此 Rootkit 現在同時知道:
NtCreateFile 在哪
以及:
SSDT 在哪
接下來就可以修改 SSDT。
取得兩個 Address 之後,Rootkit 開始跑一個 Loop。
概念大致上是:
從 SSDT 開始
↓
讀取一個 Entry
↓
是不是 NtCreateFile?
↓
不是 → 下一個 Entry
↓
是不是 NtCreateFile?
↓
...
因為每個 Address 為 4 Bytes,所以程式中會看到:
add ecx, 4
接著:
cmp [ecx], ebx
比較目前 SSDT Entry 與真正的 NtCreateFile Address。
找到之後,Rootkit 最後執行:
mov dword ptr [ecx], offset sub_104A4
這行就是整個 SSDT Hooking 最關鍵的地方。
原本:
[ecx] → NtCreateFile
現在被改成:
[ecx] → sub_104A4
而 sub_104A4 就是 Rootkit 自己的 Hook Function。
Hook 到這裡正式完成。
接下來分析:
sub_104A4
也就是當程式呼叫 NtCreateFile 時,Rootkit 實際會執行的 Hook Function。
教材中的 Function 主要有兩條路。
其中一條最後:
jmp NtCreateFile
也就是:
這個 Request 沒有問題,交回原本的 NtCreateFile 處理。
另一條則:
mov eax, 0C0000034h
retn 2Ch
其中:
0xC0000034
代表:
STATUS_OBJECT_NAME_NOT_FOUND
也就是告訴呼叫者:
找不到這個 Object。
Hook Function 中會檢查傳入的 ObjectAttributes,其中包含 Filename 等資訊。
如果 Rootkit 判斷這個 File 可以正常存取:
Rootkit Hook
↓
Original NtCreateFile
如果判斷這個 File 應該被隱藏:
Rootkit Hook
↓
STATUS_OBJECT_NAME_NOT_FOUND
User Mode Application 最後得到的結果就是:
File does not exist
即使那個 File 實際上存在。
這也是 Rootkit 很重要的一個概念:
不是把東西真的刪掉,而是修改系統呈現給其他程式的結果。
除了 SSDT Hooking 之外,教材最後還介紹另一個 Rootkit 可能利用的機制:
Interrupts。
現代處理器會使用 Interrupt,讓 Hardware 能夠觸發 Software Event。
例如系統向某個 Hardware 發出 Command,Hardware 完成工作之後,就可以透過 Interrupt 通知 Processor。
Driver 也會利用這套機制。
Driver 可以呼叫:
IoConnectInterrupt
為某個 Interrupt Code 註冊 Handler,並指定:
Interrupt Service Routine
也就是 ISR。
之後每當這個 Interrupt 發生,OS 就會執行對應的 ISR。
既然系統需要知道:
Interrupt → 哪一個 ISR
自然也需要一個地方保存這些資訊。
這就是:
Interrupt Descriptor Table,IDT
在 WinDbg 中可以使用:
!idt
查看 Interrupt Descriptor Table。
正常情況下,我們可能會看到:
hal!HalpApcInterrupt
atapi!IdePortInterrupt
NDIS!ndisMIsr
USBPORT!USBPORT_InterruptService
i8042prt!I8042KeyboardInterruptService
這些 Interrupt 都指向已知的正常 Driver。
因此分析 Rootkit 時,也可以檢查 IDT。
如果發現 Interrupt 指向:
Unknown Driver
Unsigned Driver
名稱或來源可疑的 Driver
就值得進一步調查。
這不代表看到陌生 Driver 就一定是 Rootkit,但它可以成為分析 Kernel Malware 時的重要線索。
今天介紹的 Rootkit,和之前分析的一般 User Mode Malware 有很大的不同。
一般 Malware Analysis 常常是在觀察:
CreateProcess
CreateFile
Registry
Network
Persistence
但進入 Rootkit Analysis 之後,我們開始關心:
Kernel
Driver
System Call
SSDT
Hook
Interrupt
IDT
其中教材最主要的案例就是 SSDT Hooking。
整個流程可以濃縮成:
User Mode Application
↓
NtCreateFile
↓
SYSENTER
↓
SSDT
↓
Rootkit Hook
↓
檢查 Request
↙ ↘
允許 阻止
↓ ↓
NtCreateFile STATUS_OBJECT_NAME_NOT_FOUND
而站在 Malware Analyst 的角度,分析流程則剛好反過來:
檢查 SSDT
↓
找到異常 Function Address
↓
確認 Address 屬於哪個 Driver
↓
Reverse Engineering Driver
↓
找到安裝 Hook 的程式碼
↓
找到 Hook Function
↓
分析它到底過濾、隱藏了什麼
Rootkit 最值得理解的地方,就是它讓我們看到一件事情:
我們從作業系統 API 得到的資訊,本身也可能是不可信的。
當惡意程式已經進入 Kernel 並修改 OS 的內部行為時,分析就不能只停留在 User Mode 觀察 Process 或 File,而需要進一步理解 System Call、Kernel Driver、SSDT,甚至 Interrupt 等底層機制。
這也是為什麼前面學習的 Kernel Debugging 很重要——當惡意程式躲進 Kernel 裡,就必須跟著它進入 Kernel,才能真正看見它做了什麼。
