root detection
mobile security
rooted device
Android security
device integrity

Determine if running on a rooted device

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

On Android, you cannot prove with perfect certainty that a device is not rooted from inside the app. What you can do is collect signals that strongly suggest root access or system tampering. In practice, good root detection is layered: use several heuristics, treat the result as risk scoring, and avoid pretending it is mathematically airtight.

Why Root Detection Is Imperfect

Rooted users control the device more than the app does. That means they can:

  • hide binaries
  • patch system properties
  • hook your code
  • fake filesystem checks

So the honest goal is not perfect detection. The real goal is to raise the bar and make integrity checks meaningful enough for your threat model.

Common Root Indicators

Typical checks include:

  • presence of su binaries
  • presence of root-management apps such as Magisk or SuperSU
  • writable system partitions in suspicious places
  • dangerous test build tags such as test-keys
  • successful execution of privileged shell commands

None of these is individually conclusive. Together, they form a stronger signal.

Example Heuristic In Kotlin

Here is a small Kotlin example that combines several basic checks.

kotlin
1import android.content.Context
2import android.os.Build
3import java.io.File
4
5object RootDetector {
6    private val rootPaths = listOf(
7        "/system/bin/su",
8        "/system/xbin/su",
9        "/sbin/su",
10        "/system/app/Superuser.apk",
11        "/system/bin/.ext/su"
12    )
13
14    private val rootPackages = listOf(
15        "com.topjohnwu.magisk",
16        "eu.chainfire.supersu"
17    )
18
19    fun isProbablyRooted(context: Context): Boolean {
20        if (Build.TAGS?.contains("test-keys") == true) return true
21        if (rootPaths.any { File(it).exists() }) return true
22        if (hasKnownRootPackage(context)) return true
23        return false
24    }
25
26    private fun hasKnownRootPackage(context: Context): Boolean {
27        val pm = context.packageManager
28        return rootPackages.any { packageName ->
29            try {
30                pm.getPackageInfo(packageName, 0)
31                true
32            } catch (e: Exception) {
33                false
34            }
35        }
36    }
37}

This is not bulletproof, but it is a reasonable baseline.

Add Runtime Command Checks Carefully

Some apps also try executing shell commands such as which su.

kotlin
1fun canFindSuOnPath(): Boolean {
2    return try {
3        val process = Runtime.getRuntime().exec(arrayOf("/system/xbin/which", "su"))
4        process.inputStream.bufferedReader().readLine() != null
5    } catch (e: Exception) {
6        false
7    }
8}

This can catch devices where the su binary is present on the path. But it is still only one signal, and on some devices the command itself may not exist.

Prefer Integrity Services For High-Risk Apps

If your app handles sensitive actions such as payments or anti-cheat enforcement, local heuristics alone are usually not enough. You should also use platform-backed integrity services when available.

The basic pattern is:

  • run local root and tamper heuristics
  • request a device-integrity attestation from a trusted service
  • make server-side decisions based on combined evidence

That is stronger than trusting an on-device check in isolation.

What To Do When You Detect Root

Detection is only half the problem. Decide your response deliberately.

Options include:

  • log and continue with reduced trust
  • block only sensitive features
  • require server-side review
  • deny execution for high-risk workflows

A blanket hard stop may be appropriate for banking or DRM use cases, but not every app needs that policy.

Common Pitfalls

A common mistake is trusting one check such as the existence of /system/bin/su and calling root detection solved. Modern root-hiding tools can bypass simplistic checks easily.

Another issue is making security decisions entirely on-device. If the attacker controls the device, local-only enforcement is fragile.

Developers also sometimes crash or block too aggressively on uncertain signals such as test-keys, which may appear on development builds or custom ROMs without the exact threat the app cares about.

Finally, do not promise users or auditors that your app can infallibly detect rooted devices. That claim is stronger than what the platform realistically allows.

Summary

  • Root detection on Android is heuristic, not perfect proof.
  • Use several signals such as su binaries, root packages, and build tags instead of relying on one check.
  • Treat root status as a risk signal and align your response with the application's threat model.
  • For high-risk apps, combine local checks with stronger integrity or attestation mechanisms.
  • Focus on layered defense rather than pretending a single on-device check can be unbeatable.

Course illustration
Course illustration

All Rights Reserved.