iOS冰與火之歌 – UAF and Kernel Pwn

wyzsk發表於2020-08-19
作者: 蒸米 · 2016/06/12 17:08

作者:耀刺,蒸米,黑雪 @阿里移動安全
英文版:http://translate.wooyun.io/2016/06/12/54.html

0x00 序


冰指的是使用者態,火指的是核心態。如何突破像冰箱一樣的使用者態沙盒最終到達並控制如火焰一般燃燒的核心就是《iOS冰與火之歌》這一系列文章將要講述的內容。這次給大家帶來的是 Use After Free 的漏洞利用方式以及iOS 9.0上如何利用UAF攻擊iOS核心的技術。除此以外我們還公佈了一個在iOS9.3.2中剛剛被修復的核心堆溢位漏洞,可以用來攻擊iOS 9.3.2以下版本的iOS核心並實現iOS Pwn的終極目標 - 越獄。

《iOS冰與火之歌》系列的目錄如下:

  1. Objective-C Pwn and iOS arm64 ROP

  2. 在非越獄的iOS上進行App Hook(番外篇)

  3. App Hook答疑以及iOS 9砸殼(番外篇)

  4. 利用XPC過App沙盒

  5. UAF and Kernel Pwn

另外文中涉及程式碼可在我的github下載:
https://github.com/zhengmin1989/iOS_ICE_AND_FIRE

0x01 Use After Free


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

0x02 iOS 9.0 UAF in IOHIDResourceUserClient


簡單瞭解了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等等,因為篇幅原因,這裡就不一一介紹了,歡迎繼續關注我們以後的文章。

0x03 inpuTbag – 一個影響了蘋果裝置15年的核心堆溢位漏洞


蘋果在不久前釋出的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。

0x04 One More Thing – iOS 9.2.1越獄


我們已經成功的利用inpuTbag堆溢位漏洞完成了iOS 9.2.1的越獄,如下是越獄的影片和截圖 (因為Cydia的安裝不太穩定,容易造成白蘋果,所以我們的demo改成安裝一個未簽名的terminal app,並且可以用root許可權執行任意指令,並且可以在系統的根目錄下建立任意檔案):

iOS冰與火之歌 – UAF and Kernel Pwn

iOS冰與火之歌 – UAF and Kernel Pwn

0x05 總結


這篇文章介紹了Use After Free的漏洞利用方式以及iOS 9.0上如何利用UAF攻擊iOS核心的技術。除此以外我們還公佈了一個在iOS9.3.2中剛剛被修復的核心堆溢位漏洞,並展示了iOS 9.2.1的越獄。另外文中涉及程式碼可在我的github下載:

https://github.com/zhengmin1989/iOS_ICE_AND_FIRE

0x06 參考資料


  1. Hacking from iOS 8 to iOS 9, POC 2015
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章