作者:耀刺,蒸米,黑雪 @阿里移動安全
英文版:http://translate.wooyun.io/2016/06/12/54.html
冰指的是用戶態,火指的是內核態。如何突破像冰箱一樣的用戶態沙盒最終到達并控制如火焰一般燃燒的內核就是《iOS冰與火之歌》這一系列文章將要講述的內容。這次給大家帶來的是 Use After Free 的漏洞利用方式以及iOS 9.0上如何利用UAF攻擊iOS內核的技術。除此以外我們還公布了一個在iOS9.3.2中剛剛被修復的內核堆溢出漏洞,可以用來攻擊iOS 9.3.2以下版本的iOS內核并實現iOS Pwn的終極目標 - 越獄。
《iOS冰與火之歌》系列的目錄如下:
Objective-C Pwn and iOS arm64 ROP
在非越獄的iOS上進行App Hook(番外篇)
App Hook答疑以及iOS 9砸殼(番外篇)
利用XPC過App沙盒
UAF and Kernel Pwn
另外文中涉及代碼可在我的github下載:
https://github.com/zhengmin1989/iOS_ICE_AND_FIRE
Use After Free簡稱UAF,是一種常見的堆漏洞利用方式。在Pangu9的越獄中,就是利用了iOS 9內核中的一處UAF漏洞獲取了iOS最高權限并完成了越獄。在我們講這個漏洞之前,可能有很多同學對UAF并不是很了解,所以我們先簡單介紹一下什么是UAF以及如何利用UAF漏洞(老鳥的話可以直接跳過這一節)。
我們先來看一段程序(全部源碼在github):
#!c
class Human
{
public:
virtual void setValue(int value)=0;
virtual int getValue()=0;
protected:
int mValue;
};
class Talker : public Human
{
public:
void setValue(int value){
mValue = value;
}
int getValue(){
mValue += 1;
cout<<"This is Talker's getValue"<<endl;
return mValue;
}
};
class Worker : public Human
{
public:
void setValue(int value){
mValue = value;
}
int getValue(){
cout<<"This is Worker's getValue"<<endl;
mValue += 100;
return mValue;
}
};
void handleObject(Human* human)
{
human->setValue(0);
cout<<human->getValue()<<endl;
}
這段程序有三個類,其中Worker和Talker都繼承Human這個類,并且他們都分別實現了setValue()
和getValue()
這兩個函數。因此當程序調用handleObject()
的時候,無論傳入的參數是Worker還是Talker,handleObject()
都可以進行處理。
我們接下來看main函數:
#!c
int main(void) {
Talker *myTalker = new Talker();
printf("myTalker=%p\n",myTalker);
handleObject(myTalker);
free(myTalker);
Worker *myWorker = new Worker();
printf("myWorker=%p\n",myWorker);
handleObject(myTalker);
}
我們先new一個Talker,然后調用handleObject()
打印它的value,然后free掉這個Talker。接著我們new一個Worker,然后繼續調用handleObject()
打印Talker(注意:不是Worker)的value,會發生什么呢?正常情況下,程序員調用handleObject(myTalker)
,是期望處理Talker這個對象,但是Talker這個對象已經被free了,并且指針沒有置為NULL。這時候,如果有另一個對象(e.g., Worker)剛好被分配在了Talker指針所指向的地址,handleObject()
也會對這個對象進行處理,并且不會報錯。這,就是一個典型的UAF漏洞。我們來看一下程序執行的結果:
#!shell
Minde-iPad:/tmp root# ./hello
myTalker=0x17d6b150
This is Talker's getValue
1
myWorker=0x17d6b150
This is Worker's getValue
100
可以看到MyTalker對象在內存中的地址為0x17d6b150,然后MyTalker就被free掉了。隨后,程序又創建了另一個對象myWorker。因為堆的特性,系統會把剛剛free掉的內存再分配給Worker。因此myWorker在內存中的地址也是0x17d6b150。所以當程序調用handleObject(myTalker)
的時候,本應該期待調用Talker's getValue()
函數卻調用了Worker's getValue()
函數,這就造成一個UAF錯誤。
如果程序不是在myTalker后面創建一個myWorker對象,而是自己malloc()
一段可控的內存呢?我們再來看下面這段程序:
#!c
int main(void) {
Talker *myTalker = new Talker();
printf("myTalker=%p\n",myTalker);
handleObject(myTalker);
free(myTalker);
int size=16;
void *uafTalker = (void*) malloc(size);
memset(uafTalker, 0x41,size);
printf("uafTalker=%p\n",uafTalker);
handleObject(myTalker);
return 0;
}
我們并沒有malloc一個Worker對象,而是自己malloc了一段16個字節的內存,并且把數據全部填充為0x41。如果free掉的myTalker指針指向了這段內存,并調用了handleObject(myTalker)
會發生什么事情呢?
在運行程序前,我們先用lldb對程序進行調試:
然后用lldb連接程序并在main函數下一個斷點:
確保程序正常進入main函數后,繼續運行程序:
當程序執行到handleObject(myTalker)
的時候,我們就能看到很有意思的error了:程序試圖從r0這個地址讀取數值到r2,然后blx r2
,但是r0的值為0x41414141。這是因為我們在myTalker free后的malloc的內存剛好又重新分配在了myTalker指針指向的地址,隨后程序調用了myTalker的的函數,根據c++的機制,程序會從myTalker的vTable里獲取函數地址,但是我們已經利用UAF把vTable給填充成了0x41414141,所以才會報錯。既然我們可以利用UAF控制r0的內容,只要配合上heap spray,我們就可以做到控制pc并執行rop指令了。Heap Spray的技巧可以參考我之前寫的文章:Objective-C Pwn and iOS arm64 ROP。
簡單了解了UAF的原理以后,我們來看一下pangu越獄中搞定iOS 9.0內核的UAF漏洞。這個漏洞存在于IOHIDResource這個內核服務中。有一個好消息是這個服務是開源的(在IOHIDFamily中,我會把源碼放到github上),
所以我們來看一下有漏洞的代碼:
在terminateDevice()
這個函數中,內核服務調用OSSafeRelease()
來釋放一個device,但我們可以發現,雖然_device這個指針所指向的device被釋放了,但是_device并沒有置為NULL。如果我們再次調用已經釋放后的device的函數的話就會觸發UAF漏洞。隨后Apple在9.1中修復了該漏洞,可以看一下修復的代碼:
可以看到OSSafeRelease()
已經變成了OSSafeReleaseNULL()
,從而修復了UAF漏洞。
那么如何利用這個UAF漏洞呢?首先我們先利用IOKit提供的API創建一個device:
然后我們調用terminateDevice()
方法將這個device釋放掉:
然后我們再用釋放后的device去調用一些需要用到device的函數,比如說IOHIDResourceDeviceUserClient::handleReport()
就會觸發UAF漏洞:
因為_device已經被釋放掉了,但是_device這個指針的內容并沒有賦值為NULL,所以函數會繼續執到_device->handleReportWithTime(timestamp, report)
。接著服務會去_device指向的內存地址查找vtable中的函數,如果我們能夠在內存中malloc一段可控的內存并偽造一個fake的vtable,并且讓這段內存剛好分配在_device所指向的地址,我們就可以成功的控制內核的PC指針了。
利用這個思路,我們成功的寫出了利用程序,運行程序后,手機會重啟,隨后在~/Library/Logs/CrashReporter/MobileDevice/
目錄下的panic log中可以看到,我們已經成功的控制了pc指針并指向了0xdeadbeefdeadbeef:
對越獄來說,控制了內核的PC指針還只是一個開始,隨后還要獲取KASLR,利用ROP對內核進行讀寫,然后對內核進行patch,將簽名校驗disable等等,因為篇幅原因,這里就不一一介紹了,歡迎繼續關注我們以后的文章。
蘋果在不久前發布的9.3.2中,修補一個非常典型的內核堆溢出漏洞,該漏洞存在于IOHIDFamily中。配合用戶態漏洞觸發,該漏洞能繞過內核所有安全機制,轉化成內核任意讀寫,從而完成越獄。有該漏洞的代碼最早是在2002年發布的mac os 10.2中引入,幾乎影響了Apple全系設備15年之久。
出現漏洞的內核代碼如下 (http://opensource.apple.com/source/IOHIDFamily/IOHIDFamily-701.20.10/IOHIDFamily/IOHIDDevice.cpp):
#!c
IOHIDDevice::postElementValues(…) {
…
?maxReportLength = max(_maxOutputReportSize, _maxFeatureReportSize);?? ? ? ? ??- - - - - - - a
?report = IOBufferMemoryDescriptor::withCapacity(maxReportLength, kIODirectionNone);?? ? ? ? ??- - - - - - - b
…
?reportData = (UInt8 *)report->getBytesNoCopy()?? ? ? ? ??- - - - - - - c
…
?element->createReport(reportID, reportData, &reportLength, &element);//IOHIDElementPrivate::createReport?? ? ? ? ??- - - - - - - d
…
}
IOHIDElementPrivate::createReport(…) {
…
? ? ? ? ? ? writeReportBits( _elementValue->value, ? /* source buffer ? ? ?*/?? ? ? ? ??- - - - - - - e
? ? ? ? ? ? ? ? ? ? ? ? ? ?(UInt8 *) reportData, ? /* destination buffer */
? ? ? ? ? ? ? ? ? ? ? ? ? ?(_reportBits * _reportCount),/* bits to copy ? ? ? */
? ? ? ? ? ? ? ? ? ? ? ? ? ?_reportStartBit); ? ? ? /* dst start bit ? ? ?*/ ? ? ? ? ? ? ? ? ? ? ? ? ??
…
}
代碼行-a是漏洞的關鍵,IOHIDDevice的Report總共有三種類型:Output,Feature,Input;這些Report的Size是在創建IOHIDDevice時用戶輸入指定。這里只是根據Output,Feature來判斷可能最大的Report Size是錯誤的,因為Input的size可能比OutPut和Feature都大。
代碼行-b根據maxReportLength創建內核堆buffer。
代碼行-c用來拿到創建的內核堆的buffer指針。
代碼行-d是將Report內容保存到代碼行-b創建的buffer,那么只要Post的Report類型是Input而且Size > max(_maxOutputReportSize, _maxFeatureReportSize)
便能成功溢出。
代碼行-e 是createReport ()
的一部分。writeReportBits()
在report count等于1的情況下等同于memmove操作,將clientMemoryForType中設定的Report內容拷貝到代碼行-b創建的Buffer。
因此該漏洞可以從任意kalloc zone,達成任意長度的堆溢出。因為該漏洞是利用inpuT report來攻擊iOS內核,再加上Tbag是《越獄》中一個非常有名的角色,所以我們將這個漏洞命名為inpuTbag。
我們已經成功的利用inpuTbag堆溢出漏洞完成了iOS 9.2.1的越獄,如下是越獄的視頻和截圖 (因為Cydia的安裝不太穩定,容易造成白蘋果,所以我們的demo改成安裝一個未簽名的terminal app,并且可以用root權限執行任意指令,并且可以在系統的根目錄下創建任意文件):
這篇文章介紹了Use After Free的漏洞利用方式以及iOS 9.0上如何利用UAF攻擊iOS內核的技術。除此以外我們還公布了一個在iOS9.3.2中剛剛被修復的內核堆溢出漏洞,并展示了iOS 9.2.1的越獄。另外文中涉及代碼可在我的github下載:
https://github.com/zhengmin1989/iOS_ICE_AND_FIRE