The Annotated (informal) guide to TaiG - Part the 1st

(because it's patched and noone has done it yet)

Jonathan Levin, (@Technologeeks) - 2/08/15

1. About

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:

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:

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 - TaiXXX.tmp, wherein 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 TGHelp.dll.

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

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:

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.

The exploits

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 /private/var/mobile/Media (where, among other things photos and books are stored - so obviously AFC can write to there!). First, as _exhelp, then as _mv help. See here:

 126 afcd	Created dir  /private/var/mobile/Media/_exhelp	
  126 afcd	Created dir  /private/var/mobile/Media/_exhelp/a	
  126 afcd	Created dir  /private/var/mobile/Media/_exhelp/a/a	
  126 afcd	Created dir  /private/var/mobile/Media/_exhelp/a/a/a	
  126 afcd	Created dir  /private/var/mobile/Media/_exhelp/var	
  126 afcd	Created dir  /private/var/mobile/Media/_exhelp/var/mobile	
  126 afcd	Created dir  /private/var/mobile/Media/_exhelp/var/mobile/Media	
  126 afcd	Created dir  /private/var/mobile/Media/_exhelp/var/mobile/Media/Books	
  126 afcd	Created dir  /private/var/mobile/Media/_exhelp/var/mobile/Media/Books/Purchases	
  126 afcd	Created  /private/var/mobile/Media/_exhelp/var/mobile/Media/Books/Purchases/mload	
  126 afcd	Created  /private/var/mobile/Media/_exhelp/a/a/a/c	 (symlink)
  126 afcd	Created dir  /private/var/mobile/Media/_mvhelp	
  126 afcd	Created dir  /private/var/mobile/Media/_mvhelp/a	
  126 afcd	Created dir  /private/var/mobile/Media/_mvhelp/a/a	
  126 afcd	Created dir  /private/var/mobile/Media/_mvhelp/a/a/a	
  126 afcd	Created dir  /private/var/mobile/Media/_mvhelp/a/a/a/a	
  126 afcd	Created dir  /private/var/mobile/Media/_mvhelp/a/a/a/a/a	
  126 afcd	Created dir  /private/var/mobile/Media/_mvhelp/a/a/a/a/a/a	
  126 afcd	Created dir  /private/var/mobile/Media/_mvhelp/a/a/a/a/a/a/a	
  126 afcd	Created dir  /private/var/mobile/Media/_mvhelp/private	
  126 afcd	Created dir  /private/var/mobile/Media/_mvhelp/private/var	
  126 afcd	Created  /private/var/mobile/Media/_mvhelp/private/var/run	
  126 afcd	Created  /private/var/mobile/Media/_mvhelp/a/a/a/a/a/a/a/c  (symlink)
-- BackupAgent wakes up
  279 BackupAgent	Chowned  /private/var/MobileDevice/ProvisioningProfiles	
Some events dropped
*** Warning: Some events may be lost
  279 BackupAgent	Created dir  /private/var/.backup.i/var/MobileDevice	
  279 BackupAgent	Created dir  /private/var/.backup.i/var/MobileDevice/ProvisioningProfiles	
  279 BackupAgent	Chowned  /private/var/.backup.i/var/MobileDevice/ProvisioningProfiles	
  279 BackupAgent	Created dir  /private/var/.backup.i/var/mobile/Media	
  279 BackupAgent	Created dir  /private/var/.backup.i/var/mobile/Media/PhotoData	
  279 BackupAgent	Renamed  /private/var/mobile/Media/_mvhelp/a/a/a/a/a/a/a/c	/private/var/.backup.i/var/mobile/Media/PhotoData/c 
  279 BackupAgent	Chowned  /private/var/run	
  279 BackupAgent	Chowned  /private/var/run
279 BackupAgent	Renamed  /private/var/mobile/Media/_exhelp/a/a/a/c	/private/var/run/mobile_image_mounter 
  279 BackupAgent	Chowned  /private/var/mobile/Media/Books/Purchases/mload	

# Done - so can remove this

  126 afcd	Deleted  /private/var/mobile/Media/_mvhelp/a/a/a/a/a/a/a	
  126 afcd	Deleted  /private/var/mobile/Media/_mvhelp/a/a/a/a/a/a	
  126 afcd	Deleted  /private/var/mobile/Media/_mvhelp/a/a/a/a/a	
  126 afcd	Deleted  /private/var/mobile/Media/_mvhelp/a/a/a/a	
  126 afcd	Deleted  /private/var/mobile/Media/_mvhelp/a/a/a	
  126 afcd	Deleted  /private/var/mobile/Media/_mvhelp/a/a	
  126 afcd	Deleted  /private/var/mobile/Media/_mvhelp/a	
  126 afcd	Deleted  /private/var/mobile/Media/_mvhelp/private/var/run	
  126 afcd	Deleted  /private/var/mobile/Media/_mvhelp/private/var	
  126 afcd	Deleted  /private/var/mobile/Media/_mvhelp/private	
  126 afcd	Deleted  /private/var/mobile/Media/_mvhelp	
  126 afcd	Deleted  /private/var/mobile/Media/_exhelp/a/a/a	
  126 afcd	Deleted  /private/var/mobile/Media/_exhelp/a/a	
  126 afcd	Deleted  /private/var/mobile/Media/_exhelp/a	
  126 afcd	Deleted  /private/var/mobile/Media/_exhelp/var/mobile/Media/Books/Purchases/mload	
  126 afcd	Deleted  /private/var/mobile/Media/_exhelp/var/mobile/Media/Books/Purchases	
  126 afcd	Deleted  /private/var/mobile/Media/_exhelp/var/mobile/Media/Books	
  126 afcd	Deleted  /private/var/mobile/Media/_exhelp/var/mobile/Media	
  126 afcd	Deleted  /private/var/mobile/Media/_exhelp/var/mobile	
  126 afcd	Deleted  /private/var/mobile/Media/_exhelp/var	
  126 afcd	Deleted  /private/var/mobile/Media/_exhelp	

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 /Developer/Library/Frameworks can be searched along with /System/Library/Frameworks ,etc. This isn't unique in that Apple does that also for /AppleInternal as well (nudge, nudge, wink, wink). So if one can provide a fake DeveloperDiskImage - its contents would be mounted, and actually supersede those of the OS itself. HMM.

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 (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 input (found here), along with a benign (and valid) DeveloperDiskImage called input2. It then repeatedly tries to get MSP to mount the crafted DMG, by submitting the benign DMG and switching it. This can be seen clearly in the output of FileMon (fse), below:

# Create the crafted DMG:
  126 afcd	Created  /private/var/mobile/Media/Books/Purchases/mload/input	
  126 afcd	Modified  /private/var/mobile/Media/Books/Purchases/mload/input	
# Submit to MSP: Note that MSP is redirected (via symlink) to directories under AFC control!
 6362 mobile_storage_p	Created  /private/var/mobile/Media/Books/Purchases/mload/6d55c2edf0583c63adc540dbe8bf8547b49d54957
 6362 mobile_storage_p	Modified /private/var/mobile/Media/Books/Purchases/mload/6d55c2edf0583c63adc540dbe8bf8547b49d54957
# Bait - move MSP's DMG to input2
  126 afcd	Renamed  /private/var/mobile/Media/Books/Purchases/mload/6d55c2edf0583c63adc540dbe8bf8547b49d54957ce9dc803
ff430b0c97bf3c6210fc39f35e1c239d1bf7d568be613aafef53104f3bc1801eda87ef963a7abeb57b8369/37kpPQ.dmg to
# Switch - put the crafted one instead
  126 afcd	Renamed  /private/var/mobile/Media/Books/Purchases/mload/input	/private/var/mobile/Media/Books/Purchases/
# Doesn't always work, though - MSM isn't (usually that) stupid
 6347 MobileStorageMou	Deleted  /private/var/mobile/Media/Books/Purchases/mload/6d55c2edf0583c63adc540dbe8bf8547b49d54957
# MSP complains..
 6362 	Modified  /private/var/mobile/Library/Logs/Device-O-Matic/	
# Curses! Foiled again!
  126 afcd	Deleted  /private/var/mobile/Media/Books/Purchases/mload/input2	
# But if you don't succeed at first, try, try again...

Race conditions don't always work the first time, so MSP begrudges, as if anyone's listening:

Pademonium-II:~ root# cat /private/var/mobile/Library/Logs/Device-O-Matic/
Sun Feb  8 05:19:14 2015 [604]  (0x19b626310) handle_mount_image: Could not mount the disk image
Sun Feb  8 05:19:14 2015 [604]  (0x19b626310) main: Could not mount the image
.. # Many more messages..

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..)

Notice /Volumes/DeveloperDiskImage is empty. That's irrelevant, really. What's more interesting is /Volumes/DeveloperLib - which gets mounted over/along /usr/lib! Likewise, /Volumes/DeveloperCaches goes over /System/Library/Caches, and contains apticket.der (likely a leftover), and - - a file used more times than I can recount to force the system to bypass the shared library cache (where all key libraries and frameworks, including those overridden here, are prelinked). So -- we now have the ability to force two important libraries - XPCD's cache support and libmis - the perennial favorite which provides the "brains" for the ogre that is 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 kCFBooleanTrue. AppleMobileFileIntegrity.kext, when faced with an unknown (non adhoc) code signature, would summon (via the host special Mach port) /usr/libexec/amfid, which in turn would load the library (because it is *snicker* modular to support provisioning profiles). Replace the check with something that says "yeah, whatever, ok, I know this", and you've defeated code signing.

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 __restricted, so 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.

Running 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:

morpheus@Zephyr (~/Documents/iOS/JB/TaiG) % jtool -l /Volumes/DeveloperLib/libmis.dylib                                        
Fat binary, big-endian,  3 architectures: armv7, armv7s, armv8
Specify one of these architectures with -arch switch, or export the ARCH environment variable
# variation of the same evasi0n trick
morpheus@Zephyr (~/Documents/iOS/JB/TaiG) % ARCH=armv7 jtool -S /Volumes/DeveloperLib/libmis.dylib           
         I _MISValidateSignature (indirect for _CFEqual)
         I _kMISValidationInfoEntitlements (indirect for _kCFUserNotificationTokenKey)
         I _kMISValidationInfoSignerCertificate (indirect for _kCFUserNotificationTokenKey)
         I _kMISValidationInfoSigningID (indirect for _kCFUserNotificationTokenKey)
         I _kMISValidationInfoValidatedByProfile (indirect for _kCFUserNotificationTokenKey)
         I _kMISValidationOptionAllowAdHocSigning (indirect for _kCFUserNotificationTokenKey)
         I _kMISValidationOptionExpectedHash (indirect for _kCFUserNotificationTimeoutKey)
         I _kMISValidationOptionLogResourceErrors (indirect for _kCFUserNotificationTokenKey)
         I _kMISValidationOptionUniversalFileOffset (indirect for _kCFUserNotificationTokenKey)
         I _kMISValidationOptionValidateSignatureOnly (indirect for _kCFUserNotificationTokenKey)
         U _CFEqual
         U _kCFUserNotificationTimeoutKey
         U _kCFUserNotificationTokenKey
         U dyld_stub_binder
# but what's THAT?
morpheus@Zephyr (~/Documents/iOS/JB/TaiG) % ARCH=armv7 jtool -l /Volumes/DeveloperLib/libmis.dylib  
LC 00: LC_SEGMENT               Mem: 0x00000000-0x00001000      __TEXT
        Mem: 0x00001000-0x00001000              __TEXT.__text   (Normal) # Look Ma, no code!
LC 01: LC_SEGMENT               Mem: 0x00001000-0x00002000      __LINKEDIT
LC 02: LC_ID_DYLIB              /usr/lib/libmis.dylib
LC 15: LC_CODE_SIGNATURE        Offset: 38576, Size: 752 (0x96b0-0x99a0) 
LC 16: LC_SEGMENT               Mem: 0xfffff000-0x1ffff000      __DATA

# And in 64-bit:
morpheus@Zephyr (~/Documents/iOS/JB/TaiG) % ARCH=armv8 jtool -l /Volumes/DeveloperLib/libmis.dylib                             
LC 00: LC_SEGMENT_64          Mem: 0x000000000-0x4000   __TEXT
        Mem: 0x000004000-0x000004000            __TEXT.__text   (Normal)
LC 01: LC_SEGMENT_64          Mem: 0x000004000-0x8000   __LINKEDIT
LC 02: LC_ID_DYLIB              /usr/lib/libmis.dylib
LC 16: LC_SEGMENT_64          Mem: 0xffffffffffffc000-0x1fffc000        __DATA

And there's your "overlapping segments", yet again.

Note: because TaiG's libmis doesn't bother to provide all symbols, your amfid may occasionally crash. By 'occasionally' I mean whenever it checks for provisioning profiles:
{"app_name":"amfid","app_version":"","os_version":"iPhone OS 8.1.2 (12B440)",
Incident Identifier: 59E17AA8-469A-4395-AC6B-53C1F2B9657B (yep, Apple, that was me :-)
CrashReporter Key:   a72d111b6b52f7c1ae1360e923e7c0da675b9261
Hardware Model:      iPhone7,2
Process:             amfid [22]
Path:                /usr/libexec/amfid
Identifier:          amfid
Version:             ???
Code Type:           ARM-64 (Native)
Parent Process:      launchd [1]

Date/Time:           2015-05-21 08:13:39.385 -0400
Launch Time:         2015-05-21 08:13:21.101 -0400
OS Version:          iOS 8.1.2 (12B440)
Report Version:      105

Exception Codes: 0x0000000000000001, 0x00000001200fd088
Triggered by Thread:  2

Dyld Error Message:
  Symbol not found: _MISCopyInstalledProvisioningProfiles
  Referenced from: /usr/libexec/amfid
  Expected in: /usr/lib/libmis.dylib
  Dyld Version: 353.6

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 - /taig/taig, run it, and restart the device. The binary will be used to effectuate the changes (remounting root, moving the libraries to their permanent home on the file system, adding a "/DeveloperPatch" containing AFC2, and a couple more things, described later). It also leaves one major last task - last, but not least - patching the kernel.

To part II
* - Hi Dude!