寫在前面的話

上週,Google曾發佈過一系列來對五個iOS漏洞利用鏈進行過詳細分析。據瞭解,Google的威脅分析團隊(TAG)在2019年2月份發現了有攻擊者正在使用這些iOS漏洞利用鏈來實施攻擊,本人此前也對其中的進行過分析,那麼在這篇文章中,我將會對這條漏洞利用鏈中的iOS內核堆棧進行分析,並使用該漏洞利用鏈中的技術來實現堆溢出攻擊。

堆概念回顧

在開始討論具體的技術實現細節之前,我們需要大致回顧一下iOS內核堆的概念以及堆溢出是如何實現的。

堆環境,是採用了類似C或C++這種編程語言開發的應用程序的上下文環境,它允許函數分配一個內存的臨時獨佔區來存儲變量和結構化數據。分配完成後,這些內存區域將被永久保留,直到程序員手動將內存釋放回堆管理器,此時堆管理器可以“回收”它以供其他代碼或程序使用。

爲了保證程序的正常運行,使用堆環境的程序員需要遵循一些簡單的規則,而這一點非常重要。比如說,它們必須避免寫入已分配區域之外的地址,而且還要小心不要在已釋放回堆的分配內存中寫入或讀取數據。

如果不遵循這些規則,堆的獨佔性保證將失效。這可能會導致不同的變量意外地被分配到相同的底層內存地址。這些變量可能具有不同的安全屬性,例如一個可能包含要處理的數據,另一個則包含安全關鍵屬性(如函數指針),而攻擊者將有可能利用這種特性來接管某個進程,但在一般情況下,接管進程的是iOS操作系統內核。

iOS堆溢出漏洞利用分析

在本文所介紹的iOS漏洞利用技術中,主要的目標是iOS的圖形驅動器。這個圖形驅動器中有一個對象,該對象通過IOMalloc函數進行分配,而這個分配器使用了kalloc分配器,其中存在的堆溢出漏洞將導致該對象的數據溢出到kalloc分配的相鄰對象中。

kalloc堆分配器的工作方式與glibc堆分配器不同,但是它的大致用途基本相同,即它允許內核驅動程序開發人員在管理系統的正常過程中爲變量分配和釋放內存。

這裏需要注意的是,kalloc的對象分配操作是與操作系統無關的,設備上運行的應用程序是看不到這些對象分配的位置和數據內容的。其次,使用kalloc分配的對象通常不會使用活動對象之間的堆元數據來跟蹤分配塊長度之類的信息。當釋放kalloc堆分配時,開發人員必須提醒kfree目標區域的原始分配長度,以便kfree可以將其回收並用於其他分配。如果錯誤這個原始分配長度出現錯誤,則會觸發區域傳輸漏洞,從而導致堆溢出,不過iOS 9之後的iOS系統現在已經有了抵禦此類攻擊的緩解措施。出於這個原因,iOS內核開發人員通常會利用更加易於使用的API來間接調用kalloc,比如說_MALLOC。

kalloc分配器在內部使用區域分配策略(通過zalloc實現),它會根據分配目標的大小將分配集羣在一起。例如,大小爲4096字節的分配將與其他4096字節的分配一起分配到kalloc.4096區域中,而大小爲2048的分配將在kalloc.2048區域中分配。這意味着,當2048字節kalloc分配的對象發生堆溢出時,在kalloc.2048區域中,被損壞的相鄰受害者對象大小是相似的。

那麼在這個特定的攻擊中,如果目標對象的長度可以被修改,那麼就會存在該漏洞。因此,漏洞利用開發人員可以選擇在哪個kalloc區域觸發漏洞。開發人員可以選擇在kalloc.4096區域中分配他們所感興趣的對象,這個區域比那些存儲空間小的區域操作次數要少,因爲程序傾向於分配和回收分配較小的區域,因此存儲空間小的區域操作會更頻繁。

漏洞成因

