Introduction: The Necessity of Bypassing Root Detection
Android mobile application penetration testing often hits a roadblock: root detection. Many applications, especially those handling sensitive data or financial transactions, implement robust mechanisms to detect if they are running on a rooted device. This is a security measure designed to prevent malicious actors from gaining elevated privileges and tampering with the app’s integrity, data, or runtime behavior. However, for security researchers and penetration testers, root access is often indispensable for dynamic analysis, memory manipulation, and reverse engineering.
This “Frida Cheat Sheet” aims to equip you with powerful Frida scripts and techniques to effectively bypass common Android root detection mechanisms. By the end of this guide, you’ll have a practical toolkit to overcome these hurdles, enabling deeper insights into application vulnerabilities.
Prerequisites for Your Frida Journey
Before diving into the scripts, ensure you have the following tools and setup ready:
- Rooted Android Device or Emulator: A device with root access is essential for both understanding root detection and deploying Frida. Android emulators like Genymotion or Android Studio’s AVD with a rooted image are suitable.
- ADB (Android Debug Bridge): Installed and configured on your host machine to communicate with your Android device.
- Python 3: Required for installing and running Frida tools.
- Frida-tools: Installable via pip:
pip install frida-tools. - Frida Server: The Frida server binary must be running on your Android device. Download the correct architecture (e.g.,
frida-server-16.x.x-android-arm64) from Frida’s GitHub releases, push it to your device, set execute permissions, and run it.
Setting up Frida Server on Device:
adb push frida-server-16.x.x-android-arm64 /data/local/tmp/frida-server
adb shell "chmod 755 /data/local/tmp/frida-server"
adb shell "/data/local/tmp/frida-server &"
Understanding Common Root Detection Mechanisms
Android applications employ various strategies to detect root. Knowing these helps in crafting targeted bypass scripts.
1. File and Path-Based Checks
Applications often check for the presence of files or directories commonly associated with root tools:
/system/app/Superuser.apk,/sbin/su,/system/bin/su,/system/xbin/su/data/local/tmp/su(Magisk often places temporary `su` binaries here)/data/adb/magisk(Magisk’s core directory)/system/etc/init.d/99superuser/system/bin/busybox,/sbin/busybox
2. Property-Based Checks
Checking system properties to identify debuggable builds or specific ROM characteristics:
ro.build.tags(often contains `test-keys` on custom ROMs)ro.debuggable(checks if the device is debuggable)ro.secure(checks if the device is secure)ro.boot.flash.locked(bootloader status)
3. Package Manager Checks
Scanning for installed packages that indicate root management tools:
com.noshufou.android.su(SuperSU)eu.chainfire.supersu(SuperSU)com.topjohnwu.magisk(Magisk Manager)com.koushikdutta.rommanager
4. Command Execution Checks
Attempting to execute `su` or other root-specific commands and checking the exit status or output.
5. Native Library Checks (C/C++ Level)
More sophisticated apps might use native code to perform root checks, e.g., checking for specific `/proc` entries, calling low-level system functions (`access`, `stat`, `fopen`, `syscall`) on known root paths, or looking for specific process names/PIDs like `frida-server`.
Frida Basics for Dynamic Instrumentation
Frida allows you to inject snippets of JavaScript or your own library into native apps on Windows, macOS, GNU/Linux, iOS, Android, and QNX. For Android, we typically attach to a running process or spawn a new one.
To attach to a running app:
frida -U -f [PACKAGE_NAME] --no-pause
Here, -U specifies a USB-connected device, -f spawns the application and immediately attaches, and --no-pause allows the app to run immediately without waiting for user input.
Essential Frida Scripts for Bypassing Root Detection
Let’s craft some powerful scripts to counter the detection mechanisms discussed.
1. Hiding Root Indicators (File & Path Checks)
This script hooks `java.io.File` methods to return `false` for known root-related paths.
Java.perform(function() {
console.log("[*] Initiating File/Path-based Root Detection Bypass");
var File = Java.use('java.io.File');
var paths = [
"/system/app/Superuser.apk",
"/sbin/su",
"/system/bin/su",
"/system/xbin/su",
"/data/local/tmp/su",
"/data/adb/magisk",
"/system/etc/init.d/99superuser",
"/system/bin/busybox",
"/sbin/busybox",
"/dev/magisk",
"/proc/mounts",
"/proc/self/mountinfo"
];
File.exists.implementation = function() {
var path = this.getAbsolutePath();
if (paths.indexOf(path) > -1) {
console.log("[!] Blocked File.exists() check for: " + path);
return false;
}
return this.exists();
};
File.canExecute.implementation = function() {
var path = this.getAbsolutePath();
if (paths.indexOf(path) > -1) {
console.log("[!] Blocked File.canExecute() check for: " + path);
return false;
}
return this.canExecute();
};
// Hooking Runtime.exec() for 'su' command execution
var Runtime = Java.use('java.lang.Runtime');
Runtime.exec.overload('java.lang.String').implementation = function(cmd) {
if (cmd.indexOf("su") > -1) {
console.log("[!] Blocked Runtime.exec() call: " + cmd);
// Return a dummy process, effectively preventing 'su' execution
return null;
}
return this.exec(cmd);
};
Runtime.exec.overload('[Ljava.lang.String;').implementation = function(cmdArray) {
for (var i = 0; i -1) {
console.log("[!] Blocked Runtime.exec() call (array): " + cmdArray.join(" "));
return null;
}
}
return this.exec(cmdArray);
};
console.log("[*] File/Path-based Root Detection Bypass Complete");
});
2. Bypassing Package Manager & Property Checks
This script targets checks for known root management apps and modifies system properties.
Java.perform(function() {
console.log("[*] Initiating Package Manager & Property-based Root Detection Bypass");
// Hooking PackageManager for known root apps
var PackageManager = Java.use('android.app.ApplicationPackageManager');
var rootPackages = [
"com.noshufou.android.su",
"eu.chainfire.supersu",
"com.topjohnwu.magisk"
];
PackageManager.getPackageInfo.overload('java.lang.String', 'int').implementation = function(packageName, flags) {
if (rootPackages.indexOf(packageName) > -1) {
console.log("[!] Blocked getPackageInfo() check for: " + packageName);
throw Java.use('android.content.pm.PackageManager$NameNotFoundException').$new();
}
return this.getPackageInfo(packageName, flags);
};
PackageManager.getApplicationInfo.overload('java.lang.String', 'int').implementation = function(packageName, flags) {
if (rootPackages.indexOf(packageName) > -1) {
console.log("[!] Blocked getApplicationInfo() check for: " + packageName);
throw Java.use('android.content.pm.PackageManager$NameNotFoundException').$new();
}
return this.getApplicationInfo(packageName, flags);
};
// Bypassing specific system properties
var Build = Java.use('android.os.Build');
var propertiesToModify = {
"ro.debuggable": "0",
"ro.secure": "1",
"ro.build.tags": "release-keys"
};
for (var prop in propertiesToModify) {
try {
var field = Build.class.getDeclaredField(prop.replace(/./g, '_').toUpperCase()); // Example: RO_DEBUGGABLE
field.setAccessible(true);
field.set(null, Java.use('java.lang.String').$new(propertiesToModify[prop]));
console.log("[!] Modified Build property: " + prop + " to " + propertiesToModify[prop]);
} catch (e) {
// Some properties might not be directly modifiable this way or might not exist
// Attempt to hook System.getProperty instead
var SystemProperties = Java.use('android.os.SystemProperties');
SystemProperties.get.overload('java.lang.String').implementation = function(name) {
if (name === prop) {
console.log("[!] Hooked SystemProperties.get() for: " + name + " returning " + propertiesToModify[prop]);
return propertiesToModify[prop];
}
return this.get(name);
};
SystemProperties.get.overload('java.lang.String', 'java.lang.String').implementation = function(name, defaultValue) {
if (name === prop) {
console.log("[!] Hooked SystemProperties.get() with default for: " + name + " returning " + propertiesToModify[prop]);
return propertiesToModify[prop];
}
return this.get(name, defaultValue);
};
}
}
console.log("[*] Package Manager & Property-based Root Detection Bypass Complete");
});
3. Native/C-level Function Hooking (Advanced)
For applications that use native libraries to perform root checks, Frida’s `Interceptor` module is crucial. This example shows how to hook `access` and `stat` syscalls often used for file existence checks in native code. Replace `libc.so` with the actual library if the calls are internal to a specific app library.
Java.perform(function() {
console.log("[*] Initiating Native Root Detection Bypass");
var libc = Module.findExportByName(null, "access"); // Find access in any loaded module
if (!libc) {
libc = Module.findExportByName("libc.so", "access"); // Explicitly in libc
}
if (libc) {
Interceptor.attach(libc, {
onEnter: function(args) {
this.path = Memory.readUtf8String(args[0]);
this.mode = args[1].toInt32();
},
onLeave: function(retval) {
var rootPaths = [
"/system/bin/su",
"/system/xbin/su",
"/data/local/tmp/su"
];
if (rootPaths.indexOf(this.path) > -1 && retval.toInt32() === 0) {
console.log("[!] Blocked native access() check for: " + this.path + ", mode: " + this.mode);
retval.replace(ptr(-1)); // Return -1 (failure)
}
}
});
console.log("[!] Hooked native access() function");
}
var stat_func = Module.findExportByName(null, "__stat"); // stat variant
if (!stat_func) {
stat_func = Module.findExportByName("libc.so", "__xstat"); // another common variant
}
if (stat_func) {
Interceptor.attach(stat_func, {
onEnter: function(args) {
this.path = Memory.readUtf8String(args[1]); // Path is usually the second argument
},
onLeave: function(retval) {
var rootPaths = [
"/system/bin/su",
"/system/xbin/su",
"/data/local/tmp/su"
];
if (rootPaths.indexOf(this.path) > -1 && retval.toInt32() === 0) {
console.log("[!] Blocked native stat() check for: " + this.path);
retval.replace(ptr(-1));
}
}
});
console.log("[!] Hooked native stat() function");
}
console.log("[*] Native Root Detection Bypass Complete");
});
4. A Comprehensive Bypass Script (Combined)
For convenience, you can combine all these individual bypasses into one master script, let’s call it `frida_universal_root_bypass.js`.
Executing Your Frida Scripts
Save your chosen script (e.g., `frida_universal_root_bypass.js`) and execute it against your target application:
frida -U -l frida_universal_root_bypass.js -f com.example.targetapp --no-pause
Replace `com.example.targetapp` with the actual package name of the application you are testing. The output in your console will show you which checks were hooked and bypassed.
Advanced Considerations: Anti-Frida and Obfuscation
While these scripts cover common root detection methods, advanced applications might employ anti-Frida techniques or heavy obfuscation:
- Anti-Frida Detection: Apps can detect Frida by checking for `frida-server` process, specific memory patterns, or loaded libraries. Countermeasures include renaming `frida-server`, using Frida Gadget for pre-loading, or custom Frida builds.
- Code Obfuscation: Obfuscation makes identifying the root checking logic more challenging. Static analysis tools (like Jadx, Ghidra) combined with dynamic analysis are crucial here.
- Runtime Integrity Checks: Apps may verify their own code integrity at runtime, making dynamic patching difficult.
For these scenarios, a deeper dive into static analysis, reverse engineering of native libraries, and more sophisticated Frida techniques (like tracing arguments, modifying return values in custom ways, or even patching the application binary) may be required.
Conclusion
Frida is an indispensable tool in the Android penetration tester’s arsenal, especially when dealing with root detection. By understanding the common detection mechanisms and leveraging Frida’s powerful dynamic instrumentation capabilities, you can effectively bypass these obstacles. The scripts provided in this cheat sheet offer a solid foundation, empowering you to proceed with your security assessments and uncover vulnerabilities that would otherwise remain hidden on a rooted device. Continue to experiment, learn, and adapt these techniques as mobile security evolves.
Android Mobile Specs & Compare Directory
Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!
Compare Devices Specs →