The Annotated (informal) guide to TaiG - Part the 1st
(because it's patched and noone has done it yet)
Jonathan Levin, http://www.newosxbook.com/ (@Technologeeks) - 2/08/15
With iOS 8.1.3 out, Apple has patched the bugs that made both Pangu8 (
xuanyuansword) and TaiG work. Apple has credited both teams with the vulnerabilities, but (as can be expected) the vulnerability descriptions in CVE entries are somewhat laconic. Virtually no documentation exists on either exploit. What better way to pass a long transpacific flight but to do a write up? (what more, I figure I'll need this anyway for the upcoming 2nd edition of MOXiI, which has a full chapter on Jailbreaks).
Why should you care? (Target Audience)
Yes, the vulnerabilities have been patched. So no, this isn't exploitable anymore (at least, for those people who [voluntarily|force] upgraded to 8.1.3. But TaiG is a particularly elegant jailbreak (much "cleaner" in most terms than XuanYuan), and there is much to be learned from it.
While I'm all for security, full disclosure and all that, I have a somewhat different stance when it involves iOS. IMMHO, the true potential of iOS is when jailbroken. I therefore don't believe that every version merits a jailbreak - only major ones (e.g. the upcoming 8.2, because it will support the Watch). Jailbreaking a minor update achieves nothing but forcing Apple to patch. In fact, TaiG blew a perfectly good set of exploits which could have and likely should have been saved for later. In a similar manner, documenting exploits before they are patched will only force Apple to patch them faster, because every 0-day, if properly detailed, can be used for malware. This writeup is therefore only published now because these are "burnt" exploits.
Toolset and process
Taig is a Windows based app. Unlike Pangu (which consistently froze my Win7 in VMware Fusion), it actually works well in a VM, which proved useful since I've long "converted" to an all-Apple system. The tools used to analyze on Windows were:
- Process Explorer: Mark Russinovich's legendary tool (I named my OS X ncurses
topreplacement in homage of it). Provides unbelieveable information about all processes in the system.
- Process Monitor: Yet another awesome, awesome tool from Mark - this one providing API logs.
- IDA: Because there is no better disassembler for Windows
- WinDBG: My favorite debugger. Use here was limited since TaiG does not cooperate with debuggers.
In the iOS realm, I used an already pre-JB 8.x device (broken with Pangu) to run my own set diagnostic tools, and those of Apple:
FileMon: My FSEvents viewer, recently re-written to be more stable and
fs_usage: The kdebug based utility (same functionality as in OS X). Terrible output, but used to double check FileMon, since FSEvents can sometimes get lost.
jtool: Because there is no better disassembler for iOS. But then, I'm heavily biased on that one ;-)
Having a device already jailbroken proved invaluable - runtime analysis is much more easier than static analysis. The added 'bonus' is that Pangu leaves the kernel is such a shaky state that any access using OSKextRequest (even something entirely benign like my own jketstat tool), causes a panic - so there was no risk of TaiG making any permanent changes, and thereby potentially bricking my device.
The Windows Binary of TaIG is huge - 51,938,816 bytes, to be exact. This is because it uses the PE format to embed pretty much everything - its code, graphic resources, and the payload to the device - inside the PE's .rdata sections. IDA was somewhat disappointing in figuring out these, although less interesting data (such as the Base64 encoded PNGs) did stick out like a sore thumb.
At first glance, what's interesting is that there are no direct dependencies on Apple's libraries (CoreFoundation, AppleMobileDevice, iTuneSMobileDevice and friends) even though they are clearly used. The mystery was clearly solved by looking at the file system, wherein a small DLL appeared in the temp directory -
XXX is a temporary file name. The file is easily recognizable as a DLL (MZ..). Further, looking at TaiG through SysInternals' Process Explorer one could see the DLL was loaded, and had a thread - CreateDevHelp - which is an exported symbol. Its "true name", btw, is
The Windows part isn't all THAT interesting, so I'll summarize the key points (and, more importantly, the addresses, if you want to disassemble the DLL on your own). The DLL, incidentally, can be found here
- 0x10011E60: is the function which gets all the symbols from Apple's libraries (
CoreFoundation.dll, MobileDevice.dll, iTunesMobileDevice.dll and AirTrafficHost.dll. It does so by calling 0x10011DE0, which uses
ntdll.dll) rather than use
kernel32), likely in an attempt to not be that blatantly obvious. The symbols are all linked - whether or not they're actually used. You can see the relevant code in the following figure: Note that it's a little tricky, since the
GetProcAddresscalls (which get the function pointer back into EAX) and the actual store in memory of the pointer (
mov ...., eax) are sometimes interrupted by
pushinstructions, which provide the symbol to be obtained by the
GetProcAddress) - you can see that with the
CreatePairingMaterialcall, above. Further, as I mentioned - ALL symbols are obtained, even though a large part are not used. This is why I left
AMdeviceCreateFromProperties) because it's simply unused. The symbols are are linked and stored beginning at
dword_100865A0(a static which gets "1" if the linkage is complete, so as not to do the process all over again). The symbols that are used are shown in the following table:
DLL Address(es) Notes CoreFoundation.dll 0x10086784 (
CFDataAppendBytes) through 0x1008683c (
Most of the CoreFoundation symbols, with few exceptions (e.g.
CFAbsoluteTimeGetCurrent). The symbols are linked in reverse order (i.e
MobileDevice.dll see above See above - that's why I put the figure iTunesMobileDevice.dll 0x10086774
called by 0x10013250 (which also calls AMDServiceConnectionCreate and handles pairing), and 0x10013160 (which creates a socket to USBMux) 0x10086770
called by 0x10014574, which sets 0x10014310 as a callback 0x10086768
All called by 0x10013740 (which handles pairing) 0x1008672c
Used by 0x10013740, as well as 0x100114D0, which wraps it along with all the annoying CF* calls required to convert the value (from a char * in the 3rd arg) to a CFString which can then be obtained. TaiG uses this to detect device lock ("PasswordProtected"), Find-My-iPhone ("com.apple.fmip"), "DeviceName","DeviceClass", "ProductVersion, "UniqueDeviceID", "ProductType", "ProductVersion" (all used for the GUI), and "isAssociated" 0x100866c4
Setting 0x10014410 (dfu_connect), 0x10014490 (recovery_connect), 0x10014450 (dfu_disconnect) and again 0x10014490 (recovery_disconnect) as callbacks. 0x100086718
called by 0x100164E0, which starts its second argument by pairing (0x10013740) and then working the CF* magic. syslog_relay and afc2d (used also to detect an already TaiG-JB device) are started this way. ... todo...
- 1001465C - checks activation state
- 10013AC9 - checks PasswordProtected
- 10017DE0 - Connects to com.apple.mobilebackup2
- 10011360 - connects to com.apple.afc2
The list, while not comprehensive, should provide enough breakpoints/focus-for-reverse-engineering. You can get a very detailed reference of AMD APIs from libmobiledevice or any of the various GIT repositories from people who reversed it (with some blatantly claiming copyright over the source.. funny :-). Google is (for a change) your friend here. If you're reading this, however, you're probably more interested in what happens on the device, right? So let's get to that.
Recap: Jailbreaking steps
Apple lists several vulnerabilities in APPLE-SA-2015-01-27-2, with CVE #s and all, but as I mentioned, doesn't go into detail of which vulnerability is responsible for what. Due to iOS's hardened (but not bulletproof) structure, it requires a combination of exploits to achieve the much coveted jailbreak. Specifically, we're looking at the following, which holds true for *all* jailbreaks (past, present, and likely future:
- Breaking out of the sandbox: As all apps are confined by default, thanks to
Sandbox.kext, and its evil henchKext/accomplice,
- Obtaining arbitrary (unsigned) code execution: which either involves exploiting a valid code signed binary (mostly Apple's services) by injecting an ROP payload, or bypassing code signatures. The latter involves messing with amfid (the user mode lackey of the AMFI kext), usually through
libmis, which we'll discuss in a little bit here.
- Obtaining root: Which involves ROP in a native iOS daemon which already runs as root, or - via the filesystem - dropping a binary which
launchdwill happily execute for you as root, if you know how to ask (and form a plist)
- Patching the kernel: To hit AMFI where it hurts - home base. This can be had by patching the boot-args (in memory, not in nvram, since iBoot long ignores them), or patching other parts of the kernel using the set of patches attributed to comex.
The above doesn't imply you need four different payloads - sometimes you can achieve a combination of the first three by directly exploiting a built-in service (which is, in fact, what TaiG does, thanks to AFC's dull-witted and overly helpful servitude). Patching the kernel always requires a separate payload. XNU proper has long been hardened (due to a rough history of repeat exploitation and its (slowly diminishing) open source code base. IOKit drivers, however, provide a rife breeding ground for many an exploit.
I: AFC (CVE-2014-4480)
If TaiG was a greek tragedy, its protagonist would most likely AFC. In it, we have a daemon, whose sole raison d'etre is to provide assistance and support for moving files back and forth from the host to the iDevice. Through the AFC* set of APIs, an application (which is supposedly iTunes, but in our case, TaiG) can direct the daemon to create directories, read and write files, and remove them at will. TaiG uses it to create directories in
so at the end of it we have:
/private/var/run/mobile_image_mounter -> ../../../var/mobile/Media/Books/Purchases/mload
But why all this trouble? And why mobile_image_mounter? Well, AFCD provides the "entry point" that TaiG uses into the device, but only one of two "footholds" it requires - the other being iOS's DeveloperDiskImage support. The latter is required when attaching a device and using it with XCode - the DDI is a part of the SDK, and contains a bunch of tools and frameworks. When mounted (using IOHDIX) under
/Developer, these libraries are magically "merged" so that
Ib: The race condition
Ok. It's not THAT simple. The DDI is signed - Otherwise, it's trivial to upload your own DDI and get the mobile storage proxy (also another abject minion) and the MobileStorageMounter.app (in CoreServices) to mount your image, and effectively replace system binaries. But here comes an age old challenge - TOCTOU. If one can get in between the time of check (verifying the signature) and the time of use (mounting), an old bait and switch can get MSP to mount the wrong image (I say here "old" because it's been used before, but never really documented, with only an obiter dictum by iH8sn0w*).
TaiG therefore does that - in between the "manipulating data" (30% or so) and "checking manipulate result" (40%), it uploads a carefully crated DMG called
Race conditions don't always work the first time, so MSP begrudges, as if anyone's listening:
But after a while (usually not more than 30 seconds), the race succeeds, and MSP doesn't log anymore. That's when no news is bad news - you're in trouble.
Ic: The disk image
By grabbing the
input dmg in time, it's easy to inspect it on OS X, as shown in the following figure (which I put in as a screen capture because it beats color coding everything in HTML. I have to find a better editor than vi..)
amfid - which you may recall from evasi0n's brilliant exploit - wherein an empty (and therefore, at the time, non code signed lib) redirected all of the code signature checks to a single, very affirmative
AppleMobileFileIntegrity.kext, when faced with an unknown (non adhoc) code signature, would summon (via the host special Mach port)
But.. we still have to defeat code signing at an earlier stage, because libmis.dylib itself will absolutely not be loaded by AMFId as it doesn't have a code signature. (Note it's part of the shared cache, but we got over that with enable-dylibs-to-override-cache. But AMFId is
DYLD_INSERT_LIBRARIES won't work anymore..). So even mounting replacement binaries or libraries will prove useless if AMFI will kill any non-codesigned binary before it gets a chance to run even a single opcode. Or.. will it?
II: DYLD (CVE-2014-4455)
The advisory lists a "Mach-O Overlapping segments" issue as an issue that was resolved in dyld. It's (shamefully) not the first time Mach-O had issues. Apple clings to this decades old legacy of NeXT (unlike other UN*X, which adopted ELF), and overlapping segments were also an issue in iOS7.
The DMG mounted as the fake developer image (input) contains the two libraries, which look like just your average trojan dylibs, but are not.
jtool on these actually crashed the tool. I thought it's just a(nother) bug I have in my code, but then I used
otool, and it just blatantly refused to execute. So I looked again, added a boundary check, and saw this:
And there's your "overlapping segments", yet again.
xpcd_cache follows the same idea - You can find libmis.dylib and xpcd_cache.dylib here , but they're also in the input.dmg. If you run otool, it will cowardly refuse to deal with the malformed Load commands. The present version of jtool will crash (alas!), but I'll upload the fixed version (which also has plenty of other features) soon.
The deed done, it is now possible to inject the binary that will untether all this in the future -
* - Hi Dude!