iOS jailbreak-detection bypass
TL;DR: Apps that “detect jailbreak” check a small, well-known set of side effects (Cydia paths, fork() success, dyld images, SSH ports); Frida or a substrate tweak hooks the four or five primitives that back all of them and the app stops noticing.
What it is
“Jailbreak detection” is client-side anti-tamper: refuse to run, blank sensitive screens, or notify a backend if the device looks rooted. Almost every implementation reduces to:
- Filesystem check —
stat/NSFileManager fileExistsAtPath:on/Applications/Cydia.app,/private/var/lib/apt,/usr/sbin/sshd,/bin/bash,/etc/apt. - Fork check — call
fork(); on stock iOS sandboxed processes get-1, jailbroken ones often succeed. - Image-list check —
_dyld_image_count/_dyld_get_image_namewalk loaded dylibs looking forMobileSubstrate,SubstrateLoader,libcycript,frida-agent,cynject,pspawn_payload. dlopen/dlsymprobe — try to open/usr/lib/libcycript.dylib; success = jailbroken.- URL-scheme probe —
canOpenURL:oncydia://,sileo://(requiresLSApplicationQueriesSchemesentry, often forgotten). - Symlink / sandbox probe — write to
/private/jailbreak.txtor read/etc/master.passwd; on a sandboxed app these fail. - Port probe — TCP-connect to localhost:22 (SSH).
- Entitlement check — own binary has unexpected entitlements (jailbroken devices sometimes inject
get-task-allow).
Modern detectors (commercial: Promon SHIELD, Talsec, Guardsquare iXGuard) chain dozens of these and obfuscate the underlying primitives so a naive NSFileManager hook misses checks done via raw syscall(SYS_stat).
Preconditions / where it applies
- Jailbroken iOS device + Frida or substrate tooling — or a Frida-Gadget re-pack of the IPA for non-jailbroken devices.
- Decrypted target binary so you can find the detection routines statically.
Technique
1. Map the detection routines. Class-dump (see ios-class-dump) the binary and grep:
1
2
class-dump-z -H -o h Victim
grep -ri "jailb\|cydia\|RootCheck\|isCompromised\|isTampered" h/
Cross-reference these symbols with disassembly to find every call site.
2. Frida script template. One script that handles the common primitives:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// hook stat / lstat / NSFileManager
var stat = Module.findExportByName(null, "stat");
Interceptor.attach(stat, {
onEnter(args) {
var path = args[0].readUtf8String();
if (/cydia|sileo|apt|MobileSubstrate|libimo|bash/i.test(path)) this.fake = 1;
},
onLeave(retval) { if (this.fake) retval.replace(-1); }
});
// hook NSFileManager fileExistsAtPath:
var FM = ObjC.classes.NSFileManager;
Interceptor.attach(FM['- fileExistsAtPath:'].implementation, {
onEnter(args) {
var path = new ObjC.Object(args[2]).toString();
if (/cydia|sileo|MobileSubstrate|bash|apt/i.test(path)) this.fake = 1;
},
onLeave(retval) { if (this.fake) retval.replace(0); }
});
// hook fork
Interceptor.replace(Module.findExportByName(null, "fork"),
new NativeCallback(function () { return -1; }, 'int', []));
// hook _dyld_image_count / _dyld_get_image_name
var count = Module.findExportByName(null, "_dyld_image_count");
var origCount = new NativeFunction(count, 'uint32', []);
Interceptor.replace(count, new NativeCallback(() => origCount() - 3, 'uint32', []));
// hook canOpenURL:
var UIApp = ObjC.classes.UIApplication;
Interceptor.attach(UIApp['- canOpenURL:'].implementation, {
onEnter(args) {
var u = new ObjC.Object(args[2]).absoluteString().toString();
if (/^cydia|^sileo|^undecimus/.test(u)) this.fake = 1;
},
onLeave(retval) { if (this.fake) retval.replace(0); }
});
3. Off-the-shelf options.
objectionshipsios jailbreak disablewhich applies the same hooks plus several anti-anti-Frida ones.- Liberty Lite / A-Bypass / Choicy — substrate tweaks for jailbroken devices.
- Shadow — open-source detection bypass tweak (active community fork).
4. Defeating obfuscated detectors. Commercial detectors call syscall(0x14, ...) directly to avoid libc symbol hooks. Counter:
- Use
Interceptor.attachon the rawsyscallsymbol and check the syscall number inonEnter. - Or use
Stalkerto instrument every block and intercept thesvc 0x80instruction. - For inline checks, find the call in IDA and use Frida’s
Interceptor.replaceat the exact offset.
5. Defeat integrity checks that follow. Many detectors call home/telemetry rather than blocking — silence those network calls separately (hook NSURLSession dataTaskWithRequest:).
6. Non-jailbroken devices. Repack the IPA with Frida-Gadget via objection patchipa. The gadget loads on app start and you inject scripts via TCP. Requires re-signing with a personal developer account.
Detection and defence
- Accept that client checks are speedbumps. Use Apple DeviceCheck / App Attest to get a server-attested signal that the app is unmodified on a non-jailbroken device — this is the only reliable jailbreak / tamper detection.
- Combine: client signal + server attestation + behavioural anomalies (sensor-fingerprint mismatches, impossible-to-fake hardware signals).
- Don’t telegraph detection — silently degrade rather than show “jailbreak detected”; this raises the cost of finding which check fired.
References
- OWASP MASTG — Anti-Reversing Defenses for iOS
- Frida Codeshare — iOS jailbreak bypass scripts — community scripts
- Apple — DeviceCheck and App Attest — server-side attestation
- Shadow — open-source bypass tweak