Android 确定是否在有根设备上运行

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1101380/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 02:41:01  来源:igfitidea点击:

Determine if running on a rooted device

androidroot

提问by miracle2k

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.

我的应用程序具有某些功能,该功能只能在 root 可用的设备上运行。与其让这个功能在使用时失败(然后向用户显示适当的错误消息),我更喜欢先静默检查 root 是否可用,如果没有,首先隐藏相应的选项.

Is there a way to do this?

有没有办法做到这一点?

回答by Kevin Parker

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

这是一个将检查 Root 三种方式之一的类。

/** @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();
        }
    }
}

回答by kingston

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

如果您已经在使用 Fabric/Firebase Crashlytics,则可以调用

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();
        }
    }
}

回答by Intrications

The RootTools library offers simple methods to check for root:

RootTools 库提供了检查 root 的简单方法:

RootTools.isRootAvailable()

Reference

参考

回答by Devrim

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?

在我的应用程序中,我通过执行“su”命令检查设备是否已植根。但是今天我删除了这部分代码。为什么?

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.

有一些抱怨说我的应用程序会减慢设备的速度(当然我认为这不可能是真的)。我试图找出原因。所以我使用 MAT 来获取堆转储并进行分析,一切看起来都很完美。但是在多次重新启动我的应用程序后,我意识到设备真的变慢了,停止我的应用程序并没有使它更快(除非我重新启动设备)。当设备非常慢时,我再次分析了转储文件。但是一切对于转储文件来说仍然是完美的。然后我做了一开始必须做的事情。我列出了流程。

$ 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.

使用具有单个活动并仅执行“su”命令的示例应用程序,我意识到每次启动应用程序时都会创建一个僵尸进程。起初,这些僵尸分配了 0KB,但后来发生了一些事情,僵尸进程持有与我的应用程序的主进程几乎相同的 KB,它们变成了标准进程。

There is a bug report for same issue on bugs.sun.com: http://bugs.sun.com/view_bug.do?bug_id=6474073this 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)

bugs.sun.com 上有关于相同问题的错误报告:http://bugs.sun.com/view_bug.do?bug_id=6474073这解释了如果没有找到命令,将使用 exec() 方法创建僵尸. 但我仍然不明白它们为什么以及如何成为标准流程并拥有重要的知识库。(这不是一直发生的)

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().

总结; 我没有建议您确定设备是否已植根。但如果我是你,我不会使用 Runtime.getRuntime().exec()。

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

顺便一提; RootTools.isRootAvailable() 导致同样的问题。

回答by rsimp

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
  • 检查测试密钥与 root 访问权限相关,但不一定保证它
  • “PATH”目录应该来自实际的“PATH”环境变量而不是硬编码
  • “su”可执行文件的存在并不一定意味着该设备已植根
  • “which”可执行文件可能已安装,也可能未安装,如果可能,您应该让系统解析其路径
  • 仅仅因为 SuperUser 应用程序安装在设备上并不意味着该设备还具有 root 访问权限

The RootToolslibrary 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.

RootTools从Stericson库似乎更合理检查根。它还有很多额外的工具和实用程序,所以我强烈推荐它。但是,没有解释它是如何专门检查 root 的,而且它可能比大多数应用程序真正需要的要重一些。

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:

我已经创建了一些基于 RootTools 库的实用方法。如果您只是想检查“su”可执行文件是否在设备上,您可以使用以下方法:

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.

此方法仅循环遍历“PATH”环境变量中列出的目录,并检查其中一个中是否存在“su”文件。

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).

为了真正检查 root 访问权限,必须实际运行“su”命令。如果安装了像 SuperUser 这样的应用程序,那么此时它可能会要求 root 访问,或者如果它已经被授予/拒绝,则可能会显示一个吐司,指示是否授予/拒绝访问。一个好的运行命令是“id”,这样您就可以验证用户 id 实际上是 0(root)。

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

以下是确定是否授予 root 访问权限的示例方法:

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.

实际测试运行“su”命令很重要,因为一些模拟器预先安装了“su”可执行文件,但只允许某些用户像 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.

在尝试运行“su”可执行文件之前检查它是否存在也很重要,因为众所周知 android 无法正确处理尝试运行丢失命令的进程。随着时间的推移,这些幽灵进程可能会消耗大量内存。

回答by Hitesh Sahu

Update 2017

2017 年更新

You can do it now with Google Safetynet API. The SafetyNet API provides Attestation API which helps you assess the security and compatibility of the Android environments in which your apps run.

您现在可以使用Google Safetynet API做到这一点。SafetyNet API 提供了 Attestation API,可帮助您评估应用运行所在的 Android 环境的安全性和兼容性。

This attestation can helps to determine whether or not the particular device has been tampered with or otherwise modified.

该证明可以帮助确定特定设备是否已被篡改或以其他方式修改。

The Attestation API returns a JWS response like this

Attestation API 返回这样的 JWS 响应

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

Parsing this response can help you determine if device is rooted or not

解析此响应可以帮助您确定设备是否已植根

Rooted devices seem to cause ctsProfileMatch=false.

植根设备似乎导致 ctsProfileMatch=false。

You can do it on client side but parsing response on server side is recommend. A basic client server archtecture with safety net API will look like this:-

您可以在客户端执行此操作,但建议在服务器端解析响应。具有安全网 API 的基本客户端服务器架构将如下所示:-

enter image description here

在此处输入图片说明

回答by Alok Kulkarni

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.

Java 级别的根检查不是一个安全的解决方案。如果您的应用有安全问题要在 Rooted 设备上运行,请使用此解决方案。

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.

除非手机也有像 RootCloak 这样的应用程序,否则凯文的回答是有效的。一旦手机被植根,此类应用程序就会通过 Java API 进行处理,并且它们会模拟这些 API 以返回电话未被植根。

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.

我已经根据凯文的回答编写了一个本机级别的代码,它甚至可以与 RootCloak 一起使用!它也不会导致任何内存泄漏问题。

#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

在您的 Java 代码中,您需要创建包装类 RootUtils 来进行本机调用

    public boolean checkRooted() {

       if( rootUtils.checkRootAccessMethod3()  == 0 || rootUtils.checkRootAccessMethod1()  == 0 || rootUtils.checkRootAccessMethod2()  == 0 )
           return true;
      return false;
     }

回答by noobProgrammer

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

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

If you do not want to use the jar filejust use the code:

如果您不想使用 jar 文件,只需使用以下代码:

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:

程序将尝试查找 su 文件夹:

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

Example:

例子:

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

} else {
   textView.setText("Device Unrooted");
}

回答by saulobrito

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

您可以使用 isAccessGiven() 代替 isRootAvailable()。直接来自 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.

RootTools.isAccessGiven() 不仅检查设备是否已获得 root 权限,它还为您的应用程序调用 su,请求权限,如果您的应用程序已成功授予 root 权限,则返回 true。这可以用作应用程序中的第一个检查,以确保您在需要时获得访问权限。

Reference

参考

回答by Chris Boyle

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

一些修改过的版本用于为此目的设置系统属性ro.modversion。事情似乎有了进展;我几个月前从 TheDude 构建的有这个:

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:

另一方面,来自 1.5 SDK 的模拟器运行 1.5 映像,也有 root,可能类似于Android Dev Phone 1(您可能希望允许)并且具有以下内容:

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.comare informative. Here is a G1 in the Netherlands, you can see that ro.build.tagsdoes not have test-keys, and I think that's probably the most reliable property to use.

至于零售建筑,我手头没有,但下面的各种搜索site:xda-developers.com都提供了丰富的信息。这是荷兰G1,您可以看到ro.build.tags它没有test-keys,我认为这可能是最可靠的属性。