Guess-Talt Reversing iOS's system property provider Jonathan Levin, NewOSXBook.com, 05/23/2015

gestalt |gəˈSHtält, -ˈSHtôlt | (also Gestalt)

noun (pl. gestalten |-ˈSHtältn, -ˈSHtôltn| or gestalts)

Psychology an organized whole that is perceived as more than the sum of its parts.

About

One of the criticisms I've been getting with the 1st edition of "Mac OS X and iOS Internals" (MOXiI) is that I provided no coverage of Apple's extremely rich and versatile's frameworks and libraries - merely mentioning them in passing as part of the overview of Chapter II. The 2nd edition aims to correct that, by dissecting them - especially the private ones, so as to provide a clearer view for developers who wish to draw on their amazing offerings. (Naturally, this only applies to non-AppStore apps).

Partial headers exist for frameworks, mostly the output of automatic tools such as classdump-z (for Objective-C) and their ilk. But the header information is often lacking, and there exists no documentation as to how the frameworks work - and that's what I'm trying to provide. MOXiI 2 will have key private frameworks reversed, and explained in detail. It will, however, provide the results of the reversing, and not the process itself. That's where these writeups come in, as additional resources for the avid reader. In previous ones I had explained the process of reversing and reincarnating the 802.11 framework, and now the current victim is MobileGestalt .

MobileGestalt

The libMobileGestalt.dylib , even though technically not a framework per se, it is of utmost importance, as it serves iOS as a central repository for all of the system's properties - both static and runtime. In that, it can be conceived as the parallel of OS X's Gestalt, which is part of CoreServices (in CarbonCore ). But similarities end in name only. OS X's Gestalt is somewhat documented (in its header file), and has been effectively deprecared as of 10.8. MobileGestalt is entirely undocumented, and isn't going away any time soon.

Quite a few system daemons and apps in iOS rely on MobileGestalt , which rests in /usr/lib/libMobileGestalt.dylib . Its placement in /usr/lib makes it more of a "simple library", but its exposed APIs follow the framework conventions, and uses the MG prefix. Apple's own definition of "framework" is loose, at best (every framework is a special case of a dylib, though usually with additional coponents - data files, helper daemons, etc).

If you look for MG on the iOS filesystem, however, you won't find it - like all key frameworks and libraries, it has been prelinked into the /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm[v7|64] . Fortunately, jtool and other tools (like decache ) can extract from the shared cache - so you might want to follow along by breaking out MG from a decrypted DMG or from your device itself. I never got to complete JTool's ARM32 disassembler, instead focusing on ARM64, so I'll be demonstrating MG on an iPhone 6, like so:

Output 1: Extracting libMobileGestalt from the shared cache

root@Phontifex(/tmp)# jtool -e libMobileGestalt.dylib /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64 Extracting /usr/lib/libMobileGestalt.dylib at 0x118a4000 into dyld_shared_cache_arm64.libMobileGestalt.dylib root@Phontifex(/tmp)# ls -l dyld_shared_cache_arm64.libMobileGestalt.dylib -rw------- 1 root wheel 45629440 May 23 16:36 dyld_shared_cache_arm64.libMobileGestalt.dylib

The resulting file is big - because the shared cache merges all of its components' __LINKEDIT into one huge segment - but nonetheless works for reversing, being a legitimate and well formatted Mach-O. You can use jtool (or otool and nm) to examine its dependencies and exports: Output 2: Examining the extracted libMobileGestalt # Can also use otool -L root@Phontifex(/tmp)# jtool -L dyld_shared_cache_arm64.libMobileGestalt.dylib /System/Library/Frameworks/CoreFoundation.framework/CoreFoundation /System/Library/Frameworks/IOKit.framework/Versions/A/IOKit /usr/lib/libSystem.B.dylib # Can also use nm root@Phontifex(/tmp)# jtool -S dyld_shared_cache_arm64.libMobileGestalt.dylib | grep " T" 00000001918a8c90 T _MGCancelNotifications 00000001918a80f8 T _MGCopyAnswer 00000001918a7dbc T _MGCopyAnswerWithError 00000001918a8448 T _MGCopyMultipleAnswers 00000001918a8518 T _MGGetBoolAnswer 00000001918a87b4 T _MGGetFloat32Answer 00000001918b47e8 T _MGGetProductType 00000001918a85ec T _MGGetSInt32Answer 00000001918a876c T _MGGetSInt64Answer 00000001918a87fc T _MGIsDeviceOfType 00000001918a8848 T _MGIsDeviceOneOfType 00000001918a7df4 T _MGIsQuestionValid 00000001918a889c T _MGRegisterForBulkUpdates 00000001918a8ba0 T _MGRegisterForUpdates 00000001918a7e14 T _MGSetAnswer 00000001918a4e20 T _MGSetLogHandler 00000001918a6aa0 T __MGCacheValid 00000001918a73bc T __MGClearInProcessCache 00000001918b4bc0 T __MGCopyDeviceDescription 00000001918b514c T __MGCopyIteratedDeviceDescription 00000001918b48b4 T __MGIterateDevices 00000001918a5158 T __MGLog 00000001918b4fc4 T __MGPrintIteratedDeviceDescription 00000001918a7b90 T __MGServerCopyAnswerWithError 00000001918a7b7c T __MGSetServer 00000001918b4a74 T __MGWaitForDevices 00000001918a7064 T __MGWriteCache

