Tutorial
Android Root Security: Bypassing Attestation with Frida and eBPF
A deep-dive tutorial on modern Android root detection mechanisms and how to bypass them using Frida hooks, KernelSU, and eBPF-based kernel introspection.
The arms race
Android’s security model has evolved significantly. Modern apps no longer
check for su binaries alone. They probe for SELinux policy violations,
check PROCESS_STATUS through SafetyNet/Play Integrity, and scan for
compromised kernel structures via eBPF programs.
This tutorial walks through each layer and how to bypass it.
Layer 1: Userspace detection
The simplest checks look for:
/system/xbin/suor/system/bin/suexistenceBuild.TAGS.contains("test-keys")Magiskpackage presenceselinuxcontext anomalies
Bypass: Frida hooks each check method and returns the unrooted value.
Java.perform(() => {
const File = Java.use('java.io.File');
File.exists.implementation = function () {
const path = this.getPath();
if (path.includes('su')) return false;
return this.exists.call(this);
};
});
Layer 2: Kernel-level detection
Google’s eBPF-based root detection attaches programs to tracepoints that monitor:
sys_opencalls to blacklisted pathssys_execvefor suspicious binary launches- Process ancestry chains (is
zygotespawningsh?)
Bypass: KernelSU allows loading a kernel module that detaches the eBPF programs before they can log events.
static int __init bypass_init(void) {
// Find the bpf_prog array in the kernel
struct bpf_prog_array *progs =
rcu_dereference(init_task.signal->bpf_prog_array);
if (progs) {
// Remove the attestation program from the array
bpf_prog_array_delete(progs, BPF_TRACE_RUN);
}
return 0;
}
Layer 3: Play Integrity (formerly SafetyNet)
Google’s Play Integrity API returns a verdict token signed by Google’s servers. It attests to the device’s boot state and system integrity. The token is cryptographically verified server-side, so it cannot be forged without the TSL signing key.
Bypass: Route the attestation through a trusted environment. KernelSU
provides a “sensitive byp” mode that temporarily masks the unlocked_boot
flag and presents a locked-boot state to the Integrity API.
kernelsu --set-boot-state locked
# Now call the Integrity API. It sees a locked device
kernelsu --set-boot-state unlocked
Putting it together
A complete root-hiding setup combines:
- KernelSU for kernel-level masking
- Frida for userspace hooking
- ZygiskNext to hide root from Zygote forks
Test each layer with adb shell dmesg | grep integrity to verify no kernel
alerts are triggered. Then validate with the Play Integrity API tester app.