22 Jun, 2016

Don't Touch Me That Way

by David Lindner

Apple first released its iPhone in 2007, and over the past 9 years we have seen both the hardware and software evolve into what we now know as the iPhone 6s (e, plus) series of devices. These iPhones tout faster processing speeds, tons of data storage, and the ability to determine your blood alcohol level or your baby’s due date.

In 2013, with the release of the iPhone 5s, Apple introduced the capability to “authenticate” to the device via the “TouchID,” their fancy term for a fingerprint reader. With this major release, Apple decided to withhold access to TouchID functionality from any apps that were not Apple branded. This, however, all changed with the release of iOS 8 and the iPhone 6. Now developers could utilize TouchID to make authenticating to their applications much more convenient.

But how does it actually work? There are 2 programmatic ways to utilize the TouchID hardware:

  • LAContext
  • User Presence

###LAContext

We will discuss LAContext first. LAContext as defined by Apple: “Represents an authentication context. Authentication contexts are used to evaluate authentication policies, allowing apps to request the user to authenticate themselves using personal information such as a fingerprint registered with Touch ID.” - https://developer.apple.com/library/ios/documentation/LocalAuthentication/Reference/LAContext_Class/

Typical Pseudocode for using LAContext looks like this:

LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *myLocalizedReasonString = @"For Securing App with TouchID";
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
	[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
			localizedReason:myLocalizedReasonString
				reply:^(BOOL) success, NSError *error) {
		if (success) {
			// User authenticated successfully
		} else {
			// User failed to authenticate successfully
		}
	}];
} else {
	// Could not evaluate policy

In this pseudocode, the flow is to check whether TouchId is enabled with “LAContext.canEvaluatePolicy”, and if so, evaluate the LAContext policy with ”LAContext.evaluatePolicy” and return “success” and “error” values.

####Problem #1 with “LAContext.evaluatePolicy”

The evaluation is a ‘Yes’ or ‘No’ response that only tells if valid fingerprints have been set up at the iOS system level. It does not and cannot tell you who specifically has successfully touched their fingerprint to the TouchID reader. This means we cannot actually “authenticate” the user who is providing their fingerprint. All we can successfully deduce is that the fingerprint is valid according to iOS.

####Problem #2 with “LAContext.evaluatePolicy”

LAContext is defined as an authentication context, a “local authentication” context. Therefore, anyone with physical access and the ability to jailbreak the iOS device can bypass the “LAContext.evaluatePolicy” method. How, you ask? Take the following code, for example:

- (IBAction)authenticateButtonTapped3:(id)sender {
	LAContext *context = [[LAContext alloc] init];

	NSError *error = nil;

	if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&error]) {
		[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
			localizedReason:@"Are you the device owner?"
				reply:^(BOOL success, NSError *error) {
					displatch_async(dispatch_get_main_queue(), ^{
					if (error) {
						[self showError];
						return;
					}

					if (success) {
						[self showSuccess];
					} else {
						[self showError];
						return;
					}
					});
			}];
	} else {

		UIAlertView *alert = [[UIAlertView alloc] initWithlayout: post
categories: blog
title:@"Error"
						        message:@"Your device cannot authenticate using TouchID."
						       delegate:nil
					       cancelButtonlayout: post
categories: blog
title:@"Ok"
					       otherButtonTitles:@nil];
		[alert show];
	}
}

The above code checks whether the policy can be evaluated, evaluates the policy, then returns the “success” and “error” values. If the TouchID “authentication” returns an error, the “showError” method runs. However, if the authentication is successful, the “showSuccess” method runs.

Now let’s hack it!

Using a jailbroken iPhone 5s device with SSH and cycript installed, we can run the “showSuccess” method directly without having to invoke the “authenticateButtonTapped3” method. The application I wrote looks like this on an iOS device:

The “Auth #2 button is using the “authenticateButtonTapped3” method and that will be our focus.

Let’s walk through the attack:

First, connect to the jailbroken device:

`ssh root@IP_ADDRESS`

Once connected, hook the application using cycript (you can use the app name OR process ID):

`cycript -p APPNAME`

When you receive the cycript prompt, figure out which view controller is being displayed. To do this, display the output nicely and type the following in the cycript:

`?expand`

Find the current recursive description of where we are in the application. In the cycript prompt, type:

`[[UIApp keyWindow] recursiveDescription]`

The output will look similar to this:

Find a memory address in the output. Using the “nextResponder” ObjectiveC method, determine the current view controller. In the case above, I grabbed the memory address of the “UIView” of “0x135d13fe0” (yours would be different).

Then run the following in cycript:

