Patching what Apple doesn’t want to or how to make your “old” OS X versions a bit safer

Today a local privilege escalation vulnerability was disclosed in this blog post. It describes a vulnerability in IOBluetoothFamily kernel extension (IOKit is a never-ending hole of security vulnerabilities…).

Mavericks and most probably all previous versions are vulnerable but not Yosemite.
The reason for this is that Apple silently patched the bug in Yosemite.
This is not a new practice, where Apple patches bugs in the latest and newly released OS X version and doesn’t care about older versions. Mavericks 10.9.5 update was released more or less around Yosemite date and this doesn’t look like a last minute bug found (although I’m going to confirm this). I bet that the bugs disclosed at SyScan 2013 by Stefan Esser still aren’t patched in Mountain Lion.

The blog post authors seem to experience the same “we don’t care attitude”. Their conclusions state:
“We contacted Apple on October 20th, 2014, asking for their intention to back-port the security fix to OS X Mavericks. Unfortunately, we got no reply, so we decided to publicly disclose the details of this vulnerability: Yosemite has now been released since a while and is available for free for Apple customers; thus, we don’t think the public disclosure of this bug could endanger end-users.”

The patch is very simple with only two instructions. With some luck we can patch it ourselves. What we need is a bit of unused space for installing the patch instructions. The mach-o header is usually a good place in userland but in kernel extensions you get a no execute (NX) kernel panic. They are also not wired memory so it’s not a good place to install a patch. We are left with alignment space.
If you search there are quite a few places with 15 alignment bytes (tip: load the driver into IDA, do a text search for align or a byte search for 90 90 90). That’s good enough for our patching, and we will need two of those islands since my proposed patch is 19 bytes long.

The two patch instructions are:
test ecx,ecx
js location

To install the patch we need to replace the first original instruction with a jump to the first island. Then we restore the original instruction and add the new patch instructions. We need to use a second island because the first doesn’t have enough space for this.
The new code should be something like this:
original_address:
jmp first_island
nop
remaining original_instructions
(…)
first_island:
jge location (original instruction)
test ecx,ecx (patch instruction)
jmp second_island
second_island:
js location (patch instruction)
jmp next_original_instruction

For Mavericks 10.9.5 you want to patch the following file /System/Library/Extensions/IOBluetoothFamily.kext/Contents/MacOS/IOBluetoothFamily.

Use the following file offsets and bytes:
Original instructions:
0x2855C: E9 B0 F7 FF FF 90
First island:
0x27D11: 0F 8D 43 0B 00 00
0x27D17: 85 C9
0x27D19: E9 23 2E 00 00
Second island:
0x2AB41: 0F 88 13 DD FF FF
0x2AB47: E9 16 DA FF FF

Save file, copy back to the original location, touch /System/Library/Extensions and reboot :-). Most probably this patch can be improved and reduced in size using smaller jump offsets if nearer islands are available. I didn’t bother to check.

Now go write to Apple and ask them to issue a proper patch. This total crap security policy must come to an end. Just in case you are wondering if this is an isolated case, check this Google Zero blog post. Two bugs that remain unpatched on OS X ;-).

Oh, this will break the code signature but in Mavericks that’s just a warning and not a fatal error. You can resign with a developer kext certificate if you have one.

Have fun,
fG!

P.S.: Another fine example of this crap security policy is that Apple fixed a few integer overflows in C++ code of libkern in Yosemite but didn’t bother to backport to Mavericks 10.9.5.  This is just insane…

11 thoughts on “Patching what Apple doesn’t want to or how to make your “old” OS X versions a bit safer

    1. Thanks fg!,
      patched mine and feeling a bit better now ;-)! Another question, can you direct me a little further to the libkern bugs? A file name would be enough…

  1. Why don’t you simply change the signed comparison to an unsigned? JGE -> JAE, or if assuming near jumps, 0F 8D -> 0F 83. No need for islands.

      1. You wanna bet? Sadly, kids these days are weak on simple binary arithmetics. Just try to come up with a single “negative number” and you’ll see.

          1. Where the JGE was in the original code of course, as I said initially. The entire patch amounts to changing a single byte (a nibble even).

            You don’t seem to realize that the only thing memory or registers contain are bits. It’s up to the code to interpret those bits as anything beyond that, like treat groups of them as signed or unsigned numbers. In this case the code block receives 32 bits. The original code treated those bits as a signed number (in twos-complement representation), in which case the number indeed would be deemed negative if and only if its most significant bit (the sign bit) was 1. The code compared this signed number with another (signed) number, namely the number of functions (a small positive constant number). It bailed out if the former was Greater or Equal to the latter. In assembly, that is implemented as a CMP instruction followed by JGE.

            (A CMP instruction is essentially a subtraction that doesn’t store its result, only modifying ALU status flags. The resulting bits of an addition or subtraction would be the same regardless of whether both operands and the result were treated as unsigned or as signed (in twos-complement representation) numbers, as long as the result would fit within the bits. The only difference is detecting if the result fits, or overflows, and this is handled by two different status flags.)

            The bug in the original code is triggered by supplying it with 32 bits where the MSB was 1, since the code treats that as a signed negative number which fails to trigger the bailout test.

            Now, if the code had instead treated those 32 bits as an unsigned number, it cannot be negative. Setting the MSB to 1 is simply treated as a very large number (over 2 “American billions”). Thus, a corrected version of the code should compare that number with another unsigned number, namely the number of functions (still a small positive constant number). It should bail out if the former is Above or Equal to the latter. In assembly, that is implemented as a CMP instruction followed by JAE.

            1. Please spare me the condescending tone, the net is already full of that crap 😉

              Yes you are correct, I was thinking about the integer overflow case which is obviously not the case here since there are no operations other than the sign extension before the comparison.

              Oh, and I do want the islands, not every patch is one nibble long.

    1. Sure, they just left other public vulnerabilities with public working exploits unpatched.
      So take your own conclusions about how great Apple is regarding security 😉

Leave a Reply

Your email address will not be published. Required fields are marked *