android-root-detection-bypass
Intentionally vulnerable — run in an isolated VM only. No network access to production systems.
Download lab package →What’s in the package
A debug-signed APK (practice-app.apk) with three independent root-detection mechanisms stacked together,
plus a README.txt covering install steps for a rooted emulator (Android Studio AVD with a Google APIs
image works fine — you need root for this one, that’s the point).
This lab pairs with the writeup on this exact bypass — the writeup covers the approach in narrative form, this page is the hands-on version with the actual APK to practice against.
Install only on an emulator or a dedicated test device. Do not install on a personal device.
Setup
adb install practice-app.apk
frida-ps -U # confirm frida-server is running on the device/emulator
Launch the app once normally first — it’ll show a “Root detected, exiting” screen. That’s the baseline you’re bypassing.
Walkthrough
1. Identify the checks
Decompile with jadx and search for anything referencing root, su, or build tags:
jadx-gui practice-app.apk
You’re looking for three things: a hardcoded path check for su binaries, a Build.TAGS string check, and
a call into a bundled root-detection library (RootBeer, in this build).
2. Hook each check independently
Don’t try to write one hook that handles everything — handle each check on its own terms, since they each work differently:
Java.perform(function () {
Java.use('java.io.File').exists.implementation = function () {
return this.getAbsolutePath().indexOf('su') !== -1 ? false : this.exists();
};
Java.use('android.os.Build').TAGS.value = 'release-keys';
Java.use('com.scottyab.rootbeer.RootBeer').isRooted.implementation = function () {
return false;
};
});
3. Attach and verify
frida -U -f com.compilelog.practiceapp -l bypass.js --no-pause
The app should now boot straight past the detection screen.
Why hook behaviour, not bytes
Static patching means re-signing the APK and re-installing every time you tweak the patch. Hooking the
methods that each check calls into — File.exists(), a static field read, a library’s public method — means
the same script works again after a minor app update, as long as the underlying detection approach hasn’t
changed. Worth treating this script as a reusable starting point rather than a one-off for this specific APK.