As the above shows, MobileGestalt 's main dependency is on the IOKit.framework , which is what it uses to get hardware related information. Additionally, it exports to sets of symbols ( MG* and _MG* ), with the main ones allowing to retrieve "Answers" (Using Get for float, Bool, Int32/64, and Copy for CF* answers), and notifications (the MGRegisterFor[Bulk]Updates and MGCancelNotifications ).

Ask me no questions..

The "Questions" for which the "Answers" will be returned are string properties. This is well known by reversed headers, and many StackOverflow cases. To figure out what the common questions are, I've added support in JTool (v0.91+) for recognizing MGCopyAnswer , which takes a CFString . It then became a simple task to iterate over the MG users with a shell script, and let JTool retrieve the keys automatically:. Output 3: Using jtool to retrieve use cases of MobileGestalt # Iterate over daemons in /usr/libexec, disassemble, and isolate decompiled lines # (beginning with a semicolon) which show decompilation of an MG function root@Phontifex (/usr/libexec)# for d in *; do jtool -d $d 2> /dev/null | grep "^; " | grep _MG ; done > /tmp/mg.txt # You can also use this pattern over apps, though this takes a long time: root@Phontifex(/Applications)# for i in *.app; do a=`echo $i | cut -d'.' -f1`; jtool -d $i/$a | grep MG | grep "^; " ; done # Getting SpringBoard's MobileGestalt Usage root@Phontifex (/usr/libexec)# jtool -d /System/Library/CoreServices/SpringBoard.app/SpringBoard | grep MG | grep "^; " >> /tmp/mg.txt # Remove duplicates root@Phontifex (/usr/libexec)# sort -u /tmp/mg.txt # JTool sometimes fails to figure out the arguments if a value in R0 (X0) is computed with # an as yet unsupported assembly command. Fortunately, these cases are rare; I've stripped them out # and will (eventually) fix them. If you stumble upon any, please do drop me a line so I can add them. ; R0 = _MGCopyAnswer(@"5MSZn7w3nnJp22VbpqaxLQ"); ; R0 = _MGCopyAnswer(@"7mV26K/1a+wTtqiunvHMUQ"); ; R0 = _MGCopyAnswer(@"BasebandAPTimeSync"); ; R0 = _MGCopyAnswer(@"BasebandPostponementStatus"); ; R0 = _MGCopyAnswer(@"BasebandPostponementStatusBlob"); ; R0 = _MGCopyAnswer(@"BasebandSecurityInfoBlob"); ; R0 = _MGCopyAnswer(@"BasebandStatus"); ; R0 = _MGCopyAnswer(@"BuildVersion"); ; R0 = _MGCopyAnswer(@"CoreRoutineCapability"); ; R0 = _MGCopyAnswer(@"DeviceClass"); ; R0 = _MGCopyAnswer(@"DeviceClassNumber"); ; R0 = _MGCopyAnswer(@"DeviceName"); ; R0 = _MGCopyAnswer(@"DeviceSupports1080p"); ; R0 = _MGCopyAnswer(@"DeviceSupports720p"); ; R0 = _MGCopyAnswer(@"DiskUsage"); ; R0 = _MGCopyAnswer(@"GSDeviceName"); ; R0 = _MGCopyAnswer(@"HWModelStr"); ; R0 = _MGCopyAnswer(@"HasBaseband"); ; R0 = _MGCopyAnswer(@"InternalBuild"); ; R0 = _MGCopyAnswer(@"InverseDeviceID"); ; R0 = _MGCopyAnswer(@"IsSimulator"); ; R0 = _MGCopyAnswer(@"MLBSerialNumber"); ; R0 = _MGCopyAnswer(@"MaxH264PlaybackLevel"); ; R0 = _MGCopyAnswer(@"MinimumSupportediTunesVersion"); ; R0 = _MGCopyAnswer(@"PasswordConfigured"); ; R0 = _MGCopyAnswer(@"PasswordProtected"); ; R0 = _MGCopyAnswer(@"ProductType"); ; R0 = _MGCopyAnswer(@"ProductVersion"); ; R0 = _MGCopyAnswer(@"RegionCode"); ; R0 = _MGCopyAnswer(@"RegionalBehaviorNTSC"); ; R0 = _MGCopyAnswer(@"RegionalBehaviorNoPasscodeLocationTiles"); ; R0 = _MGCopyAnswer(@"ReleaseType"); ; R0 = _MGCopyAnswer(@"SIMStatus"); ; R0 = _MGCopyAnswer(@"SerialNumber"); ; R0 = _MGCopyAnswer(@"SigningFuse"); ; R0 = _MGCopyAnswer(@"SupportedDeviceFamilies"); ; R0 = _MGCopyAnswer(@"UniqueDeviceID"); ; R0 = _MGCopyAnswer(@"UniqueDeviceIDData"); ; R0 = _MGCopyAnswer(@"UserAssignedDeviceName"); ; R0 = _MGCopyAnswer(@"WLANBkgScanCache"); ; R0 = _MGCopyAnswer(@"contains-cellular-radio"); ; R0 = _MGCopyAnswer(@"device-name-localized"); ; R0 = _MGCopyAnswer(@"iTunesFamilyID"); ; R0 = _MGCopyAnswer(@"location-reminders"); ; R0 = _MGCopyAnswer(@"main-screen-class"); ; R0 = _MGCopyAnswer(@"main-screen-height"); ; R0 = _MGCopyAnswer(@"main-screen-pitch"); ; R0 = _MGCopyAnswer(@"main-screen-scale"); ; R0 = _MGCopyAnswer(@"main-screen-width"); ; R0 = _MGCopyAnswer(@"setting %@/%@ failed"); ; R0 = _MGCopyAnswerWithError(@"0dnM19zBqLw5ZPhIo4GEkg"); ; R0 = _MGCopyAnswerWithError(@"DeviceClassNumber"); ; R0 = _MGCopyAnswerWithError(@"InternalBuild"); ; R0 = _MGCopyAnswerWithError(@"ProductVersion"); ; R0 = _MGCopyAnswerWithError(@"zxMIgVSILN6S5ee6MZhf+Q"); ; R0 = _MGGetBoolAnswer(@"0dnM19zBqLw5ZPhIo4GEkg"); ; R0 = _MGGetBoolAnswer(@"8DHlxr5ECKhTSL3HmlZQGQ"); ; R0 = _MGGetBoolAnswer(@"CarrierInstallCapability"); ; R0 = _MGGetBoolAnswer(@"CellularTelephonyCapability"); ; R0 = _MGGetBoolAnswer(@"ContinuityCapability"); ; R0 = _MGGetBoolAnswer(@"DeviceSupportsTethering"); ; R0 = _MGGetBoolAnswer(@"HasSpringBoard"); ; R0 = _MGGetBoolAnswer(@"InternalBuild"); ; R0 = _MGGetBoolAnswer(@"LBJfwOEzExRxzlAnSuI7eg"); ; R0 = _MGGetBoolAnswer(@"OBqqs000I0SR+EbJ7VO8UQ"); ; R0 = _MGGetBoolAnswer(@"SBCanForceDebuggingInfo"); ; R0 = _MGGetBoolAnswer(@"apple-internal-install"); ; R0 = _MGGetBoolAnswer(@"arm64"); ; R0 = _MGGetBoolAnswer(@"assistant"); ; R0 = _MGGetBoolAnswer(@"bluetooth"); ; R0 = _MGGetBoolAnswer(@"cZflGJ39lJHTCPy35/N14Q"); ; R0 = _MGGetBoolAnswer(@"cameraRestriction"); ; R0 = _MGGetBoolAnswer(@"cellular-data"); ; R0 = _MGGetBoolAnswer(@"contains-cellular-radio"); ; R0 = _MGGetBoolAnswer(@"data-plan"); ; R0 = _MGGetBoolAnswer(@"delay-sleep-for-headset-click"); ; R0 = _MGGetBoolAnswer(@"displayport"); ; R0 = _MGGetBoolAnswer(@"euampscYbKXqj/bSaHD0QA"); ; R0 = _MGGetBoolAnswer(@"gas-gauge-battery"); ; R0 = _MGGetBoolAnswer(@"green-tea"); ; R0 = _MGGetBoolAnswer(@"hide-non-default-apps"); ; R0 = _MGGetBoolAnswer(@"ipad"); ; R0 = _MGGetBoolAnswer(@"multitasking-gestures"); ; R0 = _MGGetBoolAnswer(@"opengles-2"); ; R0 = _MGGetBoolAnswer(@"ringer-switch"); ; R0 = _MGGetBoolAnswer(@"sim"); ; R0 = _MGGetBoolAnswer(@"sms"); ; R0 = _MGGetBoolAnswer(@"still-camera"); ; R0 = _MGGetBoolAnswer(@"venice"); ; R0 = _MGGetBoolAnswer(@"voice-control"); ; R0 = _MGGetBoolAnswer(@"wapi"); ; R0 = _MGGetBoolAnswer(@"wi-fi"); ; R0 = _MGGetBoolAnswer(@"zxMIgVSILN6S5ee6MZhf+Q"); ; R0 = _MGGetSInt32Answer(@"DeviceClassNumber"); ; R0 = _MGGetSInt32Answer(@"DeviceColorMapPolicy");

