Taking apart iOS OTA Updates Peeking into Over-The-Air Update bundles in iOS Jonathan Levin, http://newosxbook.com/ - 03/18/14

About iOS introduced Over-The-Air (OTA) updates sometime back in 5.x, which was after the 1st edition of MOXiI was already published. As such was the case, this very important discussion was left out of where it should have been included (Chapter 6). With the 2nd edition in the works, OTAs will naturally be included - but since this information is immediately useful (as you'll see shortly), I figured it made sense to publish it as an article. Why should you care? (Target Audience) As you probably know, iOS software images (the well known .IPSWs, which I did cover in the book) are encrypted. Thanks to the hard work of the folks at the iPhoneWiki, decryption keys are made available fairly regularly for A4 and A5 devices - the former via limera1n, and the latter through an undisclosed but pretty well known exploit somewhere in the boot chain. The problem, however, is that no keys exist for A6+ devices, most notably the 64-bit devices (5S, 6, iPad Air, etc). This is exacerbated by Apple effectively phasing out A4 with 8, and the very likely end-of-line for A5 when iOS 9 is likely to be announced this summer. Thankfully, OTA updates exist and are unencrypted. This means that if you have a jailbroken device of version 8.1.2 or lower, you can do two pretty amazing things: Obtain the OTA update, and - using the techniques demonstrated here - unpack it so as to reverse engineer it to your heart's content or Update the iOS filesystem image manually - while maintaining the jailbreak. That's actually a long and very delicate process which, if done improperly, can render your device unbootable and force you to restore to the latest, non jailbreakable version. If carried out correctly, however, you get the latest and greatest iOS - without sacrificing a jailbreak. A manual update is effectively indistinguishable from a stock update, provided you don't mess with the kernelcache or dyld, thanks to which the jailbreak remains feasible. In fact, this is important enough to merit a nice div of its own: If you ARE going to try this at home on a real device - caveat - if you accidentally update dyld or the core libs you might end up locking yourself out of the user mode component of a given jailbreak. If you touch the kernelcache you will render the apticket.der invalid - which is even worse, since iBoot will refuse to boot you - and force you to restore, when the only IPSWs Apple presently signs are no longer jailbreakable. At least, publicly. A future post will demonstrate how to do so - but it requires meticulous attention to detail. Believe me. Both the above are great, but especially the latter. In the case of 8.2, for example, that gives you the Apple Watch support, in case you absolutely must send your heartbeat to your friends (Awwww). But going forward, this will likely be useful for whatever 9.x brings - if only to reverse the hell out of it. So let's get started, but before that - the usual disclaimer: If you know me, you know I'm all for keeping iOS jailbreakable - despite Apple's herculean efforts. I wouldn't be publishing this if I thought Apple could plug this. Since an OTA is started from user mode when iOS is already running - unlike a full restore/upgrade which is carried out via iBoot - it does not require (and, in fact, can't access) the UID/GID keys. This also means that Apple can't easily encrypt the OTA updates. At least not on the present generation of devices.

OTA Update bundles OTA update bundles are zip files which Apple makes readily available for download at http://appldnld.apple.com/. The zip files are cryptically named, in what's probably a SHA-1 of their "real" name. The convention appears to be: http://appldnld.apple.com/ios#.#/system-dmg-##-YYYYMMDD-GUID/com_apple_MobileAsset_SoftwareUpdate/40-hex-digits.zip Fortunately, you can find all the files easily through the iPhoneWiki OTA page - and download them easily. You don't even have to fake a User-Agent (Have I mentioned, Cupertino Syndrome? Lock the door (encrypt the fs), but leave the Window wide open :). As you can see, each OTA update is basically keyed to two factors - the device build, and the base iOS version from which the update is taking place. This makes it easy to pick out the update that's just "right for you", given whichever device you have, and whichever version you're stuck in with the jailbreak. In our example, let's pick the version I used, which is to iOS 8.2 from iOS 8.1.2 on an iPhone 6. That's http://appldnld.apple.com/ios8.2/031-15794-20150309-8FF6FDB8-C21F-11E4-8DFB-9EA63780E501/com_apple_MobileAsset_SoftwareUpdate/58af7e9d9d6ade039f47f01bcd420887645204bb.zip. Downloading this (or any other) update, an unpacking the zip, expect to see the same directory structure, which is something like this: META-INF/ : Subdirectory containing a single file, com.apple.ZipMetadata.plist , which is a binary plist: morpheus@Zephyr(/tmp/OTA)$ cat META-INF/com.apple.ZipMetadata.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>RecordCount</key> <integer>3162</integer> <!-- Count of files in Zip --> <key>StandardDirectoryPerms</key> <integer>16877</integer> <!-- 40755 for directories --> <key>StandardFilePerms</key> <integer>-32348</integer> <!-- 644 for files --> <key>Version</key> <integer>2</integer> </dict> </plist> all in all, nothing too interesting.

: Subdirectory containing a single file, , which is a binary plist: all in all, nothing too interesting. Info.plist - As with any bundle, the required manifest. This is a standard Plist, which looks like the following: morpheus@Zephyr (/tmp/OTA)$ cat Info.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>CFBundleIdentifier</key> <string>com.apple.MobileAsset.SoftwareUpdate</string> <key>MobileAssetProperties</key> <dict> <key>ActualMinimumSystemPartition</key> <integer>2648</integer> <key>Build</key> <string>12D508</string> Build of update (12D508 = 8.2) <key>InstallationSize</key> <string>0</string> <key>MinimumSystemPartition</key> Min. partition size for upgrade <integer>2648</integer> <key>OSVersion</key> Version of update <string>8.2</string> <key>PrerequisiteBuild</key> Build of origin (12B440 = 8.1.2) <string>12B440</string> <key>PrerequisiteOSVersion</key> Version of origin <string>8.1.2</string> <key>SUDocumentationID</key> <string>PreRelease</string> <key>SUProductSystemName</key> as can be expected.. <string>iOS</string> <key>SUPublisher</key> <string>Apple Inc.</string> <key>SupportedDevices</key> <array> <string>iPhone7,2</string> Internal number of device (7,2 = 6) </array> <key>SystemPartitionPadding</key> Specified in MB <dict> <key>128</key> <integer>1280</integer> <key>16</key> <integer>260</integer> <key>32</key> <integer>320</integer> <key>64</key> <integer>640</integer> <key>8</key> <integer>180</integer> </dict> <key>__AssetDefaultGarbageCollectionBehavior</key> <string>NeverCollected</string> </dict> </dict> </plist>

- As with any bundle, the required manifest. This is a standard Plist, which looks like the following: AssetData - Where the update binaries, etc are.

- Where the update binaries, etc are. AssetData has an internal directory structure, like so: Info.plist - Once again ( *sigh* ) we have a plist here, containing similar keys to the previous one, but with the addition of a SystemUpdatePathMap dictionary, containing the names of the non-filesystem images (i.e. baseband, RAMdisk, glyphs, etc) which (from what I can see) aren't part of the update, or are present only in patch form)

- Once again ( ) we have a plist here, containing similar keys to the previous one, but with the addition of a dictionary, containing the names of the non-filesystem images (i.e. baseband, RAMdisk, glyphs, etc) which (from what I can see) aren't part of the update, or are present only in patch form) boot/ - A subdirectory containing actual non-filesystem images. These are all in the boot/Firmware/... directory, and contain the kernelcache.release. boardId , iBec/iBSS (in boot/Firmware/dfu ), and iBoot and friends (in boot/Firmware/all_flash/all_flash. boardId ap.production. . These are all in im4p (or, pre-A7, IMG3) format, which means that they're encrypted. :-( Apple can encrypt those because they're handled during the boot process, wherein the GID key is still accessible.

- A subdirectory containing actual non-filesystem images. These are all in the directory, and contain the , (in ), and iBoot and friends (in . These are all in im4p (or, pre-A7, IMG3) format, which means that they're encrypted. :-( Apple can encrypt those because they're handled during the boot process, wherein the GID key is still accessible. boot/ also contains a BuildManifest.plist , a lengthy manifest containing the update rules for the various im4ps. I'll leave this rather detailed description for the book..

also contains a , a lengthy manifest containing the update rules for the various im4ps. I'll leave this rather detailed description for the book.. payloadv2/patches : A directory containing deltae to be patched. This is, of course, assuming, that the versions you're patching from are exactly the ones which came with stock iOS. These are in BXDIFF format, specifying offsets and patch data.

: A directory containing deltae to be patched. This is, of course, assuming, that the versions you're patching from are exactly the ones which came with stock iOS. These are in BXDIFF format, specifying offsets and patch data. payloadv2/prepare_payload : A PBZX file (see later), containing an (encrypted) IM4P. Likely used by iBoot and friends

: A PBZX file (see later), containing an (encrypted) IM4P. Likely used by iBoot and friends payloadv2/removed.txt : Specifying a list of files which will be removed (potentially replaced), one per line.

: Specifying a list of files which will be removed (potentially replaced), one per line. payloadv2/links.txt : Specifying a list of symbolic links to link to (=) and add (+).

: Specifying a list of symbolic links to link to (=) and add (+). payloadv2/payload : More on that in a moment

: More on that in a moment Various BOM files The BOM files Apple uses the venerable NeXTSTEP Bill-Of-Materials (BOM) proprietary format for their bundles. The private Bom.Framework (which likely hasn't changed from NextSTEP, aside from 64-bit support..) handles these files, and you can view them on OS X using the lsbom(8) command, or package them yourself with mkbom(8) . The files are: pre.bom Bill of materials of files pre-application of payload post.bom Bill of materials of files post-application of payload payload.bom Bill of materials of files in payload An additional file, payload.bom.signature protects payload.bom from tampering, though in practice that's meaningless (binaries in BOM have to be codesigned anyway, and if you're on a jailbroken device this, too, can be bypassed). I've discussed the BOM format in the book (Chapter 6), so no sense adding more here. What's more interesting is the payload file itself. The payload and pbzx files The patches in the payloadv2 directory are only applicable to files which already exist on the system, and - by means of patch application - can be updated to a new file version. iOS updates, however, often contain new functionality (again, the Watch support in 8.2 being a great example). New functionality can be found in the payloadv2/payload directory. Inspecting the file reveals a "pbzx" magic, like so: morpheus@zephyr(/tmp/OTA/payloadv2)$ od -t x1 -t c payload | more 0000000 70 62 7a 78 00 00 00 00 01 00 00 00 00 00 00 00 p b z x \0 \0 \0 \0 001 \0 \0 \0 \0 \0 \0 \0 0000020 01 00 00 00 00 00 00 00 00 ed 37 20 fd 37 7a 58 001 \0 \0 \0 \0 \0 \0 \0 \0 355 7 <fd> 7 z X 0000040 5a 00 00 00 ff 12 d9 41 04 c0 e2 ed b4 07 80 80 Z \0 \0 \0 377 022 331 A 004 300 342 355 264 \a 200 200 ... ........ a1 86 1f 56 0d d3 56 37 03 00 00 00 00 00 59 5a 241 206 037 V \r 323 V 7 003 \0 \0 \0 \0 \0 Y Z The <FD>7zX you see in the first row, and the YZ at the end of the file are clearly indicative of the XZ compression header and trailer . That Apple adopted LZMA is not surprising, considering that it's already used in the kernelcaches (as of 10.10/8.0). Outright using XZ was a surprise. But that makes our life easier - grabbing the XZ sources from tukaani.org, you can quickly build the XZ binary which will decompress it. But just stripping the pbzx encapsulation won't work - the XZ data is separated into chunks - somewhat akin to how Apple compresses DMGs, if you saw the other article of mine. So you need a small "extractor" for these chunks, before you can get to apply XZ on them. I've just the tool for that, which you can compile yourself from this listing: #include <stdio.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> typedef unsigned long long uint64_t; int main(int argc, const char * argv[]) { // Dumps a pbzx to stdout. Can work as a filter if no argument is specified char buffer[1024]; int fd = 0; if (argc < 2) { fd = 0 ;} else { fd = open (argv[1], O_RDONLY); if (fd < 0) { perror (argv[1]); exit(5); } } read (fd, buffer, 4); if (strncmp(buffer, " pbzx ", 4)) { fprintf(stderr, " Can't find pbzx magic

"); exit(0);} // Now, if it IS a pbzx uint64_t length = 0, flags = 0; read (fd, &flags, sizeof (uint64_t)); flags = __builtin_bswap64(flags); fprintf(stderr,"Flags: 0x%llx

", flags); int i = 0; int off = 0; while (flags & 0x01000000) { // have more chunks i++; read (fd, &flags, sizeof (uint64_t)); flags = __builtin_bswap64(flags); read (fd, &length, sizeof (uint64_t)); length = __builtin_bswap64(length); fprintf(stderr," Chunk #%d (flags: %llx, length: %lld bytes)

",i, flags,length); // Let's ignore the fact I'm allocating based on user input, etc.. char *buf = malloc (length); read (fd, buf, length); // We want the XZ header/footer if it's the payload, but prepare_payload doesn't have that, // so just warn. if (strncmp(buf, "\xfd""7zXZ\0", 6)) { fprintf (stderr, " Warning: Can't find XZ header. This is likely not XZ data.

"); } else // if we have the header, we had better have a footer, too if (strncmp(buf + length -2, "YZ", 2)) { fprintf (stderr, " Warning: Can't find XZ footer. This is bad.

"); } write (1, buf, length); } return 0; } EDIT: There's a fix and better version of this now - q.v. Part III And use like so: morpheus@Zephyr (/tmp/OTA/payloadv2)$ cc pbzx.c -o pbzx morpheus@Zephyr (/tmp/OTA/payloadv2)$ ./pbzx < payload > p.xz morpheus@Zephyr (/tmp/OTA/payloadv2)$ ls -l payload p.xz -rw-r--r-- 1 root admin 443537788 Mar 18 13:47 p.xz -rw-r--r--@ 1 morpheus admin 443539064 Feb 26 07:16 payload morpheus@Zephyr (/tmp/OTA/payloadv2)$ xz --decompress p.xz morpheus@Zephyr (/tmp/OTA/payloadv2)$ ls -l p -rw-r--r-- 1 root admin 1310837211 Mar 18 13:47 p morpheus@Zeyphr (/tmp/OTA/payloadv2)$ file p p: VAX demand paged (first page unmapped) pure executable not stripped The compression is evident - and quite impressive (1.2GB compressed into 423MB - some 65% - not bad, considering this is binary data). And we now have a single archive - that p - which is the last part of the bundle we need to deal with, and is obviously not a VAX executable..

The payload

At this point, it's obvious that "p" is some proprietary format, of some archive. This is further corroborated by attempting to view it with more :

morpheus@Zeyphr (/tmp/OTA/payloadv2)$ more p ^P^A^@^@^@^@^@^@^@.^@^@^@^@T<EE><FB>^E^@^@^@^@^@ESC^@c^@c<81><80> .fseventsd/000000002633e7a7 ^_<8B>^H^@^@^@^@^@^@^C2^L <F6>q<F1>=^^<FD>Y<92>^A^H<96>=7Vc<80>^@&^@^@^@^@<FF><FF>^C^@=<EF>R<DF>^Y^@^@^@^P^A^@^@^@^@^@^@^@H^@^@^@^@T<EE><FB> ^E^@^@^@^@^@ESC^@c^@c<81><80> .fseventsd/000000002633e7a8 ^_<8B>^H^@^@^@^@^@^@^C2^L<F6>q<89>^K<8B>^R<B5>b``<D0>K+N-K <CD>+)N<D1>/<CE><D1>M<CE><CF>-H,aX<FE><DC>X<8D>^A^B^X^YV 8<8A>^@^@^@^@<FF><FF>^C^@<BA><ED>^WH:^@^@^@^P^A^@^@^@^@^@^@^@ $^@^@^@^@T<EE><FB>^E^@^@^@^@^@^Y^@c^@c<81><80> .fseventsd/fseventsd-uuid8076230C-C685-4C73-88B5-DF66AEB7E122 ^P^A^@^@^@^@^@^@ ^E^E^@^@^@^@T<EE><F4><D7>^@^@^@ ^@6^@^@^@P<81><B4> Applications/AACredentialRecoveryDialog.app/Info.plist bplist00<DF>^P^]^A^B^C^D^E^F^G^H ^K^L^M^N^O^P^Q^R^S^T^U^V^W^X^Y^ZESC^\^]^^^_ !#$%&()%*+-.012789:9<^^=>?B\CFBundleNameYDTSDKNameWDTXcodeYSBAppTagsZDTSDKBuild_^P^YCFBundleDevelopmentRegion_^P^OCFBundleVersion_^P^QUIBackgroundModes_^P^SBuildMachineOSBuild^DTPlatformName_^P^ZCFBundleShortVersionString_^P^SCFBundlePackageType_^P^ZCFBundleSupportedPlatforms_^P^]CFBundleInfoDictionaryVersion_^P^\UIRequiredDeviceCapabilities_^P^RCFBundleExecutableZDTCompiler_^P%UISupportedInterfaceOrientations~ipad_^P^RCFBundleIdentifier_^P^PMinimumOSVersion_^P^Y_UILaunchAlwaysFullScreen\DTXcodeBuild_^P^RLSRequiresIPhoneOS_^P UISupportedInterfaceOrientations_^P^SCFBundleDisplayName_^P^QDTPlatformVersion_^P^QCFBundleSignature^UIDeviceFamily_^P^ODTPlatformBuild_^P^ZAACredentialRecoveryDialog_^P^Tiphoneos8.2.internalT0610<A1>"VhiddenV12D432RenS1.0<A1>'ZcontinuousV13A603XiphoneosTAPPL<A1>,XiPhoneOSS6.0<A1>/Uarmv7_^P^ZAACredentialRecoveryDialog_^P"com.apple.compilers.llvm.clang.1_0<A4>3456_^P^^UIInterfaceOrientationPortrait_^P(UIInterfaceOrientationPortraitUpsideDown_^P#UIInterfaceOrientationLandscapeLeft_^P$UIInterfaceOrientationLandscapeRight_^P1com.apple.appleaccount.AACredentialRecoveryDialogS8.2 W6A1052c <A3>356S8.2T????<A2>@A^P^A^P^BP^@^H^@E^@R^@\^@d^@n^@y^@<95>^@<A7>^@<BB>^@<D1>^@<E0>^@<FD>^A^S^A0^AP^Ao^A<84>^A<8F>^A<B7>^A<CC>^A <DF>^A<FB>^B^H^B^]^B@^BV^Bj^B~^B<8D>^B<9F>^B<BC>^B<D3>^B<D8>^B<DA>^B<E1>^B<E8>^B<EB>^B<EF>^B<F1>^B<FC>^C^C^C^L^C^Q^C^S^C^\^C ^C"^C(^CE^Cj^Co^C<90>^C<BB>^C<E1>^D^H^D<^D@^DA^DI^DJ^DN^DR^DW^DZ^D\^D^^@^@^@^@^@^@^B^A^@^@^@^@^@^@^@C^@^@^@^@^@^@^@^@^@^@^@^@^@^@^D_^P^A ^@^@^@^@^@^@^L<D9>^@^@^@^@T<EE><F4><D7>^@^@^@ ^@H^@^@^@P<81><B4>Applications/AACredentialRecoveryDialog.app/_CodeSignature/CodeResources<?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"> ...

It would make sense if this were a cpio or (Apple's favorite) pax archive, but it's apparently neither ( pax actually uses cpio internally, and complains about name lengths being out of range when fed this). Some of my readers are reverse-engineers, so this makes for a nice (and easy) exercise in reversing. Observe the following:

File names are clearly visible (highlighted), as are their contents. Because there is no NULL byte in between the filename and the contents, it follows there must be some length specifier for the name.

Likewise, it follows there has to be a length specifier for the size of the file

Using od makes it a bit easier to figure things out - specifically, where the file contents are raw XML and not a binary plist: 0000690 00 00 00 00 00 00 00 00 00 00 04 5f 10 01 00 00 \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 004 _ 020 001 \0 \0 |-- file size--| 00006a0 00 00 00 00 0c d9 00 00 00 00 54 ee f4 d7 00 00 \0 \0 \0 \0 \f 331 \0 \0 \0 \0 T 356 364 327 \0 \0 |Nlen| |uid | |gid | |perms| |---Name starts here-- 00006b0 00 20 00 48 00 00 00 50 81 b4 41 70 70 6c 69 63 \0 \0 H \0 \0 \0 P 201 264 A p p l i c 00006c0 61 74 69 6f 6e 73 2f 41 41 43 72 65 64 65 6e 74 a t i o n s / A A C r e d e n t 00006d0 69 61 6c 52 65 63 6f 76 65 72 79 44 69 61 6c 6f i a l R e c o v e r y D i a l o 00006e0 67 2e 61 70 70 2f 5f 43 6f 64 65 53 69 67 6e 61 g . a p p / _ C o d e S i g n a 00006f0 74 75 72 65 2f 43 6f 64 65 52 65 73 6f 75 72 63 t u r e / C o d e R e s o u r c end (48) |-------------------------------File Contents ------- 0000700 65 73 3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e 3d e s < ? x m l v e r s i o n = 0000710 22 31 2e 30 22 20 65 6e 63 6f 64 69 6e 67 3d 22 " 1 . 0 " e n c o d i n g = " 0000720 55 54 46 2d 38 22 3f 3e 0a 3c 21 44 4f 43 54 59 U T F - 8 " ? >

< ! D O C T Y ... 00013c0 0a 09 3c 2f 64 69 63 74 3e 0a 3c 2f 64 69 63 74

\t < / d i c t >

< / d i c t 00013d0 3e 0a 3c 2f 70 6c 69 73 74 3e 0a 10 01 00 00 00 >

< / p l i s t >

020 001 \0 \0 \0 Contents End here-----| |-----------| --------| |--file size--| 00013e0 00 00 00 09 5a 00 00 00 00 54 ee f4 c6 00 00 00 |--namelen---| ......... |------- Name Starts here 00013f0 20 00 37 00 00 00 50 81 b4 41 70 70 6c 69 63 61 \0 7 \0 \0 \0 P 201 264 A p p l i c a 0001400 74 69 6f 6e 73 2f 41 63 63 6f 75 6e 74 41 75 74 t i o n s / A c c o u n t A u t 0001410 68 65 6e 74 69 63 61 74 69 6f 6e 44 69 61 6c 6f h e n t i c a t i o n D i a l o 0001420 67 2e 61 70 70 2f 49 6e 66 6f 2e 70 6c 69 73 74 g . a p p / I n f o . p l i s t ------- end ends here (0x37) ----------| 0001430 3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e 3d 22 31 < ? x m l v e r s i o n = " 1 ... |------------| |----| 0001d80 0a 3c 2f 70 6c 69 73 74 3e 0a 10 01 00 00 00 00

< / p l i s t >

020 001 \0 \0 \0 \0 |-- file size-| |---------------------------| |----| 0001d90 00 00 0b 3c 00 00 00 00 54 ee f4 c6 00 00 00 20 |Nlen| | uid| | gid| |perms| |--- Name starts here 0001da0 00 49 00 00 00 50 81 b4 41 70 70 6c 69 63 61 74 \0 I \0 \0 \0 P 201 264 A p p l i c a t 0001db0 69 6f 6e 73 2f 41 63 63 6f 75 6e 74 41 75 74 68 i o n s / A c c o u n t A u t h 0001dc0 65 6e 74 69 63 61 74 69 6f 6e 44 69 61 6c 6f 67 e n t i c a t i o n D i a l o g 0001dd0 2e 61 70 70 2f 5f 43 6f 64 65 53 69 67 6e 61 74 . a p p / _ C o d e S i g n a t 0001de0 75 72 65 2f 43 6f 64 65 52 65 73 6f 75 72 63 65 u r e / C o d e R e s o u r c e 0001df0 73 3c 3f 78 6d 6c 20 76 65 72 73 69 6f 6e 3d 22 s < ? x m l v e r s i o n = " 0001e00 31 2e 30 22 20 65 6e 63 6f 64 69 6e 67 3d 22 55

How does the file format fall apart so quickly? Well, in this case, it's possible to cheat, using lsbom(1) : morpheus@Zephyr(/tmp/OTA)$ lsbom ../post.bom | grep AccountAuthenticationDialog.app/_CodeSignature/CodeReso ./Applications/AccountAuthenticationDialog.app/_CodeSignature/CodeResources 100664 0/80 2876 3005659373 morpheus@Zephyr(/tmp/OTA)$ printf "%x

" 0100664 81b4

(If you're wondering why the endian-ness seems off - thank PPC)

If you just want the struct, it's something like: #pragma pack(1) struct entry { // Don't care about most of the fields here, really. // N.B : use ntohl/ntohs on the fields (or swap16/32) to // correct for legacy PPC endian-ness nobody will // ever use nor care about, but Apple still supports unsigned int usually_0x10_01_00_00; unsigned short usually_0x00_00; //_00_00; unsigned int fileSize; // But we need this .. unsigned short whatever; unsigned long long timestamp_likely; unsigned short _usually_0x20_yada_yada; unsigned short nameLen; // and this.. // And we don't need these, though I have them figured out. unsigned short uid; unsigned short gid; unsigned short perms; char name[0]; // nameLen bytes here.. // Followed by file contents }; and you can get the source of my OTA payload extractor (that is, past the pbxz and the xz) right here. Using it, it super simple: Zephyr:payloadv2 morpheus$ curl -download http://newosxbook.com/code/listings/ota.c > otaa.c % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 4185 100 4178 100 7 63171 105 --:--:-- --:--:-- --:--:-- 63303 Zephyr:payloadv2 morpheus$ cc otaa.c -o otaa Zephyr:xx morpheus$ mkdir xx; cd xx Zephyr:xx morpheus$ ../otaa -e '*' ../p Got anchor Entry @0x84: UID: 0 GID: 80 Size: 1285 (0x505) Namelen: 54 Name: Applications/AACredentialRecoveryDialog.app/Info.plist Entry @0x1471: UID: 0 GID: 80 Size: 3289 (0xcd9) Namelen: 72 Name: Applications/AACredentialRecoveryDialog.app/_CodeSignature/CodeResources Entry @0x4845: UID: 0 GID: 80 Size: 2394 (0x95a) Namelen: 55 Name: Applications/AccountAuthenticationDialog.app/Info.plist Entry @0x7342: UID: 0 GID: 80 Size: 2876 (0xb3c) Namelen: 73 Name: Applications/AccountAuthenticationDialog.app/_CodeSignature/CodeResources Entry @0x10283: UID: 0 GID: 80 Size: 1732 (0x6c4) Namelen: 35 Name: Applications/AdSheet.app/Info.plist ... Entry @0x1310821791: UID: 0 GID: 0 Size: 57 (0x39) Namelen: 42 Name: usr/share/zoneinfo.default/Pacific/Pohnpei Entry @0x1310821928: UID: 0 GID: 0 Size: 14960 (0x3a70) Namelen: 50 Name: usr/standalone/firmware/oscar/memtest-oscar2.image # Make sure the extraction makes sense Zephyr:xx morpheus$ file Applications/AACredentialRecoveryDialog.app/Info.plist Applications/AACredentialRecoveryDialog.app/Info.plist: Apple binary property list # Yep :-) # # and, the two most important files, boys and girls: Zephyr:xx morpheus$ ../otaa -v -e 'dyld' ../p Got anchor Dumping 507404290 bytes to System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64 Dumping 416062019 bytes to System/Library/Caches/com.apple.dyld/dyld_shared_cache_armv7s # Use Jtool to list, extract dylibs from, or otherwise manipulate shared cache Zephyr:xx morpheus$ jtool -v -l System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64 File is a shared cache containing 921 images mapping ?? 309MB 180000000 -> 193510000 mapping ?? 63MB 193510000 -> 197494000 mapping ?? 47MB 197494000 -> 19a480000 0: 180008000 /System/Library/AccessibilityBundles/AXSpeechImplementation.bundle/AXSpeechImplementation 1: 180010000 /System/Library/AccessibilityBundles/AccessibilitySettingsLoader.bundle/AccessibilitySettingsLoader 2: 18001c000 /System/Library/AccessibilityBundles/AccountsUI.axbundle/AccountsUI 3: 180020000 /System/Library/AccessibilityBundles/AddressBookUIFramework.axbundle/AddressBookUIFramework ...

The code is quick and dirty (I haven't removed the anchor, etc), but - it works. And it can extract all the files from the update payload (and you can patch the rest). Happy reversing :-)

And..

This is only a part of the picture, but it's the important part. I'll cover mobileassetd and friends in the 2nd ed. So that's all for now. As usual, comments, questions, flames can go to j@.... And I'm still looking for more topics for the 2nd Edition At the Website Forum. Also, check out the RSS Feed for updates, or follow @Technologeeks for more. You might want to consider the Training we offer, especially OS X/iOS Reversing.