Android Root Detection Bypass: Every Method Explained

Android root detection is the first wall most security-conscious apps build. Before any other protection kicks in, the app checks: is this device rooted? If yes, it refuses to run. For pentesters and security researchers, bypassing root detection is a prerequisite for doing almost any dynamic analysis.

This guide covers every root detection technique apps use and the bypass for each — from basic file checks to native library detection to advanced anti-debug protections. By the end, you will have a complete toolkit for handling any root detection implementation you encounter.

Why Apps Implement Root Detection

On a rooted device, an attacker can: read app private data from /data/data, hook any method with Frida without restrictions, patch the APK to remove security checks, extract hardcoded keys and tokens, and bypass certificate pinning. Root detection is a signal — if root is detected, the threat model changes significantly.

Banking apps, fintech, DRM-protected media, and high-security enterprise apps are the most aggressive implementers. But root detection is not security — it is a speed bump. Every check can be bypassed, usually in minutes. The goal of bypassing it is to enable legitimate security testing, not to circumvent app security for malicious use.

How Apps Detect Root

Before bypassing, understand what the app is checking. Common root detection methods:

1. File System Checks

The simplest method: check for files that only exist on rooted devices.

// Common files checked for root detection
/system/app/Superuser.apk
/system/xbin/su
/system/bin/su
/sbin/su
/system/xbin/busybox
/data/local/xbin/su
/data/local/bin/su
/system/sd/xbin/su
/system/bin/failsafe/su
/data/local/su

// Magisk-specific paths
/sbin/.magisk/
/dev/.magisk/
/data/adb/magisk/

2. Package Manager Checks

Looking for root management app packages installed on the device:

// Root app package names commonly checked
com.noshufou.android.su
com.noshufou.android.su.elite
eu.chainfire.supersu
com.koushikdutta.superuser
com.thirdparty.superuser
com.yellowes.su
com.topjohnwu.magisk
com.kingroot.kinguser
com.kingo.root
com.smedialink.oneclickroot
com.zhiqupk.root.global

3. Build Tags Check

Stock Android builds have Build.TAGS set to “release-keys”. Root-friendly custom ROMs often ship with “test-keys” or “dev-keys”.

// Java code checking build tags
String buildTags = android.os.Build.TAGS;
if (buildTags != null && buildTags.contains("test-keys")) {
    // Device may be rooted/custom ROM
}

4. Runtime Command Execution

Some apps try to execute su and check if the command succeeds:

try {
    Process process = Runtime.getRuntime().exec("su");
    // If this succeeds without exception, su is available
    return true;
} catch (IOException e) {
    return false;
}

5. Native Library Checks

Advanced apps run root detection in native C/C++ code, making it harder to hook at the Java layer. These perform the same file/package/su checks but from native code where standard Frida Java hooks do not reach.

6. SafetyNet / Play Integrity API

Google SafetyNet (deprecated, replaced by Play Integrity API) communicated with Google servers to verify device integrity. Apps that relied on this required network calls to determine root status. Play Integrity API is the modern replacement and is significantly harder to bypass without specific Magisk modules.

Bypass Method 1: Magisk Hide (MagiskHide / Shamiko)

Best for: File system and package checks, SafetyNet/Play Integrity

If you are using Magisk for root, MagiskHide (or its successor Shamiko) makes the device appear unrooted to specific apps. It hides the Magisk installation, su binary, and related files from the app process.

  1. Install Magisk on your device/emulator
  2. Enable Zygisk in Magisk settings (required for Shamiko)
  3. Install the Shamiko Magisk module from the Magisk module repository
  4. In Magisk, go to the DenyList and add the target app

This handles most file-system-based root detection automatically. The target app cannot see /sbin/.magisk, su binaries, or root management apps.

Bypass Method 2: Frida Hooks (Most Flexible)

Best for: Java-layer detection, custom implementations

The most versatile approach: hook the root detection methods themselves and make them return false.

Hook File Existence Checks

