Jailbreak/Root detection in mobile applications
Jailbreak and root remove OS sandbox restrictions. On jailbroken iOS, app loses Secure Enclave isolation guarantees, Frida can hook any method, file system is readable without restrictions. On rooted Android — similarly, plus ability to modify system libraries via Magisk modules. For banking, medical and corporate apps, running on such devices — unacceptable risk.
What we check on Android
Three independent check levels give better result than one "silver bullet" method.
File artifacts. Presence of /su, /system/bin/su, /system/xbin/su, /sbin/su — most old way. Hidden via Magisk DenyList, but works on devices with older root solutions.
Attempt to execute su. Runtime.getRuntime().exec("su") — if doesn't throw exception and doesn't return non-zero code immediately, su process exists. More reliable than file check, worse bypassed.
Google Play Integrity API. Most reliable variant today. Server requests from Google verdict on device token:
-
MEETS_DEVICE_INTEGRITY— device passed Android integrity check -
MEETS_STRONG_INTEGRITY— hardware attestation, harder to forge -
MEETS_BASIC_INTEGRITY— minimal level
On rooted devices with Magisk without additional setup Play Integrity often returns MEETS_BASIC_INTEGRITY or lower. With MagiskHide/DenyList of modern versions — may pass MEETS_DEVICE_INTEGRITY. Absolute protection doesn't exist, but it's serious barrier.
RootBeer — popular open-source library, aggregates several checks. Suitable for basic level, but its presence in APK itself signals to attackers — easily found in jadx and disabled. For serious apps — only as addition to native checks.
What we check on iOS
File artifacts. /Applications/Cydia.app, /usr/sbin/sshd, /etc/apt, /private/var/lib/apt — artifacts of Cydia and Sileo (jailbreak package managers). Checkra1n leaves /var/checkra1n.dmg. Check via C API (stat(), access()), not Swift FileManager — Objective-C bridging is easier to hook.
Sandbox escape test. On jailbroken device app can write outside its container. Try creating file in /private/:
let path = "/private/jailbreak_test_\(UUID().uuidString)"
let result = FileManager.default.createFile(atPath: path, contents: nil)
// On clean device — false, on jailbroken — true
Cydia URL scheme presence. UIApplication.shared.canOpenURL(URL(string: "cydia://")!) — simple and often bypassed check, but combined with others adds level.
Apple App Attest. iOS equivalent of Play Integrity. DCAppAttestService generates attestation key in Secure Enclave, server verifies via Apple API. On jailbroken device App Attest often fails — trust chain is broken.
Bypassing detection and countermeasures
Problem is that all userspace checks are bypassed by Frida-level tools — return value of method is hooked. JailMonkey.isJailBroken() returns false regardless of device state in one line of Frida script.
Three principles complicating bypass:
Checks in native code (JNI/NDK). Native code is harder to hook from script than Java/Kotlin. Place critical checks in .so library. Obfuscate function names.
Scattered check. Not one isRooted() function that's patched in one place, but dozens of small checks scattered across code, results XOR-combined at runtime. Patching is more expensive.
Server validation. Client sends Play Integrity / App Attest token to server. Server decides. Attacker can't forge Google/Apple signature without physical access to HSM.
What to do on detection
Hard crash — bad UX practice and ineffective security (app just won't launch, attacker will fix check). Better: degrade functionality (hide sensitive operations), log event on server with device fingerprint, invalidate session on next server request.
Timeline for implementing basic checks (file artifacts + native checks + Play Integrity/App Attest) — 2–3 days per platform. Server validation integration with result handling — additional 1–2 days.







