task_t指針重大風險預報-Exploit篇
首發:【翻譯】task_t指針重大風險預報-Exploit篇-『iOS安全』-看雪安全論壇
引言:大家都知道知名義大利天才少年Luca放出來的針對<=10.2版本的yalu越獄使用的是對kernel port的buffer overflow拿到了kernel_task_port,本文對類似的task_t指針做出了針對性的分析,從mach埠背景知識,到IOkit的相關處理,最終如何利用在堆棧上寫出Exploit,最終甚至給蘋果團隊給出了修復漏洞的建議,由淺入深,偏辟入里,值得推薦。
本文分三篇推出,分別是分析篇,Exploit篇,和修復建議篇。
-------------------------------------------------------
譯者註:
·一些諸如bug,exploit之類的行話選擇性的翻譯,這通常取決於句子的流暢性。
·不確定的地方在括弧中附註了原句
·超鏈接附在括弧內,方便查看
by ruanbonan
-------------------------------------------------------
Exploit篇
查看文檔
關於OS X內核擴展的資料不是很多。蘋果在他們的開發者網站上發布了名為AppleSamplePCI的kext樣本,這為多樣化的IOKit設計樣式提供了示例。下面是AppleSamplePCI.kext對於initWithTask的實現:
bool SamplePCIUserClientClassName::initWithTask(
task_t owningTask,
void* securityID,
UInt32 type,
OSDictionary* properties)
{
bool success = super::initWithTask(owningTask,
securityID,
type,
properties);
fTask = owningTask;
fDriver = NULL;
return success;
}
這個樣本的userclient把owningTask參數存儲在fTask成員變數中,沒有引用。沒有引用就無法保證在這個方法返回後fTask指向的任務結構體後不被釋放。觀察剩下的kext樣本,我們可以發現一些外部方法使用fTask指針來創建內存描述符
如果我們可以使fTask指針指向的任務結構體釋放,那麼它們就是在使用一個懸掛指針了。
在IDA中打開一些其他OS X kext文件可以很清楚地看到它們中的許多都遵循了一個反面模式:保存一個task_t指針,卻沒有引用。
創建一個task_t類型的懸掛指針
mach消息提供了非常靈活強大的IPC構件。你可以做一些靈活的事,比如向你擁有發送或接受許可權的mach埠發送其他進程的「發送許可權」。
因為任務埠給了你對於其他任務的完全控制權,向一個任務埠請求其他任務的api(task_for_pid)是具有特權的,但是因為所有任務都它們自己的任務埠的發送權,所以,如果我們可以在兩個任務中執行代碼,我們可以將第二個任務的任務埠的發送權發給第一個任務埠。
在這種情況下,我們使用Robert Sesek描述的技術(鏈接:Changes to XNU Mach IPC)通過把發送權隱藏在特定的bootstrap_port埠上,然後在父進程和它fork出的子進程之間創建一個共享mach埠。在fork之後,子進程可以恢復這個隱藏埠,返回bootstrap埠並設置一個雙向的IPC通道,這樣一來,它就可以通過這個通道來把它的任務埠發回給父進程。
在這個存在漏洞的反面模式中觸發UAF的方法如下:
·父進程fork出子進程
·子進程把它的任務埠發回給父進程
·子進程自旋
·父進程收到子進程的任務埠,創建一個有漏洞的IOKitnuserclient,它將子進程任務埠作為owningTask傳遞
·父進程銷毀它對於子進程任務埠的發送權
·父進程殺死子進程,釋放子進程的任務結構體
·父進程有了一個帶有任務結構體類型懸掛指針的userclient
第一個exploit
查看IOKit包含有這個bug的驅動,其中一個特別有趣——IOSurfaceRootUserClient。下面是蘋果開發者文檔對於IOSurface的敘述:
IOSurface框架提供了適用於跨進程共享的框架緩衝對象。它被廣泛用於允許應用程序移動一個複雜的鏡像解壓文件,以及將邏輯流放入一個單獨進程來增強安全性。
實際上IOSurfaces僅僅用來包裹共享內存緩衝區。另外,在OS X上我們可以與來自Safari內部渲染沙盒以及ChromeGPU沙盒的IOSurface內核擴展通信。
IOSurfaceRootUserClient類也有反面模式,與我們在AppSamplePCI客戶端看到的相同,userclient把owningTask指針的拷貝當做一個成員變數存儲,卻沒有引用。通過一些逆向,我們知道IOSurfaceRootUserClient的外部方法是create_surface,它接受一個鍵值對作為參數來創建共享內存對象,其他進程可以把這個對象映射到它們自己的地址空間中。通過傳遞下面的鍵和值,我們可以獲得IOSurfaceRootUserClient 來把IOSurface 中一個已存在的用戶空間頁包裹起來,而不是重新分配一個緩衝區。
IOSurfaceAddress:n base_address
IOSurfaceAllocSize: size
IOSurfaceIsGlobal: true
IOSurface對象實際上僅僅通過調用下面的代碼來把IOSurface::allocate分配的IOMemoryDescriptor 包裹起來:
IOMemoryDescriptor *
IOMemoryDescriptor::withAddressRange(
mach_vm_address_t address,
mach_vm_size_t length,
IOOptionBits options,
task_tn task);
IOMemoryDescriptor::withAddressRange的最後一個task_t task參數定義了文件描述符應該為哪一個任務的虛擬內存創建。IOSurface在這裡傳遞儲存了owningTask的拷貝的成員變數,卻沒有對它加以引用 !如果我們可以讓這個任務結構體在初始任務退出時內存先被釋放,另一個任務開始時再次分配,並被用作一個具有更高許可權的任務的任務結構體,那麼IOMemoryDescriptor會相信它正在包裹當前進程地址空間的一部分,然而,事實上它正在包裹IOMemoryDescriptor內支持這個IOSurface的另一個更高許可權任務的虛擬內存。
設置IOSurfaceIsGlobal=true使得這一個其他進程可以接觸這一個surface,因此通過對另一個擁有我們自己的合法任務埠作為owningTask的 IOSurfaceRootUserClient調用外部方法lookup_surface,我們可以建立一個primitive,它允許我們把其他進程地址空間的任意位置映射到我們自己的上面
由於IOMemoryDescriptor實際上創建了那些我們可以寫入的頁的共享內存映射,這些寫入也會反映到其他進程中。IOSurfaceRootUserClient不允許我們把受害的可執行頁映射過來,但是我們仍然可以映射一些東西,比如庫的__DATA段。而這是很容易實現的,因為共享庫緩存對於所有進程都是相同的虛擬地址。
把Exploit合在一起
我們需要設法讓任務指針被更高許可權的進程重用,然後我們需要一些東西來覆蓋目標,已達到代碼執行的目的。
任務指針從它們的內核堆空間分配出來,這一點大大簡化了事情。我們可以僅僅殺死一個子進程,然後fork並exec一些帶有suid標誌的二進位程序,它們很可能重用懸掛指針task_t指向的空間。
為了覆蓋目標,我選擇libc中的__cleanup指針作為目標。在進程退出時,它將被調用。我們使用一些技巧來在程序退出前阻塞它,這可以通過把它的stderr文件描述符設置成一個滿的pipe並強制它寫入錯誤信息實現,從而給我們大量的時間來利用父進程中的bug,並在清空父進程的pipe之前覆蓋__cleanup指針。我選擇把這個函數指針指向一個gadget,這個gadget的功能是給RSP寄存器加上一個大的常數,然後返回。這樣做會把棧指針向上移動到argv,當我們執行這個程序時,我在那裡放了一個ROP棧,來調用setuid(0)並執行/bin/bash。ROP載荷附加有很長的ret-slide前綴,因此它在大多數版本的OS X上都應該是穩定有效的。
你可以下載這個Exploit(鏈接:https://bugs.chromium.org/p/project-zero/issues/attachment?aid=237183)
並查看初始的bug報告(鏈接:https://bugs.chromium.org/p/project-zero/issues/detail?id=831)
由於除了root以外,這個bug也允許我們獲得其他任何許可權,所以很容易利用它來繞過OS X上的內核代碼簽名,並載入一個未簽名的內核擴展。方法之一,參考CVE-2016-1757的Exploit(鏈接:https://googleprojectzero.blogspot.ch/2016/03/race-you-to-kernel.html)
雖然這個Exp使用了fork和execve,事實上它們是不必要的——觸發bug的先決條件是你要在兩個能夠互相發送大量消息的協同操作的進程中執行代碼,對於這個bug來說,還需要能夠和IOSurface通信。Damien DeVille寫了一篇博客(鏈接:http://ddeville.me/2015/02/interprocess-communication-on-ios-with-mach-messages)討論使用應用程序組來從iOS app沙箱內部達到此目的的方法。執行帶有suid標誌的程序也是不必要的:我們可以通過launchd或者故意崩潰導致launchd運行CrashRepoter來找一個mach服務,來造成已釋放的任務結構體被另一個更高許可權的任務重用。
這個bug的許多特例已經在OS Xn10.11.6/iOS 9.3.3中被修復,蘋果已經做了緩解措施,避免傳遞其他任務的任務埠到特定的IOKit方法。
後退一些
這個UAF bug很有意思,但是它掩蓋了更深層次更值得關注的問題。如果IOSurfaceRootUserClient現在對owningTask調用task_reference(),且owningTask不得不成為一個userclient的初始創建者呢?這是否又是一個bug?
今年早些時候osxreverser@和我均獨立地發布了關於execve系統調用的問題報告。在那個案例中,由於在加在一個suid程序時execve執行特定操作順序不同導致了競態條件,這使得新內存映射和舊的無效任務埠之間存在小的競態窗口。
這是一個更為基礎的問題:execve系統調用實際上不會創建一個新的任務結構體,即使它執行一個更高級的suid程序。它僅僅修改已存在的任務結構體來做替代,所有之前就有一個task_t指針的對象現在仍然有一個指向更高級任務。
這不是暫時的內存安全——沒有UAF被牽扯進來。讓我們仔細看一看為什麼這對於XNU是一個大問題。
譯者:ruanbonan
原文鏈接:https://googleprojectzero.blogspot.kr/2016/10/taskt-considered-harmful.html原文作者:lan Beer,Project Zero
微信公眾號:看雪iOS安全小組
我們的微博:http://weibo.com/pediyiosteam
我們的知乎:http://zhihu.com/people/pediyiosteam
加入我們:看雪iOS安全小組成員募集中:iOS安全小組成員招募中-『iOS安全』-看雪安全論壇
n[看雪iOS安全小組]置頂嚮導集合貼: [新人請看][看雪iOS安全小組]置頂嚮導集合貼-『iOS安全』-看雪安全論壇推薦閱讀:
※iOS 平台越來越多標榜全手勢操作的應用,有何利弊?如何解決弊端?
※最終幻想8 Final Fantasy Ⅷ為什麼沒有移植到iOS上?
※說說Clash of Clans這個遊戲,大家有沒有充值?值得充值嗎
TAG:iOS |