As the above shows, (and for the moment, ignore the weird-looking base64 encodings), MobileGestalt provides a plethora of information - around 200 or so questions - on various aspects of the system. And those 200 make just about half of the possible keys - there's many more still, including useful ones (e.g. AirplaneMode , MobileEquipmentIdentifier , etc), which can be reversed as well. I've cobbled together a wonderful list of about 400(!) thus far, which these narrow margins cannot contain - But MOXiI 2 will.

Arguably, you can get the information in other ways, yes - I/O kit provides the lion's share of the above. But it's much easier to use a centralized mechanism, rather than walk the I/O Registry. Further, some of these values are from user mode programs (e.g. PasswordProtected - wicked useful - it changes depending on whether the lock screen is enabled or not).

Using MobileGestalt

Using MG is easy, as the following code shows:

Listing 1: The code for a simple gestalt tool

#include <CoreFoundation/CoreFoundation.h> #include <dlfcn.h> char *_CFNumberToString (CFNumberRef Num); // I loathe CoreFoundation CFTypeRef (*MGCopyAnswer_func)(CFStringRef question); // Simple wrapper to get CF* type answers from MG, hiding the underlying implementation // You really don't have to use this - you can extern define MGCopyAnswer(...) or // MGCopyAnswerWithError, and link with -lMobileGestalt // // I should note that A) Apple won't allow this in an App store App // and B) A lot of answers are now sandbox enforced (e.g. UDID) // const char *gestaltAnswer (char *Question) { if (!MGCopyAnswer_func) { void *gestaltLib = dlopen ("/usr/lib/libMobileGestalt.dylib", RTLD_LAZY); if (!gestaltLib) { /* error */ return NULL;} // Found library, now find symbol MGCopyAnswer_func = dlsym (gestaltLib, "MGCopyAnswer"); if (!MGCopyAnswer_func) { /* error */ return NULL;} } // end if !MGCopy.. // Still here, so gestalt is at our disposal! int error = 0; CFStringRef q = CFStringCreateWithCString(NULL, Question, kCFStringEncodingASCII); // if (getenv("DEBUG")) { __asm ("BRK 0");} // Can also call CopyAnswerwithError.. // CFTypeRef answer = MGCopyAnswer_func(q, &error,0); CFTypeRef answer = MGCopyAnswer_func(q); if (!answer) { fprintf(stderr, "Key %s not found - Error %d", Question, error); return (""); /* error */ } CFTypeID typeId = CFGetTypeID (answer); const char *out = NULL; if (typeId == CFStringGetTypeID()) { out = CFStringGetCStringPtr(answer, kCFStringEncodingASCII); } if (typeId == CFBooleanGetTypeID()) { out = (CFBooleanGetValue(answer) ? "true" : "false"); } if (typeId == CFNumberGetTypeID()) { uint32_t num; char *returned = malloc(128); out = _CFNumberToString (answer); } // if TypeId == CFArrayGetTypeID()) //{ // ... //} if (typeId == CFDictionaryGetTypeID()) { CFDataRef xml = CFPropertyListCreateXMLData(kCFAllocatorDefault, (CFPropertyListRef)answer); if (xml) { out = CFDataGetBytePtr(xml); } // should really CFRelease(xml); } if (typeId == CFDataGetTypeID()) { CFIndex len = CFDataGetLength(answer); const UInt8 *data = CFDataGetBytePtr(answer); // We want to dump this to hex: char *returned = (char *) calloc (len * 3,1); int i = 0; for (i = 0 ; i < len; i++) { sprintf(returned + strlen(returned), "%02X ", data[i]); } return (returned); } return (out); } int main(int argc, char **argv) { printf ("%s: %s

