Defeating iOS Jailbreak Detection


This blog is a cursory breakdown of defeating less advanced jailbreak detection code. There are several ways to employ jailbreak detection in a security conscious mobile  application. Many of easier-to-defeat methods involve checking the iOS file system to see if any jailbreak relevant files exist. If we need test an application that employs this type of protection, we need to figure out a way to defeat it, so we can still use our jailbroken testing device.

Some of these protection routines use the NSFileManager methods fileExistsAtPath and fileExistsAtPath:isDirectory to check to see if certain Cydia files and directories exist:

  •  /Applications/Cydia.app/
  • /private/var/stash
  • /private/var/lib/apt
  • /Library/MobileSubstrate/
  •  /bin/bash
  • /var/cache/apt

There are several ways to defeat this check.

  1. Patch the binary itself and modify the path it is looking for using a hex editor. Resign the code, run the app, viola!
  2. Use MobileSubtrate to hook the methods NSFileManager fileExistsAtPath and fileExistsAtPath:isDirecotry to always return false.
  3. The last is to identify the class where the protection is being called (sometimes obvious when dumping classes using class-dump-z) and using runtime hacking in GDB or Cycript to, at runtime, replace the value of the return.

Patching

The 1st solution is the least elegant but quickest answer. Take your application off the device, unzip it (.ipa files are just zip files), and find your binary. If you open up your binary in a Hex editor you might catch a glimpse of some strings with the above paths in them:

1-10-2013 12-32-31 AM

Change those strings (making sure to keep the same character count). Something like “/private/dog/lib/apt” . Then you can resign the binary and deploy on the phone. Now that jailbreak check should never work.

Hooking

The second way is my personal favorite. Since we already have a jailbroken device, this means we have mobile substrate.

MobileSubstrate is the de facto framework that allows 3rd-party developers to provide run-time patches (“MobileSubstrate extensions”) to iOS system functions.

MobileSubstrate consists of 3 major components: MobileHooker, MobileLoader and safe mode.

Mobile hooker can be invoked before the app even runs. This replaces the code that the app will use to check for the existence of our jailbreak relevant files. In this case something like the below will to set those pesky NSFileManager fileExistsAtPath and fileExistsAtPath:isDirectorychecks to return always return false:

void* (*old_fileExistsAtPath)(void* self, SEL _cmd,NSString* path) = NULL; 
void* st_fileExistsAtPath(void* self, SEL _cmd, NSString* path){
 if ([path isEqualToString:@"/private/var/lib/apt"){
 NSLog(@"=>replaced %@", path); 
 return 0;
 } 
 return old_fileExistsAtPath(self,_cmd,path);
} 
__attribute__((constructor)) static void initialize() {
 NSLog(@"Hooking..."); 
 MSHookMessageEx([NSFileManager class], @selector(fileExistsAtPath:),
(IMP)st_fileExistsAtPath, (IMP *)&old_fileExistsAtPath);
}

Runtime Hacking

If you just need a PoC or a one time hack of the check, you can use GDB to set breakpoints at execution time. Here we borrow a snippet of a blog from nopsledsleigh (with some edits):

We use gdb to perform the bypass. In order to stop execution in the correct moment, we create a breakpoint in the [NSFileManager fileExistsAtPath:] API call.

Attaching to process 1172.

Reading symbols for shared libraries + done

Reading symbols for shared libraries ++ done

Reading symbols for shared libraries + done

0x2feb8470 in __dyld_strcmp ()

(gdb) b fileExistsAtPath:

Breakpoint 1 at 0x37fd9c3a

(gdb) c

The app runs and we hit the breakpoint.

Breakpoint 1, 0x37fd9c3a in -[NSFileManager fileExistsAtPath:] ()

Now take a look at the source in the beginning. It is easy to assemble the calling stack, even manually:

[NSFileManager fileExistsAtPath:]

[AppDelegate isJailbroken]

[AppDelegate jailbreakDetection]

[…]

gdb confirms our theory:

(gdb) bt

#0  0x37fd9c3a in -[NSFileManager fileExistsAtPath:] ()

#1  0x000a146e in ?? ()

#2  0x000a140c in ?? ()

#3  0x000a13ec in ?? ()

#4  0x31515c30 in -[UIApplication _stopDeactivatingForReason:] ()

#5  0x31503914 in -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] ()

[…]

We create another breakpoint, in the [AppDelegateisJailbroken] function, right after the return of the [NSFileManager fileExistsAtPath:].

(gdb) b *0x000a146e

Breakpoint 2 at 0xa146e

(gdb) c

Continuing.

We hit the second breakpoint – at this point, in accordance with ARM calling conventions, the returned value from the fileExistsAtPath: is in $r0. The code is asking if our directory exists, and since it does the value 1 (representing true) is set in the register. All we need to do is to set it to 0:

(gdb) i r $r0

r0             0x1 1

(gdb) set $r0=0

(gdb) c

Continuing.

…and the application pops up an alert with “Clean device. :)” Easy, isn’t it?

Cycript

Another way of doing this is to use the runtime hacking tool Cycript. This comes in especially handy if the developer has used anti debugging techniques. This is just a generic, fictitious example.

You can identify the classes in the binary by running class-dump-z. If you search for the Application Delegate Interface, below that, you should see relevant methods… look for things that check for booleans like -(BOOL)jailbreakCheck or something such as that.

Then you can attach Cycript to your running app:

cycript -p 1337

Identify the view controller:

cy# UIApp.keyWindow.delegate

This will have the methods in it usually, like our fictitious (BOOL)jailbreakCheck

You can make sure by dumping the methods using the command:

cy# UIApp.keyWindow.delegate->isa.messages

If you see your method there you can replace it  with something that always returns false:

cy# UIApp.keywindow.delegate-isa.messages[‘jailBreakcheck’] = function () { return false; }

Now your app would be return that the device is not jailbroken.

Further Reading , References, and Shout-outs:


Leave a Reply

Your email address will not be published. Required fields are marked *