精品国产一级在线观看,国产成人综合久久精品亚洲,免费一级欧美大片在线观看

當前位置:安全行業(yè)動態(tài) → 正文

Linux內(nèi)核中的遞歸漏洞利用

責(zé)任編輯:editor007 作者:Michael23 |來源:企業(yè)網(wǎng)D1Net  2016-06-30 14:58:22 本文摘自:FreeBuf.COM

6月1號,我提交了一個linux內(nèi)核中的任意遞歸漏洞。如果安裝Ubuntu系統(tǒng)時選擇了home目錄加密的話,該漏洞即可由本地用戶觸發(fā)。如果想了解漏洞利用代碼和短一點的漏洞報告的話,請訪問https://bugs.chromium.org/p/project-zero/issues/detail?id=836

背景知識

在Linux系統(tǒng)中,用戶態(tài)的棧空間通常大約是8MB。如果有程序發(fā)生了棧溢出的話(比如無限遞歸),棧所在的內(nèi)存保護頁一般會捕捉到。

Linux內(nèi)核棧(可以用來處理系統(tǒng)調(diào)用)和用戶態(tài)的棧很不一樣。內(nèi)核棧相對來說更短:32位x86架構(gòu)平臺為4096byte , 64位系統(tǒng)則有16384byte(內(nèi)核棧大小由THREAD_SIZE_ORDER 和 THREAD_SIZE 確定)。它們是由內(nèi)核的伙伴內(nèi)存分配器分配,伙伴內(nèi)存分配器是內(nèi)核常用來分配頁大小(以及頁大小倍數(shù))內(nèi)存的分配器,它不創(chuàng)建內(nèi)存保護頁。也就是說,如果內(nèi)核棧溢出的話,它將直接覆蓋正常的數(shù)據(jù)。正因如此,內(nèi)核代碼必須(通常也是)在棧上分配大內(nèi)存的時候非常小心,并且必須阻止過多的遞歸。

Linux上的大多數(shù)文件系統(tǒng)既不用底層設(shè)備(偽文件系統(tǒng),比如sysfs, procfs, tmpfs等),也不用塊設(shè)備(一般是硬盤上的一塊)作為備用存儲設(shè)備。然而, ecryptfs 和overlayfs是例外。這兩者是堆疊的文件系統(tǒng),這種文件系統(tǒng)會使用其他文件系統(tǒng)上的文件夾作為備用存儲設(shè)備(overlayfs則使用多個不同文件系統(tǒng)上的多個文件作為備用存儲設(shè)備)。被用作備用存儲設(shè)備的文件系統(tǒng)稱為底層文件系統(tǒng),其上的文件稱為底層文件。這種層疊文件系統(tǒng)的特點是它或多或少的會訪問底層文件系統(tǒng),并對訪問的數(shù)據(jù)做一些修改。 Overlayfs融合多個文件系統(tǒng),ecryptfs則進行了相應(yīng)的加密。

層疊文件系統(tǒng)實際上存在潛在風(fēng)險,因為其訪問虛擬文件系統(tǒng)的函數(shù)常會訪問到底層文件系統(tǒng)的函數(shù),相較直接訪問底層文件系統(tǒng)的句柄,這會增大棧空間。考慮這樣一個場景:如果用層疊文件系統(tǒng)作為另外一個層疊系統(tǒng)的備用存儲設(shè)備,由于每一層文件系統(tǒng)的堆疊都增大了棧空間,內(nèi)核棧就會在某些情況下溢出。但是,設(shè)置FILESYSTEM_MAX_STACK_DEPTH 限制文件系統(tǒng)的層數(shù),只允許最多兩層層疊文件系統(tǒng)放在非層疊文件系統(tǒng)上,就可以避免這個問題。

在Procfs偽文件系統(tǒng)上,系統(tǒng)中運行的每一個進程都有一個文件夾,每個文件夾包含一些描述該進程的文件。值得注意的是每個進程的“mem”,“ environ”和“cmdline”文件,因為訪問這些文件會同步訪問目標進程的虛擬內(nèi)存。這些文件顯示了不同的虛擬內(nèi)存地址范圍:

1.“mem”文件顯示了整個虛擬內(nèi)存地址范圍(需要PTRACE_MODE_ATTACH 權(quán)限)

2.“environ”文件顯示了mm->env_start 到mm->env_end的內(nèi)存范圍(需要PTRACE_MODE_READ權(quán)限)

3.“cmdline”文件顯示了mm->arg_start 到mm->arg_end的地址范圍(如果mm->arg_end的前一個字符是null 的話)

如果可以用mmap()函數(shù)映射“mem”文件的話(啥意義也沒有,別想太多),就可以映射成如下圖所示的樣子:

接下來,假設(shè)/proc/$pid/mem的映射有一些錯誤,那么在進程C里的內(nèi)存讀取錯誤,將會導(dǎo)致從進程B中映射的內(nèi)存出錯,進而導(dǎo)致進程B里出現(xiàn)其它的內(nèi)存錯誤,進而導(dǎo)致從A進程映射的內(nèi)存出錯,這就是一個遞歸內(nèi)存錯誤。

可是,現(xiàn)實中這是不可行的,“mem”,“environ”,“cmdline ”文件只能用VFS函數(shù)讀寫,mmap無法使用:

staticconst struct file_operations proc_pid_cmdline_ops = {

.read = proc_pid_cmdline_read,

.llseek = generic_file_llseek,

};

