The Annotated (informal) Guide to TaiG - Part the 2nd
Jonathan Levin, http://newosxbook.com/ - 2/14/14
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 /Developer mounted with a replacement /usr/lib/libmis.dylib and /usr/lib/xpcd_cache.dylib, both malformed with the (now deceased) Mach-O overlapping segments bug, to allow the running of unsigned code. It is at that point which TaiG's UI claims "success" (around 40%, was it?), and proceeds to "injecting jailbreak program". The binary can now run, and install itself on the device so as to effectuate the jailbreak changes and make them persist after a reboot.
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 /taig/taig).
The first thing I normally run on an unknown binary is my jtool. Doing so on taig (as shown in the following listing) reveals several interesting things:
Listing ...: Running jtool -l on taig's 64-bit Mach-O (32-bit is pretty much same)
The binary is not malformed in any way, which makes sense, because by this time we have the fake /usr/lib/libmis.dylib over the real one. It's not encrypted, which is great for reverse engineering, and frankly couldn't have been employed to resist it since it's only doable (with Encryption Type: 1) for app store Apps.
Dependency-wise, there are:
libsystem.B: Which no (dynamically linked binary) on iOS or OS X can move without. Windows people can think of it as a combination of Kernel32+NtDll+MSVCRT. Linux people can think of it as (among other things) libc.so.
libc++: Which indicates the use of C++ in the binary. (nm -m taig | grep c++ or jtool -S -v taig | grep c++
libobjc, and the Foudation/CoreFoundation frameworks: providing a clear indication (along with the objc_ segments) that there's Objective-C involved
IOKit: Which is the clearest indication the binary will tackle the kernel - IOKit is THE attack vector for that
ServiceManagementA private framework used to interface with launchd. taig uses SMCopyDictionary and SMJobSubmit, later.
libafc.dylib: Not sure if this is a red herring, a left over -lafc, or something I've missed, but there's no actual symbols imported from here
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:
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)
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 :-)
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 known HOST_AMFID_PORT.
If amfid isn't already running, launchd spawns it, and provides it with the port.
amfid-imwit loads libmis.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 lets taig 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..)
Listing ..:Taig's main() Show disassembly
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:
WARNING: taig isn't smart enough to ignore -s when it's already installed (or check if /DeveloperLib and friends are mounted). Running it would cause the binary /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 (using IOHDIXController) like so:
The "afcd2" is an unrestricted AFC (AppleFileConnect) daemon, which is how we got here in the first place:
To ensure we get run every time on boot, we turn to launchd, by shoving /System/Library/LaunchDaemons/com.taig.untether.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 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:
Listing ..:Taig's OSBundleMachOHeaders exploit Show disassembly
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:
Listing ..:The output returned from kext_request() Show all
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).
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:
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 #defined. 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 #defines would have been advisable (hmm.. what other interesting debug functions is there that shouldn't be? tsk tsk).
IOHIDFamily and friends
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:
Listing ..:Taig's obfuscation Show disassembly
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..
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 get to me personally, save a tweet (I don't really follow that). You can just drop me a line at j@.
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