Java.perform(function() {
    // Hook File.exists() to hide root files
    var File = Java.use("java.io.File");
    File.exists.implementation = function() {
        var path = this.getAbsolutePath();
        var rootPaths = ["/su", "/sbin/su", "/system/xbin/su", "/system/bin/su",
                        "/system/app/Superuser.apk", "/.magisk", "/data/adb/magisk"];
        for (var i = 0; i < rootPaths.length; i++) {
            if (path.indexOf(rootPaths[i]) !== -1) {
                console.log("[*] Hiding root file: " + path);
                return false;
            }
        }
        return this.exists();
    };

    // Hook File.canExecute() similarly
    File.canExecute.implementation = function() {
        var path = this.getAbsolutePath();
        if (path.indexOf("/su") !== -1 || path.indexOf("busybox") !== -1) {
            return false;
        }
        return this.canExecute();
    };
});

Hook Package Manager Checks

Java.perform(function() {
    var PackageManager = Java.use("android.app.ApplicationPackageManager");
    PackageManager.getPackageInfo.overload("java.lang.String", "int").implementation = function(pkg, flags) {
        var rootPackages = ["com.topjohnwu.magisk", "eu.chainfire.supersu",
                           "com.koushikdutta.superuser", "com.noshufou.android.su"];
        for (var i = 0; i < rootPackages.length; i++) {
            if (pkg === rootPackages[i]) {
                console.log("[*] Hiding root package: " + pkg);
                throw Java.use("android.content.pm.PackageManager$NameNotFoundException").$new(pkg);
            }
        }
        return this.getPackageInfo(pkg, flags);
    };
});

Hook Build Tags

Java.perform(function() {
    var Build = Java.use("android.os.Build");
    Build.TAGS.value = "release-keys";  // Override the static field
    console.log("[*] Build.TAGS overridden to release-keys");
});

Universal Root Detection Bypass Script

Combine all common checks into a single script:

Java.perform(function() {
    // File system hooks
    var File = Java.use("java.io.File");
    var rootIndicators = ["/su", "/sbin/su", "/system/xbin/su", "/system/bin/su",
        "/system/app/Superuser.apk", "/.magisk", "/data/adb/magisk", "/sbin/.magisk",
        "busybox", "Superuser.apk", "supersu"];

    function isRootPath(path) {
        for (var i = 0; i < rootIndicators.length; i++) {
            if (path.toLowerCase().indexOf(rootIndicators[i].toLowerCase()) !== -1) return true;
        }
        return false;
    }

    File.exists.implementation = function() {
        if (isRootPath(this.getAbsolutePath())) return false;
        return this.exists();
    };

    // Runtime.exec hook - prevent su execution
    var Runtime = Java.use("java.lang.Runtime");
    Runtime.exec.overload("java.lang.String").implementation = function(cmd) {
        if (cmd.indexOf("su") !== -1 || cmd.indexOf("which") !== -1) {
            console.log("[*] Blocked root check command: " + cmd);
            throw Java.use("java.io.IOException").$new("Permission denied");
        }
        return this.exec(cmd);
    };

    // Build tags
    var Build = Java.use("android.os.Build");
    Build.TAGS.value = "release-keys";

    console.log("[+] Root detection bypass active");
});

Bypass Method 3: RootCloak / RootBeer Bypass

RootBeer is a popular open-source root detection library used by many Android apps. If you see com.scottyab.rootbeer in the decompiled code, a targeted hook handles it:

Java.perform(function() {
    try {
        var RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");

        // Hook all detection methods to return false
        RootBeer.isRooted.implementation = function() { return false; };
        RootBeer.isRootedWithoutBusyBoxCheck.implementation = function() { return false; };
        RootBeer.detectRootManagementApps.implementation = function() { return false; };
        RootBeer.detectPotentiallyDangerousApps.implementation = function() { return false; };
        RootBeer.checkForBinary.implementation = function(binary) { return false; };
        RootBeer.detectTestKeys.implementation = function() { return false; };

        console.log("[+] RootBeer bypassed");
    } catch(e) {
        console.log("[-] RootBeer not found: " + e);
    }
});

Bypass Method 4: objection (One Command)

Objection has built-in root detection bypass that handles the most common implementations:

