Facebook.app for iOS [v. 88.0] cleans up duplicates

Posted: April 15th, 2017 | Author: | Filed under: Analysis, Debugging, iOS, Programming | Tags: , | No Comments »

This post follows up the Analysis of the Facebook.app for iOS [v. 87.0].
The version 88.0 of the Facebook.app has now been released:

Download Info

As you can see from the smaller download size, the duplicated resources have been removed. This is confirmed by looking at the app content using GrandPerspective:

Facebook 88.0 content

Only some really small resources escaped the cleanup. The ‘FBFacecastTipJarResources’ resources are indeed still duplicated. Example:

  • Facebook.app/Frameworks/FBSharedFramework.framework/FBFacecastTipJarResources/tip3b.json.gz
  • Facebook.app/Frameworks/FBSharedFramework.framework/tip3b.json.gz

Analysis of the Facebook.app for iOS [v. 87.0]

Posted: April 10th, 2017 | Author: | Filed under: Analysis, Debugging, iOS, Programming | Tags: , | 16 Comments »

6 months ago I analyzed the version 66.0 of the Facebook.app for iOS:

The version 66.0 was a 165 MB app on an iPad Air 2 (64-bit). It was a monolithic app with its main binary being more than 100 MB.

The version 87.0 is now available: 253 MB on the same iPad Air 2 with only 64-bit code. In just 6 months, the Facebook.app size grew by 88 MB!

Download Info

Let’s see what changed…

App content

Looking at the app content of the version 87.0 using GrandPerspective gives a good overview:

Facebook 87.0 content

Here is how the content of the version 66.0 looked like:

Facebook 66.0 content

As you can see, the Facebook.app has been completely restructured:

  • the main binary is now only 19 MB
  • there is a huge 136 MB framework ‘FBSharedFramework’
  • there is another 26 MB framework called ‘FBNotOnStartupPathFramework’

Duplicated resources

One of the reason the app size grew is due to multiple copies of the same resources inside the app.


You can actually see in the previous screenshot 3 copies of the same file ‘unetshallow_init.pb’:

  • Facebook.app/Frameworks/FBNotOnStartupPathFramework.framework/opticalflow_resource/unetshallow_init.pb
  • Facebook.app/Frameworks/FBSharedFramework.framework/opticalflow_resource/unetshallow_init.pb
  • Facebook.app/opticalflow_resource/unetshallow_init.pb


Keeping only one copy would save 7.2 MB.


Similarly the DataFiles folder appears 3 times. Keeping a single copy would save around 2 MB:

  • Facebook.app/DataFiles/
  • Facebook.app/Frameworks/FBNotOnStartupPathFramework.framework/DataFiles/
  • Facebook.app/Frameworks/FBSharedFramework.framework/DataFiles/



Some resources called ‘FBFacecastTipJarResources’ appear to be duplicated no less than 6 times in the app! The ‘FBFacecastTipJarResources’ resources only take 150 KB on disk but 6 times makes 900 KB.

One example:

  • Facebook.app/FBFacecastTipJarResources/sent.m4a
  • Facebook.app/Frameworks/FBNotOnStartupPathFramework.framework/FBFacecastTipJarResources/sent.m4a
  • Facebook.app/Frameworks/FBNotOnStartupPathFramework.framework/sent.m4a
  • Facebook.app/Frameworks/FBSharedFramework.framework/FBFacecastTipJarResources/sent.m4a
  • Facebook.app/Frameworks/FBSharedFramework.framework/sent.m4a
  • Facebook.app/sent.m4a

Other duplicated resources

There are a bunch of other duplicated resources, amongst them:

  • modelMetaData.bin: 4 * 1 MB
  • schemaMetaData.bin: 4 * 830 KB
  • FBCommunicationSoundKit.bundle: 3 * 741 KB
  • MNSounds.bundle: 3 * 528 KB
  • FBSoundControllerResources: 3 * 500 KB
  • RelaySchema.json: 3 * 319 KB
  • libPhoneNumber.bundle: 3 * 172 KB
  • CACerts.plist: 3 * 168 KB
  • FBFacecastBroadcastKitResources: 3 * 98 KB
  • Montserrat-SemiBold.ttf: 3 * 70 KB
  • ReactMobileConfigMetadata.json: 3 * 33 KB
  • FBEntityCardsModuleResources: 3 * 33 KB
  • add-photo@2x.jpg: 3 * 29 KB
  • FBFacecastWithKitResources: 3 * 20 KB
  • FBNativeArticleEngagementActionsResources: 3 * 20 KB
  • FBFeedbackReactionsKitResources: 3 * 12 KB

These listed resources count for 15.5 MB. By removing the duplicated resources, you could save at least 10 MB.

Duplicated images

The Facebook.app contains 3 assets.car files:

  • Facebook.app/Assets.car: 11.3 MB for 2267 items
  • Facebook.app/Frameworks/FBNotOnStartupPathFramework.framework/Assets.car: 10.3 MB for 2126 items
  • Facebook.app/Frameworks/FBSharedFramework.framework/Assets.car: 9.5 MB for 1972 items


All the 1972 images in FBSharedFramework are inside the main Assets.car and also part of the FBNotOnStartupPathFramework Assets.car. So there are 3 times the same 1972 images taking 3 * 9.5 MB = 28.5 MB.

Keeping a single set of these 1972 images would save 19 MB.

The remaining images in the main Assets.car (2267 – 1972 = 295) and in the FBNotOnStartupPathFramework Assets.car (2126 – 1972 = 154) are unique.

New localizations

The Facebook.app gained 5 new localizations, increasing the app size by 4.2 MB:
hi.lproj (1.3 MB)
hr.lproj (692 KB)
hu.lproj (750 KB)
ro.lproj (709 KB)
sk.lproj (713 KB)

Also each localization got a new 12 KB file called ‘AdsCountriesConfig.json’. This adds 336 KB.


When analyzing the version 66.0 I completely missed some amusing Objective-C interfaces, protocols and methods:

@protocol FBDeprecatedAppModule_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
@protocol FBLoginFacilitatingAppModule
@interface FBTimelineModule : FBNativeAppModule_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
@interface FBNotificationsModule : FBNativeAppModule_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
@interface FBProductionLockoutModule : FBNativeAppModule_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
@interface FBSearchModule : FBNativeAppModule_DO_NOT_USE_OR_YOU_WILL_BE_FIRED

__RODATA segment

Although the main binary is much smaller than the App Review limitation, the Facebook.app still uses a __RODATA segment containing sections generally found inside the __TEXT segment. For more information about it, please look at the previous post http://blog.timac.org/?p=1303.


Between version 66.0 and 87.0, the Facebook.app has been completely restructured. The main -and only- binary has been split in several frameworks.

It appears however that during this process a couple of resources have been unnecessarily duplicated:

  • opticalflow_resource: 3 * 3.6 MB
  • DataFiles: 3 * 1 MB
  • images: 3 * 9.5 MB
  • FBFacecastTipJarResources: 750 KB
  • Other duplicated resources: 15.5 MB

Removing the duplicated resources would save at least 40 MB. This partially explains why the app size has increased by 90 MB.

Update 15.04.2017: Facebook.app for iOS [v. 88.0] cleans up duplicates

Deobfuscating libMobileGestalt keys

Posted: January 24th, 2017 | Author: | Filed under: crypto, Debugging, iOS, Programming, Reverse Engineering | Tags: , , , , , , , , , , , , | 2 Comments »

/usr/lib/libMobileGestalt.dylib is a private library which provides an API to retrieve the capabilities of the iOS device, as well as some runtime information: system version, build version, device type, current status of the airplane mode, …

The implementation is similar to a key-value database. The library exposes a simple function to retrieve the value for a specified key:

id MGCopyAnswer(NSString *inKey);

When calling this method with a key, it returns the associated value stored in the database, or nil if the key does not exist.

MGCopyAnswer examples

The UIKit framework uses the libMobileGestalt.dylib in several methods. Let’s look at 4 examples.

-[UIDevice systemVersion]

The implementation of -[UIDevice systemVersion] looks like the following (note that I simplified the code for clarity). As you can see -[UIDevice systemVersion] simply returns the value for the key “ProductVersion”:

-(NSString *)systemVersion
	NSString *systemVersion = MGCopyAnswer(@"ProductVersion");
    if (systemVersion != nil)
		return systemVersion;
    return @"Unknown";

-[UIDevice buildVersion]

Similarly the private -[UIDevice buildVersion] method just returns the value corresponding to the key “BuildVersion”:

-(NSString *)buildVersion
	NSString *buildVersion = MGCopyAnswer(@"BuildVersion");
    if (buildVersion != nil)
		return buildVersion;
	return @"Unknown";

-[UIScreen _pointsPerInch]

Another interesting method is -[UIScreen _pointsPerInch] which uses the value corresponding to the key “main-screen-pitch”.

-[UIDevice systemName]

A last example is -[UIDevice systemName] (also simplified):

-(NSString *)systemName
	NSString *systemName = MGCopyAnswer(@"j9Th5smJpdztHwc+i39zIg");
	if (systemName != nil)
		return systemName;
	systemName = MGCopyAnswer(@"ProductName");
	if (systemName != nil)
		return systemName;
	return @"Unknown";

Note the string “j9Th5smJpdztHwc+i39zIg” used by -[UIDevice systemName] which I will refer as “obfuscated key”.

MGCopyAnswer implementation

Let’s look at how MGCopyAnswer is implemented:

  • It first checks if the string passed as parameter exists as key in the database. If this is the case the value associated to the key is returned.
  • Otherwise it calculates the obfuscated key from the key and checks if the obfuscated key exists in the database. In this case the value associated to the obfuscated key is returned.

Obfuscated keys

The code to calculate the obfuscated key is quite simple. Below is the annotated function:

CalculateObfuscatedKey, arm64, iOS 10.1.1

As you can see the implementation does the following:

  • add a “MGCopyAnswer” prefix to the string
  • calculate the MD5
  • use the CommonCrypto CNEncode function to calculate the base64 value

An equivalent Objective-C implementation is:

NSString *CalculateObfuscatedKey(const char *inString)
	// Add a prefix
	char buffer[256] = { 0 };
	snprintf(buffer, sizeof(buffer), "%s%s", "MGCopyAnswer", inString);
	// Calculate MD5
	unsigned char md5Hash[CC_MD5_DIGEST_LENGTH] = { 0 };
	CC_MD5(buffer, (CC_LONG)strlen(buffer), md5Hash);
	// Base64
	NSData *md5Data = [NSData dataWithBytes:md5Hash length:CC_MD5_DIGEST_LENGTH];
	NSString *obfuscatedKey = [md5Data base64EncodedStringWithOptions:0];
	// Base64 converts 3 bytes into 4 encoded characters.
	// The MD5 contains 16 bytes. 16 bytes not being a multiple of 3, the Base64 is padded with "=="
	// and we end up with (16+2) * 4 / 3 = 24 encoded characters
	// The last 2 characters are useless and they are dropped
	// (i.e. only keep the first 22 characters).
	return [obfuscatedKey substringToIndex:22];

Getting the list of all valid obfuscated keys

Is it possible to get the list of all valid keys? Not easy…

However getting the list of obfuscated keys is simple because it is stored inside the libMobileGestalt.dylib binary. You can get the list of the 646 obfuscated keys here: obfuscatedkeys.txt


Note however that there is no guarantee the list is complete. It is possible that the libMobileGestalt.dylib binary does not include some obfuscated keys.

Obfuscated key -> key?

It is now possible to get the values for all the obfuscated keys… but we don’t know the key and thus the meaning of the value. For example the following information is not really useful:

MGCopyAnswer(“03hWmMtMs+4nzama4/PzHQ”) = YES
MGCopyAnswer(“cBy4BcYs5YWtFHbBpt4C6A”) = YES

Whereas if we knew the key, that would be much more interesting:

MGCopyAnswer(“CameraLiveEffectsCapability”) = YES
MGCopyAnswer(“DeviceSupportsHaptics”) = YES

Can we get the key from the obfuscated key? As we have seen, Apple uses the weak MD5 algorithm to calculate the obfuscated key. Moreover we can guess the possible formats for the key based on the examples:

  • word (lowercase word)
  • Word1Word2…Wordn (camel case words combination)
  • word1-word2-…-wordn (dash separated lowercase words)

This means that we can brute force the obfuscated keys to retrieve the keys with a good percentage of success.

Getting the list of raw MD5

To run a brute force attack, we first need to get the MD5 hashes. This is easy to get:

- (NSString *)md5StringForObfuscatedKey:(NSString *)inObfuscatedKey
	if([inObfuscatedKey length] <= 0)
		return nil;
	// As seen in CalculateObfuscatedKey(), the last 2 base64 characters
	// are dropped because it's a useless "==" padding sequence.
	// To decode the base64 encoded string to the MD5 hash,
	// we need to append back the "==" padding sequence.
	NSString *base64String = [NSString stringWithFormat:@"%@==", inObfuscatedKey];
	// Decode the base64 string
	NSData *md5Data = [[NSData alloc] initWithBase64EncodedString:base64String options:0];
	if([md5Data length] < 16)
		return nil;
	// Format the MD5 hash
	const uint8_t *md5 = [md5Data bytes];
	NSString *md5String = [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
										md5[0], md5[1], md5[2], md5[3], md5[4], md5[5], md5[6], md5[7], md5[8], md5[9],
										md5[10], md5[11], md5[12], md5[13], md5[14], md5[15]];
	return md5String;

You can get the list of the 646 md5 hashes here: md5hashes.txt


Now we just need to brute force these hashes…


