A while back I found a consistent kernel crash on OS X by filling invalid values in the 64-bit DRX registers. The exact issue was happening in Yosemite and reported to Apple on July 28, 2015. This issue is now fixed in El Capitan as listed in the release notes.
The issue was assigned CVE-2015-5902, which can be described as CWE-284: Improper Access Control and according to CVSSv2 we believe this vulnerability to have a Base Score of 4.4 (AV:L/AC:M/Au:S/C:N/I:N/A:C) which is low-moderate.
- 2015-06-05 Found Crash
- 2015-07-20 Write PoC and investigate the issue
- 2015-07-28 Report vulnerability to Apple
- 2015-09-03 Apple responds with a fix in beta5
- 2015-09-30 Apple releases El Capitan
In order to investigate the issue I installed Yosemite in VMWare and configured the kernel for remote debugging:
# nvram boot-args="-v debug=0x1"
Proof Of Concept
At this point, after rebooting the VM, the kernel waits for an UDP connection via ethernet for debugging. This requires a quick static ARP entry like this:
# arp -s 192.168.242.128 00:0c:29:e6:4c:fb
And then just spawn an
$ lldb (lldb) kdp-remote 192.168.1.242.128 (lldb) continue
By executing the PoC (see below) you will get an interactive lldb prompt, which shows the following:
- Illegal Instruction Exception
- Happens in
- The offending instruction is
mov dr7, rax
- Value of
DR registers can only be modified in ring0, therefore the kernel is responsible for verifying that the values set in there are valid and to avoid users being able to set global watchpoints or breakpoints in kernel addresses.
The higher bits of the 64bit DR7 register are undocumented and suposed to be reserved for future use, but the thing is that all X86-64 CPUs I have tried result in an
illegal instruction when trying to use those bits. In my testing, XNU doesnUt handle this exception, so it reboots without being able to fully determine what precisely was happening, therefore requiring to use the kernel debugger.
At this point, I wrote a cleaner PoC, designed the mandatory logo and reported the issue to Apple. They assigned the CVE-2015-5902 and addressed the issue in El Capitan beta5.
Click below to read the source code of the PoC:
Analysing The Fix
Also, I installed El CapitanUs beta5 in another VMWare and took the kernel from
/System/Library/Kernels/kernel in order to compare it with the YosemiteUs one.
I quickly diffed the the
ret_to_user symbol, but it turns to contain no relevant changes in it:
$ radiff2 -g sym._ret_to_user yosemite elcapitan | dot -Tpng > ret_to_user.png
The graph shows few changes between them, and the
mov dr7, rax line remains intact, therefore the fix must be in the setter instead of the getter.
Which makes sense for performance reasons because
ret_to_user will be executed much more times than
Reading the source of
thread_set_state we can observe the stack of calls and which checks are performed to validate the
Backtrace: sym._dr7_is_valid sym._debug_state_is_valid64 sym._machine_task_set_state sym._thread_set_state
The key function is
_debug_state_is_valid64 which have been completely rewritten in El Capitan in order to avoid those unused bits of the DR7 register, the reason for fixing
thread_set_state instead of
ret_to_user is clearly for performance reasons, because
ret_to_user is called everytime the kernel jumps back into userland.
HereUs how the old DR7 verification looks in Yosemite:
And this how itUs done in El Capitan, the logic of this symbol is splitted into two functions, the first call takes the user dr7 value from
lea rdi, [rbx + 0x38] and calls the second function which performs the cleanup. Good job guys! the new code seems to properly verify for non-user addresses, global watchpoints and invalid dr7 bits.
In order to patch it with
lldb you will need to enable the Kernel Debugger Protocol in XNU and attach from another machine. The simplest patch consists in nopping the offending instruction, this results in being unable to use hardware breakpoints, but at least it will not crash.
As long as Yosemite have some other known vulnerabilities we can abuse it to gain writable access to kernel memory.
radare2 uses the
processor_set_tasks vulnerability to read-write kernel memory (requires root) with the classy
task-for-pid-0 (aka tfp0) in Yosemite (fixed in El Capitan). This bug is already used by Pedro Vila a (osxreverser) and Jonathan Levin.
Bear in mind that OSX uses KASLR, and the address of
ret_to_user will be different every time. But this can be solved by taking the base address of the kernel map and adding the delta, because the lower bits of the address will always be the same.
$ sudo r2 -q -s addr_of_mov_dr7_rax -c 'wao nop' mach://0
The vulnerability is a local DoS, it can be exploited without debugging or root privileges, so itUs not really a scary issue, mainly because OSX is not much used in servers and the bug only affects the x86-64 platform. Hardware breakpoints work different on ARM and AARCH64, so this vulnerability doesnUt seem to affect iOS.
The final version of OSX – El Capitan is not vulnerable, so I encourage you to upgrade as soon as possible in order to be safe against this, and many other kernel vulnerabilities that have been disclosed in the Apple release notes.