Thomas Whitmire

Frida Instrumentation

What is frida?

I wanted to show how frida can be used to inject into an application during runtime to get around certain functionality. In this example I take the Damn Vulnerable iOS Application's Jailbreak Challenge and use frida to bypass the Jailbreak check.

Is this an effcient way to bypass Jailbreak detection? Depends...

If there's information you are trying to access in the app that would otherwise get wiped by patching the app, this is an excellent technique to attempt to access that data. It is, however, not persistent. Frida only works while hooking onto those functions.

Below is the source code for the jailbreak detection challenge 2. My solution will work for both challenges though. I just liked the method call of jailbreakTest2Tapped to be displayed.

- (IBAction)jailbreakTest2Tapped:(id)sender {
    if (!TARGET_IPHONE_SIMULATOR){
    BOOL isJailbroken = NO;
    if ([[NSFileManager defaultManager] fileExistsAtPath:@"/Applications/Cydia.app"]){
        isJailbroken = YES;
    }else if([[NSFileManager defaultManager] fileExistsAtPath:@"/Library/MobileSubstrate/MobileSubstrate.dylib"]){
        isJailbroken = YES;
    }else if([[NSFileManager defaultManager] fileExistsAtPath:@"/bin/bash"]){
        isJailbroken = YES;
    }else if([[NSFileManager defaultManager] fileExistsAtPath:@"/usr/sbin/sshd"]){
        isJailbroken = YES;
    }else if([[NSFileManager defaultManager] fileExistsAtPath:@"/etc/apt"]){
        isJailbroken = YES;
    }

    NSError *error;
    NSString *stringToBeWritten = @"This is a test.";
    [stringToBeWritten writeToFile:@"/private/jailbreak.txt" atomically:YES
                          encoding:NSUTF8StringEncoding error:&error];
    if(error==nil){
        //Device is jailbroken
        isJailbroken = YES;
    } else {
        [[NSFileManager defaultManager] removeItemAtPath:@"/private/jailbreak.txt" error:nil];
    }

    if([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]]){
        //Device is jailbroken
        isJailbroken = YES;
    }
     [DamnVulnerableAppUtilities showAlertForJailbreakTestIsJailbroken:isJailbroken];

    }else{
        //User is not testing on a device
        [DamnVulnerableAppUtilities showAlertForJailbreakTestIsJailbroken:NO];
    }
}


Verifying device fails jailbreak detection...



frida-trace can essentailly be used to wildcard unknown functions. Lets say you loaded the application in Hopper or IDA Pro and found the ObjC method "jailbreakTest2Tapped" and wanted to instrument through the application to see where the function got called, you can wildcard the class or vis versa.

Below is the output from hitting the Challenge 2 button.



I will be using this python script to attach to the iOS running process. This script will inject JavaScript into the running process to hook and change the function arguments.

Here I added the jailbreakTest2Tapped method to inject and return when it gets hit just to verify the button has been pressed.

Also take note of the 3 variables getting assigned. During runtime, methods will be treated as objc_msgSend calls...

 id objc_msgSend(id self, SEL op, args) 
arg[0] = class, arg[1] = method, arg[2] = the arguments we want to change.

var hook = ObjC.classes.JailbreakDetectionVC["- jailbreakTest2Tapped:"];
	Interceptor.attach(hook.implementation, {
	onEnter: function(args) {
	var r1 = new ObjC.Object(args[0]);
	var r2 = ObjC.selectorAsString(args[1]);
	var r3 = ObjC.Object(args[2]);
	send("Jailbreak is being checked... [ "+r1+" "+r2+" ] => Data: " + r3.toString());
		}
	});


Next, I plugged in the 1st checked method for Jailbreak detection. If you go back to the source code, the method will look for file paths that exists and if found will return "Jailbroken".

var hook = ObjC.classes.NSFileManager["- fileExistsAtPath:"];
	Interceptor.attach(hook.implementation, {
	onEnter: function(args) {
		var obj = ObjC.Object(args[2]);
		send("[+][NSFileManager] File Exists at Path: " + obj.toString());
		}
	});


Since it failed on the 1st check of finding "/Applications/Cydia.app", ie. it found that path... it immediately returned that is was Jailbroken... So how do we bypass this check?

Frida's ability to hook these functions during runtime allows us to also inject into these arguments BEFORE it gets passed. We can change the argument to something we know will return False.