[...]

staticconst struct file_operations proc_mem_operations = {

.llseek = mem_lseek,

.read = mem_read,

.write = mem_write,

.open = mem_open,

.release = mem_release,

};

[...]

staticconst struct file_operations proc_environ_operations = {

.open = environ_open,

.read = environ_read,

.llseek = generic_file_llseek,

.release = mem_release,

};

相關(guān)ecryptfs文件系統(tǒng),比較有趣的一個細節(jié)在于它支持mmap()。用戶看到的內(nèi)存映射必須是解密的,而底層文件系統(tǒng)的內(nèi)存映射是加密的,因而ecryptfs 文件系統(tǒng)不能將mmap()函數(shù)直接映射到底層文件系統(tǒng)的mmap()函數(shù)上。Ecrypt 文件系統(tǒng)在內(nèi)存映射時使用了自己的頁緩存。

ecryptfs文件系統(tǒng)處理頁錯誤的時候,必須以某種方式讀取底層文件系統(tǒng)上加密的頁。這可以通過讀取底層文件文件系統(tǒng)的頁緩存(使用底層文件系統(tǒng)的mmap函數(shù))來實現(xiàn),但是這樣比較消耗內(nèi)存。于是它直接使用底層文件系統(tǒng)的 VFS讀取函數(shù)(通過kernel_read()),這樣做更加直接有效,但是這個做法有副作用,就是有可能會mmap() 到通常不能映射的解密后的文件(因為只要底層文件有讀權(quán)限并且包含合法的加密數(shù)據(jù), ecryptfs文件系統(tǒng)的mmap函數(shù)就能工作)。

漏洞分析

在此,我們就能描繪完整的攻擊方式了。首先創(chuàng)建一個進程A,進程號為$A。然后創(chuàng)建一個ecrypptfs 掛載/tmp/$A,使/proc/$A作為它的底層文件系統(tǒng)(ecryptfs 應(yīng)該只有一個 key,這樣文件名才不會被加密)。現(xiàn)在,如果/proc/$A下相應(yīng)的文件有合法的ecryptfs 文件頭的話,那么 /tmp/$A/mem, /tmp/$A/environ 和 /tmp/$A/cmdline就可以被映射。除非有 root 權(quán)限,否則無法將內(nèi)存映射到進程 A的00處,也就是 /proc/$A/mem 的開頭。因此從開始讀取 /proc/$/A 總是會返回-EIO,而且 /proc/$A/mem 不會有一個合法的 ecryptfs 文件頭。如此,environ 和 cmdline 文件才有攻擊的可能性。

在使用CONFIG_CHECKPOINT_RESTORE編譯的內(nèi)核(至少是Ubuntu的 distro 內(nèi)核)中,非特權(quán)用戶可以通過prctl(PR_SET_MM, PR_SET_MM_MAP, &mm_map,sizeof(mm_map), 0)設(shè)置mm_struct 中的 arg_start, arg_end, env_start 和 env_end值。這使得映射 /proc/$A/environ 和 /proc/$A/cmdline到任意虛擬內(nèi)存范圍成為可能。(不支持checkpoint-restore的內(nèi)核中,攻擊過程就稍微有點麻煩,但使用所需的參數(shù)區(qū)域和環(huán)境變量的長度重新執(zhí)行,然后取代部分棧空間的映射,還是有可能的。)

如果一個有效加密的ecryptfs文件被加載到進程A的內(nèi)存中,并且它的環(huán)境變量也被配置為指向這塊區(qū)域,那么環(huán)境變量區(qū)域里的解密形式的數(shù)據(jù)就可以在 /tmp/$A/environ文件中獲取。這個文件也可以被映射到進程B的內(nèi)存中。為了能夠重復(fù)該進程,某些數(shù)據(jù)需要反復(fù)加密,進而創(chuàng)建一個加密的matroska 文件,并將這個文件加載到進程 A的內(nèi)存中。這樣一來,映射互相進程解密環(huán)境變量區(qū)域的進程鏈就建立起來了:

如果映射到進程C和進程B的內(nèi)存相應(yīng)范圍內(nèi)沒有數(shù)據(jù),進程C 中的內(nèi)存錯誤(這個內(nèi)存錯誤可能是用戶空間產(chǎn)生也可能是由于用戶空間訪問內(nèi)核空間,比如通過copy_from_user()函數(shù))將會導(dǎo)致ecryptfs讀取 /proc/$B/environ ,進而導(dǎo)致進程B中的內(nèi)存錯誤,接下來導(dǎo)致ecryptfs讀取 /proc/$A/environ ,最后導(dǎo)致進程A中的進程錯誤。如此循環(huán)往復(fù),最終溢出內(nèi)核棧,使內(nèi)核崩潰。內(nèi)核棧如下:

[...]

[]handle_mm_fault+0xf8b/0x1820

[]__get_user_pages+0x135/0x620

[]get_user_pages+0x52/0x60

[]__access_remote_vm+0xe6/0x2d0

[]? alloc_pages_current+0x8c/0x110

[]access_remote_vm+0x1f/0x30

[]environ_read+0x122/0x1a0

[]? security_file_permission+0xa0/0xc0

[]__vfs_read+0x18/0x40

[]vfs_read+0x86/0x130

[]kernel_read+0x50/0x80

[]ecryptfs_read_lower+0x23/0x30

[]ecryptfs_decrypt_page+0x82/0x130

