The Annotated (informal) Guide to TaiG - Part the 2nd
Jonathan Levin, http://newosxbook.com/ - 2/14/14
I: About
My previous writeup about TaiG was widely tweeted about, but left off where things got.. interesting - specifically, what happens on the device, when the TaiG jailbreak binary is injected. Since I wrote that one on the way to Oz (to kill time on a 15 hour flight), it only makes sense to pick up on it now, on the way back. You might want to take a look at the previous part before reading this one. This also has the disclaimer, tools, etc.
So... where were we..?
After TaiG uses the DDI race condition to mount a fake Developer Disk Image, we get a
II: The jailbreak binary
The taig
binary uploaded to the device is fat ("universal"), which is a necessity since it can run on both armv7 (32-bit) and arm64 (64-bit, i.e. 5S/Air and up) devices. You might want to follow along with the binary (which you can get from here, or from your device at
morpheus@Zephyr (~/Documents/iOS/JB/TaiG) % file taig
/Volumes/VMware Shared Folders/iOS/JB/taig/taig: Mach-O universal binary with 2 architectures
/Volumes/VMware Shared Folders/iOS/JB/taig/taig (for architecture armv7): Mach-O executable arm
/Volumes/VMware Shared Folders/iOS/JB/taig/taig (for architecture arm64): Mach-O 64-bit executable
The first thing I normally run on an unknown binary is my jtool
. Doing so on
morpheus@Zephyr (~/Documents/iOS/JB/TaiG) % ARCH=armv8 jtool -l taig LC 00: LC_SEGMENT_64 Mem: 0x000000000-0x100000000 __PAGEZERO # # No malformation here - Binary is 'legit' # LC 01: LC_SEGMENT_64 Mem: 0x100000000-0x100014000 __TEXT Mem: 0x100005e2c-0x1000115f8 __TEXT.__text (Normal) Mem: 0x1000115f8-0x100011d90 __TEXT.__stubs (Symbol Stubs) Mem: 0x100011d90-0x100012528 __TEXT.__stub_helper (Normal) Mem: 0x100012528-0x100012de3 __TEXT.__cstring (C-String Literals) Mem: 0x100012de3-0x10001309e __TEXT.__objc_methname (C-String Literals) Mem: 0x1000130a0-0x100013c27 __TEXT.__const Mem: 0x100013c27-0x100013c40 __TEXT.__objc_classname (C-String Literals) Mem: 0x100013c40-0x100013caf __TEXT.__objc_methtype (C-String Literals) Mem: 0x100013cb0-0x100013ccc __TEXT.__ustring Mem: 0x100013ccc-0x100013db4 __TEXT.__gcc_except_tab Mem: 0x100013db4-0x100014000 __TEXT.__unwind_info LC 02: LC_SEGMENT_64 Mem: 0x100014000-0x100018000 __DATA Mem: 0x100014000-0x100014098 __DATA.__got (Non-Lazy Symbol Ptrs) Mem: 0x100014098-0x1000145a8 __DATA.__la_symbol_ptr (Lazy Symbol Ptrs) Mem: 0x1000145a8-0x1000145b0 __DATA.__mod_init_func (Module Init Function Ptrs) Mem: 0x1000145b0-0x100014890 __DATA.__const Mem: 0x100014890-0x100014898 __DATA.__objc_classlist (Normal) Mem: 0x100014898-0x1000148a0 __DATA.__objc_catlist (Normal) Mem: 0x1000148a0-0x1000148a8 __DATA.__objc_imageinfo Mem: 0x1000148a8-0x100014b28 __DATA.__objc_const Mem: 0x100014b28-0x100014bc0 __DATA.__objc_selrefs (Literal Pointers) Mem: 0x100014bc0-0x100014be0 __DATA.__objc_classrefs (Normal) Mem: 0x100014be0-0x100014c30 __DATA.__objc_data Mem: 0x100014c30-0x100014f10 __DATA.__cfstring Mem: 0x100014f10-0x100015138 __DATA.__data Mem: 0x100015140-0x100016b70 __DATA.__bss (Zero Fill) LC 03: LC_SEGMENT_64 Mem: 0x100018000-0x100020000 __LINKEDIT LC 04: LC_DYLD_INFO LC 05: LC_SYMTAB Symbol table is at offset 0x19a70 (105072), 186 entries String table is at offset 0x1ab6c (109420), 2984 bytes LC 06: LC_DYSYMTAB 2 local symbols at index 0 1 external symbols at index 2 183 undefined symbols at index 3 No TOC No modtab 343 Indirect symbols at offset 0x1a610 LC 07: LC_LOAD_DYLINKER /usr/lib/dyld LC 08: LC_UUID UUID: AC536657-237F-39BE-A7B2-ACBC3A671692 LC 09: LC_VERSION_MIN_IPHONEOS Minimum iOS version: 7.0.0 LC 10: LC_SOURCE_VERSION Source Version: 0.0.0.0.0 LC 11: LC_MAIN Entry Point: 0x9674 (Mem: 100009674) # # Binary is not encrypted in any way (yay!) # LC 12: LC_ENCRYPTION_INFO_64 Encryption: 0 from offset 16384 spanning 65536 bytes # # Dependencies: LC 13: LC_LOAD_DYLIB /usr/lib/libafc.dylib # Not actually used LC 14: LC_LOAD_DYLIB /usr/lib/libc++.1.dylib # Compiled with C++ LC 15: LC_LOAD_DYLIB /System/Library/Frameworks/IOKit.framework/Versions/A/IOKit # We'll own the kernel LC 16: LC_LOAD_DYLIB /System/Library/PrivateFrameworks/.../ServiceManagement # We'll mess with launchd LC 17: LC_LOAD_DYLIB /System/Library/Frameworks/Foundation.framework/Foundation # Obj-C LC 18: LC_LOAD_DYLIB /usr/lib/libobjc.A.dylib # Obj-C LC 19: LC_LOAD_DYLIB /usr/lib/libSystem.B.dylib # Because everyone needs it LC 20: LC_LOAD_DYLIB /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation # Obj-C LC 21: LC_RPATH @executable_path/Frameworks # # XCode's default (and utterly otherwise useless) information leakage by design: # LC 22: LC_FUNCTION_STARTS Offset: 104640, Size: 344 (0x198c0-0x19a18) LC 23: LC_DATA_IN_CODE Offset: 104984, Size: 0 (0x19a18-0x19a18) # Hmmm... LC 24: LC_DYLIB_CODE_SIGN_DRS Offset: 104984, Size: 88 (0x19a18-0x19a70) LC 25: LC_CODE_SIGNATURE Offset: 112416, Size: 1104 (0x1b720-0x1bb70)
The binary is not malformed in any way, which makes sense, because by this time we have the fake
Dependency-wise, there are:
nm -m taig | grep c++
or jtool -S -v taig | grep c++
objc_
segments) that there's Objective-C involvedtaig
uses SMCopyDictionary and SMJobSubmit, later.Code signature and entitlements
A code signature and designated requirements can be seen at the end of the binary. OS X's codesign
claims that "code object is not signed at all". But what does it know? Without a code signature blob, AMFI.kext
would kill us on the spot. And clearly, there's something there. Time to unleash jtool --sig -v
on it:
morpheus@Zephyr (~/Documents/iOS/JB/TaiG) % JCOLOR=1 jtool -arch armv8 --sig -v --ent taig
Blob at offset: 112416 (1104 bytes) is an embedded signature of 1089 bytes, and 3 blobs
Blob 0: Type: 0 @36: Code Directory (709 bytes)
Version: 20001
Flags: none (0x0)
Identifier: taig
CDHash: 09059af87d64372658826d73e419d42383e2cd75
# of Hashes: 28 code + 5 special
Hashes @149 size: 20 Type: SHA-1
Entitlements blob: 3b3feeeb6676cb7bcd61e03436a8a39742feb44c (OK)
Application Specific: Not Bound
Resource Directory: Not Bound
Requirements blob: 3a75f6db058529148e14dd7ea1b4729cc09ec973 (OK)
Bound Info.plist: Not Bound
Slot 0 (File page @0x0000): b00b96b29f6a86c3bf2538d5b94c5d5a9ee0429e (OK)
Slot 1 (File page @0x1000): 1ceaf73df40e531df3bfb26b4fb7cd95fb7bff1d (OK)
# Hashes, and all ok, so omitted for brevity
Slot 27 (File page @0x1b000): bf05426e9a67734564f1e8bcd752885fd6182b05 (OK)
Blob 1: Type: 2 @745: Empty requirement set
Blob 2: Type: 5 @757: Entitlements (332 bytes)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>platform-application</key>
<true/>
<key>get-task-allow</key>
<true/>
<key>task_for_pid-allow</key>
<true/>
</dict>
</plist>
#
# Normally, there'd be a (potentially empty) CMS (RFC3852) signature blob here. This confuses codesign
Superblob ends @36
I'm not going to the internals of code signing yet - I'm doing a talk at the upcoming RSA Conference on that anyway (and I'll make the slides public). But what do we see here? In a nutshell, we have:
- The code pages are validly hashed (all the "slots" are OK)
- Signature has an identifier (
taig
) - Signature is not ad hoc: ad hoc signatures are used for Apple binaries, which that despicable
AMFI.kext
hard codes in the so called "trust cache". This enables hash-based validation, as then all one needs is to look for the CDHash in the trust cache. - Signature has no actual signature: No CMS blob, which is likely why
codesign
gives up on it. - Signature contains entitlements
The last point is also the most important one, and, in fact, the other reason we need the code signature blob anyway (The first was to avoid premature execution by AMFI.kext
). The entitlements get copied along with the code signature blob into kernel memory, so the binary has no way to modify them, and it is through the entitlements that we can achieve unrestricted execution, specifically:
Entitlement (all boolean) | Achieves |
---|---|
platform-application | That's our "Get out of jail" card: amfid would normally execute us if we try to run outside the sandbox. Specifying this basically says we're an Apple binary, so it's ok :-) |
get-task-allow | Allows us to get the task port of any process on the system and (if patched), the kernel_task as well. The task port is the root of all evil, and allows complete control over the task, including memory modification. Naturally, such a powerful API must be protected. In OS X there's a taskgated(8) daemon, but in iOS amfid serves the role of bouncer on this, and you can't use this nefarious mach trap unless you have the entitlement. I should note that both these are well known from Apple's own debugserver (part of the real DDI), and have been used since the dawn of time (or, at least since comex). |
At this point, what's going to happen is (roughly) this:
- The binary launches
AMFI.kext
doesn't kill us because we have a code signature blob. So far so good- The code signature is actually inspected.
AMFI.kext
sees it's not adhoc, and has an identifier. For all it knows, this could be a valid App-store app, right? So it requests its minion,amfid
to check on it, through a message to the well knownHOST_AMFID_PORT
. - If
amfid
isn't already running,launchd
spawns it, and provides it with the port. amfid
-imwit loadslibmis.dylib
, and calls on the_MISValidateSignature()
export. But, 'lo and behold - it's an indirect for_CFEqual
! So .. err. I guess that means it's ok?AMFI.kext
gets the message that.. .. ok? and letstaig
run. As root. Let's play ball.
Tracing the flow of Taig
taig
is actually relatively easy to disassemble because it's not really that obfuscated, and it's fat - so if your favorite assembly flavor is 32-bit you can target the armv7 portion, and if you prefer 64-bit, the armv8 one. Another helpful feature is LC_FUNCTION_STARTS
, which xcode's compiler puts in by default, along with LC_DATA_IN_CODE
. Both load commands serve no purpose but to make the debugger's (and thus, our) life easier, by clearly pointing out where functions begin and where packed switch data is*.
If you plan on disassembling yourself, you can use IDA (expensive, pretty good on 32-bit, but still dismal for 64), otool -tV
(free and built-in), or jtool -d __TEXT.__text
. I naturally used the last on armv8. As I went along, I added support for most of the instructions in the flow (at least those which matter), along with auto-decompilation. If you follow along, make sure you grab v0.85 or later from my site.The command line to try is ARCH=armv8 JCOLOR=1 jtool -d taig
(to start at the entry point). The following is basically the output from that, with a few more annotations by me (in yellow), and a ".jtool" companion file so that jtool
could dump meaningful names instead of func_XXXX
). You can toggle disassembly on/off in the following listing, which I've worked hard on to capture the original colors and provide JS for (@TODO: I also need to convert more curses to spans. Sheesh. This was hard with one listing, now I have three..)
main: ; func_10009674: ; Prolog: Saves x30 through x19, since we use them. ; ; watchdog_disable(600); // 10 minutes ; ; get_leak_1(); ; get_leak_2(); ; ; if (argc < 2) goto no_args (set w24 to 0) ; ; ; argv[] loop: for (w26 = argc - 1; w26 > 0; w26--) ; { ; if (_strcmp(argv[w26],"-u")) { w24 = w27 = 1; } ; if (_strcmp(argv[w26],"-s")) { w24 = w28 = 2; } ; if (_strcmp(argv[w26],"-l")) { w24 = w19 = 3; } ; } // end of argv[] loop ; ... ; if (user didn't select -s) goto common; ; _setup() ; no_args: // 0000000100009750 ; w24 = 0; ; At this point, w24 holds one of: ; 0: if no recognized argument was detected ; 1: for the -u argument ; 2: for the -s argument ; 3: for the -l argument common: // 0000000100009754 ; ; kern_return_t = task_for_pid (mach_task_self, ; 0, ; &kernel_task); = sp+0x28 ; ; memcpy (SP + 0x178 + 0x20, (SP + 0x20) + 0x20, 312); ; ; ; deobfuscate_object_names (SP + 0x178) - copies the IORegistry Object names exploit uses ; ; ; if (kernel_task != 0) goto task_for_pid_already_patched ; ; ; if we're still here, we need to exploit the kernel ; ; This is the IOHID payload, with a sprinkle of mach_port_kobject for good taste ; ; rc = exploit (SP + 32, 0); (exploit is @ 0x10000a204) ; ; if (rc < 0) goto exploit_failed; ; if (x24 == 0) goto args_none ; ; _func_100009970(X20 + 112, 1); args_none: ; Apply kernel patches (modified set of Comex patches, updated for ARMv8) ; apply_patches (SP + 32); ; to_close_IO_Services (SP + 32) ; ; We also get here if task_for_pid was already patched: ; task_for_pid_already_patched:; ; watchdog_disable(610); ; if (w24 == 2) goto args_s ; if (w24 != 1) goto args_not_u args_u: patch_libmis_and_xpcd_cache(); ; goto ok args_s: ; rc = remount_root() ; if (rc !=0) goto faiure; ; do_setup (*X19); ; goto 0x1000983c args_not_u: ; if (w24 !=0) goto ok ; rc = remount_root(); ; if (rc != 0) goto after_mess_with_dirs_and_SB ; _makes_dirs ; _mess_with_SB() ; func_0x10000cc74 after_mess_with_dirs_and_SB: ; func_0x10000cd38 (); ; usleep(...); ; Display "TaiG, Made in China" :-) ; NSLog(@"太极 中国制造,sw_pl"); _ok: ; r0 = 0; 000000010000983c MOVZ W0, #0 ; R0 = 0x0 exit: ; Epilog - restores X19-X30 000000010000985c RET ; exploit_failed: we get here if exploit returns negative value. ; we either reboot, or just fail by exiting with -1.
setup
When invoked with "-s", taig enters installation (I guess -s
is for "setup") mode. This mode is required only once, on first invocation (that is, when "injecting the jailbreak program"). In fact, caveat lector - Don't say I didn't warn you:
taig
isn't smart enough to ignore -s
when it's already installed (or check if /taig/taig
to be recreated as an empty file, and on the next reboot your system will hang as launchd is unable to boot to SpringBoard, *or* start ssh! Believe me - I speak from experience. I barely managed, in the nick of time, to restore to 8.1.2 before Apple stopped signing it - and forced an 8.1.3 upgrade.The installation function can be found at 0x100007fa0. Basically, it's pretty straightforward, and involves:
- Copying Taig's files to
/taig : Two files are involved. The binary (/taig/taig ) and the DMG image (/taig/lockdown_patch.dmg ). The latter is automounted by the binary (usingIOHDIXController
) like so:Phontifex:~ root# ls -lR /DeveloperPatch /DeveloperPatch: total 0 drwxrwxrwx 4 mobile staff 136 Oct 12 20:55 Library drwxrwxrwx 4 mobile staff 136 Oct 21 00:19 bin /DeveloperPatch/Library: total 0 drwxrwxrwx 4 mobile staff 136 Nov 27 2013 Lockdown /DeveloperPatch/Library/Lockdown: total 0 drwxrwxrwx 4 mobile staff 136 Oct 21 19:54 ServiceAgents /DeveloperPatch/Library/Lockdown/ServiceAgents: total 4 -rwxrwxrwx 1 mobile staff 179 Oct 21 19:54 com.apple.afc2.plist /DeveloperPatch/bin: total 28 -rwxrwxrwx 1 mobile staff 24928 Oct 12 23:36 afcd2
The "afcd2" is an unrestricted AFC (AppleFileConnect) daemon, which is how we got here in the first place:
Phontifex:~ root# cat /DeveloperPatch/Library/Lockdown/ServiceAgents/com.apple.afc2.plist | plutil -convert xml1 -o - - <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>AllowUnauthenticatedServices</key> <true/> <key>Label</key> <string>com.apple.afc2</string> <key>ProgramArguments</key> <array> <string>/DeveloperPatch/bin/afcd2</string> <string>--lockdown</string> <string>-d</string> <string>/</string> </array> </dict>
- To ensure we get run every time on boot, we turn to
launchd
, by shoving/System/Library/LaunchDaemons/com.taig.untether.plist :<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.taig.untether</string> <key>LaunchOnlyOnce</key> <true/> <key>POSIXSpawnType</key> <string>Interactive</string> <key>ProgramArguments</key> <array> <string>/taig/taig</string> </array> <key>RunAtLoad</key> <true/> <key>UserName</key> <string>root</string> </dict> </plist>
- There's also a couple of other things, such as repatching libmis/xpcd permanently (which is done on install since we don't have the multi-part
input
dmg from Part I), etc, as well as mess with springboard apps (SBShowNonDefaultSystemApps
), but generally that's that.
Let's move on to the good stuff.
The Exploits
OSBundleMachOHeaders (CVE-2014-4491)
The Apple Bulletin states that "An information disclosure issue existed in the handling of APIs related to kernel extensions. Responses containing an OSBundleMachOHeaders key may have included kernel addresses, which may aid in bypassing address space layout randomization protection". Apple credits TaiG and Stefan Esser with this bug, and it is indeed used by Pangu8.
TaiG wastes no time with exploiting the bug. In fact, it does so before even checking its command line arguments! The main function used is 0x10000d2fc (leak_kernel_addresses
), which is used through two wrappers: _get_leak_1
(0x10000d2bc
, which returns 0x100016b58
) and get__leak_2
(0x10000d2dc
, which returns 0x100016b60
). The function itself is shown here:
_leak_kernel_address:; ; mach_port_t hs = mach_host_self; ; ; ; X8 gets value @0x10016b58 - a global. If it's the first time we're here, it's 0, and we proceed ; to set this value from leak_kernel_address. If it's not, then we can simply exit ; ; if (global_cached_value) goto _Exit ; ; Now craft a kext_request (remember we're running as root - yes we can): ; kr = kext_request (mach_host_self, // host_priv_t host_priv ; 0, // uint32_t user_log_flags ; "", ; 0x54, // mach_msg_type_number_t request_dataCnt, ; sp + 0x18 , // vm_offset_t *response_data ; sp + 0x14, // mach_msg_type_number_t *response_dataCnt ; sp + 0x20, // vm_offset_t *log_data ; sp + 0x10, // mach_msg_type_number_t *log_dataCnt ; sp + 0x2c);// kern_return_t *op_result ; ; if (! kr) goto Fail ; ; char *MOBH = strstr (response_data , " Kext Request Predicate Get Loaded Kext Info OSBundleMachOHeaders "); ; ; ; Get start of Base64 data of OSBundleMachO - the contents of the element ; char *endData = strstr (MOBH + 44, ""); ; ; if (!endData) goto Fail; ; ; *endData = '\0'; ; Pass Base64 data to NSString, to create a string ; ; NSStringRef str = _objc_msgSend(_OBJC_CLASS_$_NSString,"stringWithUTF8String:", MOBH + 0x2c); ; ; Or, in Obj-C parlance: [ NSString stringWithUTFString: (MOBH + 44)] ; ; ; ; Decode Base64 or bust ; ; R0 = _objc_msgSend(_OBJC_CLASS_$_NSString,"decodeString:", str); ; if (!R0) goto Fail; ; ; ; ; Get count of bytes ; ; R0 = _objc_msgSend(_OBJC_CLASS_$_NSString,"bytes",str); loop: ; ; R0 = _strcmp(...decoded string data,"__TEXT"); ; ; ; R0 = _strcmp(..decoded string data,"__PRELINK_STATE"); ; ; ; R0 = _strcmp(decoded string data..,"__PRELINK_INFO"); ; ; if (!R0) goto 0x10000d494 ; (loops 15 times) _Fail: ; X0 = -1 _Exit: ; return (X0); ; usual epilog...
The code is fairly simple, and jtool
brought me so close to decompilation I did the rest manually. The call to kext_request
retrieves a huge plist (this is what my kextstat tool does, via OSKextGetLoadedKextInfo()
). It will look something like this:
3c 64 69 63 74 20 49 44 3d 22 30 22 3e 3c 6b 65 <dict ID="0"><ke 79 3e 5f 5f 6b 65 72 6e 65 6c 5f 5f 3c 2f 6b 65 y>__kernel__</ke 79 3e 3c 64 69 63 74 20 49 44 3d 22 31 22 3e 3c y><dict ID="1">< 6b 65 79 3e 4f 53 42 75 6e 64 6c 65 4d 61 63 68 key>OSBundleMach 4f 48 65 61 64 65 72 73 3c 2f 6b 65 79 3e 3c 64 OHeaders</key><d 61 74 61 20 49 44 3d 22 32 22 3e 7a 2f 72 74 2f ata ID="2">z/rt/ 67 77 41 41 41 45 41 41 41 41 41 41 67 41 41 41 gwAAAEAAAAAAgAAA 41 38 41 41 41 42 41 43 77 41 41 41 51 41 67 41 A8AAABACwAAAQAgA.... 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 41 41 41 41 41 41 41 41 41 41 41 57 49 41 4e 41 AAAAAAAAAAAWIANA 6f 44 2f 2f 2f 38 41 41 41 41 41 41 41 41 41 41 oD///8AAAAAAAAAA 43 59 41 41 41 41 51 41 41 41 41 71 46 39 50 41 CYAAAAQAAAAqF9PA 45 67 2b 41 41 41 3d 3c 2f 64 61 74 61 3e 3c 6b Eg+AAA=</data><k 65 79 3e 4f 53 42 75 6e 64 6c 65 43 50 55 54 79 ey>OSBundleCPUTy 70 65 3c 2f 6b 65 79 3e 3c 69 6e 74 65 67 65 72 pe</key><integer 20 73 69 7a 65 3d 22 33 32 22 20 49 44 3d 22 33 size="32" ID="3
As you can see, it's (yet another) plist, and it has a ton of base64 (those AAAAA are encodings of binary 0s). You can grab the raw output from here if you want to play around with it. I only included up to the end of ID="2", because that's also what TaiG cares about.
Taig "zooms in" (using strstr) on the "z/rt/.." which is the content of the <data ID="2">
element.You can follow the code with the raw data, (or just set a breakpoint if you debug) but I'll save you the ordeal - _get_leak_1
will return 0xffffff8018402000 or similar address (slid) and _get_leak_2
will return 0xffffff8019754000 (again, slid). These addresses will look different for you if you debug on your device, but they are nonetheless 0x1352000 apart. We can deduce the slide, and that sure is a great start, considering the kernelcache is encrypted, so we couldn't just do an jtool -S
on it. So this is how responses "may have included kernel addresses". It's not "may". It's "DO". But for some reasons security advisories like modals.
evasi0ners may recall MachOBundleHeaders from iOS6, where it was a well known bug until it was burned by @mdowd. Somewhat surprisingly, this is a close variation of the same bug. Apple states "This issue was addressed by unsliding the addresses before returning them". The exact words appeared in iOS 6.0.1 Security Notes. (edit) Come to think of it, the entire description is a verbatim copy of) the section dealing with the 6.0 bug, a.k.a CVE-2012-3749. Learn from history, or be condemned to repeat it).
mach_port_kobject (CVE-2014-4496)
The Apple bulletin states that "The mach_port_kobject kernel interface leaked kernel addresses and heap permutation value, which may aid in bypassing address space layout randomization protection". and credits TaiG for it. This is most likely a mistake, as the bug could not possibly have been discovered by anyone but Stephan Esser, who explains it clearly (in his SektionEins blog). Esser leaves exploitation as an "exercise for the reader", and it seems that TaiG has solved it:
dey_can_stealz_hiz_bugz:
000000010000d250 STP X20, X19, [X31, #124]!
000000010000d254 STP x29, X30, [X31, #2]
000000010000d258 >ADD x29, x31, #0x10
000000010000d25c sub sp, sp, #16
000000010000d260 STP w31, W31, [X31, #2]
000000010000d264 STR X31, [ X31, #0]
000000010000d268 ADR x19, 39144
000000010000d26c NOP
000000010000d270 LDR X0, [X19, #0]
000000010000d274 cbnz x0, 0x10000d2ac
000000010000d278 BL 0x100011988
000000010000d27c >ADD x1, x31, #0xc
000000010000d280 BL 0x100011934
000000010000d284 NOP
000000010000d288 LDR X8, #7036
000000010000d28c LDR W0, [X8, #0]
000000010000d290 LDR W1, [X31, #3]
000000010000d294 >ADD x2, x31, #0x8
000000010000d298 >ADD x3, x31, #0x0
;
; ignored = mach_port_kobject(mach_task_self, // ipc_state_t task,
; [X31, #3] , // mach_port_name_t name,
; SP + 0x8 , // natural_t *object_type,
; iomaster_port_kobject_leak) // mach_vm_address_t *object_addr);
000000010000d29c BL 0x1000119b8
000000010000d2a0 LDR X8, [X31, #0]
000000010000d2a4 sub x0, x8, #1
000000010000d2a8 STR X0, [ X19, #0]
outta_here:
000000010000d2ac sub sp, fp, #16
000000010000d2b0 ldp fp, lr, [sp, #16]
000000010000d2b4 ldp x20, x19, [sp], #32
000000010000d2b8 RET X30
This function should not have even existed in XNU, much less on iOS - A look at the source clearly shows it is supposed to return KERN_FAILURE
, unless MACH_IPC_DEBUG
is #define
d. Apple states that "This was addressed by disabling the mach_port_kobject interface in production
configurations". Am I to understand iOS wasn't considered production up until this point? :-) Maybe a better check on #define
s would have been advisable (hmm.. what other interesting debug functions is there that shouldn't be? tsk tsk).
IOHIDFamily and friends
deobfuscate_object_names
This function is used to deobfuscate the IORegistry objects TaiG use in the exploit. The deobfuscation is fairly simple, taking a "key" (0x100012c46 ; "rgca/[204';b/[]/?"
, which is likely UTF-8 Chinese for something), and iterating over the "encrypted" area of memory, xoring with the "key", for several objects, as shown here:
; Setup... loop_to_copy_IOPMRootDomain: ; ; This is the first of several loops used in TaiG to copy the names of the ; IORegistry objects used in the exploit. Here's how it works: ; ; x8 is the loop iterator, holding the strlen(IORegistryNameBeingCopied) + 1 ; w11 is the byte to be copied and "decrypted". ; w12 is the "key", which is simply xor'ed. ; x10 is the destination string ; ; For the first loop we have: ; ; x9 = 0x100014f38; ; x10 = 0x100015011; ; ; for (x8 = 0; x8 < 15; x8++) ; { ; w11 = x9[x8] + 99; ; w12 = x20[x8]; ; x10[i] = w11 xor w12; ; } ; ; strcpy (X19 + 0x180, "IOPMrootDomain"); ; loop_to_copy_IOHIDResource: ; strcpy (X19 + 0x1c0, "IOHIDResource"); ; You get the idea of the rest, I hope.. ; ; 000000010000c7a0: loop to copy IOHIDLibUserClient ; ; ; 0x10000c7e8: loop_to_copy_IOHIDEventService ; ; 0x10000c830: loop_to_copy_IOUserClientClass ; ; 0x10000c878: loop_to_copy_ReportDescriptor ; ; 0x10000c8c0: loop_to_copy_ReportInterval ; ; Optimization: Branch to strcpy, so we return as it returns (saving a RET)@TODO: finish this sometime
Exploiting the bug requires quite a few IOUserClientConnect
messages as well as direct mach_msg
calls. If you're interested in that, drop me a line. it's too complicated to write this and there's only so much I could do on one flight. Incidentally, the Apple bulletin states no less than *three* bugs in IOHIDFamily. Human interface devices, indeed :-)
Final notes (and shameless plug)
I hope you liked this, and found this informative. It certainly passed my time :-) And..
- Normally I'd be suggesting my book here - but I won't - don't buy it. Really. There's a much better second edition in the works. I'm still soliciting feedback for the table of contents for the second edition.
- If you're into Android, check out my other book
- You're welcome to also to check out my company (Technologeeks.com) - we offer training and consulting on all things internal, in particular OS X/iOS and Linux/Android. I'm personally incorporating details of this writeup into our OS X and iOS for Reverse Engineers course.
- To keep abreast of more OS X/iOS updates from me, either get the RSS feed, or follow @Technologeeks on Twitter.
- To get to me personally, save a tweet (I don't really follow that). You can just drop me a line at j@.
Post Scriptum
Happy Valentine's Day everybody. I hope yours is better than mine. Upon landing, I just learned that my ongoing flight to BOS was canceled (weather). I guess I'm taking the long way home.
* - One can argue that it's not information leakage per se, since you can also figure out function starts by doing a first pass to find
BL
instructions (akin to x86's call). Still, it makes it easier (certainly for jtool
) to auto-detect functions, and not bother with disassembling data, which would be nonsensical - or worse - misleading