Determine if running on a rooted device
Asked Answered
W

29

368

My app has a certain piece of functionality that will only work on a device where root is available. Rather than having this feature fail when it is used (and then show an appropriate error message to the user), I'd prefer an ability to silently check if root is available first, and if not,hide the respective options in the first place.

Is there a way to do this?

Weltanschauung answered 9/7, 2009 at 1:22 Comment(4)
There is no reliable way to do so; the answers below check common characteristics but a given device may not be rooted in a common way. If checking for root becomes prevalent, root solutions will probably start going to an effort to hide themselves. Since they can modify operating system behavior they have plenty of options for doing so.Coblenz
It might be better to indicate that the function is not available due to the lack of root capability providing more information to the user rather than hiding the capabilities of your app adding ambiguity to the overall experience.Laurenlaurena
Does below answers work for Systemless Root ?Subordinate
It works, but if root cloaking/hiding tool such as MagiskHide is enabled, then you're doomed, because it's finally a dead end and there's no way to get over it.Puleo
W
295

Here is a class that will check for Root one of three ways.

/** @author Kevin Kowalewski */
public class RootUtil {
    public static boolean isDeviceRooted() {
        return checkRootMethod1() || checkRootMethod2() || checkRootMethod3();
    }

    private static boolean checkRootMethod1() {
        String buildTags = android.os.Build.TAGS;
        return buildTags != null && buildTags.contains("test-keys");
    }

    private static boolean checkRootMethod2() {
        String[] paths = { "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
                "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"};
        for (String path : paths) {
            if (new File(path).exists()) return true;
        }
        return false;
    }

    private static boolean checkRootMethod3() {
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(new String[] { "/system/xbin/which", "su" });
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            if (in.readLine() != null) return true;
            return false;
        } catch (Throwable t) {
            return false;
        } finally {
            if (process != null) process.destroy();
        }
    }
}
Wasserman answered 11/11, 2011 at 17:37 Comment(25)
If a two questions warrant identical answers then they are 99% of the time duplicates, so flag as dupes instead of posting the same answer on both. Thanks.Autarch
Its not 100% identical. The answer I provided contains code to actually attempt rooted commands, that would not prompt a Super User request to the user. Checking for test-keys does not work on all rooted devices (didn't work on mine). Testing for SuperUser.apk is also not perfect, users can uninstall it after gaining root.Wasserman
That may be so, however I am just letting you know that exact duplicate answers are flagged by the community. You should tailor your answers and address the specifics of the OP's problem. Copy and paste answers are at risk from attracting downvotes.Autarch
Thanks for the heads up, there is no reason why I would post an exact duplicate. My answer also includes code on how the Execute shell commands, which is useful in determining if the device running root.Wasserman
Pardon my ignorance, but is it possible to detect a rooted phone without the "reboot"? TIA!Pesade
-1, this method is not viable, because some phones include the su binary while not rooted.Pitapat
Just wanted to let you know, the Fox Digital Copy (Beta) app uses your code nearly verbatim, including the Root and ExecShell classes, as well as the checkRootMethod1/2/3 methods. Found it highly amusing.Cunaxa
I can sue them like Fox sued countless others?Wasserman
Is /system/xbin/which installed when the device is rooted? I could not find it on the emulator. But /system/xbin/su is there.Germinal
see below for checkRootAnswer4()Clothilde
@MattJoseph does this code undermine the ability of rootcloak to 'hide itself'?Norvil
I use noobProgrammer's answer as checkRootMethod4(). Apparently 3 weren't enough. :PKelby
noobProgrammer's answer performs identically to checkRootMethod2.Knickknack
This will slow down your app user's phoneSolfatara
How many times are you calling it? It doesn't slow down your phone.Wasserman
Xposed module Root Cloak can pass those checksSip
@Picci Please check my solution .Richart
@Kevin Do you mind if I use your code in an open source React Native project? I'll make sure I mention you in contributors. Thanks.Bushhammer
I have root on my Android 7.0 Nougat and root checker tells me binary is located at "/su/bin/su". Added that to my code and my apps now works fine on Nougat 7.0. I will edit @Kevin 's answer to include this.Selfsacrifice
For method 1 correlation does not equal causation. Just because its a non-official build doesn't mean you automatically have access to root even if that is usually correlated. Also why not just iterate through the PATH variable directories and look for a "su" file? Way simpler than hard coding a bunch of typical PATH directories.Bulgarian
In each device which has even official build it shows "test-keys" i.e. return true in first case ,hows that possible if device has official ROM?Bimbo
Although some devices like 'Moto Z'(XT1650) have built in /sbin/su but are NOT ROOTed.Beberg
THANK YOU. Your script is breaking the Android app from the Irish bank AIB.Ade
@PedroPauloAmorim That's not the author fault, but AIB's engineering and QA team, who hadn't tested script behavior on a various devices and environments. And once again, there's NO reliable way to determine whether device is rooted or not. This method is as error-prone as any other.Pawsner
I have an android app with iap and ads do i need to add this code snippet ? meaning can a user get a premium version of the app without actually paying for in a rooted device ? should i add it or not ?Ebony
I
110

If you are already using Fabric/Firebase Crashlytics you can call

CommonUtils.isRooted(context)

This is the current implementation of that method:

public static boolean isRooted(Context context) {
    boolean isEmulator = isEmulator(context);
    String buildTags = Build.TAGS;
    if (!isEmulator && buildTags != null && buildTags.contains("test-keys")) {
        return true;
    } else {
        File file = new File("/system/app/Superuser.apk");
        if (file.exists()) {
            return true;
        } else {
            file = new File("/system/xbin/su");
            return !isEmulator && file.exists();
        }
    }
}

public static boolean isEmulator(Context context) {
    String androidId = Secure.getString(context.getContentResolver(), "android_id");
    return "sdk".equals(Build.PRODUCT) || "google_sdk".equals(Build.PRODUCT) || androidId == null;
}
Idolah answered 25/2, 2016 at 13:41 Comment(8)
Best answer ever. Please use this over any library, there are a lot of false positive running on Chinese devices.Ade
Is there any false positive in this method?Nereid
I tested this on nexus 5 with download.chainfire.eu/363/CF-Root/CF-Auto-Root/…, this one is not accurate.Pentstemon
This code failed. A group of testers where able to root multiple devices with Magisk (default configuration) and the root was not detected by this code.Klaxon
what about renaming /system/xbin/su to /system/xbin/ru, this method will failBowlin
I have an android app with iap and ads do i need to add this code snippet ? meaning can a user get a premium version of the app without actually paying for in a rooted device ? should i add it or not ?Ebony
@Ebony I guess that depends on how safe your service is. To be honest I have never added this check to protect against that kind of frauds. I used it to protect the user when they need to share credit cards etc.Idolah
Tried this method on Emulator for Api32. It doesn't work. isEmulator returns falseCircumsolar
G
60

The RootTools library offers simple methods to check for root:

RootTools.isRootAvailable()

Reference

Giglio answered 3/7, 2011 at 14:57 Comment(6)
isRootAvailable() just checks for the existence of su in the path and some other hard-coded directories. I've heard that some unrooting tools will leave su there, so this will give a false positive.Webby
RootTools.isAccessGiven() will not only check for root, but also request root permission; so an unrooted device will always return false with this method.Teethe
@aggregate1166877, you are right, but it is not good enough, what if I don't require root permission when I ask? I just want to know if it is rooted, but I don't need root permission at the moment.Pitapat
isAccessGiven() returns false when the user denies the permission even though the device was a rooted one.Mccants
This is the only answer I find worth up-voting. Check out my answer below if you want something similar to just copy paste in, or would like some more detailsBulgarian
Thanks ..Best solution to find the device rooted or not...:)Clemmie
S
60