[]ecryptfs_readpage+0xcd/0x110

[]filemap_fault+0x23b/0x3f0

[]__do_fault+0x50/0xe0

[]handle_mm_fault+0xf8b/0x1820

[]__get_user_pages+0x135/0x620

[]get_user_pages+0x52/0x60

[]__access_remote_vm+0xe6/0x2d0

[]? alloc_pages_current+0x8c/0x110

[]access_remote_vm+0x1f/0x30

[]environ_read+0x122/0x1a0

[...]

關(guān)于這個漏洞的可利用性:利用該漏洞,需要能夠掛載/proc/$pid為ecryptfs文件系統(tǒng)。安裝完ecryptfs-utils包之后(如果安裝Ubuntu時選擇了home目錄加密, Ubuntu 會自動安裝),使用 /sbin/mount.ecryptfs_私有的setuid輔助函數(shù)就可以做到這一點。

漏洞利用

接下來的描述是平臺相關(guān)的,這里指amd64。

以前要利用這一類漏洞還是相當簡單的,可以直接覆蓋棧底的thread_info結(jié)構(gòu)體,用合適的數(shù)值重寫restart_block或者 addr_limit,然后根據(jù)所用方式,選擇執(zhí)行用戶空間映射的代碼,還是用copy_from_user() 和 copy_to_user() 直接讀寫內(nèi)核數(shù)據(jù)。

但是,restart_block已經(jīng)從thread_info結(jié)構(gòu)體中移除,并且由于棧溢出觸發(fā)時棧中有 kernel_read() 的棧幀,所以addr_limit已經(jīng)是KERNEL_DS,而且函數(shù)退出時將會重置成 USER_DS 。另外, Ubuntu 16.04以后的內(nèi)核都打開了CONFIG_SCHED_STACHK_END_CHECK 內(nèi)核配置選項。打開這個選項以后,每次調(diào)度到這個線程時, thread_info 結(jié)構(gòu)體上方的金絲雀值都會被檢查;如果金絲雀值不正確的話,內(nèi)核遞歸就會出錯然后崩潰。

由于thread_info結(jié)構(gòu)體中很難照到有價值的攻擊目標(同時移除thread_info中的數(shù)據(jù)并非有效的緩解措施),我就選擇了其它方式:溢出棧到棧之前的空間,然后利用棧和其它內(nèi)存空間之間會重合這一點。這種方式的問題就是一定要保證金絲雀值和 thread_info結(jié)構(gòu)中的其它成員不被覆蓋。棧溢出的內(nèi)存布局如下所示(綠色表示可以覆蓋,紅色表示不能覆蓋,黃色表示覆蓋后可能會有問題):

幸運的是,有些棧幀中存在空洞(如果遞歸的最底部采用cmdline而不是environ),遞歸的過程中就會有一個5個QWORD空洞沒有被訪問到。這些空洞足夠用來存放從SRACK_END_MAIC到flags的所有數(shù)據(jù)。這一點可以通過一個安全遞歸和一個內(nèi)核調(diào)試模塊來實現(xiàn),這個內(nèi)核調(diào)試模塊將棧中的所有空洞標綠便于觀察:

接下來的問題是空洞只會出現(xiàn)在特定的位置,而漏洞利用就需要空洞在準確的位置出現(xiàn)。下面有一些技巧可以用來對齊棧空間:

1.在每個遞歸層上都可以選擇“environ”文件或者“cmdline”文件,它們的棧幀大小和空洞模式都不一樣。

2.任何調(diào)用copy_from_user()都會導(dǎo)致內(nèi)存錯誤。甚至可以將寫入系統(tǒng)調(diào)用和VFS寫入句柄結(jié)合起來,所以每一個寫入系統(tǒng)調(diào)用和 VFS寫入句柄都會影響深度(合并深度可以計算出來,而不用測試每個變量)。

在測試了各種組合之后,我找到一組environ文件和cmdline文件, 還有write ()系統(tǒng)調(diào)用和進程的VFS寫句柄的組合。

隨后,就可以遞歸到之前分配的空間,而不會覆蓋任何危險數(shù)據(jù)了。然后暫停內(nèi)核線程的執(zhí)行,此時棧指針指向之前分配的內(nèi)存空間,這些內(nèi)存空間應(yīng)該用新的棧來覆蓋,然后繼續(xù)內(nèi)核線程的執(zhí)行。

為了暫停遞歸中內(nèi)核線程的執(zhí)行,在建立起映射鏈后,映射鏈最后的annonymous映射可以用FUSE映射取代( userfaultfd 函數(shù)并不適用,它不能捕捉遠程的內(nèi)存訪問)。

對于先前分配的內(nèi)存,我的exp使用管道(Pipes)。當寫入數(shù)據(jù)到新分配的空管道時,伙伴內(nèi)存分配器會分配一個內(nèi)存頁,來存放這些數(shù)據(jù)。我的exp通過管道內(nèi)存頁分配來填充大量內(nèi)存,所以使用clone()創(chuàng)建新進程時就會觸發(fā)內(nèi)存錯誤。這里使用clone() 而非fork(),因為調(diào)用clone()時只要控制好參數(shù),系統(tǒng)就會復(fù)制較少的信息,可以減少內(nèi)存分配的干擾。 Clone( ) 函數(shù)調(diào)用過程中,所有的管道內(nèi)存頁都被填充滿,除了第一次保存的 RIP值——遞歸進程暫停在FUSE中時,它保存在期望的 RSP 值之后。寫入較少的數(shù)據(jù)就能致使第二個管道寫入目標棧數(shù)據(jù),這些數(shù)據(jù)在 RIP控制實現(xiàn)之前就被使用,可能會導(dǎo)致內(nèi)核崩潰。隨后,遞歸進程在FUSE 中暫停時,第二次向所有管道寫入數(shù)據(jù),會覆蓋保存的 RIP值和其后的數(shù)據(jù),攻擊者也就能夠完全控制全新的棧了。