", argv[1],gestaltAnswer(argv[1])); return (0); }

If you prefer to define the functions directly, rather than using dlopen(3)/dlsym(3) , you can compile the code with something like gcc-iphone mg.c -o mg -L/${PATH_TO_YOUR_IPHONE_DEVELOPER_LIBS}/SDKs/iPhoneOS8.3.sdk/usr/lib -lMobileGestalt . And, no, you don't need to cut/paste - the code + binary is provided at the end of the article.

By trying the code on the list of keys above, you'll see it works in most cases. In a few, it can fail - as if the keys don't exist. The failures are for the more "sensitive" or "Protected" keys. For this, MobileGestalt makes use of an entitlement - Output 4: Dumping the entitlements for SpringBoard root@Phontifex(/System/Library/CoreServices/SpringBoard.app)# jtool --ent SpringBoard | grep -A 3 Gest <key>com.apple.private.MobileGestalt.AllowedProtectedKeys</key> <array> <string>InverseDeviceID</string> </array>

On my jailbroken devices (8.1.2) nearly all keys are recoverable, even sans entitlement, but Apple is cracking down on this in 8.3, and starting to enforce the entitlement, which is a fine-grained array with entries for the unique IDs (e.g. MLBSerialNumber, BluetoothAddress, UniqueChipID, MobileEquipmentIdentifier, etc, and everything which can contribute to UDID). I haven't had a chance to disassemble 8.3 or 8.4b's libs (yet), but if I were Apple I'd move everything out of process to XPC - which is required for proper enforcement of entitlements, and isn't hard, since it's already used in some cases (see next).

Help

MobileGestalt dynamically loads libMobileGestaltExtensions.dylib , which in turn calls on several private frameworks ( IOMobileFrameBuffer , ManagedConfiguration , MobileKeyBag , MobileWiFi , StoreServices , SoftwareBehaviorServices and Sharing ). That's a bit more complicated, though, so I'll leave it for the book.

In some cases, MobileGestalt calls out to its helper daemon - /usr/libexec/MobileGestaltHelper via - you guessed it - XPC. A simple trick to tell you when the Helper is involved is to kill -STOP it, and see if your gestalt question hangs. The Helper is a tiny daemon (~500 lines of assembly), which uses MobileGestalt itself as well (It calls MGSetServer , then sets up an XPC connection handler and answers connections by MGServerCopyAnswerWithError over [NSXPCConnection currentConnection] ). Its plist (associating it with the Mach service) is defined thus: Listing 2 The property list for launching MobileGestaltHelper <?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>EnableTransactions</key> <true/> <key>Label</key> <string>com.apple.mobilegestalt.xpc</string> <key>MachServices</key> <dict> <key>com.apple.mobilegestalt.xpc</key> <true/> </dict> <key>POSIXSpawnType</key> <string>Adaptive</string> <key>Program</key> <string>/usr/libexec/MobileGestaltHelper</string> <key>UserName</key> <string>mobile</string> </dict> </plist>

From the looks of things, the daemon isn't enforcing any entitlement (that is it doesn't directly call csops or SecTask... APIs, though with all the dlopen() ing performed by the library, I've yet to verify that its extensions don't). The daemon itself, however, is running as uid mobile , and declares entitlements for itself as follows:

Output 5: Dumping the entitlements for MobileGestaltHelper

root@Phontifex (/tmp)# jtool --ent /usr/libexec/MobileGestaltHelper Entitlements: <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>com.apple.CommCenter.fine-grained</key> <array> <string>spi</string> <string>identity</string> </array> <key>com.apple.private.lockdown.finegrained-get</key> <array> <string>NULL/ActivationRegulatoryVariant</string> </array> <key>com.apple.wifi.manager-access</key> <true/> </dict> </plist>