In my application I was checking if device is rooted or not by executing "su" command. But today I've removed this part of my code. Why?

Because my application became a memory killer. How? Let me tell you my story.

There were some complaints that my application was slowing down devices(Of course I thought that can not be true). I tried to figure out why. So I used MAT to get heap dumps and analyze, and everything seemed perfect. But after relaunching my app many times I realized that device is really getting slower and stopping my application didn't make it faster (unless I restart device). I analyzed dump files again while device is very slow. But everything was still perfect for dump file. Then I did what must be done at first. I listed processes.

$ adb shell ps

Surprize; there were many processes for my application (with my application's process tag at manifest). Some of them was zombie some of them not.

With a sample application which has a single Activity and executes just "su" command, I realized that a zombie process is being created on every launch of application. At first these zombies allocate 0KB but than something happens and zombie processes are holding nearly same KBs as my application's main process and they became standart processes.

There is a bug report for same issue on bugs.sun.com: https://bugs.java.com/bugdatabase/view_bug?bug_id=6474073 this explains if command is not found zombies are going to be created with exec() method. But I still don't understand why and how can they become standart processes and hold significant KBs. (This is not happening all the time)

You can try if you want with code sample below;

String commandToExecute = "su";
executeShellCommand(commandToExecute);

Simple command execution method;

private boolean executeShellCommand(String command){
    Process process = null;            
    try{
        process = Runtime.getRuntime().exec(command);
        return true;
    } catch (Exception e) {
        return false;
    } finally{
        if(process != null){
            try{
                process.destroy();
            }catch (Exception e) {
            }
        }
    }
}

To sum up; I have no advice for you to determine if device is rooted or not. But if I were you I would not use Runtime.getRuntime().exec().

By the way; RootTools.isRootAvailable() causes same problem.

Scanlon answered 18/3, 2013 at 19:26 Comment(11)
That is very worrisome. I had a rooted device detection class that did the same thing - after reading this I confirmed what aegean detailed above. Occasional zombie processes being left behind, device slowdowns, etc...Overdraft
I confirm the problem with RootTools 3.4 on a GT-S5830i android 2.3.6. Most of the zombie got allocated memory and the problem is systematic. I need to restart the device after 3-4 test. I recommend to save the test result to shared preference.Typo
Google now recommends using ProcessBuilder() and the start() command.Behlau
does using processbuilder help to get over the issue of zombie process ?Endogamy
@Behlau I tried Runtime.getRuntime().exec("su") and new ProcessBuilder().command("su").start() and noticed zombie processes in both cases.Counterweigh
@NickS Interesting, but what command did you launch? I don't have the same issue here issuing commands on numerous Android phones of different API level from 9 - 23.Behlau
@EntangledLoops. Thank you. I launch my own binary and interact with it through stdin/stdout. I checked again how I stop it and found out that I missed Process.destroy() in one of cases. So, no zombies.Counterweigh
Iterating over PATH to see if "su" exists is trivial, see my answer below if you need an exampleBulgarian
Executing a command like this on some devices it may display a popup that says: APP_NAME was denied SuperUser rights. You should consider this that the end customer may see it and get suspicious about what the application is doing.Carolinian
I have an android app with iap and ads do i need to add this code snippet ? meaning can a user get a premium version of the app without actually paying for in a rooted device ? should i add it or not ?Ebony
what if we kill those ZOmbies programatically anyway? @Scanlon ?Sade
B
41

Many of the answers listed here have inherent issues:

  • Checking for test-keys is correlated with root access but doesn't necessarily guarantee it
  • "PATH" directories should be derived from the actual "PATH" environment variable instead of being hard coded
  • The existence of the "su" executable doesn't necessarily mean the device has been rooted
  • The "which" executable may or may not be installed, and you should let the system resolve its path if possible
  • Just because the SuperUser app is installed on the device does not mean the device has root access yet

The RootTools library from Stericson seems to be checking for root more legitimately. It also has lots of extra tools and utilities so I highly recommend it. However, there's no explanation of how it specifically checks for root, and it may be a bit heavier than most apps really need.

I've made a couple of utility methods that are loosely based on the RootTools library. If you simply want to check if the "su" executable is on the device you can use the following method:

public static boolean isRootAvailable(){
    for(String pathDir : System.getenv("PATH").split(":")){
        if(new File(pathDir, "su").exists()) {
            return true;
        }
    }
    return false;
}

This method simply loops through the directories listed in the "PATH" environment variable and checks if a "su" file exists in one of them.

In order to truly check for root access the "su" command must actually be run. If an app like SuperUser is installed, then at this point it may ask for root access, or if its already been granted/denied a toast may be shown indicating whether access was granted/denied. A good command to run is "id" so that you can verify that the user id is in fact 0 (root).

Here's a sample method to determine whether root access has been granted:

public static boolean isRootGiven(){
    if (isRootAvailable()) {
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(new String[]{"su", "-c", "id"});
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String output = in.readLine();
            if (output != null && output.toLowerCase().contains("uid=0"))
                return true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (process != null)
                process.destroy();
        }
    }

    return false;
}

It's important to actually test running the "su" command because some emulators have the "su" executable pre-installed, but only allow certain users to access it like the adb shell.

It's also important to check for the existence of the "su" executable before trying to run it, because android has been known to not properly dispose of processes that try to run missing commands. These ghost processes can run up memory consumption over time.

Bulgarian answered 9/9, 2016 at 21:54 Comment(12)
The isRootAvailable() method works great, thank you. I do recommend not running this on the main thread however to avoid an ANR, such as call from an AsyncTaskUnbonnet
@Thunderstick, right the file exists call is "disk" IO, but along the lines of a file stat which should be very quick. If you're trying to search for the su executable, like most answers here, then you can't avoid this. I agree though, probably best to run off the main thread.Bulgarian
isRootGiven() above works well, thanks. It also has the advantage of telling you if root has been denied on a rooted phone...Virtuosic
I think it's the diff between wanting to ensure root is not available and wanting to ensure it is. If you want to ensure a device is not rooted, the suggested checks are good. You will get false positives but that's okay when not running your code on a compromised device is your main concern.Mclin
@JeffreyBlattman I agree completely. However if you want to go the extra mile I would probably use the java native interface. Even so I believe iterating over PATH is a better solution than hard coding typical PATH directories. The checks for super user management apps and build tags are decent checks in that context though.Bulgarian
i am getting permission alert from the rooting application(Kingo ROOT) and up on giving Deny, this function return false. IS there any way to check this case..Seena
@Seena I'm not sure I understand your question. If you run isRootGiven and deny on your rooting application then it should return false. Is that not what's happening? If you want to avoid the alert you can just use isRootAvailable which could also be named doesSUExist. You could also try to configure your root application to give out root freely and not manage it.Bulgarian
"The existence of the "su" executable doesn't necessarily mean the device has been rooted", does this mean I cannot judge that a phone is rooted by checking existence of 'su'?Railroad
@BeeingJk no not really, although that's really the most you can check for without running su which is the real test. You need to check for su in PATH before trying to execute it though. However actually executing su often results in a toast message or an interaction with a root managing app which may not be what you want. For your own logic you may deem the mere existence of su sufficient. This can still give false positives in some emulators which may contain a su executable but lockdown access to it.Bulgarian
@BeeingJk isRootAvailable is probably all you need, but the point I'm trying to make is that a name like that or even doesSUExist provides better semantics than a method name like isDeviceRooted which isn't quite right. If you really need to verify full root access before proceeding you need to try to run a command with su like the one coded in isRootGivenBulgarian
What does it mean that the existence of the "su" executable doesn't necessarily mean the device has been rooted?Pronty
@Pronty When I wrote this answer I mostly noticed it in certain emulators limiting access to adb. But essentially any situation where the file exists but the user doesn't have executable access to it, or if the executable fails to give you root. It's a more technically correct solution. Even if you don't want to pre-check running su, I would still call the function something like isRootAvailable or hasSuCommand for better semantics. Whatever works best for youBulgarian
R
31

