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

CVE-2017-5123 漏洞利用全攻略

原文:https://salls.github.io/Linux-Kernel-CVE-2017-5123/
譯者:知道創(chuàng)宇404實驗室

本文介紹如何利用Linux內核漏洞CVE-2017-5123提升權限,突破SEMP、SMAP、Chrome沙箱全方位保護。

背景

在系統調用處理階段,內核需要具備讀取和寫入觸發(fā)系統調用進程內存的能力。為此,內核設有copy_from_user與put_user等特殊函數,用于將數據復制進出用戶區(qū)。在較高級別,put_user的功能大致如下:

put_user(x, void __user *ptr)
    if (access_ok(VERIFY_WRITE, ptr, sizeof(*ptr)))
        return -EFAULT
    user_access_begin()
    *ptr = x
    user_access_end()

access_ok() 調用檢查ptr是否位于用戶區(qū)而非內核內存。如果檢查通過,user_access_begin()調用禁用SMAP,允許內核訪問用戶區(qū)。內核寫入內存后重新啟用SMAP。需要注意的一點是:這些用戶訪問函數在內存讀寫過程中處理頁面錯誤,在訪問未映射內存時不會導致崩潰。

漏洞

某些系統調用要求多次調用put/get_user以實現內核與用戶區(qū)之間的數據復制。為避免重復檢查和SMAP啟用/禁用的額外開銷,內核開發(fā)人員將缺少必要檢查的不安全版本_put_user與unsafe_put_user涵蓋進來。這樣一來,忘記額外檢查就在意料之中了。CVE-2017-5123就是一個很好的例子。在內核版本4.13中,為了能夠正常使用unsafe_put_user,專門對waitid syscall進行了更新,但access_ok檢查仍處于缺失狀態(tài)。漏洞代碼如下所示。

SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
                                  infop, int, options, struct rusage __user *, ru)
{
    struct rusage r;
    struct waitid_info info = {.status = 0};
    long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
    int signo = 0;

    if (err > 0) {
        signo = SIGCHLD;
        err = 0;
        if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
            return -EFAULT;
        }
        if (!infop)
            return err;

        user_access_begin();
        unsafe_put_user(signo, &infop->si_signo, Efault);    <-    no access_ok call
        unsafe_put_user(0, &infop->si_errno, Efault);
        unsafe_put_user(info.cause, &infop->si_code, Efault);
        unsafe_put_user(info.pid, &infop->si_pid, Efault);
        unsafe_put_user(info.uid, &infop->si_uid, Efault);
        unsafe_put_user(info.status, &infop->si_status, Efault);
        user_access_end();
        return err;
Efault:
        user_access_end();
        return -EFAULT;
}

原語

缺少access_ok檢查意味著允許提供內核地址并將其作為waitid syscall的infop參數。syscall將使用unsafe_put_user覆蓋內核地址,因為此項操作可以逃避檢查。該原語的棘手部分在于無法對寫入內容(6個不同字段中的任何1個)施與足夠控制。info.status 是32位int,但被限制為0 < status < 256。info.pid可在某種程度上通過重復fork操作進行控制,但最大值為0x8000。

以下是漏洞利用階段將引用到的寫入字段概況。

struct siginfo {
    int si_signo;
    int si_errno;
    int si_code;
    int padding;   // this remains unchanged by waitid
    int pid;       // process id
    int uid;       // user id
    int status;    // return code
}

谷歌Chrome沙箱

該漏洞的特色在于可從Chrome瀏覽器沙箱內部實現提權。首先介紹Chrome沙箱概況與工作原理。

谷歌Chrome采用沙箱保護瀏覽器,即便成功利用漏洞實現代碼執(zhí)行也無法touch系統其它部分。沙箱分兩層:第一層通過改變user id與chroot限制資源訪問;第二層嘗試通過seccomp filter限制內核攻擊面,阻止沙箱進程中不必要的系統調用。通常情況下,Chrome沙箱行之有效,因為Linux內核漏洞多位于syscall,由seccomp沙箱攔截。

然而,waitid syscall在seccomp沙箱中普遍存在,當然也包括Chrome沙箱(chrome seccomp source)。也就是說,可以通過攻擊內核實現Chrome沙箱逃逸!

沙箱的局限性在于不允許使用fork,只能創(chuàng)建新線程而非進程。如果無法進行fork操作,waitid就會無法發(fā)揮作用,只能將0寫入內核內存。

喘口氣,進行 infoleak

所有困難都是暫時的,但無論采取哪種方式,都需要先獲取內核基地址。 unsafe_put_user的一個優(yōu)秀屬性是在訪問無效內存地址時不會崩潰,僅返回-EFAULT。因此,我們僅需猜測內核數據段潛在地址,直至顯示不同錯誤代碼、找到內核地址。有了內核地址就可以攻破KASLR了, 但注意不要覆蓋任何重要信息 :)

我們可以用相同做法查找內核堆棧地址或內核內存其他區(qū)域。