此時,最后一道防線就是KASLR了。Ubuntu支持KASLR ,只不過KASLR需要手動開啟。這個b最近該BUG已經(jīng)修復(fù)了,現(xiàn)在distros內(nèi)核應(yīng)該是默認就開啟KASLR的。雖說這項安全特性幫不上太大的忙,但畢竟KASLR不需要占用太多資源,開啟這項特性就顯得相當理所當然了。由于大多數(shù)的設(shè)備并不支持向內(nèi)核命令行傳輸特殊參數(shù),所以這里假設(shè)KASLR雖然編譯進了內(nèi)核,但仍處于未激活狀態(tài),攻擊者也知道內(nèi)核代碼和靜態(tài)數(shù)據(jù)的地址。

然后就可以用ROP在內(nèi)核里做各種事情了,漏洞利用具體有兩個方向可以繼續(xù)。可以使用ROP進行 commit_creds 類似操作。不過我用了另一個方法。在棧溢出過程中,原來addr_limit的KERNEL_DS 值保存了起來。棧一次次返回,最終將會把 addr_limit 重置為USER_DS。但如果我們直接返回到用戶空間, addr_limit 將保持 KERNEL_DS 。所以我這樣構(gòu)造新棧,或多或少復(fù)制了棧頂?shù)臄?shù)據(jù):

unsigned longnew_stack[] = {

0xffffffff818252f2,/* return pointer of syscall handler */

/* 16 uselessregisters */

0x1515151515151515,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

(unsignedlong) post_corruption_user_code, /* user RIP */

0x33, /* userCS */

0x246, /*EFLAGS: most importantly, turn interrupts on */

/* user RSP*/

(unsignedlong) (post_corruption_user_stack + sizeof(post_corruption_user_stack)),

0x2b /* userSS */

};

殺掉FUSE服務(wù)進程后,遞歸進程繼續(xù)運行到post_corruption_user_code函數(shù)上。這個函數(shù)可以使用管道向任意內(nèi)核地址寫數(shù)據(jù),因為 copy_to_user()中的地址檢查已經(jīng)失效。

voidkernel_write(unsigned long addr, char *buf, size_t len) {

int pipefds[2];

if (pipe(pipefds))

err(1, "pipe");

if (write(pipefds[1], buf, len) != len)

errx(1, "pipe write");

close(pipefds[1]);

if (read(pipefds[0], (char*)addr, len) !=len)

errx(1, "pipe read tokernelspace");

close(pipefds[0]);

}

現(xiàn)在你就可以在用戶態(tài)舒服地執(zhí)行任意讀寫操作了。如果你想要root shell,可以覆蓋coredump函數(shù),它存儲在一個靜態(tài)變量里,然后觸發(fā)一個 SIGSEGV,就可以以root權(quán)限執(zhí)行coredump函數(shù):

char*core_handler = "|/tmp/crash_to_root";

kernel_write(0xffffffff81e87a60,core_handler, strlen(core_handler)+1);

漏洞修復(fù)

有兩個獨立的補丁可用于修復(fù)該BUG:其中,2f36db710093禁止通過ecryptfs打開沒有mmap函數(shù)的文件, e54ad7f1ee26禁止在procfs 上層疊任何東西,因為的確沒什么道理要在其上層疊任何東西。

不過,我還是寫了一個完整的root提權(quán)漏洞利用程序。我主要想說明linux棧溢出可能會以非常隱蔽的方式出現(xiàn),即便開啟了一些現(xiàn)有的漏洞緩解措施,它們?nèi)匀豢衫谩T谖覍懙穆┒磮蟾嬷校矣刑岬浇o內(nèi)核增加內(nèi)存保護頁,移除棧底部的 thread_info結(jié)構(gòu)體,這樣緩解這類漏洞的利用,有其他操作系統(tǒng)就是這么干的。Andy Lutomirski已經(jīng)開始著手這方面的工作,并發(fā)布了增加了內(nèi)存保護頁的補丁包: https://lkml.org/lkml/2016/6/15/1064。

* 本文譯者:Michael23,文章參考來源:Blogspot,轉(zhuǎn)載請注明來自FreeBuf黑客與極客(FreeBuf.COM)

關(guān)鍵字:內(nèi)核配置遞歸內(nèi)存映射

本文摘自:FreeBuf.COM

x Linux內(nèi)核中的遞歸漏洞利用 掃一掃
分享本文到朋友圈
當前位置:安全行業(yè)動態(tài) → 正文

Linux內(nèi)核中的遞歸漏洞利用

責(zé)任編輯:editor007 作者:Michael23 |來源:企業(yè)網(wǎng)D1Net  2016-06-30 14:58:22 本文摘自:FreeBuf.COM

