In this article jailbreak detection method is analyzed and bypassed doing reversing.
Introduction
iOS jailbreaking is privilege escalation for the purpose of removing software restrictions imposed by Apple. It typically does this by using a series of kernel patches. Jailbreaking permits root access to iOS, allowing the downloading and installation of additional applications, extension, and themes that are unavailable through the official Apple App Store.
A lot of applications allow its execution in jailbroken devices, even though having jailbreak detection, because they don’t want to lose users. For example, banks allow applications to execute in all devices but some fucntionalities might be restricted to be executed only in non-jailbroken devices.
Detection
File based detection
During the jailbreaking process, some additional files are created on the device. Looking for these files is a simple way to detect a jailbreak. It’s also an easy method for a malicious individual to detect and bypass. An attacker can search for a string in the application, and then simply change the file names in question to avoid detection.
- Files or appplciations used:
/private/var/lib/apt /private/var/tmp/cydia.log /private/var/lib/cydia /private/var/mobile/Library/SBSettings/Themes /Library/MobileSubstrate/MobileSubstrate.dylib /Library/MobileSubstrate/DynamicLibraries/Veency.plist /Library/MobileSubstrate/DynamicLibraries/LiveClock.plist /System/Library/LaunchDaemons/com.ikey.bbot.plist /System/Library/LaunchDaemons/com.saurik.Cydia.Startup.plist /var/cache/apt /var/lib/apt /var/lib/cydia /var/log/syslog /var/tmp/cydia.log /bin/bash /bin/sh /usr/sbin/sshd /usr/libexec/ssh-keysign /usr/sbin/sshd /usr/bin/sshd /usr/libexec/sftp-server /etc/ssh/sshd_config /etc/apt /Applications/Cydia.app /Applications/RockApp.app /Applications/Icy.app /Applications/WinterBoard.app /Applications/SBSettings.app /Applications/MxTube.app /Applications/IntelliScreen.app /Applications/FakeCarrier.app /Applications/blackra1n.app
-
Directory permissions: Like detecting a jailbroken device by looking for certain new files, certain permissions on partitions and folders can also indicate a jailbroken device. For example, during the jailbreaking process, access to the root partition is amended. If the root partition has read/write permissions, the device has been jailbroken.
-
Size of /etc/fstab file: The /etc/fstab file contains mount points for the system. Many jailbreaking tools modify this file by adding entries to it, changing its file size. The typical iOS app isn’t capable of reading the file, but it can check the size of the file. Do note however, that the file size can change as a result of a new update from Apple.
- Existence of symbolic links: Some directories are originally located in the small system partition, however, this partition is overwritten during the jailbreak process. Therefore the data must be relocated to the larger data partition. Because the old file location must remain valid, symbolic links are created. The following list contains files or directories which would be symbolic links on a jailbroken device. An application could check for these symbolic links, and, if they exist, detect a jailbreak.
/Library/Ringtones /Library/Wallpaper /usr/arm-apple-darwin9 /usr/include /usr/libexec /usr/share /Applications
- Writing files: On jailbroken devices, applications are installed in the /Applications folder and thereby given root privileges. A jailbroken device could be detected by having the app check whether it can modify files outside of its sandbox. This can be done by having the app attempt to create a file in, for example, the /private directory. If the file is successfully created, the device has been jailbroken.
API-based detection
Some API calls provided by iOS behave differently if run on jailbroken devices. Detecting a jailbroken device based on API calls can be both effective and difficult for a malicious individual to recognize and bypass.
-
fork(): The sandbox denies process forking on non-jailbroken devices. By checking the returned pid on fork(), an app can detect if it has successfully forked. If the fork is successful, the app can deduce that it is running on a jailbroken device.
-
system(): Calling the system() function with a NULL argument on a non-jailbroken device will return 0. Doing the same on a jailbroken device will return 1. This is because the function will check whether /bin/sh exists, and it only exists on jailbroken devices.
-
dyld functions: This detection method starts with calling functions like _dyld_image_count() and _dyld_get_image_name() to see what dylibs are currently loaded. This method is very difficult to dynamically patch due to the fact that the patches themselves are part of dylibs.
OpenSSH Service Detection
Jailbroken devices can run services that aren’t normally present on non-jailbroken devices - the most common is the OpenSSH service.
Note that this detection method can be very slow. If SSH is not installed or running on the device, it can take some time for the connection to timeout. Attackers can also easily bypass this method by simply changing the port for the OpenSSH service.
Cydia Scheme Detection
Most jailbroken devices have Cydia installed. While an attacker can change the location of the Cydia app, it’s unlikely they will also change the URL scheme the Cydia app is registered with.
If calling the Cydia’s URL scheme (cydia://) from your application is successful, you can be sure that the device is jailbroken.
It’s difficult to change the scheme for Cydia, but it is possible to simply remove Cydia during the testing process.
Objective-C
Objective-C calls from one method to another are compiled as calls to objc_msgSend(). One effect of this is that IDA Pro cross references do not reflect the actual functions being called at runtime. This function is defined with the following function signature:
id objc_msgSend(id self, SEL op,...)
This implies that for any Objective-C method call it is made, the first two arguments are the object’s self pointer, and the selector, which is a string representation of the method being called on self.
return_value = objc_msgSend(class_receiver, method_signature, arg1, arg2,...)
where:
- return_value: return value of the method called is stored in R0
- class_receiver: value should be stored in R0
- method_signature: value should be stored in R1
- arg1: value should be stored in R2
- arg2: value should be stored in R3
Reversing
There is a method which initialize everything so the application can start. One of the checks it does before starting is whether the application is running in a jailbroken device or not:
Below we will analyze the method which is used to detect if the device is jailbroken. It has the following structure:
- Makes N checks. The more checks it does, the more likely is to verify that the device is jailbroken. However, it is not related with the difficulty of bypassing the control.
- If the check is negative (not jailbroken), it goes to the next check. Otherwise, it just returns that the device is jailbroken.
Each check will be analyze individually. They are distributed in the following categories:
- Directory check: check 1 to 6
- Cydia scheme detection: check 7
- Files or appplciations used: check 8 to 12
- Writing files: check 13
Check 1
- First of all it obtains the object of the NSFileManager class which is stored in R0 and defaultManager method stored in R1 (blue). Later, the result is moved again in R0 (MOV R0, R8).
- Then it obtains the address of the function fileExistsAtPath which is stored in R1 (green).
- “/Applications/Cydia.app” is stored in R2. (red)
- Finally it calls _objc_msgSend with the attributes NSFileManager, fileExistsAtPath and “/Applications/Cydia.app” stored in the registers R0, R1 and R2 respectively.
- It returns a boolean (R0) which is compared with 0x00 (BNE = branch if not equal). If the path does not exists, it jumps to check 2, otherwise the method will return that the device is jailbroken.
Check 2
The second check if the library “/Library/MobileSubstrate/MobileSubstrate.dylib” exists. It uses the same procedure as before. Loads the object of NSFileManager class and calls the function fileExistsAtPath.
It returns a boolean (R0) which is compared with 0x00 (BNE = branch if not equal). If the path does not exists, it jumps to check 3, otherwise the method will return that the device is jailbroken.
Check 3
It checks if the path “/bin/bash” exists. It uses the same procedure as before.
It returns a boolean (R0) which is compared with 0x00 (BNE = branch if not equal). If the path does not exists, it jumps to check 4, otherwise the method will return that the device is jailbroken.
Check 4
It checks if the path “/usr/sbin/sshd” exists. It uses the same procedure as before.
It returns a boolean (R0) which is compared with 0x00 (CBZ = Compare and Branch on Zero). If the path does not exists, it jumps to check 5, otherwise the method will return that the device is jailbroken.
Check 5
It checks if the path “/etc/apt” exists. It uses the same procedure as before.
It returns a boolean (R0) which is compared with 0x00 (CBZ = Compare and Branch on Zero). If the path does not exists, it jumps to check 6, otherwise the method will return that the device is jailbroken.
Check 6
It checks if the path “/private/var/lib/apt/” exists. It uses the same procedure as before.
It returns a boolean (R0) which is compared with 0x00 (CBZ = Compare and Branch on Zero). If the path does not exists, it jumps to check 7, otherwise the method will return that the device is jailbroken.
Check 7
This check tries to call the Cydia’s URL scheme (cydia://). It does it in two steps: first it creates the URL object and then it opens the URL.
- First of all it obtains the object of the class UIApplication which is stored in R4 (blue).
- It obtains the object of the class NSURL which is stored in R0 (purple).
- Then it obtains the address of the method URLWithString which it is stored in R1 (green).
- “cydia://package/com.example.package” is stored in R2 (red).
- It calls _objc_msgSend with the attributes NSURL, URLWithString and “cydia://package/com.example.package” stored in the registers R0, R1 and R2 respectively.
- It returns a NSURL object initialized with URLString which is stored in first in R6 and after in R2 (orange).
- It obtains the address of the method canOpenURL which it is stored in R1 (light blue).
- The value of R4 (UIApplication class) is moved to R0 (blue).
- It calls _objc_msgSend with the attributes UIApplication, canOpenURL and NSURL object stored in the registers R0, R1 and R2 respectively.
- It returns a a Boolean value indicating whether an app is available to handle a URL scheme (R0) which is compared with 0x00 (BNE = branch if not equal). If the path exists, it jumps to check 8, otherwise the method will return that the device is jailbroken.
Check 8
It tries to open the file “/bin/bash” with readonly calling the method “_fopen”.
It returns a boolean stored in (R0) which is compared with 0x00 (CBNZ = Compare and Branch on Non-Zero). If the file can not be open, it jumps to check 9, otherwise the method will return that the device is jailbroken.
Check 9
It tries to open the file “/Application/Cydia.app” with readonly calling the method “_fopen”.
It returns a boolean stored in (R0) which is compared with 0x00 (CBNZ = Compare and Branch on Non-Zero). If the file can not be open, it jumps to check 10, otherwise the method will return that the device is jailbroken.
Check 10
It tries to open the file “/Library/MobileSubstrate/MobileSubstrate.dylib” with readonly calling the method “_fopen”.
It returns a boolean stored in (R0) which is compared with 0x00 (CBNZ = Compare and Branch on Non-Zero). If the file can not be open, it jumps to check 11, otherwise the method will return that the device is jailbroken.
Check 11
It tries to open the file “/usr/sbin/sshd” with readonly calling the method “_fopen”.
It returns a boolean stored in (R0) which is compared with 0x00 (CBNZ = Compare and Branch on Non-Zero). If the file can not be open, it jumps to check 12, otherwise the method will return that the device is jailbroken.
Check 12
It tries to open the file “/etc/apt” with readonly calling the method “_fopen”.
It returns a boolean stored in (R0) which is compared with 0x00 (CBNZ = Compare and Branch on Non-Zero). If the file can not be open, it jumps to check 13, otherwise the method will return that the device is jailbroken.
Check 13
This check tries to create a file called “jailbreak.txt” in /private.
It can be divided in three parts:
-
Create file: uses the method writeToFile, stored in R1, to create the file. In R0 there is the text inside the file and in R2 the path “/private/jailbreak.txt”. If the file has been successful created, it returns a 1 and it is stored in R6. (blue)
-
Remove file: First obtains the object of NSFileManager by calling _objc_msgSend with NSFileManager in R0 and defaultManager in R1, moved from R10 (purple). The obtained object is used to call the method removeItemAtPath, stored in R1, with the path passed as argument stored in R2. (red)
-
Comparision: Moves the result of the file creation stored in R6 to R0. It makes a comparission between R0 and 0 and stores 1 to R5 if R0 is 0. (green)
Is Jailbroken
If any of the checks fail, except check 13 which jumps to the return method, it ends here. Integer 1 is moved to R5 (device is jailbroken).
Return
There are two possible ways to get here:
- From the “Is Jailbroken” step, so R5 will be 1.
- From the “Check 13”, so R5 can be either 0 or 1.
The method will move R5 to R0 and will return 1 if the device is jailbroken or 0 if not.
Patching
Jailbreak method
Makeing the method which checks if the device is jailbroken return always 0. There are some ways to do it:
- Modifying all jumps from all the checks so they jump always to the next check and then make check 13 to return always 0 by modifying the comparision.
- Modifying is_jailbroken because is where the checks jump if they fail.
- Modify R0 before the jailbreak method ends, so it always return 0.
Parent method
An other way is to modify the behaivour of the method which calls the jailbreak check:
- Modifying the register used in the comparission by repleacing the call to the jailbreak check method:
- Modifying the comparission in a way that starts the application however the result of the jailbreak check is negative.