SMAP繞過存在的局限性

現在,我想看看是否可以利用該漏洞突破所有防線。 結果發(fā)現目前能做的事情相當有限:

  • 只能寫0;
  • 寫24個字節(jié)的0,破壞附近內存;
  • 少量信息滲出,包括內核基地址與堆棧位置,但不包括堆棧目標位置。

輾轉思考多種漏洞利用方法后確定了幾個方向:

  • 在內核數據段找到一個對象,其索引/大小/值為零將導致超出內存訪問邊界;
  • 在內核中覆蓋一個自旋鎖,用來創(chuàng)建競爭條件;
  • 嘗試覆蓋內核堆棧上的基址指針或其他值;
  • 觸發(fā)可能導致在內核堆棧上創(chuàng)建有用結構的操作,看看是否可以用任意寫入的0命中對象。

我最終選取了第四個策略,進行堆噴射。

堆噴射

task_struct(代表每個進程和線程的結構)開始部分是一些flag,其中一個flag標記是否采用seccomp過濾器。如果能夠用task_structs進行堆噴射,并且只覆蓋那些起始flag,則可從其中一個進程移除seccomp,從而獲取更多可能。

考慮到Linux內核堆棧并非自身擅長領域,先噴射10000個線程,然后使用調試器檢查任務結構在堆棧中的位置。我注意到,噴射對象達到一定數量后,大部分任務結構將在堆棧較低地址處結束。這似乎意味著隨著空閑槽被用完,堆棧將向下擴展。

接下來的計劃是:

  • 創(chuàng)建10000個線程;
  • 從堆棧最低地址起繼續(xù)猜測任務結構潛在地址;
  • 讓10000個線程繼續(xù)自檢是否仍位于seccomp沙箱;
  • 當發(fā)現某個線程不再受seccomp影響時停止。

結果竟然奏效了!這種做法雖不可靠,但作為PoC已經足夠。我認為增大噴射力度能夠提升可靠程度。如果先噴灑其他對象填充,再創(chuàng)建10000個線程釋放,可以更加確定目標任務結構將位于堆棧底部。截至目前,我電腦上的運行結果已達到50%成功率,其余半數則以內核崩潰告終。

獲得更佳“任意”寫入效果

現在,我們面臨一項seccomp沙箱外圍任務,目前已從上一步獲知task_struct地址,仍需弄清如何利用內核漏洞升級到root權限并移除chroot。

好在原語已得到優(yōu)化,可以使用fork() 來創(chuàng)建子對象,然后使waitid寫入非零值。盡管如此,我們仍無法控制多數siginfo結構。唯一可用值是pid和status,兩者都存在一定限制。 pid最大值是0x8000,狀態(tài)是單字節(jié)。

但是,由于pid緊挨著一些未使用的填充(如前文所述),可以執(zhí)行5次寫入,每次都移回一個字節(jié),構造一個任意寫入的5字節(jié)。

5字節(jié)寫入+ Physmap

5字節(jié)寫入的使用方法并非顯而易見,暫時仍無法創(chuàng)建任意地址。然而,我們可以創(chuàng)建外觀類似 0x**********000000的地址,其中*可以是任意值。

在此,我從ret2dir獲取靈感。有一段名為physmap的內核內存,其中內核保留一個映射到與用戶區(qū)內存具有相同物理內存的“alias”(虛擬地址)。因此,在用戶區(qū)創(chuàng)建一個填充0x41的頁面后,內核中確實存在一個可以找到與該頁面完全相同的網頁地址。

我的策略是在用戶區(qū)分配大量內存,然后嘗試隨機覆蓋內核physmap中的頁面,同時檢查用戶區(qū)頁面是否已經改變。如果發(fā)現變化,則說明我們已經找到了一個與用戶區(qū)地址相對應的內核虛擬地址,可以寫入用戶區(qū)并在內核內存中創(chuàng)建有效payload。我僅對內核physmap中以6個0結尾的頁面進行了嘗試,一旦找到“alias”,就可以構造一個指向內核地址的指針。

這部分內容非常可靠,但在罕見情況下也可以崩潰一個隨機過程。

真正的任意讀/寫和Root操作!

現在我覆蓋task_struct中的files指針,使其指向內核中的“alias”,在用戶區(qū)構造一個偽造的files_struct對象,該對象也將位于alias.file對象,好處在于它們包含函數指針,即用來控制使用函數(如read,lseek,ioctl)的參數。通過將ioctl指向內核中的各種ROP小工具可以創(chuàng)建一個任意讀寫原語。于是,我修復了task_struct的clobbered部分,將creds結構改為root。最后,通過重置當前的fs移除chroot。現在我們已經完全實現沙箱逃逸,能夠以root身份彈出一個計算器了!

完整漏洞參見https://github.com/salls/kernel-exploits/blob/master/CVE-2017-5123/exploit_smap_bypass.c

上一篇:glibc malloc學習筆記之fastbin

下一篇:WPA2 “KRACK” 漏洞簡介與重現