Root check at Java level is not a safe solution. If your app has Security Concerns to run on a Rooted device , then please use this solution.

Kevin's answer works unless the phone also has an app like RootCloak . Such apps have a Handle over Java APIs once phone is rooted and they mock these APIs to return phone is not rooted.

I have written a native level code based on Kevin's answer , it works even with RootCloak ! Also it does not cause any memory leak issues.

#include <string.h>
#include <jni.h>
#include <time.h>
#include <sys/stat.h>
#include <stdio.h>
#include "android_log.h"
#include <errno.h>
#include <unistd.h>
#include <sys/system_properties.h>

JNIEXPORT int JNICALL Java_com_test_RootUtils_checkRootAccessMethod1(
        JNIEnv* env, jobject thiz) {


    //Access function checks whether a particular file can be accessed
    int result = access("/system/app/Superuser.apk",F_OK);

    ANDROID_LOGV( "File Access Result %d\n", result);

    int len;
    char build_tags[PROP_VALUE_MAX]; // PROP_VALUE_MAX from <sys/system_properties.h>.
    len = __system_property_get(ANDROID_OS_BUILD_TAGS, build_tags); // On return, len will equal (int)strlen(model_id).
    if(strcmp(build_tags,"test-keys") == 0){
        ANDROID_LOGV( "Device has test keys\n", build_tags);
        result = 0;
    }
    ANDROID_LOGV( "File Access Result %s\n", build_tags);
    return result;

}

JNIEXPORT int JNICALL Java_com_test_RootUtils_checkRootAccessMethod2(
        JNIEnv* env, jobject thiz) {
    //which command is enabled only after Busy box is installed on a rooted device
    //Outpput of which command is the path to su file. On a non rooted device , we will get a null/ empty path
    //char* cmd = const_cast<char *>"which su";
    FILE* pipe = popen("which su", "r");
    if (!pipe) return -1;
    char buffer[128];
    std::string resultCmd = "";
    while(!feof(pipe)) {
        if(fgets(buffer, 128, pipe) != NULL)
            resultCmd += buffer;
    }
    pclose(pipe);

    const char *cstr = resultCmd.c_str();
    int result = -1;
    if(cstr == NULL || (strlen(cstr) == 0)){
        ANDROID_LOGV( "Result of Which command is Null");
    }else{
        result = 0;
        ANDROID_LOGV( "Result of Which command %s\n", cstr);
        }
    return result;

}

JNIEXPORT int JNICALL Java_com_test_RootUtils_checkRootAccessMethod3(
        JNIEnv* env, jobject thiz) {


    int len;
    char build_tags[PROP_VALUE_MAX]; // PROP_VALUE_MAX from <sys/system_properties.h>.
    int result = -1;
    len = __system_property_get(ANDROID_OS_BUILD_TAGS, build_tags); // On return, len will equal (int)strlen(model_id).
    if(len >0 && strstr(build_tags,"test-keys") != NULL){
        ANDROID_LOGV( "Device has test keys\n", build_tags);
        result = 0;
    }

    return result;

}

In your Java code , you need to create wrapper class RootUtils to make the native calls

    public boolean checkRooted() {

       if( rootUtils.checkRootAccessMethod3()  == 0 || rootUtils.checkRootAccessMethod1()  == 0 || rootUtils.checkRootAccessMethod2()  == 0 )
           return true;
      return false;
     }
Richart answered 15/5, 2016 at 11:6 Comment(5)
I think root detection falls into two categories, enabling root dependent features and then security based measures to try to mitigate security issues with rooted phones. For root dependent features I find Kevin's answer to be quite poor. In the context of this answer these methods make more sense. Although I would rewrite method 2 to not use which and instead iterate over the PATH environment variable to search for "su". "which" is not guaranteed to be on the phone.Bulgarian
can you please provide an example of how to use this c code in java ?Mink
@Mink Please check on how to make JNI calls from Java on Android.Richart
This method prevent root detection byepass using RootCloak app. Is there any known root byepass techniques that fail these method?Brought
termux ships with an su binary, which sets off this root detector as a false positive. Thankyou for your help in finding this issue though!Missymist
W
23

http://code.google.com/p/roottools/

If you do not want to use the jar file just use the code:

public static boolean findBinary(String binaryName) {
        boolean found = false;
        if (!found) {
            String[] places = { "/sbin/", "/system/bin/", "/system/xbin/",
                    "/data/local/xbin/", "/data/local/bin/",
                    "/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/" };
            for (String where : places) {
                if (new File(where + binaryName).exists()) {
                    found = true;

                    break;
                }
            }
        }
        return found;
    }

Program will try to find su folder:

private static boolean isRooted() {
        return findBinary("su");
    }

Example:

if (isRooted()) {
   textView.setText("Device Rooted");

} else {
   textView.setText("Device Unrooted");
}
Williamwilliams answered 30/5, 2014 at 10:58 Comment(10)
Thanks! I'm using this as checkRootMethod4() with Kevin's Answer .Kelby
Never add == true to a boolean, it adds nothing and doesn't look good.Anglian
This will slow down your app user's phoneSolfatara
@Solfatara Why would it? It's not spawning any Processes as DevrimTuncer's solution is.Drusy
A better idea would be to iterate over PATH, instead of hard coding typical PATH directoriesBulgarian
BUG: This does not work on SuperSU 2.65. Because they change the path to "/su/xbin". So You need to add this to places arrayMielke
@Mielke why not just iterate over PATH? This is exactly why hard coding these paths is a bad idea. See my answer below for an example.Bulgarian
Use, if (isRooted()) check instead of explicitly write true. Its better to follow code writing patternsLoveinamist
This is a longer and more elaborate version of "checkRootMethod2" in the selected answer.Elgin
A better idea would be to return true than breaking the loop and then returning true.Plotinus
I
14

RootBeer is a root checking Android library by Scott and Matthew. It uses various checks to indicate whether device is rooted or not.

Java checks

  • CheckRootManagementApps

  • CheckPotentiallyDangerousAppss

  • CheckRootCloakingApps

  • CheckTestKeys

  • checkForDangerousProps

  • checkForBusyBoxBinary

  • checkForSuBinary

  • checkSuExists

  • checkForRWSystem

Native checks

We call through to our native root checker to run some of it's own checks. Native checks are typically harder to cloak, so some root cloak apps just block the loading of native libraries that contain certain key words.

  • checkForSuBinary
Impenetrability answered 14/2, 2017 at 9:33 Comment(0)
I
13

Instead of using isRootAvailable() you can use isAccessGiven(). Direct from RootTools wiki:

if (RootTools.isAccessGiven()) {
    // your app has been granted root access
}