Here you can see I chose to change the argument from "/Applications/Cydia.app" to "blah", which is definitely not a file on this device.

var hook = ObjC.classes.NSFileManager["- fileExistsAtPath:"];
	Interceptor.attach(hook.implementation, {
	onEnter: function(args) {
		args[2] = ObjC.classes.NSString.stringWithString_("blah");
		var obj = ObjC.Object(args[2]);
		send("[+][NSFileManager] File Exists at Path: " + obj.toString());
		}
	});


As you can see when the button is pressed, it went through all of the "file exists" checks. This happened because for every "fileExistAtPath" call, the file was overwritten with "blah". The file didn't exist and therefore hit the next method in the detection line up, "writeToFile".



We add the "writeToFile" method to our JavaScript injection to verify the output...

var hook = ObjC.classes.NSString["- writeToFile:atomically:encoding:error:"];
	Interceptor.attach(hook.implementation, {
	onEnter: function(args) {
		var r1 = new ObjC.Object(args[0]);
		var r2 = ObjC.selectorAsString(args[1]);
		var r3 = ObjC.Object(args[2]);
		send("Writing text to file... [ "+r1+" "+r2+" ] => Data: " + r3.toString());
		}
	});


We see that the method was infact called with the value "/private/jailbreak.txt".

So we know that the method was successful in writing the file to disk, we now need to force this function to return False just like the previous method.



We can do this by changing the argument to write to a folder that doesn't exist. ie. "/blah/blah.txt"

var hook = ObjC.classes.NSString["- writeToFile:atomically:encoding:error:"];
	Interceptor.attach(hook.implementation, {
	onEnter: function(args) {
		args[2] = ObjC.classes.NSString.stringWithString_("/blah/blah.txt");
		var r1 = new ObjC.Object(args[0]);
		var r2 = ObjC.selectorAsString(args[1]);
		var r3 = ObjC.Object(args[2]);
		send("Writing text to file... [ "+r1+" "+r2+" ] => Data: " + r3.toString());
		}
	});


It worked!



Now moving onto the following method to delete the file from disk to continue the application's flow "removeItemAtPath".

First checking to see if the method is called.

var hook = ObjC.classes.NSFileManager["- removeItemAtPath:error:"];
	Interceptor.attach(hook.implementation, {
	onEnter: function(args) {
		var r1 = new ObjC.Object(args[0]);
		var r2 = ObjC.selectorAsString(args[1]);
		var r3 = ObjC.Object(args[2]);
		send("Deleting text file... [ "+r1+" "+r2+" ] => Data: " + r3.toString());
		}
	});


Method was called, so now change the argument to have it delete our file.

var hook = ObjC.classes.NSFileManager["- removeItemAtPath:error:"];
	Interceptor.attach(hook.implementation, {
	onEnter: function(args) {
		args[2] = ObjC.classes.NSString.stringWithString_("/blah/blah.txt");
		var r1 = new ObjC.Object(args[0]);
		var r2 = ObjC.selectorAsString(args[1]);
		var r3 = ObjC.Object(args[2]);
		send("Deleting text file... [ "+r1+" "+r2+" ] => Data: " + r3.toString());
		}
	});


The last function checks to see if the app has access to the cydia URL. Easy enough, lets just check if we hit the method first and then change the url to one that does not exist.

var hook = ObjC.classes.UIApplication["- _canOpenURL:publicURLsOnly:"];
	Interceptor.attach(hook.implementation, {
	onEnter: function(args) {
	var r1 = new ObjC.Object(args[0]);
	var r2 = ObjC.selectorAsString(args[1]);
	var r3 = ObjC.Object(args[2]);
	send("Jailbreak is being checked... [ "+r1+" "+r2+" ] => Data: " + r3.toString());
		}
	});
var hook = ObjC.classes.UIApplication["- _canOpenURL:publicURLsOnly:"];
	Interceptor.attach(hook.implementation, {
	onEnter: function(args) {
	args[2] = ObjC.classes.NSURL.URLWithString_("chro://blahervbwe.com");
	var r1 = new ObjC.Object(args[0]);
	var r2 = ObjC.selectorAsString(args[1]);
	var r3 = ObjC.Object(args[2]);
	send("Jailbreak is being checked... [ "+r1+" "+r2+" ] => Data: " + r3.toString());
		}
	});


When the argument is hooked and changed to a value that does not exist, the Jailbreak detection returns false and we have tricked the application to thinking that the device is not jailbroken.