# Attach to running app
objection --gadget "com.target.app" explore

# Inside objection REPL:
android root disable

This hooks the same set of common detection methods automatically. Try this first — it works for 60-70% of apps without any customization needed.

Bypass Method 5: APK Patch (Static, No Frida)

For apps where Frida is detected or unavailable, patch the root detection logic directly in smali:

  1. Decompile with apktool: apktool d app.apk -o app/
  2. Find root detection in jadx (search for "isRooted", "checkRoot", "detectRoot")
  3. Open corresponding smali file
  4. Locate the return statement and change it to always return false (0)

In smali, a boolean false return looks like:

# Original smali (returns result of detection)
invoke-virtual {v0}, Ljava/io/File;->exists()Z
move-result v1
return v1

# Patched smali (always returns false = not rooted)
const/4 v1, 0x0    # 0 = false
return v1

Rebuild with apktool, sign, and install the patched version.

Handling Native Root Detection

When Java-layer hooks fail and the app still detects root, the detection is likely in native code. Identify it:

# Search for root-related strings in native libraries
strings lib/arm64-v8a/libnative.so | grep -i "root\|su\|magisk\|superuser\|busybox"

# Check for file paths in the binary
strings lib/arm64-v8a/libnative.so | grep "/system\|/sbin\|/data/local"

Native detection bypass requires hooking the C/C++ functions with Frida Interceptor:

// Hook native open() syscall to hide root files
var openPtr = Module.findExportByName(null, "open");
Interceptor.attach(openPtr, {
    onEnter: function(args) {
        var path = args[0].readUtf8String();
        if (path && (path.indexOf("/su") !== -1 || path.indexOf("magisk") !== -1)) {
            console.log("[*] Native open() blocked for: " + path);
            args[0] = Memory.allocUtf8String("/this_does_not_exist");
        }
    }
});

// Hook access() syscall
var accessPtr = Module.findExportByName(null, "access");
Interceptor.attach(accessPtr, {
    onEnter: function(args) {
        var path = args[0].readUtf8String();
        if (path && (path.indexOf("/su") !== -1 || path.indexOf("magisk") !== -1)) {
            this.block = true;
        }
    },
    onLeave: function(retval) {
        if (this.block) {
            retval.replace(-1);  // Return -1 = file not found
        }
    }
});

Dealing with Play Integrity API

Play Integrity API (successor to SafetyNet) is the hardest root detection to bypass because it contacts Google servers. The device must pass Google attestation for the API to return a clean verdict.

Options for bypass:

  • Magisk + Play Integrity Fix module — a Magisk module that spoofs the attestation hardware response. Works on many devices.
  • Use an unrooted device for these specific tests — if Play Integrity is the only block, it may be simpler to use a stock device and proxy through a VPN/WiFi bridge rather than fight the attestation.
  • Hook the response processing — if the app processes the Play Integrity verdict in Java, hook the method that reads the verdict and return a spoofed "passes" response. This does not bypass the server-side check but the local processing.

Diagnosing Which Checks Are Blocking You

If your bypass is not working, diagnose systematically:

  1. Run objection and enable verbose Frida tracing: frida-trace -U -f com.target.app -j "*!*check*" -j "*!*root*" -j "*!*detect*"
  2. Look for which root-related method is being called and returning true
  3. Write a targeted hook for that specific method
  4. If no Java method shows up: check native code with strings on .so files

The key insight: you always have more information than the app does about what it is checking. Your job is to find the detection function, not build a generic bypass.

Practice with Mobile Hacking Lab

Root detection bypass is a skill that requires practice against real implementations. Mobile Hacking Lab includes dedicated labs for root and emulator detection bypass — designed to expose you to common real-world patterns in a controlled environment.

The free tier includes introductory bypass labs. The advanced courses cover multi-layer detection (apps that combine file checks, package checks, native checks, and SafetyNet/Play Integrity all in one) — which is what you encounter in production banking and fintech apps.


Practice these techniques on real vulnerable apps. Mobile Hacking Lab hands-on labs give you root detection challenges that match what you will find in production security testing. Start free at mobilehackinglab.com.