6月1號,我提交了一個linux內(nèi)核中的任意遞歸漏洞。如果安裝Ubuntu系統(tǒng)時選擇了home目錄加密的話,該漏洞即可由本地用戶觸發(fā)。如果想了解漏洞利用代碼和短一點的漏洞報告的話,請訪問https://bugs.chromium.org/p/project-zero/issues/detail?id=836

背景知識

在Linux系統(tǒng)中,用戶態(tài)的棧空間通常大約是8MB。如果有程序發(fā)生了棧溢出的話(比如無限遞歸),棧所在的內(nèi)存保護頁一般會捕捉到。

Linux內(nèi)核棧(可以用來處理系統(tǒng)調(diào)用)和用戶態(tài)的棧很不一樣。內(nèi)核棧相對來說更短:32位x86架構(gòu)平臺為4096byte , 64位系統(tǒng)則有16384byte(內(nèi)核棧大小由THREAD_SIZE_ORDER 和 THREAD_SIZE 確定)。它們是由內(nèi)核的伙伴內(nèi)存分配器分配,伙伴內(nèi)存分配器是內(nèi)核常用來分配頁大小(以及頁大小倍數(shù))內(nèi)存的分配器,它不創(chuàng)建內(nèi)存保護頁。也就是說,如果內(nèi)核棧溢出的話,它將直接覆蓋正常的數(shù)據(jù)。正因如此,內(nèi)核代碼必須(通常也是)在棧上分配大內(nèi)存的時候非常小心,并且必須阻止過多的遞歸。

Linux上的大多數(shù)文件系統(tǒng)既不用底層設(shè)備(偽文件系統(tǒng),比如sysfs, procfs, tmpfs等),也不用塊設(shè)備(一般是硬盤上的一塊)作為備用存儲設(shè)備。然而, ecryptfs 和overlayfs是例外。這兩者是堆疊的文件系統(tǒng),這種文件系統(tǒng)會使用其他文件系統(tǒng)上的文件夾作為備用存儲設(shè)備(overlayfs則使用多個不同文件系統(tǒng)上的多個文件作為備用存儲設(shè)備)。被用作備用存儲設(shè)備的文件系統(tǒng)稱為底層文件系統(tǒng),其上的文件稱為底層文件。這種層疊文件系統(tǒng)的特點是它或多或少的會訪問底層文件系統(tǒng),并對訪問的數(shù)據(jù)做一些修改。 Overlayfs融合多個文件系統(tǒng),ecryptfs則進行了相應(yīng)的加密。

層疊文件系統(tǒng)實際上存在潛在風(fēng)險,因為其訪問虛擬文件系統(tǒng)的函數(shù)常會訪問到底層文件系統(tǒng)的函數(shù),相較直接訪問底層文件系統(tǒng)的句柄,這會增大棧空間。考慮這樣一個場景:如果用層疊文件系統(tǒng)作為另外一個層疊系統(tǒng)的備用存儲設(shè)備,由于每一層文件系統(tǒng)的堆疊都增大了棧空間,內(nèi)核棧就會在某些情況下溢出。但是,設(shè)置FILESYSTEM_MAX_STACK_DEPTH 限制文件系統(tǒng)的層數(shù),只允許最多兩層層疊文件系統(tǒng)放在非層疊文件系統(tǒng)上,就可以避免這個問題。

在Procfs偽文件系統(tǒng)上,系統(tǒng)中運行的每一個進程都有一個文件夾,每個文件夾包含一些描述該進程的文件。值得注意的是每個進程的“mem”,“ environ”和“cmdline”文件,因為訪問這些文件會同步訪問目標進程的虛擬內(nèi)存。這些文件顯示了不同的虛擬內(nèi)存地址范圍:

1.“mem”文件顯示了整個虛擬內(nèi)存地址范圍(需要PTRACE_MODE_ATTACH 權(quán)限)

2.“environ”文件顯示了mm->env_start 到mm->env_end的內(nèi)存范圍(需要PTRACE_MODE_READ權(quán)限)

3.“cmdline”文件顯示了mm->arg_start 到mm->arg_end的地址范圍(如果mm->arg_end的前一個字符是null 的話)

如果可以用mmap()函數(shù)映射“mem”文件的話(啥意義也沒有,別想太多),就可以映射成如下圖所示的樣子:

接下來,假設(shè)/proc/$pid/mem的映射有一些錯誤,那么在進程C里的內(nèi)存讀取錯誤,將會導(dǎo)致從進程B中映射的內(nèi)存出錯,進而導(dǎo)致進程B里出現(xiàn)其它的內(nèi)存錯誤,進而導(dǎo)致從A進程映射的內(nèi)存出錯,這就是一個遞歸內(nèi)存錯誤。

可是,現(xiàn)實中這是不可行的,“mem”,“environ”,“cmdline ”文件只能用VFS函數(shù)讀寫,mmap無法使用:

staticconst struct file_operations proc_pid_cmdline_ops = {

.read = proc_pid_cmdline_read,

.llseek = generic_file_llseek,

};

[...]

staticconst struct file_operations proc_mem_operations = {

.llseek = mem_lseek,

.read = mem_read,

.write = mem_write,

.open = mem_open,

.release = mem_release,

};

[...]

staticconst struct file_operations proc_environ_operations = {

.open = environ_open,

.read = environ_read,

.llseek = generic_file_llseek,

.release = mem_release,

};

