← Back to writing
Tools & Cheatsheets

Android Root Detection Bypass

Apr 01, 2025
4 min read
lawbyte

Root detection blocks security testing on production apps. Bypassing it is a standard step in mobile penetration testing to assess the app’s true security posture.

Common Root Detection Methods

Apps check for root via several mechanisms:

1. File existence checks
/su, /system/bin/su, /system/xbin/su, /sbin/su
/system/app/Superuser.apk, /data/data/com.noshufou.android.su/
Magisk Manager, SuperSU, KingUser

2. Directory permissions
/system is writable (should be read-only on production)
/data is accessible

3. Package presence
com.noshufou.android.su, eu.chainfire.supersu
com.koushikdutta.superuser, com.thirdparty.superuser
com.topjohnwu.magisk

4. Command execution
Runtime.exec("su") and check result
which su

5. Build props
ro.build.tags = test-keys (custom ROM)
ro.debuggable = 1
ro.secure = 0

6. System properties
getprop ro.build.type = user (should be for production)

7. Native library checks
libc.so hooking detection

8. SafetyNet / Play Integrity API (Google attestation)

Frida — Bypass Root Detection

Universal Root Bypass Script

// root_bypass.js — hooks common root detection patterns
Java.perform(function() {

// Hook RootBeer library (popular root detection library)
try {
var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
RootBeer.isRooted.overload().implementation = function() {
console.log("[*] RootBeer.isRooted() → false");
return false;
};
RootBeer.isRootedWithoutBusyBoxCheck.overload().implementation = function() {
return false;
};
} catch(e) {}

// Hook File.exists() to lie about su/root files
var File = Java.use("java.io.File");
File.exists.implementation = function() {
var name = this.getAbsolutePath();
if (name.indexOf("su") !== -1 || name.indexOf("magisk") !== -1 ||
name.indexOf("Superuser") !== -1 || name.indexOf("superuser") !== -1) {
console.log("[*] File.exists() blocked: " + name);
return false;
}
return this.exists.call(this);
};

// Hook Runtime.exec() — prevent su command execution
var Runtime = Java.use("java.lang.Runtime");
Runtime.exec.overload('[Ljava.lang.String;').implementation = function(cmd) {
var cmdStr = cmd.join(' ');
if (cmdStr.indexOf("su") !== -1 || cmdStr.indexOf("which") !== -1) {
console.log("[*] Runtime.exec() blocked: " + cmdStr);
throw Java.use("java.io.IOException").$new("not found");
}
return this.exec.overload('[Ljava.lang.String;').call(this, cmd);
};

// Hook System.getProperty for build tags
var System = Java.use("java.lang.System");
System.getProperty.overload('java.lang.String').implementation = function(key) {
if (key === "ro.build.tags") return "release-keys";
return this.getProperty.overload('java.lang.String').call(this, key);
};

console.log("[+] Root detection bypass loaded");
});
frida -U -l root_bypass.js -f com.target.app --no-pause

Objection — Automated Root Bypass

# Start objection
objection -g com.target.app explore

# Inside objection shell — disable root/jailbreak detection
android root disable

# Alternatively, explore root checks
android root simulate

# Check what root detection is firing
android hooking list classes | grep -i root
android hooking list classes | grep -i jail
android hooking list classes | grep -i detect

Magisk — System-Level Root Hiding

# Magisk Hide (classic Magisk < 24)
# Magisk Manager → Magisk Hide → enable for target app

# Magisk DenyList (Magisk 24+)
# Magisk App → Settings → Enable Zygisk
# Magisk App → Settings → Enable DenyList
# Configure DenyList → add target app

# Check if app is hidden
su -c magisk --hide status

# Shamiko module (enhanced hiding for Zygisk)
# Download from Magisk Module repository
# Pairs with Magisk DenyList for better coverage

Specific Library Bypasses

SafetyNet / Play Integrity

// Frida bypass for SafetyNet (deprecated but still used)
Java.perform(function() {
var SafetyNetClient = Java.use("com.google.android.gms.safetynet.SafetyNetClient");
// Hook attest() to return fake passing result
// (Complex — use MagiskHide or specific modules for full bypass)
});
# Better: use Magisk module "MagiskHide Props Config"
# Sets system properties to pass SafetyNet CTS profile match

# Or: "Play Integrity Fix" module (current best approach)
# Download: https://github.com/chiteroman/PlayIntegrityFix

Specific Library Detection

// Bypass specific detection libraries
Java.perform(function() {

// com.scottyab.rootbeer
try {
var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
var methods = ["isRooted", "isRootedWithoutBusyBoxCheck",
"detectRootManagementApps", "detectPotentiallyDangerousApps",
"detectRootCloakingApps", "checkForBinary", "checkForDangerousProps",
"checkForRWPaths", "detectTestKeys", "checkSuExists"];
methods.forEach(function(m) {
try {
RootBeer[m].overload().implementation = function() {
console.log("[*] Bypassed RootBeer." + m);
return false;
};
} catch(e) {}
});
} catch(e) { console.log("RootBeer not found"); }

// com.jrummyapps.android.shell (root shell library)
try {
var Shell = Java.use("com.jrummyapps.android.shell.Shell");
Shell.SU.available.implementation = function() { return false; };
} catch(e) {}

});

Native / C++ Root Detection Bypass

Some apps check for root in native code (NDK):

// Frida hook at native level
Interceptor.attach(Module.findExportByName("libc.so", "access"), {
onEnter: function(args) {
var path = Memory.readUtf8String(args[0]);
if (path.indexOf("su") !== -1 || path.indexOf("magisk") !== -1) {
console.log("[*] access() blocked: " + path);
Memory.writeUtf8String(args[0], "/nonexistent");
}
}
});

Interceptor.attach(Module.findExportByName("libc.so", "fopen"), {
onEnter: function(args) {
var path = Memory.readUtf8String(args[0]);
if (path.indexOf("su") !== -1) {
console.log("[*] fopen() blocked: " + path);
args[0] = Memory.allocUtf8String("/nonexistent");
}
}
});

Detecting What the App Checks

# Use objection to trace method calls
android hooking watch class com.target.app.security.RootChecker

# Monitor file access system calls
frida-trace -U -f com.target.app -i "open" -i "fopen" -i "access"

# Search smali for root detection strings
# Decompile with apktool, grep for indicators:
apktool d app.apk -o app_decompiled/
grep -r "su" app_decompiled/smali/ | grep -i "exec\|exist\|file"
grep -r "root" app_decompiled/smali/ | grep -i "check\|detect\|is"
grep -r "SafetyNet\|RootBeer\|rootbeer" app_decompiled/smali/

# jadx-gui (Java decompiler) — search for root check methods
# Open jadx-gui → File → app.apk → use Find Usage / Text Search

Frida Script Loader

# Load script on app startup
frida -U -l root_bypass.js -f com.target.app --no-pause

# Attach to running app
frida -U -l root_bypass.js com.target.app

# Spawn with extra args
frida -U -l root_bypass.js --aux="stdio=pipe" -f com.target.app

# Using codeshare (prebuilt bypass scripts)
frida --codeshare dzonerzy/fridantiroot -f com.target.app -U --no-pause
frida --codeshare pcipolloni/universal-android-ssl-pinning-bypass-with-frida -f com.target.app -U

Discussion

Leave a comment · All fields required · No spam

No comments yet. Be the first.