What is DLL hooking
很多 EDR 會 hook 一些常用的、或是比較重要的一些 API 來監測行為,像是 read file、write memory、create process 之類的,只要呼叫這些 API EDR 就會知道並去檢查這些行為有沒有可疑的地方。
How to hook
Inline unhook
直接修改 DLL 中目標 API 的內容
- 在 DLL function 開頭加上一段跳到 EDR 的 JMP/CALL
- 所以執行流程就是 call critical API → DLL API jump → EDR function → jump back to DLL API
- 他會先跑去 EDR 做他要監測、記錄的那些事情,再回去原本的 API 完成原本的事
這整個事情通常是會在 process create 的時候,EDR 會把自己的 DLL inject 到 process memory 中去修改目標 dll 的 memory 內容
- 他會去找到目標的 API 的 memory address 然後覆蓋他的開頭 (成
JMP
之類的)
那 EDR 又是怎麼知道 process create 的?有幾種方法
Windows Native API
- https://learn.microsoft.com/zh-tw/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreateprocessnotifyroutineex
- 像是
PsSetCreateProcessNotifyRoutineEX
或 WMI、user notification hook
AppInit_DLL
可讓您將自定義 DLL 載入每個互動式應用程式的位址空間,以輕鬆攔截系統 API
Kernel driver
- 驅動層攔截或修改 API(如 ZwCreateProcess),或利用 ETW(Event Tracing for Windows)做即時追蹤,直接於 process 建立階段註入自身 DLL/agent。
遠端注入機制 (Remote DLL Injection)
- 利用 API(
CreateRemoteThread
、NtWriteVirtualMemory
、NtCreateThreadEx
等)將 EDR 的監控 DLL 注入到目標 process。 - 可直接用 C/C++ API 複製 DLL path 進對方記憶體,創建 thread 來執行。
- 利用 API(
自動 traverse 現有執行中的 process
IAT/EAT Hook
- IAT Hook (Import Address Table):改 import table 的 pointer,讓本來要指向 A.dll 的 API 指向 EDR.dll。
- EAT Hook (Export Address Table): 改 export table 的 pointer,修改 DLL Export function。
這樣就不會動到 DLL 本身而是改 table pointer,更進階一點就是去 hook kernel 層或是 syscall,但這就不在 DLL 這裡了。
What is kernel32.dll
是個 Windows 中常見的 DLL,包含一些基本的系統功能給 user APP 方便使用,會去 call 一些底層的 API。
kernel32.dll 主要負責基本作業系統功能呼叫,例如檔案、記憶體、程序、執行緒、時間、DLL 管理,是 Windows 上應用程式與作業系統核心互動的最重要的基石之一。
很多行爲會經由 ntdll.dll 再進一步與 kernel 互動。
常見的 API 範例
- 檔案管理:
CreateFileW
/ReadFile
/WriteFile
/DeleteFileW
- 記憶體管理:
VirtualAlloc
/VirtualFree
/VirtualProtect
- 程序與執行緒控制:
CreateProcessW
/ExitProcess
/CreateThread
/TerminateThread
- 系統時間/計時:
GetSystemTime
/Sleep
- DLL 載入/管理:
LoadLibraryW
/FreeLibrary
/GetProcAddress
Execution Flow
所以在我的理解上程式是這樣運作的
1 |
|
可以理解成幾乎所有 Windows 應用程式啟動都會自動載入 kernel32.dll,所以 hook ntdll.dll 或 kernel32.dll 就可以攔截到大多對系統比較有影響力的行為。
Solve DLL hook
有幾種辦法,常見的有
- DLL unhook: 就是去把 DLL 還原,拿掉 hook
- Direct syscall: 繞過那些可能被 hook 的 DLL 直接去 call syscall
- Indirect syscall: 透過正常的執行 dll 去 call 到目標 syscall
Unhook
簡單來說就是去找到 hook 的地方,以 inline hook 來說就是找到那個開頭跳去 EDR 的地方來知道這個 DLL 有沒有被 hook,然後用乾淨的 DLL (from disk / windows cache) code 蓋回被 hook 的 code,讓 API 恢復原狀,這時候即使有執行 dll 裡的東西也不會被 EDR 看到了。
還有很多後來發展的做法,有時間再補上,不過基本的 unhook 就是這麼做的。
ref:
- https://www.ired.team/offensive-security/defense-evasion/how-to-unhook-a-dll-using-c++
- https://docs.redteamleaders.com/offensive-security/defense-evasion/hookchain-technique-introduction-by-helvio-junior-m4v3r1ck
- https://github.com/EvilBytecode/Nyx-Full-Dll-Unhook
- https://github.com/trickster0/LdrLoadDll-Unhooking
Direct Syscalls
直接呼叫 syscall,不通過 ntdll.dll 這種有被 hook 的 DLL 來執行那些重要 function
- https://github.com/jthuraisamy/SysWhispers2
- https://github.com/klezVirus/SysWhispers3
- https://github.com/am0nsec/HellsGate
會需要從 loaded ntdll.dll 中去讀地址、動態算出 syscall number 和 system service numbers (SSN),才不用為每一個版本都重寫一次。
但是還是有機會留下紀錄被偵測到,因為
- Syscall instruction 在 ntdll.dll 的 memory 外執行,不太尋常
- return instruction 也是
這兩件事在 Windows 上都是 non-legitimate 的。
Indirect Syscalls
用 ntdll.dll 裡的 Native API (ex. NtOpenProcess, NtWriteVirtualMemory) 做 system call,不經過 kernel32.dll 這些上層 DLL。
因為是直接在 ntdll.dll 的 memory 中執行,而且 return 也在 ntdll.dll memory 中發生 (然後指向真正要執行的 indirect syscall assembly),前面提到 direct syscall 的缺點都解決了,不過如果 EDR 有用 ETW (Event Tracing for Windows) 還是有可能會透過 call stack 發現異常。
但是如果像是 ntdll.dll 也被 hook,他還想用 ntdll.dll 去做 indirect syscall 就還是會被 EDR 看到。
ref: https://0xdarkvortex.dev/hiding-in-plainsight/
Reference
https://www.ired.team/offensive-security/defense-evasion/how-to-unhook-a-dll-using-c++
https://redops.at/en/blog/direct-syscalls-vs-indirect-syscalls