相關(guān)ecryptfs文件系統(tǒng),比較有趣的一個細節(jié)在于它支持mmap()。用戶看到的內(nèi)存映射必須是解密的,而底層文件系統(tǒng)的內(nèi)存映射是加密的,因而ecryptfs 文件系統(tǒng)不能將mmap()函數(shù)直接映射到底層文件系統(tǒng)的mmap()函數(shù)上。Ecrypt 文件系統(tǒng)在內(nèi)存映射時使用了自己的頁緩存。

ecryptfs文件系統(tǒng)處理頁錯誤的時候,必須以某種方式讀取底層文件系統(tǒng)上加密的頁。這可以通過讀取底層文件文件系統(tǒng)的頁緩存(使用底層文件系統(tǒng)的mmap函數(shù))來實現(xiàn),但是這樣比較消耗內(nèi)存。于是它直接使用底層文件系統(tǒng)的 VFS讀取函數(shù)(通過kernel_read()),這樣做更加直接有效,但是這個做法有副作用,就是有可能會mmap() 到通常不能映射的解密后的文件(因為只要底層文件有讀權(quán)限并且包含合法的加密數(shù)據(jù), ecryptfs文件系統(tǒng)的mmap函數(shù)就能工作)。

漏洞分析

在此,我們就能描繪完整的攻擊方式了。首先創(chuàng)建一個進程A,進程號為$A。然后創(chuàng)建一個ecrypptfs 掛載/tmp/$A,使/proc/$A作為它的底層文件系統(tǒng)(ecryptfs 應(yīng)該只有一個 key,這樣文件名才不會被加密)。現(xiàn)在,如果/proc/$A下相應(yīng)的文件有合法的ecryptfs 文件頭的話,那么 /tmp/$A/mem, /tmp/$A/environ 和 /tmp/$A/cmdline就可以被映射。除非有 root 權(quán)限,否則無法將內(nèi)存映射到進程 A的00處,也就是 /proc/$A/mem 的開頭。因此從開始讀取 /proc/$/A 總是會返回-EIO,而且 /proc/$A/mem 不會有一個合法的 ecryptfs 文件頭。如此,environ 和 cmdline 文件才有攻擊的可能性。

在使用CONFIG_CHECKPOINT_RESTORE編譯的內(nèi)核(至少是Ubuntu的 distro 內(nèi)核)中,非特權(quán)用戶可以通過prctl(PR_SET_MM, PR_SET_MM_MAP, &mm_map,sizeof(mm_map), 0)設(shè)置mm_struct 中的 arg_start, arg_end, env_start 和 env_end值。這使得映射 /proc/$A/environ 和 /proc/$A/cmdline到任意虛擬內(nèi)存范圍成為可能。(不支持checkpoint-restore的內(nèi)核中,攻擊過程就稍微有點麻煩,但使用所需的參數(shù)區(qū)域和環(huán)境變量的長度重新執(zhí)行,然后取代部分棧空間的映射,還是有可能的。)

如果一個有效加密的ecryptfs文件被加載到進程A的內(nèi)存中,并且它的環(huán)境變量也被配置為指向這塊區(qū)域,那么環(huán)境變量區(qū)域里的解密形式的數(shù)據(jù)就可以在 /tmp/$A/environ文件中獲取。這個文件也可以被映射到進程B的內(nèi)存中。為了能夠重復(fù)該進程,某些數(shù)據(jù)需要反復(fù)加密,進而創(chuàng)建一個加密的matroska 文件,并將這個文件加載到進程 A的內(nèi)存中。這樣一來,映射互相進程解密環(huán)境變量區(qū)域的進程鏈就建立起來了:

如果映射到進程C和進程B的內(nèi)存相應(yīng)范圍內(nèi)沒有數(shù)據(jù),進程C 中的內(nèi)存錯誤(這個內(nèi)存錯誤可能是用戶空間產(chǎn)生也可能是由于用戶空間訪問內(nèi)核空間,比如通過copy_from_user()函數(shù))將會導(dǎo)致ecryptfs讀取 /proc/$B/environ ,進而導(dǎo)致進程B中的內(nèi)存錯誤,接下來導(dǎo)致ecryptfs讀取 /proc/$A/environ ,最后導(dǎo)致進程A中的進程錯誤。如此循環(huán)往復(fù),最終溢出內(nèi)核棧,使內(nèi)核崩潰。內(nèi)核棧如下:

[...]

[]handle_mm_fault+0xf8b/0x1820

[]__get_user_pages+0x135/0x620

[]get_user_pages+0x52/0x60

[]__access_remote_vm+0xe6/0x2d0

[]? alloc_pages_current+0x8c/0x110

[]access_remote_vm+0x1f/0x30

[]environ_read+0x122/0x1a0

[]? security_file_permission+0xa0/0xc0

[]__vfs_read+0x18/0x40

[]vfs_read+0x86/0x130

[]kernel_read+0x50/0x80

[]ecryptfs_read_lower+0x23/0x30

[]ecryptfs_decrypt_page+0x82/0x130

[]ecryptfs_readpage+0xcd/0x110

[]filemap_fault+0x23b/0x3f0

[]__do_fault+0x50/0xe0

[]handle_mm_fault+0xf8b/0x1820

[]__get_user_pages+0x135/0x620

[]get_user_pages+0x52/0x60

[]__access_remote_vm+0xe6/0x2d0

[]? alloc_pages_current+0x8c/0x110

[]access_remote_vm+0x1f/0x30

[]environ_read+0x122/0x1a0

[...]

