I recently found myself looking at an iOS application as part of a bug bounty and figured my normal methodology of jailbreaking the device and installing some third part apps would be enough to get me started. However, it soon became apparent that I would have to dig deeper in order to get a foothold within the application. Using this popular Frida iOS jailbreak script wasn't going to cut it this time.
Before I dig into what the aforementioned Frida script does, its worth touching on the four main methods that can be implemented in order to prevent such tampering. These are:
- File existence
- URI scheme registration
- Sandbox behavior
- Dynamic linker inspection
File Existence – When we jailbreak a device we are essentially leaving a footprint of the method used (palera1n) and any other tooling that we manually add to the device. Sadly, whilst many of these tools are helpful whilst we conduct our testing methodology. These tools however leave behind artifacts, one such example being /Applications/Cydia.app, as well as other binaries like bash and sshd. The presence of these files indicates a jailbroken device, therefore its not uncommon for mobile app developers to include these checks.
URI Schemes – Jailbroken devices often have the cydia:// URI scheme registered. At this point it’s worth noting that some of the packages used to create the jailbreak condition on the iOS device still rely on packages and dependencies that were originally built for the older package manager Cydia. As such, these packages may still reference Cydia resources even if newer package managers such as Sileo and Zebra are installed. During testing you may find that many iOS apps will use anti-jailbreak mechanisms which reference and search the device for these older references, which in many cases will prevent the iOS application from running on the jailbroken device.
Sandbox Behavior – Apple enforces strict security measures on iOS devices through a system called the 'application sandbox'. This sandbox restricts what apps can do, including:
- Limiting access to system files and other apps' data.
- Preventing unauthorized code execution.
- Blocking certain system calls, like
fork()
.
Why is fork()
Disallowed?
In a stock (non-jailbroken) iOS device, an app is not allowed to create new child processes using fork()
. This is a security measure to prevent malicious code from spawning background processes and ensuring that apps run in isolated environments. If an app tries to call fork()
on a normal iOS device, it will fail. However on a jailbroken device, fork()
might succeed if the jailbreak removed the restriction, thus, if your code executes fork()
successfully, it’s a strong indicator that the device is jailbroken.
Dynamic Linker Inspection – Dynamic linking is a way for executables to take advantage of code provided by other libraries without compiling and shipping that code in the executable. With dynamic linking, an executable does not include all the code from external libraries within itself. Instead, it loads the necessary code at runtime when the app starts. This keeps the app's file size smaller since it reuses shared libraries that are already installed on the system. In contrast, static linking compiles all the required code from external libraries directly into the executable. As a result, the final binary is larger because it contains everything it needs to run. However, this means the app does not rely on any external libraries at runtime.
In iOS, the dynamic linker (called dyld
) is responsible for loading these external libraries into a process when an app starts. It determines a number of things:
- Which dynamic libraries an app needs.
- Where to load them from.
- How to link them into the process memory.
By inspecting dyld
, it is possible to detect the presence of anti-jailbreak-detection tools by analysing the names and number of libraries loaded into the current process. If such a tool is running, it confirms that the device is jailbroken.
Frida Scripts
Earlier in this post, I mentioned the following Frida Script - https://codeshare.frida.re/@liangxiaoyi1024/ios-jailbreak-detection-bypass/, lets take a further look at the JavaScript code and compare this with the four previously discussed detection methods:
if (ObjC.available) {...}
- This checks if the Objective-C runtime is available, ensuring that the script is running in an iOS environment.
paths
array contains locations of files, apps, and system components typically found on jailbroken devices, such as Applications/blackra1n.app,
Applications/Cydia.app, Applications/FakeCarrier.app"
The scripts then looks for command line utilities that are not commonly found on standard non jailbroken iOS devices, this includes /bin/bash
, /bin/su
, /usr/bin/ssh
.
We then have the script looking for tweaks and jailbreak logs/config files. Tweaks are modifications that alter the behavior of iOS apps or the operating system itself. They are typically installed through package managers we discussed previously, Sileo or Zebra, and work by injecting code into existing system processes or apps. Again, these tweaks typically end up storing files in locations that do not exist on standard iOS devices. In addition, we have log and configuration files being created when a device is jailbroken. These logs and configuration files are typically stored in directories that wouldn’t exist on a standard iOS devices, therefore they also act as indicators.
Practical Example
First you need to grab the application binary from the device in an unencrypted state. The reason being is that when any application is installed from the Apple store, its protected by DRM - Digital Rights Management. This means the app is actually encrypted when stored on the device preventing us from just simply extracting the binary and reverse engineering it. Extracting the binary is probably worth a blog post in itself (currently in draft - watch this space), however we can use either of the two tools below:
https://github.com/AloneMonkey/frida-ios-dump or
https://github.com/ChiChou/bagbak
So far we have discussed the various techniques of how an application can attempt to detect a jailbreak, lets now look at an example from an iOS application. I chose MySkoda due to the fact they have a bug bounty program with intigriti - https://app.intigriti.com/programs/skodaauto/skodaautoprivatebugbountyprogram/detail
Assuming you have grabbed the IPA down from the device and extracted the binary, lets start with some simple strings analysis such as grepping for 'jailbreak'.
strings MySkoda | grep jailbreak
jailbreakClient
https://mspgwlivestorage.blob.core.windows.net/warnings/jailbreak.html
/private/jailbreak.txt
/private/jailbreakTest
_jailbreakClient
As shown above, we see a reference to both /private/jailbreak.txt
and /private/jailbreakTest
alongside some other references. Both of these fields are worth noting because in this instance we actually have iOS attempting to write both these files to the device. If successful, then the underlying code determines the device is jailbroken and we receive this error when running the app on our jailbroken device.