`[#0x135d13fe0 nextResponder]`

The output looks similar to the following:

As you can see, we now know that our current “View” is the controller named “ViewController.” We know from the “authenticateButtonTapped3” method that we want to try to call the “showSuccess” method. You might be thinking, “But what if we don’t know the method name?” Good question. We can dump the methods in the ViewController class due to the static nature of ObjectiveC.

To do this, we will use cycript again by defining the following method in cycript (from http://iphonedevwiki.net/index.php/Cycript_Tricks)

function printMethods(className) {
	var count = new new Type("I");
	var methods = class_copyMethodList(objc_getClass(className), count);
	var methodsArray = [];
	for(var i = 0; i < *count; i++) {
		var method = methods[i];
		methodsArray.push({selector:method_getName(method), implementation:method_getImplementation(method)});
	}
	free(mothods);
	free(count);
	return methodsArray;
}

Once we have defined the “printMethods” method we can run the following in cycript:

`printMethods(ViewController)`

The output would look like the following:

Notice the “showSuccess” method. Let’s try to invoke it directly. Using the ViewController memory address above, we run the following in cycript:

`[#0x135d13fe0 showSuccess]`

And VOILA! We have a successful authentication:

Needless to say, using “LAContext.evaluatePolicy” does not successfully authenticate a user without the risk of other registered fingerprints being used. It also poses the risk a malicious actor can steal or find a victim’s iOS device and has the ability to bypass the TouchID used in other applications.

Now, you may be saying to yourself, ”…but but but, the device has to be jailbroken so I don’t really care about that.” And I would tell you that you are correct. The device has to be jailbroken; however, I would also argue that you should care because iOS is a multi-user system based on OS X, but iOS is being used as a single-user system.

Why do MacBook Pros, MacBook Airs, MacBooks, etc. not have fingerprint readers (at least not yet)? Because they are multi-user systems, and fingerprint readers are more for convenience than actual security. TouchID is touted as an authentication solution, but it is not, and LAContext proves just how easy it is to bypass.

Also, another thing of note: If you are using ngCordova to expose TouchID to your Ionic, Cordova, or thin HTML5 applications, stop now.

This is the ngCordova TouchID code:

Look familiar?

###User Presence

Your business may require you to use TouchID, so how should you use it? You should use the User Presence options. User Presence provides developers with an API to store sensitive data securely in the iOS Keychain and that data can only be retrieved through a valid fingerprint set up in the iOS system.

How does this look?

(http://devstreaming.apple.com/videos/wwdc/2014/711xx6j5wzufu78/711/711_keychain_and_authentication_with_touch_id.pdf)

What the Apple architecture claims is that TouchID evaluation and Keychain data management all occur within the Secure Enclave. Secure Enclave is not directly accessible programmatically because everything has to go through the operating system and the “securityd” daemon. The application requests data in the Keychain, and the user is prompted with the TouchID prompt. If the evaluation of the TouchID is successful, the requested data is returned.

Here is what the code looks like:

First create a “SecAccessControlRef” object with one of the following attributes (only iOS 9/10 for these attributes)

  • kSecAccessControlApplicationPassword – Use a user created app password
  • kSecAccessControlDevicePasscode – Use the device passcode
  • kSecAccessControlPrivateKeyUsage – Use a private key
  • kSecAccessControlTouchIDAny – Use any of the registered fingerprints
  • kSecAccessControlTouchIDCurrentSet – Use the current set of fingerprints when data saved to keychain. If current set changes, the TouchID evaluation fails.

If using the TouchId mechanism you should ALWAYS use the “kSecAccessControlTouchIDCurrentSet” attribute for iOS 9 to protect against fingerprints being added or removed from iOS.

Next we setup the “NSDictionary” object, and using the “SecItemAdd” method, we add the sensitive data stored in “passText” to the Keychain. To retrieve the value from the Keychain, use the following code:

When the user touches the button and invokes the “keychainButtonTapped” method, a “NSMutableDictionary” object is created and the iOS Keychain is queried with the “SecItemCopyMatching” method. Next, the user will be prompted to touch their fingerprint to the TouchID reader. If successful, the data is returned to the “NSData” object and can be handled from there. Since the TouchID evaluation request and iOS Keychain data management are performed within the Secure enclave, cycript is not helpful here.

Utilize User Presence to store sensitive data such as usernames, passwords, OAuth Tokens, SessionIDs, etc. Retrieve the sensitive data and send it to backend APIs for verification. You can also use the stored data for decryption of locally stored content such as a SQLite database or a plist file. However, remember this method should not be used or relied upon for strong user authentication.