關(guān)于這個漏洞的可利用性:利用該漏洞,需要能夠掛載/proc/$pid為ecryptfs文件系統(tǒng)。安裝完ecryptfs-utils包之后(如果安裝Ubuntu時選擇了home目錄加密, Ubuntu 會自動安裝),使用 /sbin/mount.ecryptfs_私有的setuid輔助函數(shù)就可以做到這一點。

漏洞利用

接下來的描述是平臺相關(guān)的,這里指amd64。

以前要利用這一類漏洞還是相當簡單的,可以直接覆蓋棧底的thread_info結(jié)構(gòu)體,用合適的數(shù)值重寫restart_block或者 addr_limit,然后根據(jù)所用方式,選擇執(zhí)行用戶空間映射的代碼,還是用copy_from_user() 和 copy_to_user() 直接讀寫內(nèi)核數(shù)據(jù)。

但是,restart_block已經(jīng)從thread_info結(jié)構(gòu)體中移除,并且由于棧溢出觸發(fā)時棧中有 kernel_read() 的棧幀,所以addr_limit已經(jīng)是KERNEL_DS,而且函數(shù)退出時將會重置成 USER_DS 。另外, Ubuntu 16.04以后的內(nèi)核都打開了CONFIG_SCHED_STACHK_END_CHECK 內(nèi)核配置選項。打開這個選項以后,每次調(diào)度到這個線程時, thread_info 結(jié)構(gòu)體上方的金絲雀值都會被檢查;如果金絲雀值不正確的話,內(nèi)核遞歸就會出錯然后崩潰。

由于thread_info結(jié)構(gòu)體中很難照到有價值的攻擊目標(同時移除thread_info中的數(shù)據(jù)并非有效的緩解措施),我就選擇了其它方式:溢出棧到棧之前的空間,然后利用棧和其它內(nèi)存空間之間會重合這一點。這種方式的問題就是一定要保證金絲雀值和 thread_info結(jié)構(gòu)中的其它成員不被覆蓋。棧溢出的內(nèi)存布局如下所示(綠色表示可以覆蓋,紅色表示不能覆蓋,黃色表示覆蓋后可能會有問題):

幸運的是,有些棧幀中存在空洞(如果遞歸的最底部采用cmdline而不是environ),遞歸的過程中就會有一個5個QWORD空洞沒有被訪問到。這些空洞足夠用來存放從SRACK_END_MAIC到flags的所有數(shù)據(jù)。這一點可以通過一個安全遞歸和一個內(nèi)核調(diào)試模塊來實現(xiàn),這個內(nèi)核調(diào)試模塊將棧中的所有空洞標綠便于觀察:

接下來的問題是空洞只會出現(xiàn)在特定的位置,而漏洞利用就需要空洞在準確的位置出現(xiàn)。下面有一些技巧可以用來對齊棧空間:

1.在每個遞歸層上都可以選擇“environ”文件或者“cmdline”文件,它們的棧幀大小和空洞模式都不一樣。

2.任何調(diào)用copy_from_user()都會導(dǎo)致內(nèi)存錯誤。甚至可以將寫入系統(tǒng)調(diào)用和VFS寫入句柄結(jié)合起來,所以每一個寫入系統(tǒng)調(diào)用和 VFS寫入句柄都會影響深度(合并深度可以計算出來,而不用測試每個變量)。

在測試了各種組合之后,我找到一組environ文件和cmdline文件, 還有write ()系統(tǒng)調(diào)用和進程的VFS寫句柄的組合。

隨后,就可以遞歸到之前分配的空間,而不會覆蓋任何危險數(shù)據(jù)了。然后暫停內(nèi)核線程的執(zhí)行,此時棧指針指向之前分配的內(nèi)存空間,這些內(nèi)存空間應(yīng)該用新的棧來覆蓋,然后繼續(xù)內(nèi)核線程的執(zhí)行。

為了暫停遞歸中內(nèi)核線程的執(zhí)行,在建立起映射鏈后,映射鏈最后的annonymous映射可以用FUSE映射取代( userfaultfd 函數(shù)并不適用,它不能捕捉遠程的內(nèi)存訪問)。

對于先前分配的內(nèi)存,我的exp使用管道(Pipes)。當寫入數(shù)據(jù)到新分配的空管道時,伙伴內(nèi)存分配器會分配一個內(nèi)存頁,來存放這些數(shù)據(jù)。我的exp通過管道內(nèi)存頁分配來填充大量內(nèi)存,所以使用clone()創(chuàng)建新進程時就會觸發(fā)內(nèi)存錯誤。這里使用clone() 而非fork(),因為調(diào)用clone()時只要控制好參數(shù),系統(tǒng)就會復(fù)制較少的信息,可以減少內(nèi)存分配的干擾。 Clone( ) 函數(shù)調(diào)用過程中,所有的管道內(nèi)存頁都被填充滿,除了第一次保存的 RIP值——遞歸進程暫停在FUSE中時,它保存在期望的 RSP 值之后。寫入較少的數(shù)據(jù)就能致使第二個管道寫入目標棧數(shù)據(jù),這些數(shù)據(jù)在 RIP控制實現(xiàn)之前就被使用,可能會導(dǎo)致內(nèi)核崩潰。隨后,遞歸進程在FUSE 中暫停時,第二次向所有管道寫入數(shù)據(jù),會覆蓋保存的 RIP值和其后的數(shù)據(jù),攻擊者也就能夠完全控制全新的棧了。

