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
- 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:
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:
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>
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>
AssetData - Where the update binaries, etc are.Info.plist - Once again (*sigh*) we have a plist here, containing similar keys to the previous one, but with the addition of aSystemUpdatePathMap 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 theboot/Firmware/... directory, and contain thekernelcache.release.boardId ,iBec/iBSS (inboot/Firmware/dfu ), and iBoot and friends (inboot/Firmware/all_flash/all_flash.boardIdap.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.boot/ also contains aBuildManifest.plist , 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.payloadv2/prepare_payload : A PBZX file (see later), containing an (encrypted) IM4P. Likely used by iBoot and friendspayloadv2/removed.txt : 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 (+).payloadv2/payload : 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 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,
The payload and pbzx files
The patches in the
New functionality can be found in the
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
#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\n"); 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\n", 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)\n",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.\n"); } 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.\n"); } write (1, buf, length); } return 0; }
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.plistbplist00<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 " ? > \n < ! D O C T Y
...
00013c0 0a 09 3c 2f 64 69 63 74 3e 0a 3c 2f 64 69 63 74
\n \t < / d i c t > \n < / d i c t
00013d0 3e 0a 3c 2f 70 6c 69 73 74 3e 0a 10 01 00 00 00
> \n < / p l i s t > \n 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
\n < / p l i s t > \n 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\n" 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
};
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.
Update: QnA on the NewOSXBook Forum as per Reddit thread.
Other articles in the series: II
III
Episode IV
Episode V
Episode VI
Episode VII
Episode VIII (the conclusion)