RootTools.isAccessGiven() not only checks that a device is rooted, it also calls su for your app, requests permission, and returns true if your app was successfully granted root permissions. This can be used as the first check in your app to make sure that you will be granted access when you need it.

Reference

Icecap answered 12/3, 2013 at 20:14 Comment(1)
but the user has to give root access right? so if my aim was to stop my app from running if the device is rooted then my options are really limitedAutotransformer
C
12

Some modified builds used to set the system property ro.modversion for this purpose. Things seem to have moved on; my build from TheDude a few months ago has this:

cmb@apollo:~$ adb -d shell getprop |grep build
[ro.build.id]: [CUPCAKE]
[ro.build.display.id]: [htc_dream-eng 1.5 CUPCAKE eng.TheDudeAbides.20090427.235325 test-keys]
[ro.build.version.incremental]: [eng.TheDude.2009027.235325]
[ro.build.version.sdk]: [3]
[ro.build.version.release]: [1.5]
[ro.build.date]: [Mon Apr 20 01:42:32 CDT 2009]
[ro.build.date.utc]: [1240209752]
[ro.build.type]: [eng]
[ro.build.user]: [TheDude]
[ro.build.host]: [ender]
[ro.build.tags]: [test-keys]
[ro.build.product]: [dream]
[ro.build.description]: [kila-user 1.1 PLAT-RC33 126986 ota-rel-keys,release-keys]
[ro.build.fingerprint]: [tmobile/kila/dream/trout:1.1/PLAT-RC33/126986:user/ota-rel-keys,release-keys]
[ro.build.changelist]: [17615# end build properties]

The emulator from the 1.5 SDK on the other hand, running the 1.5 image, also has root, is probably similar to the Android Dev Phone 1 (which you presumably want to allow) and has this:

cmb@apollo:~$ adb -e shell getprop |grep build
[ro.build.id]: [CUPCAKE]
[ro.build.display.id]: [sdk-eng 1.5 CUPCAKE 148875 test-keys]
[ro.build.version.incremental]: [148875]
[ro.build.version.sdk]: [3]
[ro.build.version.release]: [1.5]
[ro.build.date]: [Thu May 14 18:09:10 PDT 2009]
[ro.build.date.utc]: [1242349750]
[ro.build.type]: [eng]
[ro.build.user]: [android-build]
[ro.build.host]: [undroid16.mtv.corp.google.com]
[ro.build.tags]: [test-keys]
[ro.build.product]: [generic]
[ro.build.description]: [sdk-eng 1.5 CUPCAKE 148875 test-keys]
[ro.build.fingerprint]: [generic/sdk/generic/:1.5/CUPCAKE/148875:eng/test-keys]

As for the retail builds, I don't have one to hand, but various searches under site:xda-developers.com are informative. Here is a G1 in the Netherlands, you can see that ro.build.tags does not have test-keys, and I think that's probably the most reliable property to use.

Cultigen answered 9/7, 2009 at 7:33 Comment(2)
That looks interesting, but: While the emulator (and ADP) do allow root per se, they don't allow applications to use it, i.e.: $ su app_29 $ su su: uid 10029 not allowed to suWeltanschauung
Ah, I suppose they wouldn't... you could combine it with a check for ro.build.host (not) ending in google.com then, if they're the only ones that have test-keys but block su without asking the user. Depends what the build host is for newer devices, things that aren't phones... not easy.Cultigen
A
11

I suggest using native code for root detection. Here is a full working example.

enter image description here

JAVA wrapper:

package com.kozhevin.rootchecks.util;


import android.support.annotation.NonNull;

import com.kozhevin.rootchecks.BuildConfig;

public class MeatGrinder {
    private final static String LIB_NAME = "native-lib";
    private static boolean isLoaded;
    private static boolean isUnderTest = false;

    private MeatGrinder() {

    }

    public boolean isLibraryLoaded() {
        if (isLoaded) {
            return true;
        }
        try {
            if(isUnderTest) {
                throw new UnsatisfiedLinkError("under test");
            }
            System.loadLibrary(LIB_NAME);
            isLoaded = true;
        } catch (UnsatisfiedLinkError e) {
            if (BuildConfig.DEBUG) {
                e.printStackTrace();
            }
        }
        return isLoaded;
    }

    public native boolean isDetectedDevKeys();

    public native boolean isDetectedTestKeys();

    public native boolean isNotFoundReleaseKeys();

    public native boolean isFoundDangerousProps();

    public native boolean isPermissiveSelinux();

    public native boolean isSuExists();

    public native boolean isAccessedSuperuserApk();

    public native boolean isFoundSuBinary();

    public native boolean isFoundBusyboxBinary();

    public native boolean isFoundXposed();

    public native boolean isFoundResetprop();

    public native boolean isFoundWrongPathPermission();

    public native boolean isFoundHooks();

    @NonNull
    public static MeatGrinder getInstance() {
        return InstanceHolder.INSTANCE;
    }

    private static class InstanceHolder {
        private static final MeatGrinder INSTANCE = new MeatGrinder();
    }
}

JNI wrapper(native-lib.c):

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isDetectedTestKeys(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isDetectedTestKeys();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isDetectedDevKeys(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isDetectedDevKeys();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isNotFoundReleaseKeys(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isNotFoundReleaseKeys();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundDangerousProps(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundDangerousProps();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isPermissiveSelinux(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isPermissiveSelinux();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isSuExists(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isSuExists();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isAccessedSuperuserApk(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isAccessedSuperuserApk();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundSuBinary(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundSuBinary();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundBusyboxBinary(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundBusyboxBinary();
}


JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundXposed(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundXposed();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundResetprop(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundResetprop();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundWrongPathPermission(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundWrongPathPermission();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundHooks(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundHooks();
}

constants:

// Comma-separated tags describing the build, like= "unsigned,debug".
const char *const ANDROID_OS_BUILD_TAGS = "ro.build.tags";

// A string that uniquely identifies this build. 'BRAND/PRODUCT/DEVICE:RELEASE/ID/VERSION.INCREMENTAL:TYPE/TAGS'.
const char *const ANDROID_OS_BUILD_FINGERPRINT = "ro.build.fingerprint";

const char *const ANDROID_OS_SECURE = "ro.secure";

const char *const ANDROID_OS_DEBUGGABLE = "ro.debuggable";
const char *const ANDROID_OS_SYS_INITD = "sys.initd";
const char *const ANDROID_OS_BUILD_SELINUX = "ro.build.selinux";
//see https://android.googlesource.com/platform/system/core/+/master/adb/services.cpp#86
const char *const SERVICE_ADB_ROOT = "service.adb.root";

const char * const MG_SU_PATH[] = {
        "/data/local/",
        "/data/local/bin/",
        "/data/local/xbin/",
        "/sbin/",
        "/system/bin/",
        "/system/bin/.ext/",
        "/system/bin/failsafe/",
        "/system/sd/xbin/",
        "/su/xbin/",
        "/su/bin/",
        "/magisk/.core/bin/",
        "/system/usr/we-need-root/",
        "/system/xbin/",
        0
};

const char * const MG_EXPOSED_FILES[] = {
        "/system/lib/libxposed_art.so",
        "/system/lib64/libxposed_art.so",
        "/system/xposed.prop",
        "/cache/recovery/xposed.zip",
        "/system/framework/XposedBridge.jar",
        "/system/bin/app_process64_xposed",
        "/system/bin/app_process32_xposed",
        "/magisk/xposed/system/lib/libsigchain.so",
        "/magisk/xposed/system/lib/libart.so",
        "/magisk/xposed/system/lib/libart-disassembler.so",
        "/magisk/xposed/system/lib/libart-compiler.so",
        "/system/bin/app_process32_orig",
        "/system/bin/app_process64_orig",
        0
};

const char * const MG_READ_ONLY_PATH[] = {
        "/system",
        "/system/bin",
        "/system/sbin",
        "/system/xbin",
        "/vendor/bin",
        "/sbin",
        "/etc",
        0
};

root detections from native code:

struct mntent *getMntent(FILE *fp, struct mntent *e, char *buf, int buf_len) {

    while (fgets(buf, buf_len, fp) != NULL) {
        // Entries look like "/dev/block/vda /system ext4 ro,seclabel,relatime,data=ordered 0 0".
        // That is: mnt_fsname mnt_dir mnt_type mnt_opts mnt_freq mnt_passno.
        int fsname0, fsname1, dir0, dir1, type0, type1, opts0, opts1;
        if (sscanf(buf, " %n%*s%n %n%*s%n %n%*s%n %n%*s%n %d %d",
                   &fsname0, &fsname1, &dir0, &dir1, &type0, &type1, &opts0, &opts1,
                   &e->mnt_freq, &e->mnt_passno) == 2) {
            e->mnt_fsname = &buf[fsname0];
            buf[fsname1] = '\0';
            e->mnt_dir = &buf[dir0];
            buf[dir1] = '\0';
            e->mnt_type = &buf[type0];
            buf[type1] = '\0';
            e->mnt_opts = &buf[opts0];
            buf[opts1] = '\0';
            return e;
        }
    }
    return NULL;
}


bool isPresentMntOpt(const struct mntent *pMnt, const char *pOpt) {
    char *token = pMnt->mnt_opts;
    const char *end = pMnt->mnt_opts + strlen(pMnt->mnt_opts);
    const size_t optLen = strlen(pOpt);
    while (token != NULL) {
        const char *tokenEnd = token + optLen;
        if (tokenEnd > end) break;
        if (memcmp(token, pOpt, optLen) == 0 &&
            (*tokenEnd == '\0' || *tokenEnd == ',' || *tokenEnd == '=')) {
            return true;
        }
        token = strchr(token, ',');
        if (token != NULL) {
            token++;
        }
    }
    return false;
}

static char *concat2str(const char *pString1, const char *pString2) {
    char *result;
    size_t lengthBuffer = 0;

    lengthBuffer = strlen(pString1) +
                   strlen(pString2) + 1;
    result = malloc(lengthBuffer);
    if (result == NULL) {
        GR_LOGW("malloc failed\n");
        return NULL;
    }
    memset(result, 0, lengthBuffer);
    strcpy(result, pString1);
    strcat(result, pString2);
    return result;
}

static bool
isBadPropertyState(const char *key, const char *badValue, bool isObligatoryProperty, bool isExact) {
    if (badValue == NULL) {
        GR_LOGE("badValue may not be NULL");
        return false;
    }
    if (key == NULL) {
        GR_LOGE("key may not be NULL");
        return false;
    }
    char value[PROP_VALUE_MAX + 1];
    int length = __system_property_get(key, value);
    bool result = false;
    /* A length 0 value indicates that the property is not defined */
    if (length > 0) {
        GR_LOGI("property:[%s]==[%s]", key, value);
        if (isExact) {
            if (strcmp(value, badValue) == 0) {
                GR_LOGW("bad value[%s] equals to [%s] in the property [%s]", value, badValue, key);
                result = true;
            }
        } else {
            if (strlen(value) >= strlen(badValue) && strstr(value, badValue) != NULL) {
                GR_LOGW("bad value[%s] found in [%s] in the property [%s]", value, badValue, key);
                result = true;
            }
        }
    } else {
        GR_LOGI("[%s] property not found", key);
        if (isObligatoryProperty) {
            result = true;
        }
    }
    return result;
}

bool isDetectedTestKeys() {
    const char *TEST_KEYS_VALUE = "test-keys";
    return isBadPropertyState(ANDROID_OS_BUILD_TAGS, TEST_KEYS_VALUE, true, false);
}

bool isDetectedDevKeys() {
    const char *DEV_KEYS_VALUE = "dev-keys";
    return isBadPropertyState(ANDROID_OS_BUILD_TAGS, DEV_KEYS_VALUE, true, false);
}

bool isNotFoundReleaseKeys() {
    const char *RELEASE_KEYS_VALUE = "release-keys";
    return !isBadPropertyState(ANDROID_OS_BUILD_TAGS, RELEASE_KEYS_VALUE, false, true);
}

bool isFoundWrongPathPermission() {

    bool result = false;
    FILE *file = fopen("/proc/mounts", "r");
    char mntent_strings[BUFSIZ];
    if (file == NULL) {
        GR_LOGE("setmntent");
        return result;
    }

    struct mntent ent = {0};
    while (NULL != getMntent(file, &ent, mntent_strings, sizeof(mntent_strings))) {
        for (size_t i = 0; MG_READ_ONLY_PATH[i]; i++) {
            if (strcmp((&ent)->mnt_dir, MG_READ_ONLY_PATH[i]) == 0 &&
                isPresentMntOpt(&ent, "rw")) {
                GR_LOGI("%s %s %s %s\n", (&ent)->mnt_fsname, (&ent)->mnt_dir, (&ent)->mnt_opts,
                        (&ent)->mnt_type);
                result = true;
                break;
            }
        }
        memset(&ent, 0, sizeof(ent));
    }
    fclose(file);
    return result;
}


bool isFoundDangerousProps() {
    const char *BAD_DEBUGGABLE_VALUE = "1";
    const char *BAD_SECURE_VALUE = "0";
    const char *BAD_SYS_INITD_VALUE = "1";
    const char *BAD_SERVICE_ADB_ROOT_VALUE = "1";

    bool result = isBadPropertyState(ANDROID_OS_DEBUGGABLE, BAD_DEBUGGABLE_VALUE, true, true) ||
                  isBadPropertyState(SERVICE_ADB_ROOT, BAD_SERVICE_ADB_ROOT_VALUE, false, true) ||
                  isBadPropertyState(ANDROID_OS_SECURE, BAD_SECURE_VALUE, true, true) ||
                  isBadPropertyState(ANDROID_OS_SYS_INITD, BAD_SYS_INITD_VALUE, false, true);

    return result;
}

bool isPermissiveSelinux() {
    const char *BAD_VALUE = "0";
    return isBadPropertyState(ANDROID_OS_BUILD_SELINUX, BAD_VALUE, false, false);
}

bool isSuExists() {
    char buf[BUFSIZ];
    char *str = NULL;
    char *temp = NULL;
    size_t size = 1;  // start with size of 1 to make room for null terminator
    size_t strlength;

    FILE *pipe = popen("which su", "r");
    if (pipe == NULL) {
        GR_LOGI("pipe is null");
        return false;
    }

    while (fgets(buf, sizeof(buf), pipe) != NULL) {
        strlength = strlen(buf);
        temp = realloc(str, size + strlength);  // allocate room for the buf that gets appended
        if (temp == NULL) {
            // allocation error
            GR_LOGE("Error (re)allocating memory");
            pclose(pipe);
            if (str != NULL) {
                free(str);
            }
            return false;
        } else {
            str = temp;
        }
        strcpy(str + size - 1, buf);
        size += strlength;
    }
    pclose(pipe);
    GR_LOGW("A size of the result from pipe is [%zu], result:\n [%s] ", size, str);
    if (str != NULL) {
        free(str);
    }
    return size > 1 ? true : false;
}

static bool isAccessedFile(const char *path) {
    int result = access(path, F_OK);
    GR_LOGV("[%s] has been accessed with result: [%d]", path, result);
    return result == 0 ? true : false;
}

static bool isFoundBinaryFromArray(const char *const *array, const char *binary) {
    for (size_t i = 0; array[i]; ++i) {
        char *checkedPath = concat2str(array[i], binary);
        if (checkedPath == NULL) { // malloc failed
            return false;
        }
        bool result = isAccessedFile(checkedPath);
        free(checkedPath);
        if (result) {
            return result;
        }
    }
    return false;
}

bool isAccessedSuperuserApk() {
    return isAccessedFile("/system/app/Superuser.apk");
}

bool isFoundResetprop() {
    return isAccessedFile("/data/magisk/resetprop");
}

bool isFoundSuBinary() {
    return isFoundBinaryFromArray(MG_SU_PATH, "su");
}

bool isFoundBusyboxBinary() {
    return isFoundBinaryFromArray(MG_SU_PATH, "busybox");
}

bool isFoundXposed() {
    for (size_t i = 0; MG_EXPOSED_FILES[i]; ++i) {
        bool result = isAccessedFile(MG_EXPOSED_FILES[i]);
        if (result) {
            return result;
        }
    }
    return false;
}

bool isFoundHooks() {
    bool result = false;
    pid_t pid = getpid();
    char maps_file_name[512];
    sprintf(maps_file_name, "/proc/%d/maps", pid);
    GR_LOGI("try to open [%s]", maps_file_name);
    const size_t line_size = BUFSIZ;
    char *line = malloc(line_size);
    if (line == NULL) {
        return result;
    }
    FILE *fp = fopen(maps_file_name, "r");
    if (fp == NULL) {
        free(line);
        return result;
    }
    memset(line, 0, line_size);
    const char *substrate = "com.saurik.substrate";
    const char *xposed = "XposedBridge.jar";
    while (fgets(line, line_size, fp) != NULL) {
        const size_t real_line_size = strlen(line);
        if ((real_line_size >= strlen(substrate) && strstr(line, substrate) != NULL) ||
            (real_line_size >= strlen(xposed) && strstr(line, xposed) != NULL)) {
            GR_LOGI("found in [%s]: [%s]", maps_file_name, line);
            result = true;
            break;
        }
    }
    free(line);
    fclose(fp);
    return result;
}
Arbuckle answered 20/9, 2017 at 21:58 Comment(4)
Awesome tool, Dima. Thanks a lot. It even catches magisk.Humor
This is the real deal.Freezing
@klutch there is the link to the working example(github) at the first line of my postArbuckle
Stil magisk is deteting , its not magisk safe :(Kirshbaum
P
9

Here is my code based on some answers here:

 /**
   * Checks if the phone is rooted.
   * 
   * @return <code>true</code> if the phone is rooted, <code>false</code>
   * otherwise.
   */
  public static boolean isPhoneRooted() {

    // get from build info
    String buildTags = android.os.Build.TAGS;
    if (buildTags != null && buildTags.contains("test-keys")) {
      return true;
    }

    // check if /system/app/Superuser.apk is present
    try {
      File file = new File("/system/app/Superuser.apk");
      if (file.exists()) {
        return true;
      }
    } catch (Throwable e1) {
      // ignore
    }

    return false;
  }
Pickpocket answered 21/6, 2011 at 13:17 Comment(0)
C
7

Further to @Kevins answer, I've recently found while using his system, that the Nexus 7.1 was returning false for all three methods - No which command, no test-keys and SuperSU was not installed in /system/app.

I added this:

public static boolean checkRootMethod4(Context context) {
    return isPackageInstalled("eu.chainfire.supersu", context);     
}

private static boolean isPackageInstalled(String packagename, Context context) {
    PackageManager pm = context.getPackageManager();
    try {
        pm.getPackageInfo(packagename, PackageManager.GET_ACTIVITIES);
        return true;
    } catch (NameNotFoundException e) {
        return false;
    }
}

This is slightly less useful in some situations (if you need guaranteed root access) as it's completely possible for SuperSU to be installed on devices which don't have SU access.

However, since it's possible to have SuperSU installed and working but not in the /system/app directory, this extra case will root (haha) out such cases.

Clothilde answered 7/4, 2014 at 10:16 Comment(1)
This is not a good answer as you have other root packages which can be installed on your device. Hard coding other app packages would be harsh as you can't expect and list all of themLoveinamist
A
5
    public static boolean isRootAvailable(){
            Process p = null;
            try{
               p = Runtime.getRuntime().exec(new String[] {"su"});
               writeCommandToConsole(p,"exit 0");
               int result = p.waitFor();
               if(result != 0)
                   throw new Exception("Root check result with exit command " + result);
               return true;
            } catch (IOException e) {
                Log.e(LOG_TAG, "Su executable is not available ", e);
            } catch (Exception e) {
                Log.e(LOG_TAG, "Root is unavailable ", e);
            }finally {
                if(p != null)
                    p.destroy();
            }
            return false;
        }
 private static String writeCommandToConsole(Process proc, String command, boolean ignoreError) throws Exception{
            byte[] tmpArray = new byte[1024];
            proc.getOutputStream().write((command + "\n").getBytes());
            proc.getOutputStream().flush();
            int bytesRead = 0;
            if(proc.getErrorStream().available() > 0){
                if((bytesRead = proc.getErrorStream().read(tmpArray)) > 1){
                    Log.e(LOG_TAG,new String(tmpArray,0,bytesRead));
                    if(!ignoreError)
                        throw new Exception(new String(tmpArray,0,bytesRead));
                }
            }
            if(proc.getInputStream().available() > 0){
                bytesRead = proc.getInputStream().read(tmpArray);
                Log.i(LOG_TAG, new String(tmpArray,0,bytesRead));
            }
            return new String(tmpArray);
        }
Alonsoalonzo answered 24/10, 2014 at 20:3 Comment(0)
U
4

Two additional ideas, if you want to check if a device is root capable from your app:

  1. Check for the existing of the 'su' binary: run "which su" from Runtime.getRuntime().exec()
  2. Look for the SuperUser.apk in /system/app/Superuser.apk location
Unlash answered 14/6, 2011 at 18:9 Comment(0)
T
4

Using C++ with the ndk is the best approach to detect root even if the user is using applications that hide his root such as RootCloak. I tested this code with RootCloak and I was able to detect the root even if the user is trying to hide it. So your cpp file would like:

#include <jni.h>
#include <string>


/**
 *
 * function that checks for the su binary files and operates even if 
 * root cloak is installed
 * @return integer 1: device is rooted, 0: device is not 
 *rooted
*/
extern "C"
JNIEXPORT int JNICALL


Java_com_example_user_root_1native_rootFunction(JNIEnv *env,jobject thiz){
const char *paths[] ={"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su",
                      "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
                      "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"};

int counter =0;
while (counter<9){
    if(FILE *file = fopen(paths[counter],"r")){
        fclose(file);
        return 1;
    }
    counter++;
}
return 0;
}

And you will call the function from your java code as follows

public class Root_detect {



   /**
    *
    * function that calls a native function to check if the device is 
    *rooted or not
    * @return boolean: true if the device is rooted, false if the 
    *device is not rooted
   */
   public boolean check_rooted(){

        int checker = rootFunction();

        if(checker==1){
           return true;
        }else {
           return false;
        }
   }
   static {
    System.loadLibrary("cpp-root-lib");//name of your cpp file
   }

   public native int rootFunction();
}
Traditor answered 17/6, 2017 at 8:33 Comment(0)
O
3

Based on some of the answers here, I merged them and also added a check if some known root-manager app is installed:

fun isProbablyRooted(context: Context, alsoIncludeCheckingRootManagerApp: Boolean = false): Boolean {
    return hasRootManagerSystemApp(context) || (alsoIncludeCheckingRootManagerApp && hasRootManagerSystemApp(context))
}

fun hasRootManagerSystemApp(context: Context): Boolean {
    val rootAppsPackageNames = arrayOf("com.topjohnwu.magisk", "eu.chainfire.supersu", "com.koushikdutta.superuser", "com.noshufou.android.su", "me.phh.superuser")
    rootAppsPackageNames.forEach { rootAppPackageName ->
        try {
            context.packageManager.getApplicationInfo(rootAppPackageName, 0)
            return true
        } catch (e: Exception) {
        }
    }
    return false
}

fun hasSuBinary(): Boolean {
    return try {
        findBinary("su")
    } catch (e: Exception) {
        e.printStackTrace()
        false
    }
}

private fun findBinary(binaryName: String): Boolean {
    val paths = System.getenv("PATH")
    if (!paths.isNullOrBlank()) {
        val systemPlaces: List<String> = paths.split(":")
        return systemPlaces.firstOrNull { File(it, binaryName).exists() } != null
    }
    val places = arrayOf("/sbin/", "/system/bin/", "/system/xbin/", "/data/local/xbin/", "/data/local/bin/",
            "/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/")
    return places.firstOrNull { File(it, binaryName).exists() } != null
}

manifest:

<queries>
    <package android:name="com.topjohnwu.magisk" />
    <package android:name="eu.chainfire.supersu" />
    <package android:name="com.koushikdutta.superuser" />
    <package android:name="com.noshufou.android.su" />
    <package android:name="me.phh.superuser" />
</queries>

Of course, this is still a guess, like all the other solutions. Users can install Magisk without having the device rooted, for example.

Ogham answered 18/1, 2021 at 14:43 Comment(4)
Follow to a lot of method of Rooting I agree that Magisk is a nice solution although it requires custom ROM to be installed.Rumal
@Chetabahana Incorrect. You can have original firmware and flash Magisk just fine. In fact that's what I have on my Pixel 4.Ogham
I have an android app with iap and ads do i need to add this code snippet ? meaning can a user get a premium version of the app without actually paying for in a rooted device ? should i add it or not ?Ebony
@Ebony Your choice. I use it just as a clue if something seems weird in Crashlytics. Not for purchases. Users can have rooted devices and use apps normally, after all...Ogham
E
3

As last quarter of 2021 today, I tried to use SafetyNet regarding @HimanshiThakur 's answer. But I got an issue and opened a question in here. Still no answer.

So I decided to use RootBeer. It works perfectly but when Magisk hides the root, it doesn't work.

If you don't care this case (and many bank apps can't solve this issue too), you can use these steps:

  1. Add this to Gradle:
implementation 'com.scottyab:rootbeer-lib:0.1.0'
  1. Use these lines:
RootBeer rootBeer = new RootBeer(context);
if (rootBeer.isRooted()) {
    //we found indication of root
} else {
    //we didn't find indication of root
}
Eskridge answered 13/10, 2021 at 11:30 Comment(2)
I have an android app with iap and ads do i need to add this code snippet ? meaning can a user get a premium version of the app without actually paying for in a rooted device ? should i add it or not ?Ebony
@Ebony unfortunately I don't know the answer since this is about different topic. But I would add this code snippet just in case since it is too easy and simple to implementEskridge
L
2
if [[ "`adb shell which su | grep -io "permission denied"`" != "permission denied" ]]; then
   echo "Yes. Rooted device."
 else
   echo "No. Device not rooted. Only limited tasks can be performed. Done."
    zenity --warning --title="Device Not Rooted" --text="The connected Android Device is <b>NOT ROOTED</b>. Only limited tasks can be performed." --no-wrap
fi
Luciusluck answered 4/1, 2016 at 20:10 Comment(0)
A
2

Forget all that detecting root apps and su binaries. Check for the root daemon process. This can be done from the terminal and you can run terminal commands within an app. Try this one-liner.

if [ ! "$(/system/bin/ps -A | grep -v grep | grep -c daemonsu)" = "0" ]; then echo "device is rooted"; else echo "device is not rooted"; fi

You don't need root permission to achieve this either.

Edit: Now using this method for better detection!

if [ $(ps -A | grep -e ^shell -e ^root | grep -v "\[" | tr -s ' ' | cut -d ' ' -f 9 | grep -c su) ] || [ $(which su) ]; then echo 'rooted'; else echo 'not rooted'; fi
Annam answered 14/2, 2020 at 12:30 Comment(2)
both things are wrong for me (they show "rooted" on non-rooted and vice versa).Guinevere
@djdance. might not work nowadays with later android versions!Annam
I
2

Using google SafetyNet Attestation API you can easily check whether your device is rooted or not :

  1. Add dependency in build.gradle(:app)

    implementation 'com.google.android.gms:play-services-safetynet:17.0.0'

  2. Get Api key and enable Android Device Verification API using link

  3. public static void sendSafetyNetRequest(Activity context) {

     if(GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context, 13000000) == ConnectionResult.SUCCESS) {
         Log.e(TAG, "The SafetyNet Attestation API is available");
    
         // TODO(developer): Change the nonce generation to include your own, used once value,
         // ideally from your remote server.
    
         String nonceData = "Safety Net Sample: " + System.currentTimeMillis();
         ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
         Random mRandom = new SecureRandom();
    
         byte[] bytes = new byte[24];
         mRandom.nextBytes(bytes);
         try {
             byteStream.write(bytes);
             byteStream.write(nonceData.getBytes());
         } catch (IOException e) {
             e.printStackTrace();
         }
    
         byte[] nonce = byteStream.toByteArray();
    
         SafetyNetClient client = SafetyNet.getClient(context);
         Task<SafetyNetApi.AttestationResponse> task = client.attest(nonce, API_KEY_FROM_STEP_2_LINK);
    
         task.addOnSuccessListener(context, attestationResponse -> {
    
    
           /*
                  TODO(developer): Forward this result to your server together with
                  the nonce for verification.
                  You can also parse the JwsResult locally to confirm that the API
                  returned a response by checking for an 'error' field first and before
                  retrying the request with an exponential backoff.
                  NOTE: Do NOT rely on a local, client-side only check for security, you
                  must verify the response on a remote server!
                 */
    
             String jwsResult = attestationResponse.getJwsResult();
    
             Log.e(TAG, "Success! SafetyNet result:\n" + jwsResult + "\n");
    
             if (jwsResult == null) {
                 Log.e(TAG, "jwsResult Null");
    
             }
             final String[] jwtParts = jwsResult.split("\\.");
    
             if (jwtParts.length == 3) {
                 String decodedPayload = new String(Base64.decode(jwtParts[1], Base64.DEFAULT));
                 Log.e(TAG, "decodedPayload :     " + decodedPayload);
             }
    
    
         });
    
         task.addOnFailureListener(context, e -> {
             // An error occurred while communicating with the service.
             String mResult = null;
    
             if (e instanceof ApiException) {
                 // An error with the Google Play Services API contains some additional details.
                 ApiException apiException = (ApiException) e;
    
                 Util.showLog(TAG, "Error: " +
                         CommonStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": " +
                         apiException.getStatusMessage());
             } else {
                 // A different, unknown type of error occurred.
                Log.e(TAG, "ERROR! " + e.getMessage());
             }
    
         });
    
     } else {
      Log.e(TAG, "Prompt user to update Google Play services.";
    
     }
    

    } `

  4. Check your logs for decodedPayload if ctsProfileMatch and basicIntegrity both are false it means your device is rooted . The Attestation API returns a JWS response which looks like:

{ "nonce": "6pLrr9zWyl6TNzj+kpbR4LZcfPY3U2FmZXR5IE5ldCBTYW1wbGU6IDE2MTQ2NzkwMTIzNjc=", "timestampMs": 9860437986543, "apkPackageName": " your package name will be displayed here", "ctsProfileMatch": true, "apkDigestSha256": [ "base64 encoded, SHA-256 hash of the certificate used to sign requesting app" ], "basicIntegrity": true, "evaluationType": "BASIC" }

status of ctsProfileMatch and basicIntegrity For more info check this link.

Isobar answered 17/3, 2021 at 11:27 Comment(0)
F
1

There is Safety Net Attestation API of Google play services by which we can assess the device and determine if it is rooted/tampered.

Please go through my answer to deal with rooted devices:
https://mcmap.net/q/11176/-how-to-find-the-rooted-device-programmatically

Flanders answered 10/10, 2019 at 7:18 Comment(0)
R
1

You can do this by following code :

public boolean getRootInfo() {
    if (checkRootFiles() || checkTags()) {
        return true;
    }
    return false;
}

private boolean checkRootFiles() {
    boolean root = false;
    String[] paths = {"/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
            "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"};
    for (String path : paths) {
        root = new File(path).exists();
        if (root)
            break;
    }
    return root;
}

private boolean checkTags() {
    String tag = Build.TAGS;
    return tag != null && tag.trim().contains("test-keys");
}

You can also check this library RootBeer.

Raver answered 30/9, 2020 at 14:15 Comment(0)
A
1

if you don't want to use any 3rd party library or any random solution then just use google lib for detecting it.

Android Device Verification

response :

{
  "timestampMs": 9860437986543,
  "nonce": "R2Rra24fVm5xa2Mg",
  "apkPackageName": "com.package.name.of.requesting.app",
  "apkCertificateDigestSha256": ["base64 encoded, SHA-256 hash of the
                                  certificate used to sign requesting app"],
  "ctsProfileMatch": true,
  "basicIntegrity": true,
}

ctsProfileMatch it gives false if the device is rooted.

ref link: [1]: https://developer.android.com/training/safetynet/attestation

Alithia answered 7/10, 2020 at 7:42 Comment(0)
P
1

As of 2021 (today), looks like there's no any reliable way or method for detecting root, especially when powerful hiding tool such as MagiskHide is enabled. Most of the answers here are no longer relevant, so don't use it in production. Rely on proven check like SafetyNet, and instead of going the extra miles to detect root, I suggest to protect your app at both runtime, such as prevent debugger/instrumentation and make sure to use obfuscation.

Puleo answered 29/3, 2021 at 10:28 Comment(0)
W
0

Indeed it is interesting question and so far nobody has deserved award. I use the following code:

  boolean isRooted() {
      try {
                ServerSocket ss = new ServerSocket(81);
                ss.close();
                                    return true;
            } catch (Exception e) {
                // not sure
            }
    return false;
  }

The code is certainly not bulletproof, because network can be not available so you get an exception. If this method returns true then 99% you can be sure, otherwise just 50% that not. Networking permission can also spoil the solution.

Write answered 30/5, 2014 at 2:20 Comment(2)
I tested this and it does not return true with my rooted device.Ake
It is interesting to see what kind of exception you get. You may get port already bound exception, however if you can't create server port in range under 1024, it decreases a value of rooting, since still you have certain limitations.Write
T
0
fun isDeviceRooted(): Boolean {
val buildTags = android.os.Build.TAGS
if (buildTags != null && buildTags.contains("test-keys")) {
    return true
}

// Check for the existence of known superuser APKs
val superUserApkPaths = arrayOf(
    "/system/app/Superuser.apk",
    "/system/app/SuperSU.apk",
    "/system/app/MagiskManager.apk",
    "/sbin/su",
    "/system/bin/su",
    "/system/xbin/su",
    "/data/local/xbin/su",
    "/data/local/bin/su",
    "/system/sd/xbin/su",
    "/system/bin/failsafe/su",
    "/data/local/su"
)

for (path in superUserApkPaths) {
    if (File(path).exists()) {
        return true
    }
}

// Check for the presence of the "su" binary
try {
    val process = Runtime.getRuntime().exec(arrayOf("/system/xbin/which", "su"))
    val bufferedReader = BufferedReader(InputStreamReader(process.inputStream))
    val line = bufferedReader.readLine()
    bufferedReader.close()
    if (line != null) {
        return true
    }
} catch (e: Exception) {
    // Exception occurred, indicating "su" is not available
}

return false

}

Timoteo answered 10/10, 2023 at 9:33 Comment(0)
C
0

I think it is not possible to silently check if phone is rooted or not.

it is only possible if phone is not rooted. The Runtime.getRuntime().exec("su -c ls") will send an exception, without prompting anything.

If phone is rooted, every Runtime.getRuntime().exec("su -c ls") will promt a demand for granting application if not granted, and will show a Toast if granted.

I dont't know if there is a way to avoid prompting demand or Toast.

For the best solution to test if Phone is rooted or not, and if SuperUser Access granted or not, only Lexip's solution with Scanner works fine.

To complete his response:

    boolean isRooted = false;
    boolean isRootGranted = false;
    try {
        Process process = Runtime.getRuntime().exec("su -c ls");     // Ask for su and list
        Scanner scanner = new Scanner(process.getInputStream()).useDelimiter("\\A");
        if (scanner.hasNext()) isRootGranted = true;
        scanner.close();
        process.waitFor();
        process.getInputStream().close();
        process.destroy();
        isRooted = true;
        if (isRootGranted) Toast.makeText(MainActivity.this, "ROOT granted", Toast.LENGTH_SHORT).show();
        else Toast.makeText(MainActivity.this, "ROOT not granted", Toast.LENGTH_SHORT).show();
    } catch (Exception e) {
        isRooted = false;
        Toast.makeText(MainActivity.this, "ERROR #SU AUTORISATION\nPHONE NON ROOTED", Toast.LENGTH_SHORT).show();
    }

isRooted = true on a rooted phone, and false if not.

isRootGranted = true if superUser Access is granted, and false if not.

Working everytime you call it.

Regards.

Curler answered 18/12, 2023 at 6:24 Comment(0)
L
-3

Using my library at rootbox, it is pretty easy. Check the required code below:

    //Pass true to <Shell>.start(...) call to run as superuser
    Shell shell = null;
    try {
            shell = Shell.start(true);
    } catch (IOException exception) {
            exception.printStackTrace();
    }
    if (shell == null)
            // We failed to execute su binary
            return;
    if (shell.isRoot()) {
            // Verified running as uid 0 (root), can continue with commands
            ...
    } else
            throw Exception("Unable to gain root access. Make sure you pressed Allow/Grant in superuser prompt.");
Lelialelith answered 19/6, 2014 at 6:35 Comment(1)
Could you please elaborate your answer? So we can understand what you are trying to say in detail.Hematology

© 2022 - 2024 — McMap. All rights reserved.