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@Phontifiex(/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\n", 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> <!--; cache metadata as bitmap !-->
        <data>
        AAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAQAA
...
       </data>
        <key>CacheExtra</key>
        <dict>
                <key>+3Uf0Pm5F8Xy7Onyvko0vA</key> <!—- DeviceClass !-->
                <string>iPad</string>
                <key>/YYygAofPDbhrwToVsXdeA</key> <!-- HWModelStr -->
                <string>J81AP</string>
                <key>1Rm/mWYEI5ttaC0dJ3sHBQ</key> -->
                <data>
                nCa5ppL/jM4=
                </data>
                <key>5pYKlGnYYBzGvAlIU8RjEQ</key> <!--; HardwarePlatform -->
                <string>t7001</string>   
                <key>96GRvvjuBKkU4HzNsYcHPA</key> <!--; MinimumSupportediTunesVersion -->
                <string>11.4.0</string>
                <key>97JDvERpVwO+GHtthIh7hA</key> <!--; RegulatoryModelNumber -->
                <string>A1566</string>
                <key>9MZ5AdH43csAUajl/dU+IQ</key> <!--; SupportedDeviceFamilies -->
                <array>
                        <integer>1</integer>
                        <integer>2</integer>
                </array>
                <key>9s45ldrCC1WF+7b6C4H2BA</key> <!--; GSDeviceName !-->
                <string>iPad</string>
                <key>D0cJ8r7U5zve6uA6QbOiLA</key> <!--; ModelNumber -->
                <string>MH1J2</string>
                <key>DViRIxZ/ZwO007CLcEYvZw</key> <!--; SoftwareBundleVersion !-->
                <string></string>
                <key>IMLaTlxS7ITtwfbRfPYWuA</key> <!--; DeviceVariantGuess !-->
                <string>A</string>
                <key>JhEU414EIaDvAz8ki5DSqw</key> <!--; DeviceEnclosureColor !-->
                <string>#e1ccb5</string>
                <key>LeSRsiLoJCMhjn6nd6GWbQ</key> <!--; FirmwareVersion !-->
                <string>iBoot-2261.3.32</string>
                <key>NaA/zJV7myg2w4YNmSe4yQ</key>  <!-- WifiChipset !-->
               <string>4350</string>
                <key>TZ/0j62wM3D0CuRt+Nc/Lw</key>  <!—- ProductHash !-->
                <data>
                QNZhWKnaLK5Cky+3c3qlqCmpTZ4=
                </data>
                <key>Z/dqyWS6OZTRy10UcmUAhw</key> <!--; MarketingNameString !-->
                <string>iPad Air 2</string> 
                <key>c7fCSBIbX1mFaRoKT5zTIw</key> <!--; WifiVendor !-->
                <string>Murata</string>  
                <key>h63QSdBCiT/z0WU6rdQv6Q</key>  <!--; RegionCode !-->
                <string>LL</string>
                <key>h9jDsbgj7xIVeIQ8S3/X3Q</key>  <!--; ProductType !-->
                <string>iPad5,3</string>
                <key>ivIu8YTDnBSrYv/SN4G8Ag</key>  <!--; ProductName !-->
                <string>iPhone OS</string> 
                <key>k7QIBwZJJOVw+Sej/8h8VA</key>  <!--; CPUArchitecture -->
                <string>arm64</string>
                <key>mZfUC7qo4pURNhyMHZ62RQ</key>   <!--; BuildVersion -->
                <string>12B410</string>
                <key>mumHZHMLEfAuTkkd28fHlQ</key>  <!--; DeviceColor -->
                <string>#e1e4e3</string>
                <key>oBbtJ8x+s1q0OkaiocPuog</key>  <!--; MainScreenStaticInfo -->
                <data>
                AAYAAAAIAAAIAQAAAAAAQAAAAAAEAAAA
                </data>
                <key>qNNddlUK+B/YlooNoymwgA</key> <!-- ProductVersion -->
                <string>8.1</string>
                <key>rkqlwPcRHwixY4gapPjanw</key> <!—- DeviceName !-->
                <string>iPad</string>
                <key>xUHcyT2/HE8oi/4LaOI+Sw</key> <!—- PartitionType !-->
                <string></string>
                <key>zHeENZu+wbg7PUprwNwBWg</key>  <!--; RegionInfo -->
                <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)

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