← labs

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.