The entitlements are required in cases where the Helper itself needs IPC, to communicate with the CoreTelephony daemon ( CommCenter ), lockdownd , or Wifid ). For example, BasebandPostponementStatusBlob . Why the extra protection? Because that particular key has tons of "good stuff", e.g: Listing 3: The contents of the BasebandPostponementStatusBlue (requires CommCenter.fine-grained entitlement) <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>BasebandActivationTicketVersion</key> <string>V2</key> <key>BasebandChipID</key> _chip_id <key>BasebandMasterKeyHash</key> <string>_long_long_hash_</key> <key>BasebandSerialNumber</key> <data> AggsDA== </data> <key>IntegratedCircuitCardIdentity</key> <string>8901260122549983771</key> <key>InternationalMobileEquipmentIdentity</key> <string>your_device_IMEI</key> <key>InternationalMobileSubscriberIdentity</key> <string>your_imsi</key> <key>MobileEquipmentIdentifier</key> <string>_your_MEI</key> <key>SIMGID1</key> <data> FA== </data> <key>kCTPostponementInfoPRIVersion</key> <string>0.1.185</key> <key>kCTPostponementInfoPRLName</key> 0 <key>kCTPostponementInfoUniqueID</key> <string>_same_as_MEI_</key> <key>kCTPostponementStatus</key> <string>kCTPostponementStatusActivated</key> </dict> </plist>

This leaves just one quandary - what are those weird looking keys in the key dump from jtool, above?

(A rather pathetic) Obfuscation