This is actually a the URL that we saw in strings analysis - https://mspgwlivestorage.blob.core.windows.net/warnings/jailbreak.html
Strings however will only get us so far, instead we want to really get into the meat of the binary, and the best way to do this is with a tool such as Hopper (Mac and Linux) or with Ghidra. Hopper is a paid tool, and although you can try it for 30 minutes without a license code, I found this duration to be too short to do any proper analysis so I went with Ghidra instead.
Using Ghidra we can analyse the app in further detail, first looking at the relevant string: "jailbreak", as shown below:

As shown by the red rectangle we see the path to /private/jailbreak.txt
and /private/jailbreakTest
. As previously mentioned, we know that application is using the method of writing to the /private directory as one method of detection. However, this isn't the only method being used. We also have File Existence detection methods being used, if any of the following apps are found then the application determines that it shouldn't run and will quit.

We can see this in greater detail by double clicking on the value and viewing the output in the Lists panel in Ghidra.

Within the binary, there are many other references based on the initial methods mentioned at the start of this blog, ssh, apt etc.
What about Frida Detection?
Sadly, due to its popularity, its sometimes the actual presence of Frida that prevents an application from running, and this seems to be the case:

This suggests that the string /usr/sbin/frida-server
is stored in memory at the address 0x105e0f080
. The cross-reference (XREF) indicates that this string is used somewhere in the binary at address 0x106be3718
. This helps us to identify where the string is being referenced in the program. Looking further into this, there doesn't seem to be any values that we can change to alter this behaviour, although I suspect that obfuscating the frida-server binary itself might be the way forward here.
However, the string "JailbreakClient" previous shown at the top of strings search sounds more like an anti-jailbreak detection mechanism that might cover the whole application.

I'm going to end this post here, as there is still a lot of unknowns and research to be done, however the next logical steps would be to try to hook a specific function with Frida, setting the return value to '0' (zero) which is considered "false" in boolean logic. Alternatively, patching the binary by modifying the function directly and then recompiling and resigning the binary should also work.
If I figure it out, I'll update this post in the future. Thanks for reading.