iOS圖形驅動器的開發人員在這裏對複雜輸入數據結構做了一個錯誤的“假設”,並根據這個錯誤的“假設”來設定堆內存的分配大小。通過提供錯誤的數據輸入,內核將會向堆分配區域寫入比原本更多的數據條目,而最後的幾個數據條目將會溢出到與其相鄰的其他對象中。

漏洞利用

線性堆溢出漏洞通常允許利用漏洞的開發人員將攻擊者控制的數據溢出到內存分配限制之外的區域,但在這裏情況並非如此,溢出的數據必須始終是指向IOAccelResource2對象的指針。攻擊者無法控制該指針的值,也無法控制其指向的數據。

像這種域控制漏洞通常很難利用,但我們也可以使用一些創造性的方法來將該漏洞轉換爲其他更容易利用的類型。我們可以使用堆溢出來構造一個“釋放後使用”漏洞,這樣我們就可以構建出一個更容易利用的漏洞了。

首先,攻擊者需要找到一個合適的目標對象,該對象的第一個字段指向目標對象控制下的緩衝區指針。這個緩衝區需要在一個攻擊者可控的時間釋放,也可以是在目標對象本身被釋放的時候。

利用漏洞開發人員現在可以觸發線性堆溢出,此溢出將導致目標對象的指針值被替換爲指向IOAccelResource2對象的指針。

下一步是觸發資源數據本身的釋放,這將導致目標對象的指針爲空,內核中目前還沒有對象希望這個指針爲空,而此時所有需要跟該對象指針交互的行爲都將出現用後釋放事件。與此堆溢出相比,釋放後使用漏洞將爲攻擊者提供更大的控制範圍,因此這是一個相對可靠的策略。

那麼問題來了:攻擊者應該選擇哪個對象來進行攻擊呢?

攻擊者在選擇攻擊對象時有一些限制,目標對象必須與溢出對象相鄰,因此利用攻擊者需要使其位於資源區附近的同一個kalloc.4096區域中,並且要在它的開頭附近分配一個kalloc指針,並使它在一個可控的時間釋放那個指針。

在我們的分析樣例中,攻擊者使用了recv_msg_elem數組作爲攻擊對象。單個recv_msg_elem對象的長度只有32字節,但在recvmsg_x系統調用開始時,可以同時分配連續的組。通過操縱recvmsg_x系統調用的參數,我們可以安排同時分配其中的幾個參數,從而將分配作爲一個整體注入到kalloc.4096區域。此數組中的每個recv_msg_元素都以指向uio結構的指針開頭,它是基於一個可控長度字段並通過kalloc分配的,並且最終kfree在系統調用結束時會釋放recv_msg_elem數組。

選擇這個recv_msg_elems數組作爲攻擊對象,滿足了我們所有的需求,並且可以將堆溢出轉化爲用後釋放漏洞來使用。

我們的操作順序如下:首先,我們安排一個recv_msg_elems數組,位於kalloc.4096的resources_buf對象旁邊。接下來,我們觸發resources_buf的溢出,它將用指向IOAccelResource 對象的指針覆蓋第一個recv_msg_elems數組項的uio指針。

接下來,我們釋放IOAccelResource2對象和資源緩衝區,但是recv_msg_elem數組的指針覆蓋仍然存在,繼續指向IOAccelResource2對象的位置,但現在是空的。目標recv_msg_elems對象沒有理由相信其uio對象已被釋放,但對它的任何使用現在都會導致觸發用後釋放漏洞。

其實使用RevixMSGEELEM數組作爲攻擊對象,還有一個麻煩。此次分配操作發生在recvmsg_x系統調用的開始,但該系統調用會阻塞等待消息到達的線程。當請求完成或被取消時,recvmsg_x將釋放其內部的目標對象。這也就意味着,系統調用將需要發生在一個從屬線程上,因爲它可能阻塞調用線程。

參考資料

相關文章