此時,最后一道防線就是KASLR了。Ubuntu支持KASLR ,只不過KASLR需要手動開啟。這個b最近該BUG已經(jīng)修復(fù)了,現(xiàn)在distros內(nèi)核應(yīng)該是默認就開啟KASLR的。雖說這項安全特性幫不上太大的忙,但畢竟KASLR不需要占用太多資源,開啟這項特性就顯得相當理所當然了。由于大多數(shù)的設(shè)備并不支持向內(nèi)核命令行傳輸特殊參數(shù),所以這里假設(shè)KASLR雖然編譯進了內(nèi)核,但仍處于未激活狀態(tài),攻擊者也知道內(nèi)核代碼和靜態(tài)數(shù)據(jù)的地址。

然后就可以用ROP在內(nèi)核里做各種事情了,漏洞利用具體有兩個方向可以繼續(xù)。可以使用ROP進行 commit_creds 類似操作。不過我用了另一個方法。在棧溢出過程中,原來addr_limit的KERNEL_DS 值保存了起來。棧一次次返回,最終將會把 addr_limit 重置為USER_DS。但如果我們直接返回到用戶空間, addr_limit 將保持 KERNEL_DS 。所以我這樣構(gòu)造新棧,或多或少復(fù)制了棧頂?shù)臄?shù)據(jù):

unsigned longnew_stack[] = {

0xffffffff818252f2,/* return pointer of syscall handler */

/* 16 uselessregisters */

0x1515151515151515,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

(unsignedlong) post_corruption_user_code, /* user RIP */

0x33, /* userCS */

0x246, /*EFLAGS: most importantly, turn interrupts on */

/* user RSP*/

(unsignedlong) (post_corruption_user_stack + sizeof(post_corruption_user_stack)),

0x2b /* userSS */

};

殺掉FUSE服務(wù)進程后,遞歸進程繼續(xù)運行到post_corruption_user_code函數(shù)上。這個函數(shù)可以使用管道向任意內(nèi)核地址寫數(shù)據(jù),因為 copy_to_user()中的地址檢查已經(jīng)失效。

voidkernel_write(unsigned long addr, char *buf, size_t len) {

int pipefds[2];

if (pipe(pipefds))

err(1, "pipe");

if (write(pipefds[1], buf, len) != len)

errx(1, "pipe write");

close(pipefds[1]);

if (read(pipefds[0], (char*)addr, len) !=len)

errx(1, "pipe read tokernelspace");

close(pipefds[0]);

}

現(xiàn)在你就可以在用戶態(tài)舒服地執(zhí)行任意讀寫操作了。如果你想要root shell,可以覆蓋coredump函數(shù),它存儲在一個靜態(tài)變量里,然后觸發(fā)一個 SIGSEGV,就可以以root權(quán)限執(zhí)行coredump函數(shù):

char*core_handler = "|/tmp/crash_to_root";

kernel_write(0xffffffff81e87a60,core_handler, strlen(core_handler)+1);

漏洞修復(fù)

有兩個獨立的補丁可用于修復(fù)該BUG:其中,2f36db710093禁止通過ecryptfs打開沒有mmap函數(shù)的文件, e54ad7f1ee26禁止在procfs 上層疊任何東西,因為的確沒什么道理要在其上層疊任何東西。

不過,我還是寫了一個完整的root提權(quán)漏洞利用程序。我主要想說明linux棧溢出可能會以非常隱蔽的方式出現(xiàn),即便開啟了一些現(xiàn)有的漏洞緩解措施,它們?nèi)匀豢衫谩T谖覍懙穆┒磮蟾嬷校矣刑岬浇o內(nèi)核增加內(nèi)存保護頁,移除棧底部的 thread_info結(jié)構(gòu)體,這樣緩解這類漏洞的利用,有其他操作系統(tǒng)就是這么干的。Andy Lutomirski已經(jīng)開始著手這方面的工作,并發(fā)布了增加了內(nèi)存保護頁的補丁包: https://lkml.org/lkml/2016/6/15/1064。

* 本文譯者:Michael23,文章參考來源:Blogspot,轉(zhuǎn)載請注明來自FreeBuf黑客與極客(FreeBuf.COM)

關(guān)鍵字:內(nèi)核配置遞歸內(nèi)存映射

本文摘自:FreeBuf.COM

電子周刊
回到頂部

關(guān)于我們聯(lián)系我們版權(quán)聲明隱私條款廣告服務(wù)友情鏈接投稿中心招賢納士

企業(yè)網(wǎng)版權(quán)所有 ©2010-2024 京ICP備09108050號-6 京公網(wǎng)安備 11010502049343號

^
  • <menuitem id="jw4sk"></menuitem>

    1. <form id="jw4sk"><tbody id="jw4sk"><dfn id="jw4sk"></dfn></tbody></form>
      主站蜘蛛池模板: 余江县| 呼和浩特市| 南川市| 从江县| 岗巴县| 天镇县| 巨野县| 甘泉县| 五常市| 长沙县| 合山市| 清远市| 弋阳县| 福安市| 郴州市| 博罗县| 东阳市| 专栏| 望城县| 鄂托克前旗| 台湾省| 荥经县| 榆林市| 二连浩特市| 三台县| 台州市| 五莲县| 赤壁市| 仁布县| 纳雍县| 陆河县| 禹州市| 旌德县| 延吉市| 龙游县| 江华| 婺源县| 恩平市| 洛川县| 巴马| 通州区|