While most keys are self explanatory, looking at the list above, you'll no doubt see some weird garbage-y looking ones as well. Those aren't a jtool bug :) - Apple uses obfuscation for some keys. The character set chosen ([A-Za-z0-9+/]) reeks of Base64. To uncover the obfuscation, though, we have to disassemble MGCopyAnswer*: Disass 1: Dumping MGCopyAnswer _MGCopyAnswerWithError: 1918a7dbc STP X20, X19, [X31,#-32]! 1918a7dc0 STP X29, X30, [X31,#16] 1918a7dc4 ADD x29, x31, #0x10 ; ..R29 = R31 (0x25) + 0x10 = 0x2f 1918a7dc8 MOV X19, X2 1918a7dcc MOVZ X1, #0 ; ->R1 = 0x0 1918a7dd0 BL _func_1918a8100 ; 0x1918a8100 1918a7dd4 MOV X20, X0 1918a7dd8 CBZ X19, 1918a7de4 1918a7ddc BL _func_1918a8ce8 ; 0x1918a8ce8 1918a7de0 STR X0, [ X19, #0] ; *((254) + 0x0) = ??? 1918a7de4 MOV X0, X20 1918a7de8 LDP X29, X30, [X31,#16] 1918a7dec LDP X20, X19, [X31],#32 1918a7df0 RET _MGCopyAnswer: 1918a80f8 MOVZ X1, #0 ; ->R1 = 0x0 1918a80fc B _func_1918a8100 ; 0x1918a8100 ...

Both functions call on 0x1918a8100, which is the unnamed "CopyAnswer". To spare you the boring ASM, I'll focus directly on the relevant portion, which is obfuscating the key, like so (similar code in 1918a672c):

Disass 2: The obfuscation, in plain assembly

1918a7ad8 ADR x8, 68847 ; ->R8 = 0x1918b87c7 "MGCopyAnswer" 1918a7adc NOP 1918a7ae0 ADR x4, 67318 ; ->R4 = 0x1918b81d6 "%s%s" 1918a7ae4 NOP 1918a7ae8 STP X8, X20, [X31,#0] 1918a7aec MOVN X3, #0 ; ->R3 = 0xffffffffffffffff 1918a7af0 MOV X0, X21 1918a7af4 MOVZ W2, #0 ; ->R2 = 0x0 1918a7af8 BL ___snprintf_chk ; 0x1918b72b0 1918a7afc ADD x31, x31, #0x10 ; ..R31 = R31 (0x15) + 0x10 = 0x25 1918a7b00 MOV X1, X0 1918a7b04 SUB X20, X29, #56 1918a7b08 MOV X0, X21 1918a7b0c MOV X2, X20 1918a7b10 BL _CC_MD5 ; 0x1918b722c .. .. 1918a7b34 BL _CNEncode ; 0x1918b7244

In other words, we have:

Listing 4: The obfuscation, in pseudocode

snprintf(buf, "%s%s", "MGCopyAnswer", Question); _CC_MD5 (buf, strlen(buf), &hash); _CNEncode(hash); // Encode to base64

The obfuscated blobs are, therefore, an MD5 of "MGCopyAnswer" concatenated with the Question. Think keyed hash, with plaintext key :). The Resulting base64 will always have a "==" - because base64 encodes 6-bits per character, and 128 leaves a remainder of 2 from 126 - so these are removed, and we end up with that blob. MD5 is irreversible, but it's simple to create a small program which "brute forces" keys, and obtain a mapping. In fact, the code from Listing 1 can be modified to perform the obfuscation quite easily, which will give you:

Output 6: guesstalt in action (Continued from Output 3)

# Get just the keys (inside the strings) root@Phontifex (/tmp)# sort -u /tmp/mg.txt |cut -d'"' -f2 > /tmp/mg.keys # Submit to guesstalt root@Phontifex (/tmp)# for key in `cat /tmp/mg.keys` ; do guesstalt $key; done 5MSZn7w3nnJp22VbpqaxLQ (5MSZn7w3nnJp22VbpqaxLQ) : true 7mV26K/1a+wTtqiunvHMUQ (7mV26K/1a+wTtqiunvHMUQ) : true BasebandAPTimeSync (HXTqT3UXOKuTEklxz+wMAA) : 00 00 00 17 4F 49 50 47 42 00 00 00 00 01 00 00 BasebandPostponementStatus (vaiFeAcMTIDXMSxTr8JwCw) : Not found (or permission denied) BasebandPostponementStatusBlob (YUobJKXH3+ukrUe13TXL3Q) : Not found (or permission denied) BasebandSecurityInfoBlob (EImfMz+bzJrUkVQKyY6tEg) : <?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>CertID</key> <integer>3840149528</integer> <key>ChipID</key> <integer>8343777</integer> <key>ChipSerialNo</key> <data> AqvsGA== </data> <key>FusingStatus</key> <integer>3</integer> <key>PkHash</key> <data> 5OQIGNymupBn16zMKPujMp3562XDnNFkULy+gshbERM= </data> </dict> </plist> BasebandStatus (CN64p1hw1JVdTHCfBdgPLQ) : BBNotAnswering BuildVersion (mZfUC7qo4pURNhyMHZ62RQ) : 12B440 CoreRoutineCapability (g7vU4YF+9Z+wkSvw/Cm8Dg) : true DeviceClass (+3Uf0Pm5F8Xy7Onyvko0vA) : iPhone DeviceClassNumber (mtrAoWJ3gsq+I90ZnQ0vQw) : 1 DeviceName (rkqlwPcRHwixY4gapPjanw) : iPhone DeviceSupports1080p (Mk4ZslaChmO+6s3h7L1w6Q) : true DeviceSupports720p (lwHRTZNO5Jq87pVlzdNGIA) : true DiskUsage (uyejyEdaxNWSRQQwHmXz1A) : <?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>AmountDataAvailable</key> <integer>20834512896</integer> <key>AmountDataReserved</key> <integer>209715200</integer> <key>TotalDataAvailable</key> <integer>21044228096</integer> <key>TotalDataCapacity</key> <integer>60166393856</integer> <key>TotalDiskCapacity</key> <integer>63407435776</integer> <key>TotalSystemAvailable</key> <integer>550039552</integer> <key>TotalSystemCapacity</key> <integer>3241041920</integer> </dict> </plist> GSDeviceName (9s45ldrCC1WF+7b6C4H2BA) : iPhone HWModelStr (/YYygAofPDbhrwToVsXdeA) : N61AP HasBaseband (AJFQheZDyUbvI6RmBMT9Cg) : true InternalBuild (LBJfwOEzExRxzlAnSuI7eg) : false InverseDeviceID (frZQaeyWLUvLjeuEK43hmg) : b723111b3b53f7caae1a60a923e7c0da675b9261 IsSimulator (ulMliLomP737aAOJ/w/evA) : false MLBSerialNumber (Q1Ty5w8gxMWHx3p4lQ1fhA) : C7A43C30BGJG16Q24 MaxH264PlaybackLevel (4W7X4OWHjri5PGaAGsCWxw) : 42 MinimumSupportediTunesVersion (96GRvvjuBKkU4HzNsYcHPA) : 11.4.0 PasswordConfigured (xsaMbRQ5rQ+eyKMKG+ZSSg) : true PasswordProtected (yNesiJuidlesNpI/K5Ri4A) : true ProductType (h9jDsbgj7xIVeIQ8S3/X3Q) : iPhone7,2 ProductVersion (qNNddlUK+B/YlooNoymwgA) : 8.1.2 RegionCode (h63QSdBCiT/z0WU6rdQv6Q) : LL RegionalBehaviorNTSC (IFBSPGnQVFrGFW+ujtZu6Q) : true RegionalBehaviorNoPasscodeLocationTiles (0R2aiV2nJVu/v8I7Ex2GcQ) : false ReleaseType (9UCjT7Qfi4xLVvPAKIzTCQ) : Not found SIMStatus (yUCaqT4KOwJpYEb+XDPq7g) : kCTSIMSupportSIMStatusReady SerialNumber (VasUgeSzVyHdB27g2XpN0g) : E7ADAEAMA5AA SigningFuse (a5BRUxn1QBPXkAnbAHbmeg) : true SupportedDeviceFamilies (9MZ5AdH43csAUajl/dU+IQ) : Array ([0]: 1) UniqueDeviceID (re6Zb+zwFKJNlkQTUeT+/w) : 88efa213792410c80c124100980714af29e3325b # Yep, Apple, c'est moi UniqueDeviceIDData (nFRqKto/RuQAV1P+0/qkBA) : 88 EF A2 13 79 24 10 C8 0C 12 24 10 98 07 14 AF 29 E3 32 5B UserAssignedDeviceName (UserAssignedDeviceName) : Phontifex WLANBkgScanCache (PLQ6xgfGji63NbFu+sjeYg) : 1 contains-cellular-radio (yRZv0s7Dpj8ZBk0S+0+nMA) : true device-name-localized (+VIu65zA5EW4ztayJXvOUg) : iPhone iTunesFamilyID (1qJmMHedWOh43VwRKPdDrw) : -1 location-reminders (BOPZue5C0v42pU9iJFYE3A) : true main-screen-class (fdh+s6j3VijuyrK7xLjd7g) : 8 main-screen-height (OjzOua0LkOegX7pQdgMksw) : 1334 main-screen-pitch (0l4wqBtWEAK1tOkeBHkU6Q) : 326 main-screen-scale (SNfDJgQFV2Xj7+WnozcJPw) : ? main-screen-width (g7YQ1Djxh4YiKlEeaoGhzg) : 750 0dnM19zBqLw5ZPhIo4GEkg (0dnM19zBqLw5ZPhIo4GEkg) : true DeviceClassNumber (mtrAoWJ3gsq+I90ZnQ0vQw) : 1 InternalBuild (LBJfwOEzExRxzlAnSuI7eg) : false ProductVersion (qNNddlUK+B/YlooNoymwgA) : 8.1.2 zxMIgVSILN6S5ee6MZhf+Q (zxMIgVSILN6S5ee6MZhf+Q) : true 0dnM19zBqLw5ZPhIo4GEkg (0dnM19zBqLw5ZPhIo4GEkg) : true 8DHlxr5ECKhTSL3HmlZQGQ (8DHlxr5ECKhTSL3HmlZQGQ) : false CarrierInstallCapability (9n2qz3uDC5nSe1xZG1/Bkw) : false CellularTelephonyCapability (ebyBs0j3KAquBsgcfrNZIg) : true ContinuityCapability (y0jtYciPmcx3ywPM582WZw) : true DeviceSupportsTethering (xSh3mf5+Zuoz6xhxEah0zQ) : true HasSpringBoard (OBqqs000I0SR+EbJ7VO8UQ) : true InternalBuild (LBJfwOEzExRxzlAnSuI7eg) : false LBJfwOEzExRxzlAnSuI7eg (LBJfwOEzExRxzlAnSuI7eg) : false OBqqs000I0SR+EbJ7VO8UQ (OBqqs000I0SR+EbJ7VO8UQ) : true SBCanForceDebuggingInfo (gPoIZFd4NhmSKrk67qH80w) : false apple-internal-install (apple-internal-install) : false arm64 (kKgJsWN/rBUAkimOtm/wbA) : true assistant (xOJfWykLmQCc8lKlzMlrLA) : true bluetooth (XSLlJd/8sMyXO0qtvvUTBQ) : true cZflGJ39lJHTCPy35/N14Q (cZflGJ39lJHTCPy35/N14Q) : false cameraRestriction (2pxKjejpRGpWvUE+3yp5mQ) : false cellular-data (L5al7b+7JATD/izSJeH0aQ) : true contains-cellular-radio (yRZv0s7Dpj8ZBk0S+0+nMA) : true data-plan (KGlZoljMyZQSxfhROj0IFg) : false delay-sleep-for-headset-click (Mh+drGtyBfLYKN02sROzxg) : false displayport (vl45ziHlkqzh1Yt6+M9vBA) : true euampscYbKXqj/bSaHD0QA (euampscYbKXqj/bSaHD0QA) : true gas-gauge-battery (FOs+LbLUs+TajsEE4xkbrw) : true green-tea (iyfxmLogGVIaH7aEgqwcIA) : false hide-non-default-apps (cHla4KIe1wv0OvpRVrzy/w) : false ipad (uKc7FPnEO++lVhHWHFlGbQ) : false multitasking-gestures (UFqkf9tcH1ltsOMzpdwSUw) : false opengles-2 (ce5pjDJVSOxjcg1HwmAezA) : true ringer-switch (hx2qJfJRLZ9Sseb37IcQow) : true sim (PUMArrha4PFeOqINeQRM3A) : true sms (OPzhvROZUqCZhgYMyve5BA) : true still-camera (nv4RoLkNoPT0/rsO8Yaiew) : true venice (5MSZn7w3nnJp22VbpqaxLQ) : true voice-control (tuwdHA2NDGnLajCo5K3UUA) : true wapi (hiHut/WR+B9Lx/vd0WyeNg) : false wi-fi (P6z8eNrRPcv0AcKPML0iow) : true zxMIgVSILN6S5ee6MZhf+Q (zxMIgVSILN6S5ee6MZhf+Q) : true DeviceClassNumber (mtrAoWJ3gsq+I90ZnQ0vQw) : 1 DeviceColorMapPolicy (87sSAh2rboMI2TDvFBimkg) : 1

The obfuscated values are also used internally by MG - dumping its __TEXT.__cstring will show you plenty of them:

Output 7: Using jtool to isolate obfuscated keys, and a false positive or two..

# Get strings, isolating only those of length 22 - meaning the line (with the address) is 35: root@Phontifex (/System/Library/CoreServices/SpringBoard.app)# jtool -d __TEXT.__cstring /tmp/dyld_shared_cache_arm64.libMobileGestalt.dylib | grep "^.\{35\}$" | more 0x1918b80eb: h63QSdBCiT/z0WU6rdQv6Q # RegionCode 0x1918b81db: 0Y4fmR6ZHZPxDZFfPtBnRQ 0x1918b82d0: Could not fstat %s: %s # A few false positives when strlen is 22.. 0x1918b83a2: mZfUC7qo4pURNhyMHZ62RQ # BuildVersion 0x1918b86c4: DISABLE_GESTALT_DLOPEN # Tsk Tsk.. .. 0x1918b8dcd: /YYygAofPDbhrwToVsXdeA # HWModelStr .. ..

It follows, then, that MobileGestalt will allow queries on either form of keys - clear or obfuscated.You can use the tool included to corroborate this. Apple likely has some keys used mainly in obfuscated form, as they have their definitions in kMG* constants.

Caching

While quite a few of the Gestalt keys are transient, the most useful ones end up being quite static. MobileGestalt maintains a cache of values in /var/mobile/Library/Caches/com.apple.MobileGestalt.plist . The keys there are obfuscated, but we know how to get past that already:

Output 8: The MobileGestalt cache from an iPad Air 2

root@Pademonium (/tmp)# cat com.apple.MobileGestalt.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> CacheData </key> --> <data> AAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAA ... </data> <key> CacheExtra </key> <dict> <key> +3Uf0Pm5F8Xy7Onyvko0vA </key> --> <string> iPad </string> <key> /YYygAofPDbhrwToVsXdeA </key> --> <string> J81AP </string> <key> 1Rm/mWYEI5ttaC0dJ3sHBQ </key> --> <data> nCa5ppL/jM4= </data> <key> 5pYKlGnYYBzGvAlIU8RjEQ </key> --> <string> t7001 </string> <key> 96GRvvjuBKkU4HzNsYcHPA </key> --> <string> 11.4.0 </string> <key> 97JDvERpVwO+GHtthIh7hA </key> --> <string> A1566 </string> <key> 9MZ5AdH43csAUajl/dU+IQ </key> --> <array> <integer> 1 </integer> <integer> 2 </integer> </array> <key> 9s45ldrCC1WF+7b6C4H2BA </key> --> <string> iPad </string> <key> D0cJ8r7U5zve6uA6QbOiLA </key> --> <string> MH1J2 </string> <key> DViRIxZ/ZwO007CLcEYvZw </key> --> <string> </string> <key> IMLaTlxS7ITtwfbRfPYWuA </key> --> <string> A </string> <key> JhEU414EIaDvAz8ki5DSqw </key> --> <string> #e1ccb5 </string> <key> LeSRsiLoJCMhjn6nd6GWbQ </key> --> <string> iBoot-2261.3.32 </string> <key> NaA/zJV7myg2w4YNmSe4yQ </key> --> <string> 4350 </string> <key> TZ/0j62wM3D0CuRt+Nc/Lw </key> --> <data> QNZhWKnaLK5Cky+3c3qlqCmpTZ4= </data> <key> Z/dqyWS6OZTRy10UcmUAhw </key> --> <string> iPad Air 2 </string> <key> c7fCSBIbX1mFaRoKT5zTIw </key> --> <string> Murata </string> <key> h63QSdBCiT/z0WU6rdQv6Q </key> --> <string> LL </string> <key> h9jDsbgj7xIVeIQ8S3/X3Q </key> --> <string> iPad5,3 </string> <key> ivIu8YTDnBSrYv/SN4G8Ag </key> --> <string> iPhone OS </string> <key> k7QIBwZJJOVw+Sej/8h8VA </key> --> <string> arm64 </string> <key> mZfUC7qo4pURNhyMHZ62RQ </key> --> <string> 12B410 </string> <key> mumHZHMLEfAuTkkd28fHlQ </key> --> <string> #e1e4e3 </string> <key> oBbtJ8x+s1q0OkaiocPuog </key> --> <data> AAYAAAAIAAAIAQAAAAAAQAAAAAAEAAAA </data> <key> qNNddlUK+B/YlooNoymwgA </key> --> <string> 8.1 </string> <key> rkqlwPcRHwixY4gapPjanw </key> --> <string> iPad </string> <key> xUHcyT2/HE8oi/4LaOI+Sw </key> --> <string> </string> <key> zHeENZu+wbg7PUprwNwBWg </key> --> <string> LL/A </string> </dict> <key> CacheUUID </key> <string> 2357632A-5C7E-45CE-8b9A-DCEA243B28FF </string> <key> CacheVersion </key> <string> 12B410 </string> </dict>

What's still missing (my @TODO for MOXiI 2)

A list of all 400 or so keys

Explanation of GestaltExtensions, w/Example

Registering for notifications

IPC flow example with the Helper

(minor) 8.3-4 and 9.0 diffs

Final notes (and the usual book/training plug)

The code from the listing above - which I've affectionally dubbed guesstalt (as I used it to brute force values) is available for download from the website, along with other enhancements, like mapping the keys to their obfuscated form. I've also provided the entitlements required to get past protected keys, as well as CoreTelephony, Wifi, etc (in cases where MG queries these daemons directly, by dlopen()ing their respective frameworks in your process address space). There is a veritable treasure trove here. I haven't taken to documenting the above keys or other 200 or so (I'll eventually add the list in a txt file that guesstalt will be able to parse), but this should prove to be a great vantage points for iOS coders who want to get this information into their apps or tweaks. As usual, the code is free for you to use and abuse, though credit (where due), tweet, feedback or a forum post would be appreciated.

This makes guesstalt the first "official" tool (of several planned) to accompany MOXiI 2. The book itself (Volume I, User Mode) will be out around late October - once Apple releases iOS 9 and 10.11 (or 11?). This way I can ensure that the book is up-to-date with the latest and greatest, and hopefully make it relevant for the foreseeable future (unlike MOXiI 1, which my then-publisher didn't let me cover 10.8/iOS 6 in..). You can view Volume I's Table of Contents here, and see the ongoing list of requests by readers (and add your own!) on on the book's forum. In particular, I'm taking requests for other frameworks and daemons to dissect.

If you can't wait for the book - or want a deeper understanding of reversing in the context of OS X/iOS - Technologeeks' Reverse Engineering on OS X and iOS training is scheduled for March 14th, in DC. Yours truly will be there to deliver the training - and I'd love it if we had a packed classroom! Registration is open - email i/n/f/o@TG for that! You're also welcome to Follow @Technologeeks for more updates about their courses and my Android/OSX/iOS work, or check out the RSS feed if you just want book updates.

Lastly, you're always welcome to comment or ask questions on the Book's forum (People discuss these articles in Reddit and other fora - which kind of defeats the purpose when they could ask the author directly..- and that's exactly what the book forum is for).