← writeups

Android root detection bypass via Frida

A test APK with three separate root-detection mechanisms stacked on top of each other — common pattern in banking and DRM-sensitive apps. Goal: get past all three without touching the APK on disk.

Mapping the checks

Static triage with jadx first, searching for the usual suspects:

$ jadx-gui app.apk

Three distinct checks turned up:

  1. su binary path check — walks a hardcoded list of paths (/system/bin/su, /system/xbin/su, etc.) and checks File.exists() on each
  2. Build tags check — reads Build.TAGS and flags anything containing "test-keys"
  3. RootBeer library check — a third-party root detection library doing its own more thorough scan

Patching all three statically means re-signing the APK and re-installing it every time something changes. Frida avoids that entirely — hook the relevant methods at runtime and feed them fake answers.

The Frida script

Java.perform(function () {
    // 1. File.exists() — return false for su paths specifically
    var File = Java.use('java.io.File');
    File.exists.implementation = function () {
        var path = this.getAbsolutePath();
        if (path.indexOf('su') !== -1) {
            return false;
        }
        return this.exists();
    };

    // 2. Build.TAGS — spoof to release-keys
    var Build = Java.use('android.os.Build');
    Build.TAGS.value = 'release-keys';

    // 3. RootBeer — hook the public check method directly
    var RootBeer = Java.use('com.scottyab.rootbeer.RootBeer');
    RootBeer.isRooted.implementation = function () {
        return false;
    };
});
$ frida -U -f com.example.app -l bypass.js --no-pause

All three checks pass clean. App boots straight past the root-detection screen into the normal flow.

Why hook instead of patch

Patching the APK works too, but it’s a one-shot fix tied to that exact build — the moment the developer adds a fourth check or changes a string the detection logic looks for, the patch is stale and needs redoing from scratch. The Frida script targets the behaviour (file existence, a static field, a known library’s public method) rather than specific bytes, so it survives minor app updates far better. Worth keeping a small library of these hooks per detection pattern rather than writing one-off scripts each time.