There are several good solutions to brute force MD5 hashes. I used hashcat (https://hashcat.net/hashcat/) because it supports macOS, it is GPU accelerated (OpenCL), it is open source and supports the attacks I wanted to perform.

Mask attack

A direct brute force attack is not practicable due to the large keyspace. But the first obvious attack is a mask attack. In a mask attack we provide a string with placeholders. The placeholder characters will be brute force with the specified charset. Such attack can quickly recover simple keys.

The keys to recover seem to mostly contain lowercase characters, uppercase characters, digits and dashes. hashcat provides several built-in charsets:

?l = abcdefghijklmnopqrstuvwxyz
?d = 0123456789

In most of my mask attacks I used the custom charset “?l?u?d-” that consists of the characters “abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-“. In some cases when it would take too much time (or when targeting a specific format), I reduce the charset (to “?l” for example).

With the mask attacks I quickly recovered some keys. For example I got the following keys after running hashcat -a 3 -m 0 md5hashes.txt -1 ?l?u?d- MGCopyAnswer?1?1?1:


Explanation for hashcat -a 3 -m 0 md5hashes.txt -1 ?l?u?d- MGCopyAnswer?1?1?1:

  • -a 3: set the attack mode to brute-force
  • -m 0: set the hash type to MD5
  • md5hashes.txt: the list of MD5 hashes to brute force
  • -1 ?l?u?d-: define the custom charset 1 to “abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-“
  • MGCopyAnswer?1?1?1: The mask to use for the attack. The brute force will try all words starting with MGCopyAnswer followed by 3 characters from the custom charset 1

4 characters keys could easily be recovered too using hashcat -a 3 -m 0 md5hashes.txt -1 ?l?u?d- MGCopyAnswer?1?1?1?1:


5 characters keys can be quickly recovered too with hashcat -a 3 -m 0 md5hashes.txt -1 ?l?u?d- MGCopyAnswer?1?1?1?1?1:


Similar mask attacks can be used to recover keys with up to 7 characters. Starting with 8 characters the mask attack starts to take too much time with my hardware (multiple days).

It is however still possible to use the mask attack with more specific formats like:

hashcat -a 3 -m 0 md5hashes.txt -1 ?l MGCopyAnswer?1?1?1-?1?1?1?1
hashcat -a 3 -m 0 md5hashes.txt -1 ?l MGCopyAnswer?1?1-?1?1?1?1?1
hashcat -a 3 -m 0 md5hashes.txt -1 ?l MGCopyAnswer?1?1?1?1-?1?1?1
hashcat -a 3 -m 0 md5hashes.txt -1 ?l MGCopyAnswer?1?1?1?1?1-?1?1
hashcat -a 3 -m 0 md5hashes.txt -1 ?l MGCopyAnswer?1?1?1?1-?1?1?1?1
hashcat -a 3 -m 0 md5hashes.txt -1 ?l MGCopyAnswer?1?1?1?1?1?1-?1?1
hashcat -a 3 -m 0 md5hashes.txt -1 ?l MGCopyAnswer?1?1?1?1-?1?1?1?1?1
hashcat -a 3 -m 0 md5hashes.txt -1 ?l?u?d MGCopyAnswerDeviceSupports?1?1?1?1

The last example would give back:


Dictionary attack

With mask attacks I quickly recovered simple keys (and later keys for which I could guess the format). Recovering more complex keys is inefficient and I switch to a dictionary attack. A dictionary attack (also known as wordlist attack) consists of trying all words from a list.
Using a simple English dictionary, I could recover a couple of keys but obviously the words in the dictionary are less than ideal.

I built a much better dictionary by extracting all the strings from iOS. This can be achieved with the following bash script ( this script can be downloaded here ExtractStrings.sh ):


# Bash script that loops through all the files of a folder
# and extract the strings.
# Created by Alexandre Colucci on 16.01.2017
# http://blog.timac.org/?p=1570

# Force expand a wildcard pattern into the list of matching pathnames
shopt -s nullglob

# Function to print the usage
printUsage ()
	echo "Usage: ExtractStrings.sh IN_FOLDER_PATH OUT_FILE"
	echo "IN_FOLDER_PATH: Folder to extract strings from"
	echo "OUT_FILE: All the strings found will be saved to this text file"
	echo ""
	echo "Examples:"
	echo "  ExtractStrings.sh /System/Library /tmp/output.txt"
	echo "  ExtractStrings.sh /System /tmp/output.txt"
	echo "  ExtractStrings.sh / /tmp/output.txt"
	echo ""
	echo "Note: run as root in order to avoid permission issues."
	echo ""

# Check if the script was called with the expected usage
	exit 1

# Get the folder path

echo ""
echo "Start time:"
echo ""
echo "Extract strings from ${PATH_TO_CHECK}"
echo "Save strings to ${OUTPUT_TXT_FILE}"

# Find all the files in all subdirectories and extract the strings

find ${PATH_TO_CHECK} -type f -exec strings {} \; >> "${OUTPUT_TXT_FILE}"

# Finalizing
echo ""
echo "Completed at:"
echo ""

A bunch of keys can be recovered with this dictionary by running hashcat -a 0 -m 0 md5hashes.txt dictionary.txt:

  • -a 0: set the attack mode to straight
  • -m 0: set the hash type to MD5
  • md5hashes.txt: the list of MD5 hashes to brute force
  • dictionary.txt: the dictionary containing all the words to check

Improved dictionary with regular expressions

The dictionary built by extracting the iOS strings contain a lot of duplicates. There are also Objective-C methods names which are probably not valid keys. The strings can be reformatted using regular expressions to get a better attack dictionary. I wrote several scripts to improve the dictionary. One example that you can download is the bash script ImproveDictionary.sh. What it does:

  • Remove duplicated strings
  • Replace characters other than a-z, A-Z, 0-9 by a return line
  • Uppercase the first letter of the lines
  • Split words by looking at the last uppercase character: ‘AnExampleOfWord’ -> ‘AnExampleOf’ and ‘Word’
  • Split words by looking at the first uppercase character: ‘AnExampleOfWord’ -> ‘An’ and ‘ExampleOfWord’

Note the ugly syntax for the regular expressions in this script. The reason is that I wanted the script to run out-of-the-box with sed built in macOS 10.12. Using GNU sed would have resulted in a much cleaner script.

Also note that the dictionary only targets the keys with the format Word1Word2…Wordn (camel case words combination). I made a separate script to convert this dictionary to a dash separated dictionary in order to specifically target the keys with the format word1-word2-…-wordn (dash separated lowercase words).

Running hashcat on this improved dictionary recovered some complex keys.

Combinator Attack

The dictionary attack recovered a lot of keys. But we can recover even more keys with a combinator attack. This attack uses 2 dictionaries and tries all combinations of a word from the first dictionary appended to a word from the second dictionary. For a combinator attack, the first dictionary could contain strings like:


and the second dictionary strings like:


In practice I used my previously generated dictionary as both left and right dictionaries and patched hashcat to always add the prefix “MGCopyAnswer” to each combined word candidate. Running this attack was perform with the command: hashcat -a 1 -m 0 md5hashes.txt dictionary.txt dictionary.txt:

  • -a 1: set the attack mode to combination
  • -m 0: set the hash type to MD5
  • md5hashes.txt: the list of MD5 hashes to brute force
  • dictionary.txt: the left dictionary
  • dictionary.txt: the right dictionary. As previously mentioned I used the same dictionary as both left and right dictionaries and modified hashcat.

I ran a similar combinator attack to specifically target the dash separated words format: hashcat -a 1 -m 0 md5hashes.txt dictionary.txt dictionary.txt -j ‘$-‘. Note the extra option -j ‘$-‘ to append the dash character at the end of each word of the left dictionary.

List of all keys

After running all these different attacks, I managed to recover 564 out of 646 keys (87%). Here is the complete list of obfuscated key / key:

struct tKeyMapping
	const char *	obfuscatedKey;
	const char *	key;

static const struct tKeyMapping keyMappingTable[] =
	"+3Uf0Pm5F8Xy7Onyvko0vA", "DeviceClass",
	"+Ce1uSqGUXaJPl/uT6ur8g", "SDIOProductInfo",
	"+U0jSj4F2EfE+Vqj22IavA", "tnr-mode-back",
	"+VIu65zA5EW4ztayJXvOUg", "device-name-localized",
	"+bL/lKwaIAv+fzmjsHYZdw", "N78aHack",
	"+fgL2ovGydvB5CWd1JI1qg", "has-sphere",
	"+zD41v0XRR72ItZHfisZuQ", NULL,
	"/2V8H9h/+z0UxNUr9aRLeQ", "boot-nonce",
	"/6FWCRjN1yRdUABG9vF8ow", "WiFiCallingCapability",
	"/9luHerXthRoPoNt/PVkTg", "VibratorCapability",
	"/GK+yfRFY/b5ZDIDpdVImg", "hardware-keyboard",
	"/Pop5T2XQdDA60MRyxQJdQ", "hall-effect-sensor",
	"/YYygAofPDbhrwToVsXdeA", "HWModelStr",
	"/bSMNaIuUT58N/BN1nYUjw", NULL,
	"/cMWdoU/88pcjJ1egxmIYw", "wlan.background-scan-cache",
	"/ej/HWmqnKV/QQptXhUZmg", "no-coreroutine",
	"/l0Kz2akvSvEHTNmZeY0nQ", "chip-id",
	"0/2HluYMd/whD80Hua4Rpw", "io-surface-backed-images",
	"0/7QNywWU4IqDcyvTv9UYQ", NULL,
	"0/VAyl58TL5U/mAQEJNRQw", NULL,
	"03hWmMtMs+4nzama4/PzHQ", "CameraLiveEffectsCapability",
	"0AFeHRmliNJ4pSlVb8ltZA", NULL,
	"0L5PkT61qoH1b/B1USWqjQ", "RegionalBehaviorChinaBrick",
	"0R2aiV2nJVu/v8I7Ex2GcQ", "RegionalBehaviorNoPasscodeLocationTiles",
	"0VkTunHOJrrZdolQXR5gCg", NULL,
	"0Y4fmR6ZHZPxDZFfPtBnRQ", "SysCfg",
	"0Yu30fwSQVPKvHVla17kXw", "umts-device",
	"0dnM19zBqLw5ZPhIo4GEkg", "SecureElement",
	"0jjK6IVSQzA8doQeSwmujA", "software-bundle-version",
	"0l4wqBtWEAK1tOkeBHkU6Q", "main-screen-pitch",
	"0pY9r1XBV1duZ8HO3tBvFg", "location-services",
	"0uthiXrHZ212KvcJizKHEw", "BoardId",
	"0uyHvVqOLpJQBpSl/rF3Vg", "kSimultaneousCallAndDataSupported",
	"16N2bLOzcgJEsZToEX21Zg", "accessibility",
	"1DQNgySZSIjPqLWroIzfiQ", "BacklightCapability",
	"1N14oS9TeyskaTU1DxpwoQ", "load-thumbnails-while-scrolling",
	"1Rm/mWYEI5ttaC0dJ3sHBQ", "BootNonce",
	"1X0zc2JwBdYOQrMAyP81DQ", "lte-device",
	"1gsBzuZsXu2rXZJBE01M0w", "FrontFacingCameraHFRCapability",
	"1oMPwMsqxTa9BJxUs8v06w", "PlatinumCapability",
	"1qDdT/85SS7sxriK0wIbbg", "main-screen-orientation",
	"1qJmMHedWOh43VwRKPdDrw", "iTunesFamilyID",
	"1rf3rZXIZFgznqrHlPehuQ", "FaceTimeBitRate3G",
	"1uZbhSbBhsNCsVSsopZ4qg", "dictation",
	"1z6Kk4xUAVLdaBPGugsDSA", "navigation",
	"2IDxmg5KyAMBBi/b0rojgQ", "telephony-maximum-generation",
	"2OK50OGmkXM1ospsh766WQ", NULL,
	"2aIAScwtFNCz+Y7WesMOCA", NULL,
	"2lNKobEIQqX50ohy1JBqCA", "no-hi-res-buildings",
	"2pxKjejpRGpWvUE+3yp5mQ", "cameraRestriction",
	"2sWGezz2RezScIJJgiIYQg", NULL,
	"38C0kq9NiVaMsqjlUsCHcQ", "ota-activation",
	"39ZkJVEsL4pmCXbg+89QmQ", "accelerometer",
	"3kmXfug8VcxLI5yEmsqQKw", NULL,
	"3m1Q0AXlqeA2C/LmqdTndQ", NULL,
	"3sF/uRq+X+mZ2zGHSJOwpw", "crypto-hash-method",
	"3yzXj0lJhQi+r3kgQlwiOg", NULL,
	"4+qmMh9JBDh72Nq6fD64RQ", "gyroscope",
	"475wW3fne+tyzGr4wleUSQ", "CarrierBundleInfoArray",
	"4I0hOaR3n80379Vka7u+Xg", "volume-buttons",
	"4Jfu4lqX8dzru4Z+ONQ1rQ", NULL,
	"4RwhtNOmePfUXmu57rh+KA", "LisaCapability",
	"4W7X4OWHjri5PGaAGsCWxw", NULL,
	"4fT83+9coO3VAUnlxuOOcw", NULL,
	"4k6Wv56SWfITjzet+hIHMQ", "multi-touch",
	"4snMZS8LJkSctKypt2m+xA", "not-green-tea",
	"4uzgAFPkzKUmlZG5HpFIkg", "RegionalBehaviorGoogleMail",
	"50/CmBTX6jhkb8Z61hzwhQ", "class",
	"566JrJVMlDfnslGpwUzNlQ", "ChipID",
	"57eLnXynqDlQaGEi+9JAtQ", "call-forwarding",
	"5MSZn7w3nnJp22VbpqaxLQ", "venice",
	"5MXFoiW2zgxfIbaaTb/wvA", "SecondaryEthernetMacAddressData",
	"5QM8apssQbhm2ZrUx5g5Tw", "pipelined-stillimage-capability",
	"5dyhCh3dm1vSOaNK+US1Qw", "euicc-chip-id",
	"5lAK1Xp+ezh1Qu+4jnHAOg", "SupportsSOS",
	"5mvQIwu3Mqqw/zOKmwGkWw", "ProximitySensorCalibrationDictionary",
	"5pYKlGnYYBzGvAlIU8RjEQ", "HardwarePlatform",
	"5tnvmEsHQKfCoieLEYpnvg", "RFExposureSeparationDistance",
	"5v2p6i7PyIMdWOK4n/+G4A", "proximity-sensor",
	"5y8gwXr/HXkhryza4xQeFg", NULL,
	"61xs1bQ+9eTk8tlRvG9UKw", "MicrophoneCount",
	"67x5O+zO+JwnGgmKlq+qdA", "DiagData",
	"6MnVtR+c9LeR46bMxvR5Yw", "RegionalBehaviorNoVOIP",
	"6PkKE66MnKm0yiOIQLknEg", "allow-32bit-apps",
	"6RrxXzvfw2GZeUPLKXxrmA", "RearFacingCamera60fpsVideoCaptureCapability",
	"6S9CvPHPtzHQqKudHSfsag", "haptics",
	"6UUmcaeT7rJoyUVmoPJd4A", "ProximitySensorCalibration",
	"6iI7/9cVUGIjdrSvIXEt4A", "RearCameraCapability",
	"6k70IxahfOQTZbGwu++QwA", "additional-text-tones",
	"6pjDdEw65TFyL1FmIdszXw", "hd-video-capture",
	"7D54DikSnFQnbDEBwlKQTQ", "DevicePrefersCheapTrafficShaders",
	"7DfjbzhvH/GDkhio1dv8fA", "HasSEP",
	"7IgVvZZLtNjMFdInQlKg6A", "SoftwareBehavior",
	"7VoVaBmSuokzovhUkiDx6Q", "SecondaryWifiMacAddress",
	"7W2eNk/f6uewC8N58mlRrQ", "FrontFacingCameraHDRCapability",
	"7l0BaQsNxJCanoeHMJ2huA", "AllowYouTube",
	"7mV26K/1a+wTtqiunvHMUQ", "PhosphorusCapability",
	"7ot70MKj7EdRIEEi91jlcg", "bitrate-3g",
	"7qHcAWI5X9b1SiyYIg/Byw", "ui-background-quality",
	"7yzVesPANxqKQ+oqNPhTwg", "applicationInstallation",
	"8/HMvzDR3J6m0aY3NYeqcg", "bluetooth-le",
	"8/tysfSvORoyVg9IE901oQ", "DeviceRGBColor",
	"81Zj1535/jeXbmfOndlIVA", "youtubePlugin",
	"82Ono2SP03rNGkhOc5O1Mw", NULL,
	"84iheBmhAmsxIlcxG4a0zA", NULL,
	"86GizkmSK+IxgCtUMOp2NA", "front-max-video-fps-720p",
	"87sSAh2rboMI2TDvFBimkg", "DeviceColorMapPolicy",
	"8DHlxr5ECKhTSL3HmlZQGQ", "RestoreOSBuild",
	"8QNz35Yxm0KqxP1JiE0HIw", "PintoMacAddressData",
	"8Shl+AdVKo09f1Sldkb0kA", "touch-id",
	"8Wunmi1SpU1MxfBxkv3KOw", "dali-mode",
	"8bOgXKgqoLvqV/XeSomAfA", "SecondaryBluetoothMacAddress",
	"8bY/L3i3rmxrl4ZjFZZpgQ", "siri-gesture",
	"8vIFqHe5lcMGo7TvbNLmyg", "home-button-type",
	"9/J7LY7zuYSGW1BcSTRpOw", "SysCfgDict",
	"91LyMcx4z1w3SGVeqteMnA", "BasebandRegionSKU",
	"96GRvvjuBKkU4HzNsYcHPA", "MinimumSupportediTunesVersion",
	"97JDvERpVwO+GHtthIh7hA", "RegulatoryModelNumber",
	"9MZ5AdH43csAUajl/dU+IQ", "SupportedDeviceFamilies",
	"9N7qIucqhr0Cy2/Tk27/hw", "DeviceCoverGlassColor",
	"9RreaA6rTvewPbqQy5ldRg", "front-auto-hdr",
	"9UCjT7Qfi4xLVvPAKIzTCQ", "ReleaseType",
	"9ZqSL68MJyQESzQFDvKqXA", "voip",
	"9n2qz3uDC5nSe1xZG1/Bkw", "CarrierInstallCapability",
	"9s45ldrCC1WF+7b6C4H2BA", "GSDeviceName",
	"A8aFtN08Oqt21846jqnftQ", NULL,
	"AFXhxraF1bvyaCMlBIOZPA", "RequiredBatteryLevelForSoftwareUpdate",
	"AJFQheZDyUbvI6RmBMT9Cg", "HasBaseband",
	"AOIh1l3cIyDXlP4KB/084A", "iap2-protocol-supported",
	"APeknA55ZAcu0FcufqVN4Q", NULL,
	"AQiIpW0UeYQKnhy2da7AXg", "EffectiveProductionStatusAp",
	"AWEfP3xWh0q1MjO3JAy/AA", "CPUSubType",
	"Ah5yfSlY2yycIQb92HbmrA", "function-button_ringerab",
	"Ai0zsJQ3+sTFkU6/lLbd5A", "opal",
	"Aixt/MEN2O2B7f+8m4TxUA", "HasExtendedColorDisplay",
	"AklmqJyJMChzHsYT0aiBbw", NULL,
	"AthxlkPBk46HtzM9AxK9vg", "explicitContentRestriction",
	"AtmPEO/j+Pdr8+WKxv4Aaw", "DeviceEnclosureMaterial",
	"BOPZue5C0v42pU9iJFYE3A", "location-reminders",
	"BOYfUi496moe56A0RWFbqQ", "sensitive-ui",
	"BQwz+BT9b5mS7OPh2WGD6A", "SecondaryEthernetMacAddress",
	"BYYil6kIk8Hm5lRuA1k8Tw", "prox-sensor",
	"BhXj+5n3+0HcPoSArDGX7g", "EffectiveSecurityMode",
	"Birtx7GxrxCCUzsE1JQO8Q", NULL,
	"BoNz1QXiAEUgic9RdMVLIg", "FaceTimeEncodings",
	"BstyjvaCtwqls0MfbkGTSg", NULL,
	"CJcvxERO5v/3IWjDFKZKRw", "DeviceBackingColor",
	"CK3I7fFGkgeqzKEhGew9rQ", "FaceTimeFrontCameraTemporalNoiseReductionMode",
	"CN/XFuy/9Fdv0yE+28kwRQ", "cell-broadcast",
	"CN64p1hw1JVdTHCfBdgPLQ", "BasebandStatus",
	"CQ6gFmgMhpnnISvG6VakBQ", "MobileSubscriberCountryCode",
	"ChBMzkxYdT9Xmw3QN5kMWA", "DeviceSupportsSimplisticRoadMesh",
	"CpVSHukvtqlJzCgSdoHW5w", "SBAllowSensitiveUI",
	"CzGP5+8jQECJMH4h+z4TYA", "offline-dictation",
	"D0cJ8r7U5zve6uA6QbOiLA", "ModelNumber",
	"D3LeXyjrxDFYSi6pWFEL4A", NULL,
	"D4AU4tOIuGKN3G/uix65cQ", "RegionalBehaviorAll",
	"D6PT05dEJ0f9Ayk7Yi92RA", "front-max-video-fps-1080p",
	"DGBWzPMdVuqPxgH7iSz3SA", "3Gvenice",
	"DIXX7JMbm7glu49f+xi4zQ", "live-effects",
	"DViRIxZ/ZwO007CLcEYvZw", "SoftwareBundleVersion",
	"DWrhyh1BwYypYmW5q78Wuw", "armv7",
	"DdNpmCCXbeMGbqj0kAMCdw", "piezo-clicker",
	"Djv9tHz6MLdTUDjnkq0obQ", "BasebandFirmwareUpdateInfo",
	"DrSqlXYNVfYXCvAsuUrUDA", "DebugBoardRevision",
	"DzLC4o1jZ4hWtP8aB6An6g", "SDIOManufacturerTuple",
	"E2iZGHvwvi387UKi9wC2Mg", NULL,
	"EImfMz+bzJrUkVQKyY6tEg", "BasebandSecurityInfoBlob",
	"EJRyIpTkLz2+Lb6XWy10bQ", "panorama",
	"ELLSWiPOyh0roYBypZFHFA", "magnetometer",
	"ESA7FmyB3KbJFNBAsBejcg", "ui-pip",
	"Eavu9TP+iZ0Lzlk7FU6w6Q", "ActiveWirelessTechnology",
	"Ecx7M8v2wk05Fch3pFE/GA", NULL,
	"EqrsVvjcYDdxHBiQmGhAWw", "apple-internal-install",
	"ErupMbaQrM6KypD4K0kTDQ", "OpenGLESVersion",
	"Ex6ChutQnIx5Zm6c5uwYuw", NULL,
	"ExSUJyf7MblY5nBQx2OaRw", "ScreenRecorderCapability",
	"F1Xz9g1JORibBS9DYPUPrg", "BlueLightReductionSupported",
	"F2M6lgy8EHCyR6hc00hMcg", "effective-security-mode",
	"F6wqOqCR9tpYWyB1taxxnw", "board-id",
	"FDsm68UVR7l3wErusGLgKg", "hw-snapshots-need-purplegfx",
	"FOs+LbLUs+TajsEE4xkbrw", "gas-gauge-battery",
	"Fam17Ufz3CiHdZPcma0AeQ", "regulatory-model-number",
	"FgjnMkPJPpI4C38dWETwtw", "flash",
	"Fh2Ga0/Sj4T3Qn5Z9A9BMw", "DeviceSupports4k",
	"FkD7K642S082lnfw/ohHzg", "BasebandFirmwareManifestData",
	"Fralg2R4+pkggafylKbVgw", NULL,
	"FuR7SfqQmxpRW5MyqMm7FQ", "low-power-wallet-mode",
	"GO5/TJivIXtQkTkFWkRc9A", "certificate-production-status",
	"GdXjx1ixZYvN9Gg8iSf68A", NULL,
	"Glo+aTkt0Uw31BghCxnsyQ", "BridgeRestoreVersion",
	"GnhnHyRVCC8LUClgElhKXA", "effective-security-mode-ap",
	"GvmsjQ/68T7do2CJxUhMig", NULL,
	"GxXmRWHjsY5yAVhMfCH6Lg", "BridgeBuild",
	"H5HW20mJr/djc40tAehkww", "BasebandKeyHashInformation",
	"H5TSt7Pu4zNCD5RvHuk5CQ", "PhoneNumber",
	"H8Pi7AthQFVZ0B6A1J5OTw", "photo-adjustments",
	"H97G3GzDYb4wY5kwJ0AKfQ", "ForwardCameraCapability",
	"HHF6YgqEQ9Kd7MBSVnLsgg", "DeviceSupportsAOP",
	"HIaq6xVZ/V8B9pnigcvqCA", "DeviceSupports3DMaps",
	"HMKkoKYsMmBBSN6ozOtw/w", "stand-alone-contacts",
	"HV7WDiidgMf7lwAu++Lk5w", "HasMesa",
	"HXTqT3UXOKuTEklxz+wMAA", "BasebandAPTimeSync",
	"HdWhWK8BN8j/O9k8/OHblQ", "supports-always-listening",
	"HkXhfA7q9eBKIU2+6yTgQg", "screen-dimensions",
	"HnHX0gXt8RvhMQzIVMM7hw", "FrontFacingCameraHDROnCapability",
	"Ht1HDxwTOy2gyY+THjbd6w", "peer-peer",
	"HxTvMvHnum5rI8d0Nr4xBw", "RegionalBehaviorVolumeLimit",
	"HzddeW2/HtdBNAc5tsFtDg", NULL,
	"I+ptihXW+rMeySVUWURNiw", NULL,
	"I2IvpG8yJdNpvO4csuB9EA", NULL,
	"I32sGclpgl5VujQRJxyhKQ", "IsUIBuild",
	"I83EgnDEGxinSKU4QAykmA", "rear-max-video-frame_rate",
	"IAJzgzhEVk3SMNuEhChs2w", "RemoteBluetoothAddressData",
	"IBqcPacFMPIX8HMWob444A", "AirDropCapability",
	"IFBSPGnQVFrGFW+ujtZu6Q", "RegionalBehaviorNTSC",
	"IFHQSJ65DoElWQl/+eCfNQ", "fast-switch-options",
	"IGYvMgBnOsdJjgmSh7Pe1A", "SEPNonce",
	"IIYjwu1lbAyGz5t0c4ECoA", "MainDisplayRotation",
	"IMLaTlxS7ITtwfbRfPYWuA", "DeviceVariantGuess",
	"IMlsLCL69XUZau9QyPnxeQ", "rear-slowmo",
	"IUvPFaILUl3l0684dR0AlA", "front-max-video-zoom",
	"Ini7+h7Q4ZZctfFM9+KTBw", "peek-ui-width",
	"IweaHIDpz+rknAcb3+xg9g", NULL,
	"J/a5Y6rhDH0gVkYtWUylOA", NULL,
	"J912s3mVzhReVtPv4HwqNQ", "FaceTimeCameraRequiresFastSwitchOptions",
	"JLP/IinyzetEPztvoNUNKg", NULL,
	"JOlwW/P8Cw3CDCoFunq8og", "platinum",
	"JQr1mcESYcN648vrcZPJEA", "front-flash-capability",
	"JUWcn+5Ss0nvr5w/jk4WEg", "device-name",
	"JXmZWYUbLoumvz7hu/GL0A", "FaceTimeBackCameraTemporalNoiseReductionMode",
	"JhEU414EIaDvAz8ki5DSqw", "DeviceEnclosureColor",
	"Jq+xaurJgFzSwxOfTqtBGw", NULL,
	"Js8HVdVGRs8m5v94pMjkRw", "enforce-googlemail",
	"JwLB44/jEB8aFDpXQ16Tuw", "HomeButtonType",
	"K0tZN4PayAx7RgB0M+oohw", "bitrate-lte",
	"KGlZoljMyZQSxfhROj0IFg", "data-plan",
	"KLB4sM/KC38QT+dTuDC/aA", "rear-burst",
	"KMgjmT+dsqBCXu1YQEcOFg", "RegionalBehaviorValid",
	"KN7t4gQkyj5X66dBpmh9HQ", "large-format-phone",
	"KWr9OM2iqeLQEhaHS0UshQ", NULL,
	"KXUnLpPZ6IZSE+As45CkhA", "SoftwareDimmingAlpha",
	"L+KOzmOzO5DiJUZl21QGVg", "StarkCapability",
	"L2Oq7vNNUKuv+iJ4m9xtiw", "SIMTrayStatus",
	"L47fh6KehOmR+AseqK2Xsw", "DeviceSupportsCCK",
	"L5al7b+7JATD/izSJeH0aQ", "cellular-data",
	"LAB8o3Bxs3CgNuzDawjRdw", "opengles-3",
	"LBJfwOEzExRxzlAnSuI7eg", "InternalBuild",
	"LWxSM4CmFZMwzYclSh43gg", "rear-hdr",
	"LXP9TbNYtZrIKOktGXez2Q", "3d-maps",
	"LcUv5nw5flxnHlxWu9sJvw", "NavajoFusingState",
	"LeSRsiLoJCMhjn6nd6GWbQ", "FirmwareVersion",
	"Leuy56dCZmAMYoVCQGTfPA", "DeviceSupportsPeriodicALSUpdates",
	"Lg1EJX11Jb7EbveB6+YgVQ", "720p",
	"LkWb+FyA1+ef2UD1Fx+kAw", "RearFacingCameraHDROnCapability",
	"Lrs7l5cKWJ1Gk5ZzgMAKyQ", "sandman-support",
	"Lu6Mgo1O0+EoGMk1OtnMRg", "rear-max-video-fps-1080p",
	"LvCIFE6lOM10QDqIEyYWRA", "PeekUICapability",
	"M+eWZ6Zk/aJIsnIrv4zdAg", "unique-chip-id",
	"MTa8c+pEdbK0tHGXP06xOw", "AllowYouTubePlugin",
	"MW1p1oJH8nDJc6igOb8G4g", "IcefallInfo",
	"MWJNrFKpHkBEm8jAdJf1xw", NULL,
	"MWduuAGptrl7qIzWqBmOqQ", "AWDID",
	"MWpHV1VYKXmaKqhgbmTWCg", "ptp-large-files",
	"MXZ7vg7hul895QdsLUBJFA", "boot-manifest-hash",
	"Mgwtle8wzQEz/0vzBk47FQ", "DeviceSupportsLineIn",
	"Mh+drGtyBfLYKN02sROzxg", "delay-sleep-for-headset-click",
	"MjqwTHDneTCNeqhZ7vCvUw", "DeviceSupportsHiResBuildings",
	"Mk4ZslaChmO+6s3h7L1w6Q", "DeviceSupports1080p",
	"MulRZdIO3jyzkPar/CuDXA", NULL,
	"MvBKQ+GSnR3DjkovgNL+3w", "airplay-no-mirroring",
	"MzxVeXMzucmEMx2lw8Pg1Q", "opposed-power-vol-buttons",
	"ND6ZSbBBgMgwtMfh+OL5Wg", "HighestSupportedVideoMode",
	"NJsxTSI2WuD+13rxShXX9w", "DeviceHousingColor",
	"NPzjaX07XnS4KcMZ+l8ymA", "ExternalChargeCapability",
	"NUYAz1eq3Flzt7ZQxXC/ng", NULL,
	"NXJOYK7VhNn7ugbF2kx0zg", NULL,
	"NaA/zJV7myg2w4YNmSe4yQ", "WifiChipset",
	"Nhm02nvVOaQPimpOshlO1g", "FaceTimeBitRate2G",
	"NkQOJZ1xdFk5SV55PFKlIg", "wlan",
	"Nmhz54v5ZLqj2I4NPFIFqQ", "BasebandSerialNumber",
	"NrXe/KHDNhJ4r2SC2bMQyA", "any-telephony",
	"Nzu4E/VsXjEIa83CkRdZrQ", "Image4CryptoHashMethod",
	"OBqqs000I0SR+EbJ7VO8UQ", "HasSpringBoard",
	"OPzhvROZUqCZhgYMyve5BA", "sms",
	"OWLyWlUOIkl+eQB7Iq37xQ", "DesenseBuild",
	"Oji6HRoPi7rH7HPdWVakuw", "HasInternalSettingsBundle",
	"OjzOua0LkOegX7pQdgMksw", "main-screen-height",
	"OoFyZnd3oLS2Lw/KQTccGA", "SupportsTouchRemote",
	"P+UqVuzQzn3nwHSiSeDmyw", "live-photo-capture",
	"P1djMN/L3B6otgGpO9WYkw", NULL,
	"P6z8eNrRPcv0AcKPML0iow", "wi-fi",
	"PLQ6xgfGji63NbFu+sjeYg", "WLANBkgScanCache",
	"PNexcW/LBlPgAm+Skp2EAg", "EffectiveProductionStatusSEP",
	"PQzmRjKVMistuIVsqs6QXA", "BasebandFirmwareVersion",
	"PTfO3r9syJ45k+OItTlD3Q", "ui-no-procedural-wallpaper",
	"PUMArrha4PFeOqINeQRM3A", "sim",
	"PUY/n3uJEk8GSE+RjkHHAA", "video-cap",
	"Po0s2Vf6g2ZqymKGNLFxeQ", "FaceTimeCameraSupportsHardwareFaceDetection",
	"PpmzzBVLpZVubmP0tCIymg", NULL,
	"PxEDp0oOasJ92F/V7YBa8A", "RawPanelSerialNumber",
	"PxLTGkQx9GkeYcJKOaZguA", "video-stills",
	"Q/BI7GKNZY13TpxbqA8nhQ", "rear-max-video-fps-720p",
	"Q1Ty5w8gxMWHx3p4lQ1fhA", "MLBSerialNumber",
	"Q4b8YIwPgnznT5hs4qXjaA", "rear-burst-image-duration",
	"Q5QHkCRPRmiX3L/5xxKrRg", "BasebandCertId",
	"QFnvO2shAYI+d4bj3qXuFQ", "DevicePrefersBuildingStrokes",
	"QGmb9t2ZMFVwOImO6fewgQ", "all-features",
	"QJSuJ2zhdxYX/7DUA2JtUw", "EffectiveSecurityModeSEP",
	"QZgogo2DypSAZfkRW4dP/A", "InternationalMobileEquipmentIdentity",
	"QbQzuIbef01P4JeoL9EmKg", NULL,
	"QdL5XM6PGBrjvJ/k187Ueg", "IDAMCapability",
	"Qq9/Mya05P4ToEr1pMpGGg", "BootManifestHash",
	"QtLcewkuPtPV0RpO+i0Zzw", "MarketingVersion",
	"RA3I5nhSK65i1ZxkRxQKfg", "HasThinBezel",
	"RECLuzbJ6oh8q4NKR8RtNg", "multitasking",
	"RLlZndRrTB3oIDuZEihtpQ", "RearFacingCameraAutoHDRCapability",
	"RXyKSjeF239SL2vOcru01A", "MobileEquipmentInfoBaseVersion",
	"RYO2N0gqp5hHCik2TEiSVA", "armv7s",
	"RYZZuAFEfSNnEKEzwzRnqw", "photo-stream",
	"RaX3KBmG7H9fUOEtLK/I6w", "stockholm",
	"RgoxDxYGuZ0GzijFt6kSQQ", NULL,
	"RrhB72r1de9N65EG4pgqJw", "tnr-mode-front",
	"RyXm3yMC4ejlFvwlEI509w", "hearingaid-low-energy-audio",
	"S5Bf9bF00BTHrySCydAkdg", "bitrate-2g",
	"SMgTCc2v9AU2ziIHRX8qww", "hearingaid-audio-equalization",
	"SNfDJgQFV2Xj7+WnozcJPw", "main-screen-scale",
	"SjED0v6tcI1c4fqvuUYAqQ", "EffectiveProductionStatus",
	"SmUPjD77AHIMCLny9nawdw", "ConfigNumber",
	"Sr1Bmb3uoIfgKyaZguG0Gw", "front-max-video-fps-4k",
	"SvI9oDkzw4XJFd+dKen/2Q", "DeviceVariant",
	"T9MMpvl0fu59PO8lXi/Cxg", "effective-production-status",
	"TF31PAB6aO8KAbPyNKSxKA", "UniqueChipID",
	"TTZrMo1OOEUELa7asaQ7xg", "RearFacingCameraHDRCapability",
	"TXZxlSojLMQyLqusm9aa/g", "DeviceEnclosureRGBColor",
	"TZ/0j62wM3D0CuRt+Nc/Lw", "ProductHash",
	"ToJGxfcjkIerYyeL2e8c4A", "BasebandBoardSnum",
	"TqrlqJOZiAuRx8Qu3SVr+Q", NULL,
	"Tr9qG122eEQiKGfr6EGRdQ", "RegionalBehaviorEUVolumeLimit",
	"TucF/tpjeAln1I0f3g0K3w", "does-not-support-gamekit",
	"Ty5/C8UDfdjcdR853kulmA", "FrontFacingCameraBurstCapability",
	"U+73bmG4kBGj6kpreQXUTQ", NULL,
	"U1fcnNYCEu9uH1bg3/6PKQ", "image4-supported",
	"UCG5MkVahJxG1YULbbd5Bg", NULL,
	"UF3CoK9RCYXfTyzttoxNDQ", NULL,
	"UFqkf9tcH1ltsOMzpdwSUw", "multitasking-gestures",
	"UYZtXbMcIyMRZQ9pjDxRvA", "ui-weather-quality",
	"UZyrJHlX635ocWEjBkt9YA", "UIProceduralWallpaperCapability",
	"Ue0GVAyEOkP5kyQgcXKlxg", NULL,
	"V2Ykm/0M3CA6nyNhwNInsg", "64-bit",
	"V5QFNbWGgrw+UZPvgIbDvQ", NULL,
	"VG9TCKNqNLCHk0J6zTkuVQ", "EthernetMacAddress",
	"VHTcx7WQq0V7YgGKZisRWA", "FDRSealingStatus",
	"VLfT+eziZYKNjRASM6ntnQ", "FirmwareNonce",
	"VasUgeSzVyHdB27g2XpN0g", "SerialNumber",
	"VrWzQGQK9Fbp/RqeQ08VzA", "car-integration",
	"W5kTfIuxkKpHVdWTk42C9A", "n78a-mode",
	"W7MyZ3Yxoy1qD6FrI0mLUQ", "RF-exposure-separation-distance",
	"WC6wwFV23k19BlUQIAwDTg", "RearFacingCameraMaxVideoZoomFactor",
	"WbcphnnzI6Yb5r/AzwiyUA", "mix-n-match-prevention-status",
	"X9NA3D+PguwY0i0Uffl07Q", "camera-rear",
	"XACgWnmwo1t6swUPu+/UUQ", NULL,
	"XEoV4os3FAUL7yHDxWmSMw", "AppStore",
	"XEp4h49dagkYL6YrtjW1Kw", NULL,
	"XEzIXVPlevxuEIpZrMY+8A", "Skey",
	"XFtUsQP3AyqT4CazSb5VCw", "DeviceSupportsCarIntegration",
	"XFx5cRZ0tJYl+xJUCEgjnQ", NULL,
	"XI87Zkcr5j1B/p2dom/B6A", "AWDLCapability",
	"XIcF5FOyQlt/H79oFw9ciA", NULL,
	"XKpK5v7PGzbe7igjloNBpQ", "closed-loop",
	"XQBHOWjPt2P+uNqlLm1P7A", "BasebandClass",
	"XSLlJd/8sMyXO0qtvvUTBQ", "bluetooth",
	"Xa9nxhMDoHTdmrn/FufA3g", NULL,
	"XellXEQUbOIgUPoTrIj5nA", NULL,
	"XkfGVYlrkBQJgaGabHESJQ", "DeviceSupportsDClr",
	"XrPbSCNx9X7Lyw9oGPgMDQ", "WirelessBoardSnum",
	"XxnEk9uiIk5vCdbWEGt7lA", NULL,
	"Y2TwC8z+XeBBBswnLf7JsQ", "PeekUIWidth",
	"Y2Y67z0Nq/XdDXgW2EeaVg", "FMFAllowed",
	"YH5LeF090QGZQTvT76qcBg", NULL,
	"YUobJKXH3+ukrUe13TXL3Q", "BasebandPostponementStatusBlob",
	"YVNo6vlMjhgQ9yGYV8gatw", "PanelSerialNumber",
	"YcNAX0Gc6KejQPKSrA9kvg", "front-burst",
	"YdPNjYPBzyE0jJl2X/CayQ", "RotateToWakeStatus",
	"Yk5H+MlMreeaBLjv6PPFDw", "camera-front",
	"YzrS+WPEMqyh/FBv/n/jvA", NULL,
	"Z/TJS0IaLyq10dcSvUTGPg", "DieId",
	"Z/dqyWS6OZTRy10UcmUAhw", "marketing-name",
	"Z3gOHeppbL6+rblIe8H5Ag", "BasebandRegionSKURadioTechnology",
	"ZApuwHXWV8RCz0iVzMnqLw", "telephony",
	"ZEF0po63sTgpVrc9Ce7YNQ", "DMin",
	"ZYqko/XM5zD3XBfN5RmaXA", NULL,
	"ZeGw3VKUymUAbp4noKsxoQ", "FrontFacingCameraMaxVideoZoomFactor",
	"ZepchaN1K8XQJaBUMMlV5w", "iAP2Capability",
	"ZgIjA7Nwd2jSo13lhDO1Iw", "RegionalBehaviorShutterClick",
	"ZiSlYxYSkyaYImU3lrSXWw", "gps",
	"a/mex6YNO6gJ8N8SxAA71A", "DeviceSupportsCrudeProx",
	"a13V9f9x26JcQCfczZAVAw", "ui-traffic-cheap-shaders",
	"a5BRUxn1QBPXkAnbAHbmeg", "SigningFuse",
	"a6vjPkzcRjrsXmniFsm0dg", "ShouldHactivate",
	"aCQx2Qq/TChnNAq1rr6Egw", "DeviceSupportsAlwaysOnCompass",
	"aCuWsar9Ayou7Vfkh7fdLw", "front-slowmo",
	"aH78kNnsHDm9yHe6vSJYNw", "builtin-mics",
	"aHrkhDFY/f2ophZ5/MqX0w", "face-detection-support",
	"aIJva0DAnD6KdrSpPF11xQ", NULL,
	"aNZeCyuWxI6SE/KwZ129wA", "name",
	"aOq/O8u9f/bpWUnKco+xgA", "EthernetMacAddressData",
	"aoAKcHLuTUp/o3squcJkhA", "CompassCalibrationDictionary",
	"b/KizANb5o/dWqSP2GC23g", "public-key-accelerator",
	"b/k0ZT2f/WGV2qJSyPJHoQ", "config-number",
	"b5K3g59Aj+Cc982n9FQv8A", "display-rotation",
	"bUJD9zcOcitJwFA0ieIkxA", "DeviceBackGlassMaterial",
	"bbyFCcrPE+k6ZXIVbXSFUw", NULL,
	"bhPjDx/0UDk9mzia6ksZ+A", "nike-support",
	"bl+nF6e86pLBiPP4pVJ24g", "PhotoCapability",
	"bxQyyA/qJ3QQXTiHBsW2eg", NULL,
	"bysMryc4yLwQjKvUQGGXXQ", "DevicePrefersProceduralAntiAliasing",
	"c5uqoV7Z9ly+f4c5mYXILg", "OfflineDictationCapability",
	"c7fCSBIbX1mFaRoKT5zTIw", "WifiVendor",
	"cBy4BcYs5YWtFHbBpt4C6A", "DeviceSupportsHaptics",
	"cHla4KIe1wv0OvpRVrzy/w", "hide-non-default-apps",
	"cRjPy4Ef+KZwJ+nfPeBV5Q", NULL,
	"cX1+ZsVacGTXWVKB9enYow", "shoebox",
	"cZflGJ39lJHTCPy35/N14Q", NULL,
	"ce5pjDJVSOxjcg1HwmAezA", "opengles-2",
	"cganRwxlDnONJx4WeDI0kQ", "firmware-version",
	"cmkS/KVB6ubxH76sLbumbw", "rear-max-slomo-video-fps-1080p",
	"ct5Dh6u0D6WDJKg2PrMFVQ", NULL,
	"cux58RcuSiBhpxWnT3pE4A", NULL,
	"d27R2IjPvpwp+MX/kUbJ2w", "PintoMacAddress",
	"diS7AlmwFZz6NyF7CY97sA", NULL,
	"dp7SlZZQotrh8McQoH1xFA", "rear-facing-camera",
	"drPpRw0Jmqcxv1XQPn/q/Q", NULL,
	"e9aZViEIJ/riA4pRfg1ihg", "device-color-policy",
	"eH9J8yYIjbRNxg2EeYvNFg", "UIBackgroundQuality",
	"eKgHzGQ9HnWdPKX7W16OAg", "ui-reachability",
	"eLfYx8jpSlim2NZFIonPzA", "WifiCallingSecondaryDeviceCapability",
	"eNgz35a7iZnVeEMwsBQZew", "BasebandChipId",
	"eQd5mlz0BN0amTp/2ccMoA", "SupportsForceTouch",
	"eXCYx/SHTZIn5LInWvZByw", "call-waiting",
	"eZS2J+wspyGxqNYZeZ/sbA", "WifiAddressData",
	"ebyBs0j3KAquBsgcfrNZIg", "CellularTelephonyCapability",
	"edlvEg0UXOGErRDpk7O5Fg", "calibration",
	"ee9YfVjjsbVte45bYSt4Wg", "DeviceSupportsNavigation",
	"eg8KDO//lXaLwp+URGP6DA", "CertificateSecurityMode",
	"emXA9B552rnSoI7xXE91DA", NULL,
	"eu8pUYR7yo+AlS9aojlgwg", "DeviceSupports4G",
	"euampscYbKXqj/bSaHD0QA", NULL,
	"f+PE44W6AO2UENJk3p2s5A", "SupportsLowPowerMode",
	"f2DlVMUVcV+MeWs/g2ku+g", "BatteryCurrentCapacity",
	"f8peylp799CJta4Ev/vz8A", "hiccough-interval",
	"fAwIjGT2efY3MHaGNHbCeQ", NULL,
	"fGpT09KNGDBjFXnqcVbgbw", "RearFacingCameraHFRCapability",
	"fJZs6N8SqTS4RuQVh3szxA", "camera-front-flash",
	"fW9C1U4C1FR8bwe1VqEWug", "full-6",
	"fdh+s6j3VijuyrK7xLjd7g", "main-screen-class",
	"fh6DnnDGDVZ5kZ9nYn/GrQ", "hdr-image-capture",
	"fkWvcjYnYzGRhVKiBoYzgA", "HasPKA",
	"fqNtLlgbYfa9gSNrhg7VGQ", "encode-aac",
	"frZQaeyWLUvLjeuEK43hmg", "InverseDeviceID",
	"fuKL2rMywRgQF1wowOA/cg", "DeviceBrand",
	"fucd7llSuoCNHrrvLS1QQg", "watch-companion",
	"fv8ZXM/NhUHQBQqCSk19cA", "FrontFacingCameraAutoHDRCapability",
	"g+9NpjmSVwtomd8nyNJimg", "armv6",
	"g1gP4TOlkv84ezeaZrUgFg", "thin-bezel",
	"g7YQ1Djxh4YiKlEeaoGhzg", "main-screen-width",
	"g7vU4YF+9Z+wkSvw/Cm8Dg", "CoreRoutineCapability",
	"g7yZjVHqRxbfXOqns+Sm9w", "front-hdr",
	"gI6iODv8MZuiP0IA+efJCw", "WifiAddress",
	"gKzNJcweSCWA+gIGiUQJ5w", "mms",
	"gPoIZFd4NhmSKrk67qH80w", "SBCanForceDebuggingInfo",
	"ghpAuGJlPoauWijdtPi7sQ", "UserAssignedDeviceName",
	"gk8sn5Vi0s088gEvssfbOg", "front-hdr-on",
	"gnQwi8RnEeMG9qBe3IUKrg", "DeviceCoverGlassMaterial",
	"gq0j1GmcIcaD4DjJoo9pfg", NULL,
	"gqDnklGQnpv5ilgh5uHckw", NULL,
	"grEfqkFsEkgnJH1nl9sodw", "effective-production-status-sep",
	"gukw/2mRLE2GyqXJFEu7ng", "rear-hdr-on",
	"guykxGaRwHdenUK8fJRl8w", "gamekit",
	"h0OUwg53vKp+IPdzLG4NrA", "mesa",
	"h63QSdBCiT/z0WU6rdQv6Q", "RegionCode",
	"h8xonkkn/NzDMvvCXLw8cQ", "FaceTimeBitRateWiFi",
	"h9jDsbgj7xIVeIQ8S3/X3Q", "ProductType",
	"hK/qlAUuu8u6SvIRkKmjQQ", "gps-capable",
	"hLzlRnTuKAcQLWtNwCFSfg", "nfcWithRadio",
	"hOkus/A6lBEF6Ar3jV7LrQ", "DeviceSupportsRGB10",
	"heP+NTlvkhJ2zzWIUznawA", "MobileEquipmentInfoBaseProfile",
	"hfs43coKE3iWlCwnqPgBpg", "IceFallID",
	"hh/NoWpEoq1VF4lGDdPUpw", "role",
	"hiHut/WR+B9Lx/vd0WyeNg", "wapi",
	"htWSrEg/cfn3squdzvER/w", NULL,
	"hwd9ipb6eezNleN59+6n1Q", "metal",
	"hx2qJfJRLZ9Sseb37IcQow", "ringer-switch",
	"hykQtM0zkNe2/IY69+3PPQ", "ApNonce",
	"i8+iwUtLtB5jT+WNvqwrEA", "encrypted-data-partition",
	"iBLsDETxB4ATmspGucaJyg", "IsLargeFormatPhone",
	"iFOX66VmcijipO3YRc+AXg", "display-mirroring",
	"iaegRQa4jNGOuTA6hnZmcQ", "DeviceCoverMaterial",
	"ibhfX8FQ6b809N632Ey98g", NULL,
	"iifCuJmggYlB4hLzc0Zoiw", "rear-max-slomo-video-fps-720p",
	"inECTnNyR97XWxm7jU8uqA", "MobileEquipmentInfoBaseId",
	"inLiSl5OQHJ1stAIvKH8wg", "RegionalBehaviorGB18030",
	"is3zjHrEdiF6J/boqghy9A", NULL,
	"ivIu8YTDnBSrYv/SN4G8Ag", "ProductName",
	"iyfxmLogGVIaH7aEgqwcIA", "green-tea",
	"izFv/qj3IUhqAIiEuYH6xw", "MusicStore",
	"j4dedPnpeXouudM1+j3dsQ", "IntegratedCircuitCardIdentifier",
	"j9Th5smJpdztHwc+i39zIg", "MarketingProductName",
	"jBGZJ71pRJrqD8VZ6Tk2VQ", NULL,
	"jJ+5tnncQBcHo5T26MI5jA", "debug-board-revision",
	"jKFTzVOYcfTfNBh+yDrprw", NULL,
	"jMiqevikb6QWeHOhvLsw6A", NULL,
	"jPfKgbKUk+Vl6s7DaotqIA", NULL,
	"jSDzacs4RYWnWxn142UBLQ", "BluetoothAddressData",
	"jWdMTTxiAZc+KNO6Bz2jNg", "MesaSerialNumber",
	"jdLgKT+0BZmGrzd9J0cuPA", "camera-flash",
	"jewva1LRTg17HDPWdj+TLw", NULL,
	"jgE7MmmkZAG0BiWVqD7bMQ", "BatteryIsFullyCharged",
	"ji56BO1mUeT7Qg9RO7Er9w", "DeviceSupportsASTC",
	"jyEyRLza0L3StNXgFUCoTw", NULL,
	"k5lVWbXuiZHLA17KGiVUAA", "BluetoothAddress",
	"k7QIBwZJJOVw+Sej/8h8VA", "CPUArchitecture",
	"kKgJsWN/rBUAkimOtm/wbA", "arm64",
	"kMHGt7N4hx12NopZFcIz6Q", NULL,
	"kQ8nm82jb5iTBUwT2M3aIQ", NULL,
	"kWVFqaGBc3nOGmfM+ZpoEg", "tv-out-settings",
	"kc+QzHP4Frf2NvP+mclQ2A", "wifi-chipset",
	"kjKnJNt7HY90iN6rpbSeFQ", "RegionalBehaviorNoWiFi",
	"kkSkHTEei96N1ZehicOgoA", NULL,
	"knoZzQDWpu6OQWS7wgRWLg", "rear-max-video-zoom",
	"kt7gXeIiU0dLEKrgUSsLVw", "opengles-1",
	"kyszW/uUGJFTVNQwFaf6og", NULL,
	"kyxFFGn+VS4L9a/bsvm19Q", "Image4Supported",
	"l/n0Z7pMB8k0GyTKz3v3Tw", "AirplaneMode",
	"lM8BH5myz/qFGeIYnsiEoQ", NULL,
	"lMKshPs7fX9YxDSOEXnoiw", "effective-security-mode-sep",
	"lR7sjp4tOz3cMWoEMlZrLA", NULL,
	"lSpe1QHIya0aeNrjLTtaJA", "hidpi",
	"lUryPpLkvlffpEKiNnEchA", "slow-letterpress-rendering",
	"lY6RKv6ri6kfBeJCWilmmQ", "front-burst-image-duration",
	"ld2eewXs5StVwdRtwYT8sw", NULL,
	"lo3szoQ4sLy7o3+ZD0GcAQ", "ambient-light-sensor",
	"lvmEUCUwik8sO0yGU1rBOA", "rear-max-video-fps-4k",
	"lwHRTZNO5Jq87pVlzdNGIA", "DeviceSupports720p",
	"m+FD6mX8VZzP95hOhM+jow", "hw-encode-snapshots",
	"m4UsCAgXhbdnwLebiyKMiw", "UIReachability",
	"mAAbkChrX3bpxPIffHG1BQ", "front-facing-camera",
	"mVenV0u+B3ShrqRddinaFQ", "homescreen-wallpaper",
	"mZfUC7qo4pURNhyMHZ62RQ", "BuildVersion",
	"mm4/5+X4Z+eStXyipfwWVQ", "rear-cam-telephoto-capability",
	"mmu76v66k1dAtghToInT8g", "UIParallaxCapability",
	"mtHZd1H8XJ2DMVtNVUSkag", "wifi",
	"mtrAoWJ3gsq+I90ZnQ0vQw", "DeviceClassNumber",
	"mug/QuG6jZ3CYR9p7OWQaw", NULL,
	"mumHZHMLEfAuTkkd28fHlQ", "DeviceColor",
	"n/G0fQIQiom+pb4tHA4Mmw", "device-colors",
	"n/aVhqpGjESEbIjvJbEHKg", "SphereCapability",
	"nFRqKto/RuQAV1P+0/qkBA", "UniqueDeviceIDData",
	"nSo8opze5rFk+EdBoR6tBw", "RestrictedCountryCodes",
	"nVh/gwNpy7Jv1NOk00CMrw", "MedusaPIPCapability",
	"nZUUCFZgomfWUIPGGzNAqg", "SecureElementID",
	"na6PSBfq05lkAfWkwAwaGg", "ExternalPowerSourceConnected",
	"na77zbwlhy0V7shc4ORRgA", "post-effects",
	"nmOy2K5HzAAs2QNAi8wR+Q", "SupportsRotateToWake",
	"nteaxwXwZWYUI9z46VDRnA", "video-camera",
	"nudr7/i2XcxAX6widrWShg", "certificate-security-mode",
	"nv4RoLkNoPT0/rsO8Yaiew", "still-camera",
	"o/mXrucvBSYUXqq7K3UzKA", "IsThereEnoughBatteryLevelForSoftwareUpdate",
	"o0axLo6LGt8HbuEWdLZ92Q", "auto-focus-camera",
	"o60T6wXe1DDaO4a4gw10TA", "TristarID",
	"oBbtJ8x+s1q0OkaiocPuog", "MainScreenStaticInfo",
	"oJGeec0N+MudCr0LsdtbHw", "MobileSubscriberNetworkCode",
	"oPeik/9e8lQWMszEjbPzng", "ArtworkTraits",
	"oTuH4/axV9s11/jKxB2z2A", "ui-no-parallax",
	"ohnQBWkVQf2nu9Vr/9uLug", "DeviceSupports3DImagery",
	"ol92SaBpqIvQs+KBljuwGA", "kConferenceCallType",
	"oxH8p7+EaUzhkc2edrXsQQ", "aggregate-cam-photo-zoom",
	"pB5sZVvnp+QjZQtt2KfQvA", "BasebandChipset",
	"pCbWB0w5vhsKbGJHs/c1jQ", "youtube",
	"pLzf7OiX5nWAPUMj7BfI4Q", "SupportsIrisCapture",
	"pX2TxZTxWKS7QSXZDC/Z6A", "HasBattery",
	"paR6NqTecAD44x45kzV87g", "AudioPlaybackCapability",
	"pdFo85PUvIiT4FjAT6Amcw", "baseband-chipset",
	"plaYa8bKJaAF5Erc5nvZ+g", "RearFacingCameraBurstCapability",
	"po7g0ATDzGoVI1DO8ISmuw", NULL,
	"pxqIJ789zCoOILWO6cQ52Q", "no-simplistic-road-mesh",
	"q4cLktMwtrx8dCJAQTeqTg", "PanoramaCameraCapability",
	"q69vauqK9djnPlEZBFBV4A", "DeviceSupportsSiDP",
	"qHVhw5NhezD+ljFUPvQb4g", "nike-ipod",
	"qNNddlUK+B/YlooNoymwgA", "ProductVersion",
	"qOwiNS0eFEq9oi3MNsgxWg", "FirmwarePreflightInfo",
	"qWG594bTi87edQCSYxlLeA", "EUICCChipID",
	"qWGVjnlN/wWMhlWgfNcSBg", "DeviceSupports9Pin",
	"qb//mYg6KeTmjv8w4ZAMIg", "CompassCalibration",
	"r/++Z94rbTcHrTtZ/rCU4w", NULL,
	"re6Zb+zwFKJNlkQTUeT+/w", "UniqueDeviceID",
	"rkFHO5dZmWxy3QdOx7r7kA", "MobileEquipmentInfoCSN",
	"rkqlwPcRHwixY4gapPjanw", "DeviceName",
	"rxUlSnmihTL8oDg9Hrgq/A", "MixAndMatchPrevention",
	"s+gaKNe68Gs3PfqKrZhi1w", "MonarchLowEndHardware",
	"s2UwZpwDQcywU3de47/ilw", "microphone",
	"sJ0n0UZHSUVJbmyy2p54Cw", "unified-ipod",
	"sLe8lqXRlqZM74MNUoVcyQ", "BatteryIsCharging",
	"sYxZdpH3i8nwjZNet0QuAw", "fcm-type",
	"spGVujDEmyCzDznXozB3oA", "apn",
	"tOLVnZop4m/g5/iuC7zlUw", "effective-production-status-ap",
	"tUIqcYyzHuWBvBQHsLk8IQ", "international-settings",
	"tYqUcLmLfhmk7vOgdFvURg", "sim-phonebook",
	"tad3RCjcWdkyLSVI8kUBtA", "caller-id",
	"tdUWKiiM7JdctnzQuvaVVA", "c2k-device",
	"tuwdHA2NDGnLajCo5K3UUA", "voice-control",
	"u3c0R+31Df4SUTHrICQkTg", "hearingaid-power-reduction",
	"uAIY4Jb2A7Fy2aLrlDU1gg", "RemoteBluetoothAddress",
	"uB6I2WImAHd8DEGuqYcMbw", "WifiFirmwareVersion",
	"uKc7FPnEO++lVhHWHFlGbQ", "ipad",
	"uO3aPe7lfB1XxSiz/1xBvA", "CertificateProductionStatus",
	"ueuuYQk48HGWm/cJlHq/Dw", "fcc-logos-via-software",
	"ulMliLomP737aAOJ/w/evA", "IsSimulator",
	"ulPs+OBjapRJaJ6Ech3OFA", "h264-encoder",
	"uyejyEdaxNWSRQQwHmXz1A", "DiskUsage",
	"v/BP9Nx/zfo9bKi9JR2p+g", "3d-imagery",
	"v2Q3tLoD4+o/XazHC/0FyA", NULL,
	"v5YVEwERRin1v+reUNjJ5w", "ComputerName",
	"v9YZN998zL0OLA3q6SpPQA", "HasIcefall",
	"vENa/R1xAXLobl8r3PBL6w", "EffectiveSecurityModeAp",
	"vIccod02kDxScKF5s2h6OA", "BasebandUniqueId",
	"vaiFeAcMTIDXMSxTr8JwCw", "BasebandPostponementStatus",
	"vl45ziHlkqzh1Yt6+M9vBA", "displayport",
	"vmZuX/fdqt3gKhonHYLyUw", "aggregate-cam-video-zoom",
	"vwZ4ohiPF3w3M1jzHbP30g", NULL,
	"wAbB2fAjUqUc6lNBelfWMA", NULL,
	"wBVgxg3VYUU5gawcgq7MXg", "SupportedKeyboards",
	"wOVK1nhmiAawowdbIwgyaQ", NULL,
	"whbsdxpLirBFgUbLH3+0JA", NULL,
	"wlxZYdEZITDTzgs/OnnV+Q", "personal-hotspot",
	"wtUF1NceYeLT6IHovZvocw", "DeviceSubBrand",
	"xDg5n/9rR2cMhp7MK0irBA", "healthkit",
	"xJUG7IKySthRrPcxII184g", "enforce-shutter-click",
	"xOEH0P1H/1jmYe2t54+5cQ", "MobileEquipmentIdentifier",
	"xOJfWykLmQCc8lKlzMlrLA", "assistant",
	"xSh3mf5+Zuoz6xhxEah0zQ", "DeviceSupportsTethering",
	"xU1eZLRifcixnyDzjo52DQ", "tv-out-crossfade",
	"xUHcyT2/HE8oi/4LaOI+Sw", "PartitionType",
	"xXBT4e92qXwQo3SYmACj3w", "die-id",
	"xYu8vn4nnbLnNTbBe0FT+w", NULL,
	"xZm4Ky2/qswyf7ykvlchYg", "CPUType",
	"xbNo7dj2oAnz92JhEOn9tw", "DevicePrefers3DBuildingStrokes",
	"xleedMDMw0UV3fSgmHIoaQ", "kSimultaneousCallAndDataCurrentlySupported",
	"xsaMbRQ5rQ+eyKMKG+ZSSg", "PasswordConfigured",
	"xunfK8NYNCS75N2z2buKWQ", "FaceTimeBitRateLTE",
	"y0jtYciPmcx3ywPM582WZw", "ContinuityCapability",
	"yMmSdMRKEHRTCQ72ltCH1w", "airplay-mirroring",
	"yNesiJuidlesNpI/K5Ri4A", "PasswordProtected",
	"yPSUYPhrVLHnvX0TUugiwg", "rear-auto-hdr",
	"yRZv0s7Dpj8ZBk0S+0+nMA", "contains-cellular-radio",
	"yUCaqT4KOwJpYEb+XDPq7g", "SIMStatus",
	"ybGkijAwLTwevankfVzsDQ", "MainScreenCanvasSizes",
	"yeQy+rgNoD7+YIY6mSVOhg", NULL,
	"yeaE9+OrN2WJlWkDroMtZg", "bitrate-wifi",
	"yhHcB0iH0d1XzPO/CFd3ow", "DeviceSupportsApplePencil",
	"yl8qmYPdAhFLeDBho10sdQ", NULL,
	"z+5gEULGC7aEYopBd4ggpA", NULL,
	"zDBaE8nqtDP8hY4pOa6iMw", NULL,
	"zHeENZu+wbg7PUprwNwBWg", "RegionInfo",
	"zJUWenIp94snlzBD1cub3g", "function-button_halleffect",
	"zP3kBA1Biwz2d6PTIIbmUQ", "ActivationProtocol",
	"zPSNnYDFk+x5ebOtenb3Eg", "auto-focus",
	"znvmheFkjr6hiqIK9TrCVw", "pressure",
	"zxMIgVSILN6S5ee6MZhf+Q", "NFCRadio",


Having the list of keys, I created a simple iOS app that displays all the key/value pairs of libMobileGestalt:


This app runs on iOS 10.2 – jailbreak is not required. Note that it relies on private APIs and as such you shouldn’t use such code in shipping apps. You can download the source code here: MGCopyAnswerTable source


The libMobileGestalt library gives access to an incredible amount of useful data:

  • iOS information: DeviceName, ProductVersion, BuildVersion, DiskUsage, FirmwareVersion, …
  • Device capabilities: DeviceSupports4k, DeviceSupportsCarIntegration, DeviceSupportsHaptics, DeviceSupportsApplePencil, VibratorCapability, …
  • Camera capabilities: CameraLiveEffectsCapability, FrontFacingCameraHDRCapability, FaceTimeCameraSupportsHardwareFaceDetection, …
  • Device materials: DeviceRGBColor, DeviceCoverGlassColor, DeviceBackGlassMaterial, DeviceEnclosureMaterial, …
  • And a lot more: AirplaneMode, InternalBuild, FaceTimeBitRateLTE, MicrophoneCount, MinimumSupportediTunesVersion, …

It is strange that Apple only protected some of the key/value pairs behind an XPC service and not most of them. For the protected keys (like the “BluetoothAddress”), you will get a nil value and see a warning in the logs:

MobileGestalt.c:549: no access to k5lVWbXuiZHLA17KGiVUAA (see <rdar://problem/11744455>)

Due to the use of a weak algorithm, it was possible to deobfuscate most of the keys. With more GPU computing power and/or more time, it should be possible to recover the missing keys. Note that some keys did not match the formats I targeted:


Note: Before publishing this post, I discovered an excellent article from Jonathan Levin about libMobileGestalt.dylib. It provides a good overview and other details about libMobileGestalt.dylib and might be a good complement to this post.

Update 29.01.2017:
I updated the list of obfuscated keys to match the latest iOS release and to include some obfuscated keys I somehow missed. There are now 673 known obfuscated keys. I did not run a brute force to resolve the keys.

Testing if an arbitrary pointer is a valid Objective-C object

Posted: November 24th, 2016 | Author: | Filed under: Debugging, iOS, macOS, Programming | Tags: , , , , , , , , , | No Comments »

Let’s say you pick a random pointer. Can we know if it points to a valid Objective-C object? Of course without crashing… Well there is no simple solution. In this post I give a solution for 64-bit architectures. The code provided has only been tested on macOS 10.12.1 and iOS 10.1.1 with the modern Objective-C runtime.

There is not much documentation available on this subject. There is one article written in 2010 by Matt Gallagher but the content is outdated and not working properly anymore. Most of the information in this post comes from:


The content of this post – as well as the source code – relies on internal structures of the Objective-C runtime. There is no guarantee on the correctness and might possibly break with any macOS/iOS update. Don’t use this code in real applications!

In fact I started to write this post based on the objc4-680 sources (mac OS 10.11.6). But just before publishing, Apple released the sources for objc4-706 (macOS 10.12). As you can see in the image below, some internal structures I rely on have been changed:

Changes between objc4-680 and objc4-706

What is a pointer?

A pointer is just an integer referencing a location in memory. On iOS and macOS, there are however 2 kinds of pointers: regular pointers and tagged pointers. Let’s start by solving the problem for tagged pointers.

Tagged Pointers

Tagged pointers were introduced in iOS 7 and Mac OS X 10.7 for 64-bit architectures. A tagged pointer is a special pointer with data stored directly into the pointer instead of doing memory allocations. This has obvious performance advantages.

Tagged pointers are declared in objc-internal.h. In macOS 10.11 and earlier tagged pointers were simple:

  • 60 bits for the payload
  • 3 bits for the tag index
  • 1 bit to identify tagged pointer objects vs. ordinary objects

In macOS 10.12 the tagged pointer layout has been changed to also support 52 bits payload and more tag indexes:

// Tag indexes 0..<7 have a 60-bit payload.
// Tag index 7 is reserved.
// Tag indexes 8..<264 have a 52-bit payload.
// Tag index 264 is reserved.

The tag index tells you the class represented by the tagged pointer:

    OBJC_TAG_NSAtom            = 0, 
    OBJC_TAG_1                 = 1, 
    OBJC_TAG_NSString          = 2, 
    OBJC_TAG_NSNumber          = 3, 
    OBJC_TAG_NSIndexPath       = 4, 
    OBJC_TAG_NSManagedObjectID = 5, 
    OBJC_TAG_NSDate            = 6, 
    OBJC_TAG_RESERVED_7        = 7, 

    OBJC_TAG_First60BitPayload = 0, 
    OBJC_TAG_Last60BitPayload  = 6, 
    OBJC_TAG_First52BitPayload = 8, 
    OBJC_TAG_Last52BitPayload  = 263, 

    OBJC_TAG_RESERVED_264      = 264

Checking if a pointer is a tagged pointer is really simple using the functions declared in objc-internal.h:

static inline bool 
_objc_isTaggedPointer(const void *ptr) 
    return ((intptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;

static inline objc_tag_index_t 
_objc_getTaggedPointerTag(const void *ptr) 
    // assert(_objc_isTaggedPointer(ptr));
    uintptr_t basicTag = ((uintptr_t)ptr >> _OBJC_TAG_INDEX_SHIFT) & _OBJC_TAG_INDEX_MASK;
    uintptr_t extTag =   ((uintptr_t)ptr >> _OBJC_TAG_EXT_INDEX_SHIFT) & _OBJC_TAG_EXT_INDEX_MASK;
    if (basicTag == _OBJC_TAG_INDEX_MASK) {
        return (objc_tag_index_t)(extTag + OBJC_TAG_First52BitPayload);
    } else {
        return (objc_tag_index_t)basicTag;

Sadly these functions are static inline and not exported and we have no other choice but to copy the implementations in our source code.

On the other hand the function to get the registered class for the given tag is exported and can be used:

 Returns the registered class for the given tag.
 Returns nil if the tag is valid but has no registered class.

 This function searches the exported function: _objc_getClassForTag(objc_tag_index_t tag)
 declared in https://opensource.apple.com/source/objc4/objc4-706/runtime/objc-internal.h
static Class _objc_getClassForTag(objc_tag_index_t tag)
	static bool _objc_getClassForTag_searched = false;
	static Class (*_objc_getClassForTag_func)(objc_tag_index_t) = NULL;
		_objc_getClassForTag_func = (Class(*)(objc_tag_index_t))dlsym(RTLD_DEFAULT, "_objc_getClassForTag");
		_objc_getClassForTag_searched = true;
		if(_objc_getClassForTag_func == NULL)
			fprintf(stderr, "*** Could not find _objc_getClassForTag()!\n");
	if(_objc_getClassForTag_func != NULL)
		return _objc_getClassForTag_func(tag);
	return NULL;

It is now simple to create a function that checks if a pointer is a tagged pointer and thus a valid Objective-C object:

 Test if a pointer is a tagged pointer

 @param inPtr is the pointer to check
 @param outClass returns the registered class for the tagged pointer.
 @return true if the pointer is a tagged pointer.
bool IsObjcTaggedPointer(const void *inPtr, Class *outClass)
	bool isTaggedPointer = _objc_isTaggedPointer(inPtr);
	if(outClass != NULL)
			objc_tag_index_t tagIndex = _objc_getTaggedPointerTag(inPtr);
			*outClass = _objc_getClassForTag(tagIndex);
			*outClass = NULL;
	return isTaggedPointer;

If you want to read more about tagged pointers you can read these 2 articles from Mike Ash:

Now that we handled tagged pointers, let’s look at ‘regular’ pointers.


Valid pointers have to be aligned to the pointer size. Such a check is done in LLDB when trying to print a pointer in the python function ‘is_valid_pointer’ in objc_runtime.py:

    def is_valid_pointer(pointer, pointer_size, allow_tagged=0, allow_NULL=0):
        logger = lldb.formatters.Logger.Logger()
        if pointer is None:
            return 0
        if pointer == 0:
            return allow_NULL
        if allow_tagged and (pointer % 2) == 1:
            return 1
        return ((pointer % pointer_size) == 0)

The last check verifies that the pointer is aligned to the pointer size. We can implement the same check:

if (((uintptr_t)inPtr % sizeof(uintptr_t)) != 0)
  return false;

Bits used

The LLDB source code has another interesting function in objc_runtime.py:

    # Objective-C runtime has a rule that pointers in a class_t will only have bits 0 thru 46 set
    # so if any pointer has bits 47 thru 63 high we know that this is not a
    # valid isa
    def is_allowed_pointer(pointer):
        logger = lldb.formatters.Logger.Logger()
        if pointer is None:
            return 0
        return ((pointer & 0xFFFF800000000000) == 0)

Again we can easily implement the same check:

if(((uintptr_t)inPtr & 0xFFFF800000000000) != 0)
  return false;

Valid and readable memory

In order to be valid, a pointer should point to valid and readable memory. We can use vm_region_64() to ensure that the memory is readable and vm_read() to ensure that the memory is valid:

 Test if the pointer points to readable and valid memory.

 @param inPtr is the pointer
 @return true if the pointer points to readable and valid memory.
static bool IsValidReadableMemory(const void *inPtr)
	kern_return_t error = KERN_SUCCESS;
	// Check for read permissions
	bool hasReadPermissions = false;
	vm_size_t vmsize;
	vm_address_t address = (vm_address_t)inPtr;
	vm_region_basic_info_data_t info;
	mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;

	memory_object_name_t object;

	error = vm_region_64(mach_task_self(), &address, &vmsize, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &info_count, &object);
	if(error != KERN_SUCCESS)
		// vm_region/vm_region_64 returned an error
		hasReadPermissions = false;
		hasReadPermissions = (info.protection & VM_PROT_READ);
		return false;
	// Read the memory
	vm_offset_t readMem = 0;
	mach_msg_type_number_t size = 0;
	error = vm_read(mach_task_self(), (vm_address_t)inPtr, sizeof(uintptr_t), &readMem, &size);
	if(error != KERN_SUCCESS)
		// vm_read returned an error
        return false;
    return true;

Validating the isa pointer and getting the Class pointer

Now that we know the address points to valid and readable memory, we can extract the possible isa pointer and get the class pointer. This is documented by Greg Parker in [objc explain]: Non-pointer isa:

If you are writing a debugger-like tool, the Objective-C runtime exports some variables to help decode isa fields. objc_debug_isa_class_mask describes which bits are the class pointer: (isa & class_mask) == class pointer. objc_debug_isa_magic_mask and objc_debug_isa_magic_value describe some bits that help distinguish valid isa fields from other invalid values: (isa & magic_mask) == magic_value for isa fields that are not raw class pointers. These variables may change in the future so do not use them in application code.

Here is the implementation to validate the isa pointer and extract the Class pointer:

uintptr_t isa = (*(uintptr_t *)inPtr);
Class ptrClass = NULL;

if ((isa & ~ISA_MASK) == 0)
	ptrClass = (Class)isa;
		ptrClass = (Class)(isa & ISA_MASK);
		ptrClass = (Class)isa;

if(ptrClass == NULL)
	return false;

Verifying if the class exists

Now that we have the class, we can check if the Objective-C runtime knows it:

bool isKnownClass = false;

unsigned int numClasses = 0;
Class *classesList = objc_copyClassList(&numClasses);
for (int i = 0; i < numClasses; i++)
	if (classesList[i] == ptrClass)
		isKnownClass = true;

	return false;

Filtering out some false positives

A good trick from Greg Parker consists of filtering out some false positives by checking if the pointer malloc’ed size is greater than the class instance size:

size_t pointerSize = malloc_size(inPtr);
if(pointerSize > 0 && pointerSize < class_getInstanceSize(ptrClass))
	return false;

Summing it up

We now have all the elements to build a function that returns a boolean indicating if a pointer is an Objective-C object:

 Test if a pointer is an Objective-C object

 @param inPtr is the pointer to check
 @return true if the pointer is an Objective-C object
bool IsObjcObject(const void *inPtr)
	// NULL pointer is not an Objective-C object
	if(inPtr == NULL)
		return false;
	// Check for tagged pointers
	if(IsObjcTaggedPointer(inPtr, NULL))
		return true;
	// Check if the pointer is aligned
	if (((uintptr_t)inPtr % sizeof(uintptr_t)) != 0)
		return false;
	// From LLDB:
	// Objective-C runtime has a rule that pointers in a class_t will only have bits 0 thru 46 set
    // so if any pointer has bits 47 thru 63 high we know that this is not a valid isa
	// See http://llvm.org/svn/llvm-project/lldb/trunk/examples/summaries/cocoa/objc_runtime.py
	if(((uintptr_t)inPtr & 0xFFFF800000000000) != 0)
		return false;
	// Check if the memory is valid and readable
		return false;
	// Get the Class from the pointer
	// From http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html :
	// If you are writing a debugger-like tool, the Objective-C runtime exports some variables
	// to help decode isa fields. objc_debug_isa_class_mask describes which bits are the class pointer:
	// (isa & class_mask) == class pointer.
	// objc_debug_isa_magic_mask and objc_debug_isa_magic_value describe some bits that help
	// distinguish valid isa fields from other invalid values:
	// (isa & magic_mask) == magic_value for isa fields that are not raw class pointers.
	// These variables may change in the future so do not use them in application code.
	uintptr_t isa = (*(uintptr_t *)inPtr);
	Class ptrClass = NULL;
	if ((isa & ~ISA_MASK) == 0)
		ptrClass = (Class)isa;
			ptrClass = (Class)(isa & ISA_MASK);
			ptrClass = (Class)isa;
	if(ptrClass == NULL)
		return false;
	// Verifies that the found Class is a known class.
    bool isKnownClass = false;
	unsigned int numClasses = 0;
	Class *classesList = objc_copyClassList(&numClasses);
	for (int i = 0; i < numClasses; i++)
		if (classesList[i] == ptrClass)
            isKnownClass = true;
		return false;
	// From Greg Parker
	// https://twitter.com/gparker/status/801894068502433792
	// You can filter out some false positives by checking malloc_size(obj) >= class_getInstanceSize(cls).
	size_t pointerSize = malloc_size(inPtr);
	if(pointerSize > 0 && pointerSize < class_getInstanceSize(ptrClass))
		return false;
    return true;


To test this function, I built a simple iOS application that checks various pointers.
Here is the output when running on iOS 10.1.1 (64-bit):





24.11.2016: 2 changes based on feedback from Greg Parker:

  • Filtering out some false positives
  • Use objc_copyClassList() instead of objc_getClassList()

Analysis of the Facebook.app for iOS

Posted: October 18th, 2016 | Author: | Filed under: Analysis, Debugging, iOS, Programming | Tags: , , , , | 1 Comment »

Did you ever wonder why the Facebook.app for iOS is such a big download? This post tries to give some answers. The version 66.0 (released on 7 October 2016) was analyzed on an iPad Air 2 (64-bit).

Here is what you see when downloading Facebook on an iPad Air 2:

Download Info

App content

A scan of the content of the Facebook app using GrandPerspective gives already a good overview:


As you can see more than 100 MB are taken by the binary itself. The images (Assets.car), the localizations and other resources are only responsible for a small part of the size.

One note: The Facebook app seems to use the open source JSC framework. Unless there is a technical reason to use it, Facebook could remove this framework and use instead the JavaScriptCore framework built in iOS. JavaScriptCore is available on iOS 7 and later and Facebook targets iOS 8 and later.



One important point to note is that the Facebook binary I downloaded only contains 64-bit code. There is no 32-bit code. Facebook uses the Slicing mechanism which is described by Apple here:

Slicing is the process of creating and delivering variants of the app bundle for different target devices. A variant contains only the executable architecture and resources that are needed for the target device.



Let’s look at the Facebook binary. The entropy graph generated by Hopper gives a good idea of the content:


The red part (with biggest entropy) is actual code while the green and orange parts are other sections (strings, constants, …). Just by looking at the entropy graph, we know that around 50% of the binary is code.



By looking at the sections of the binary we indeed see that the (__TEXT, __text) section is more than 50 MB. Here is the list of sections sorted by size:

(__TEXT, __text): 56.60 MB
(__DATA, __objc_const): 16.57 MB
(__RODATA, __cstring): 5.02 MB
(__RODATA, __objc_methname): 4.55 MB
(__DATA, __const): 4.46 MB
(__RODATA, __gcc_except_tab): 3.87 MB
(__RODATA, __objc_methtype): 2.66 MB
(__DATA, __objc_data): 2.41 MB
(__DATA, __cfstring): 2.20 MB
(__DATA, __data): 1.66 MB
(__TEXT, __unwind_info): 1.58 MB
(__RODATA, __objc_classname): 1.42 MB
(__RODATA, __const): 1.40 MB
(__DATA, __bss): 0.89 MB
(__DATA, __objc_selrefs): 0.80 MB
(__DATA, __common): 0.54 MB
(__DATA, __objc_ivar): 0.38 MB
(__DATA, __objc_classlist): 0.24 MB
(__DATA, __objc_classrefs): 0.18 MB
(__DATA, __objc_superrefs): 0.13 MB
(__TEXT, __const): 0.07 MB
(__DATA, __objc_protolist): 0.06 MB
(__TEXT, __stubs): 0.02 MB
(__TEXT, __stub_helper): 0.02 MB
(__DATA, __la_symbol_ptr): 0.01 MB
(__DATA, __got): 0.01 MB
(__TEXT, __ustring): 0.01 MB
(__DATA, fbsessiongks): 0.01 MB
(__TEXT, __eh_frame): 0.01 MB
(__DATA, __mod_init_func): 0.00 MB
(__DATA, __objc_protorefs): 0.00 MB
(__DATA, __objc_catlist): 0.00 MB
(__DATA, __objc_nlclslist): 0.00 MB
(__DATA, FBInjectable): 0.00 MB
(__DATA, __mod_term_func): 0.00 MB
(__DATA, __objc_imageinfo): 0.00 MB


__RODATA segment

If you read carefully the list of sections, you did notice that the Facebook.app has a __RODATA segment containing sections generally found inside the __TEXT segment. The reason for this oddity is to work around an App Review limitation. As you can read on the App Review page:

Each Mach-O executable file (for example, app_name.app/app_name) must not exceed these limits:

  • For apps whose MinimumOSVersion is less than 7.0: maximum of 80 MB for the total of all __TEXT sections in the binary.
  • For apps whose MinimumOSVersion is 7.x through 8.x: maximum of 60 MB per slice for the __TEXT section of each architecture slice in the binary.
  • For apps whose MinimumOSVersion is 9.0 or greater: maximum of 400 MB for the size of the Mach-O binary file.


Facebook targets iOS 8.0 and later and as such its __TEXT segment should not exceed 60 MB. But if you sum up the __TEXT and __RODATA segments, you end up with more than 77 MB:

Total size of the __TEXT sections: 58.3 MB
Total size of the __RODATA sections: 18.9 MB

Facebook avoids this limitation by moving some if the __TEXT sections into the read only __RODATA segment. Implementing this trick is really simple: you just need to add a linker flag to rename the chosen sections. And it appears you need absolutely nothing at runtime: the renamed sections will be found automatically. This linker flag is described in the ld man page:

-rename_section orgSegment orgSection newSegment newSection
Renames section orgSegment/orgSection to newSegment/newSection.

You could use it to rename the (__TEXT, __cstring) section to (__RODATA, __cstring) by simply adding this line into the Other Linker Flags (OTHER_LDFLAGS):


You can download a sample macOS Xcode project here. If you load the original binary (without the section rename) using MachOView, you will see the (__TEXT, __cstring) section:

Standard Section

After adding the linker flag and recompiling, a (__RODATA, __cstring) section will be visible and the binary will run as expected:

Renamed Section

Note that this workaround is only needed as long as Facebook supports iOS 8. When targeting iOS 9 or later, the limitation is 400 MB which should be enough without splitting the __TEXT segment.


Third party libraries

So what does the code contain? Sadly I could not find a license/readme file containing the list of third party libraries. Also the Info view in Settings.app > Facebook > Settings > Infos is desperately empty – this seems like a Facebook.app bug.

By looking at the class names, I could build a list of third party libraries used. This list is most likely incomplete but hopefully correct:

  • AsyncDisplayKit: http://asyncdisplaykit.org
    keeps even the most complex user interfaces smooth and responsive
  • Bolts-ObjC: https://github.com/BoltsFramework/Bolts-ObjC
    Bolts is a collection of low-level libraries designed to make developing mobile apps easier
  • CocoaAsyncSocket: https://github.com/robbiehanson/CocoaAsyncSocket
    Asynchronous socket networking library for Mac and iOS
  • CocoaLumberjack: https://github.com/CocoaLumberjack/CocoaLumberjack
    A fast & simple, yet powerful & flexible logging framework for Mac and iOS
  • ComponentKit: http://componentkit.org
    ComponentKit takes a functional, declarative approach to building UI
  • EGODatabase: https://github.com/enormego/egodatabase
    EGODatabase is a thread-safe Objective-C SQLite wrapper with full support for asynchronous SQLite calls as well as built in NSOperationQueue support
  • FLAnimatedImage: https://github.com/Flipboard/FLAnimatedImage
    Performant animated GIF engine for iOS
  • FXBlurView: https://github.com/nicklockwood/FXBlurView
    UIView subclass that replicates the iOS 7 realtime background blur effect, but works on iOS 5 and above
  • Google Toolbox for Mac: https://github.com/google/google-toolbox-for-mac
    Google Toolbox for Mac
  • JSONKit: https://github.com/johnezang/JSONKit
    Objective-C JSON
  • libphonenumber: https://github.com/iziz/libPhoneNumber-iOS
    iOS port from libphonenumber
  • OAuth2Client: https://github.com/nxtbgthng/OAuth2Client
    Client library for OAuth2
  • OCMock: http://ocmock.org
    Mock objects for Objective-C
  • pop: https://github.com/facebook/pop
    An extensible iOS and OS X animation library, useful for physics-based interactions.
  • nighthawk-webrtc: https://github.com/ceaglest/nighthawk-webrtc
    Custom builds of WebRTC for iOS
  • React Native: https://facebook.github.io/react-native/
    Build Native Mobile Apps using JavaScript and React
  • ZipArchive: https://github.com/ZipArchive/ZipArchive
    ZipArchive is a simple utility class for zipping and unzipping files on iOS and Mac
  • ZipZap: https://github.com/pixelglow/ZipZap
    zip file I/O library for Mac OS X and iOS


Objective-C awards

The awards for the best Objective-C classes is attributed to:


The award for the longest Objective-C method is attributed to:

- (void)[FBComposerCompositionStateMutation matchBootstrap:



The Facebook app is a complex app and not just a simple web view. It contains a lot of third party libraries and even splits its __TEXT segment in order to not reach the 60 MB limit imposed by Apple.

Update 10.04.2017:
There is a newer blog post about the version 87.0:
Analysis of the Facebook.app for iOS [v. 87.0]

constructor and destructor attributes

Posted: July 16th, 2016 | Author: | Filed under: code injection, Debugging, macOS, Programming | Tags: , , , , , , | 3 Comments »

GCC (and Clang) supports constructor and destructor attributes:



A function marked with the __attribute__((constructor)) attribute will be called automatically before your main() function is called. Similarly a function marked with the __attribute__((destructor)) attribute will be called automatically after your main() function returns.

You can find the GCC documentation here:

The constructor attribute causes the function to be called automatically before execution enters main (). Similarly, the destructor attribute causes the function to be called automatically after main () has completed or exit () has been called. Functions with these attributes are useful for initializing data that will be used implicitly during the execution of the program.
These attributes are not currently implemented for Objective C.

Note: The GCC documentation tells that these attributes are not implemented for Objective-C. However this seems to work as expected with my tests using Clang ‘clang-703.0.31’ from Xcode 7.3.1.


Here is an example of C code to demonstrate these attributes:

// To compile:
// clang -o constructor constructor.c

#include <stdio.h>
void constructor() __attribute__((constructor));
void destructor() __attribute__((destructor));

int main()
	printf ("main called\n");
	return 0;

void constructor()
	printf ("constructor called\n");

void destructor()
	printf ("destructor called\n");

When running this application, you will see the following output logs as you would expect:

constructor called
main called
destructor called

How does it work under the hood?

When you mark functions with these attributes, the compiler will create in your binary the sections called __mod_init_func for the constructors and __mod_term_func for the destructors. These sections contain the list of function pointers. You can use the excellent MachOView to see these sections:


When your application is launched, dyld will call the constructors before your main() function is called. This is handled by the following dyld function:

void ImageLoaderMachO::doModInitFunctions(const LinkContext& context)

The destructors are handled by the dyld function:

void ImageLoaderMachO::doTermination(const LinkContext& context)

Since dyld is open source you can look at the implementation in the file ImageLoaderMachO.cpp of dyld. The source code for macOS 10.11.4 is available here.

Example of use

  • The first obvious usage is to be able to initialize some global variables with a constructor and do some cleanup with a destructor. It could be used to initialize some libraries too.

  • Another usage is code injection. In a previous post ‘Simple code injection using DYLD_INSERT_LIBRARIES’ I wrote code to replace some methods with an Objective-C +(void)load class method. Using a constructor would allow to inject code earlier in the process.

  • A constructor attribute could be used to implement a software protection. You could encrypt your executable with a custom encryption and use a constructor function to decrypt the binary just before it is loaded.

State Preservation and Restoration Debug Logs

Posted: July 8th, 2016 | Author: | Filed under: Debugging, iOS, macOS, Programming | Tags: , , , , , | No Comments »

The State Preservation and Restoration system is well documented here:
Preserving Your App’s Visual Appearance Across Launches.

But what is not well known is that there is a secret preference to enable debug logs. You can set the preference UIStateRestorationDebugLogging to YES in your main function before the call to UIApplicationMain:

[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"UIStateRestorationDebugLogging"];

There is also a less useful ‘Developer Mode’ secret preference which will skip the deletion of the restoration archive when the app crashes. To enable this mode set the preference UIStateRestorationDeveloperMode to YES in your main function before the call to UIApplicationMain:

[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"UIStateRestorationDeveloperMode"];

Checking if Reduced Motion is enabled on iOS 7

Posted: May 20th, 2014 | Author: | Filed under: Debugging, iOS, Programming | Tags: , , , | No Comments »

Apple introduced in iOS 7.0.3 a setting to reduce motion ( http://support.apple.com/kb/HT5595 ) :
Settings -> General -> Accessibility -> Reduce Motion

Reduced Motion Setting

Sadly there is no public API to know if the user enabled “Reduce motion”.
Here is how to get the value of this setting using a private API. Note that you should not use this code for applications submitted to the App Store.

#include <dlfcn.h>

+ (BOOL) reduceMotionEnabled
	BOOL (*_UIAccessibilityReduceMotionFunction)(void) = (BOOL (*)(void)) dlsym(RTLD_DEFAULT, "_UIAccessibilityReduceMotion");
	if(_UIAccessibilityReduceMotionFunction != NULL)
		return _UIAccessibilityReduceMotionFunction();
		NSLog(@"Unsupported: _UIAccessibilityReduceMotion does not exist on this iOS version");
		return NO;

Edit: Apple added a public API on iOS 8:

// Returns whether the system preference for reduce motion is enabled
UIKIT_EXTERN BOOL UIAccessibilityIsReduceMotionEnabled() NS_AVAILABLE_IOS(8_0);

QuickLook plugin to preview .strings files

Posted: March 25th, 2014 | Author: | Filed under: Debugging, macOS, Programming | Tags: | 2 Comments »

OS X and iOS use .strings files for localized text strings as described in the String Resources Documentation:

Resource files that contain localizable strings are referred to as strings files because of their filename extension, which is .strings.

It is yet annoying that OS X and Xcode don’t provide a built-in QuickLook plugin for previewing .strings files. When you preview such a file in QuickLook you see this window:

Without 'StringsFile' QuickLook plugin

To solve this issue, here is a simple QuickLook plugin called ‘StringsFile’ that lets you preview .strings files (plain text .strings and binary property plist .strings).
This QuickLook plugin is really useful to quickly check the content of a .strings file. When you preview such a file, you will now see:

With 'StringsFile' QuickLook plugin

A precompiled version can be downloaded here: StringsFile.qlgenerator.zip

You can download the source code here : Download ‘StringsFile’ Source Code


  • Download the precompiled version
  • Unzip
  • Copy the file into /Library/QuickLook/ or ~/Library/QuickLook/
  • Execute the command “qlmanage -r” in the Terminal – or restart the machine

Update (02.04.2014): The plugin has been updated to also generate a thumbnail for .strings files.


Using AddressSanitizer with Xcode 4.6.1

Posted: April 10th, 2013 | Author: | Filed under: Debugging, macOS, Programming | Tags: , , , , , | 3 Comments »

Clang 3.3 now supports AddressSanitizer. Here is the description from the Clang 3.3 documentation:

AddressSanitizer is a fast memory error detector. It consists of a compiler instrumentation module and a run-time library. The tool can detect the following types of bugs:

  • Out-of-bounds accesses to heap, stack and globals
  • Use-after-free
  • Use-after-return (to some extent)
  • Double-free, invalid free

Typical slowdown introduced by AddressSanitizer is 2x.

Using AddressSanitizer by manually compiling a file is well documented. On the other hand using Xcode 4.6.1 to build a complex project with AddressSanitizer enabled is not so easy. Following are the steps to build an Xcode project with AddressSanitizer.

Note that I used Xcode 4.6.1 when writing this article. Xcode 4.6.1 contains a Clang build based on LLVM 3.2. Most likely a future Xcode build will contain a Clang build based on LLVM 3.3 which would simplify the use of AddressSanitizer in Xcode.

1- Building Clang 3.3

Since Xcode 4.6.1 contains an old build of Clang, we need to get and build Clang trunk. This is fairly easy. Here is what I did:

– svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm
– cd llvm/tools
– svn co http://llvm.org/svn/llvm-project/cfe/trunk clang
– cd ../..
– cd llvm/projects
– svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt
– cd ../..
– mkdir build
– cd build
– ../llvm/configure –enable-optimizations
– make -j `sysctl -n hw.logicalcpu`

2- Tell Xcode 4.6.1 to use Clang 3.3 we just compiled

The simplest solution I found is to add the following user define in the Xcode target:
CC = “PATH_TO/llvm/build/Debug+Asserts/bin/clang”;
You need to set PATH_TO to the folder where you compiled llvm.

3- Make sure the deployment target is set to 10.7 or higher.

I used the deployment target set to 10.7.

4- Add the C flag and linker flag -fsanitize=address

Add the C flag -fsanitize=address
Add the linker flag -fsanitize=address

5- Compile your project

Congratulations! You now have a build of your project with AddressSanitizer enabled.

6- Running the application

Xcode 4.6.1 fails to run this executable but you can run it from the Terminal and AddressSanitizer will detect possible memory errors.
If your application has a memory error, you will see an error message like:

Memory Error with AddressSanitizer

7- Symbolize the symbols

Currently AddressSanitizer does not symbolize its output.
You will need to manually symbolize the addresses. This can easily be done with atos (see man atos) and the dSYM file produced during the compilation (you might need to turn on “DWARF with dSYM File” in the Xcode target).

For example to symbolize the symbol 0x100001d86 in the AddressSanitizer message:

Alex:MyApplication alex$ atos -o build/Debug/MyApplication.app.dSYM/Contents/Resources/DWARF/MyApplication 0x100001d86
-[AppDelegate applicationDidFinishLaunching:] (in MyApplication) (AppDelegate.mm:23)

Useful references

Clang 3.3 documentation for AddressSanitizer: http://clang.llvm.org/docs/AddressSanitizer.html

AddressSanitizer HomePage: http://code.google.com/p/address-sanitizer/