确定是否在root设备上运行

时间:2009-07-09 01:22:12

标签: android root

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

有办法做到这一点吗?

27 个答案:

答案 0 :(得分:242)

这是一个将检查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();
        }
    }
}

答案 1 :(得分:53)

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

RootTools.isRootAvailable()

Reference

答案 2 :(得分:47)

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

因为我的应用程序成了记忆杀手。怎么样?让我告诉你我的故事。

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

$ adb shell ps

Surprize;我的应用程序有很多进程(我的应用程序的进程标记在清单中)。其中一些是僵尸,其中一些不是。

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

bugs.sun.com上有同样问题的错误报告:http://bugs.sun.com/view_bug.do?bug_id=6474073这解释了是否找不到命令僵尸将使用exec()方法创建。但我仍然不明白为什么以及如何成为标准流程并保留重要的KB。 (这不会一直发生)

您可以尝试使用以下代码示例;

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

简单的命令执行方法;

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) {
            }
        }
    }
}

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

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

答案 3 :(得分:40)

如果您已经在使用Fabric / Firebase Crashlytics,可以致电

CommonUtils.isRooted(context)

这是该方法的当前实现:

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

答案 4 :(得分:32)

此处列出的许多答案都存在固有问题:

  • 检查测试密钥是否与root访问权限相关,但并不一定能保证
  • " PATH"目录应该从实际的" PATH"环境变量而不是硬编码
  • " su"的存在可执行文件并不一定意味着设备已被植根
  • """可执行文件可能已安装,也可能未安装,您应该让系统尽可能解析其路径
  • 仅仅因为设备上安装了SuperUser应用程序并不意味着设备具有root访问权限

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

我已经制作了一些基于RootTools库的松散实用方法。如果你只是想检查一下" su"可执行文件在设备上,您可以使用以下方法:

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

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

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

以下是确定是否已授予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;
}

实际测试运行" su"命令,因为一些模拟器有" su"可预先安装的可执行文件,但只允许某些用户像adb shell一样访问它。

检查" su"是否存在也很重要。在尝试运行它之前可执行文件,因为已知android不能正确处理试图运行缺失命令的进程。这些重影进程会随着时间的推移而耗尽内存消耗。

答案 5 :(得分:31)

2017年更新

您现在可以使用Google Safetynet API执行此操作。 SafetyNet API提供Attestation API,可帮助您评估运行应用的Android环境的安全性和兼容性。

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

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,
}

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

  

root设备似乎会导致ctsProfileMatch = false。

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

enter image description here

答案 6 :(得分:31)

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

除非手机还有像RootCloak这样的应用程序,凯文的回答才有效。这些应用程序在手机被植根后会有一个Handle over Java API,并且他们模拟这些API以返回手机并不是根源。

我已根据Kevin的答案编写了一个本机级代码,它甚至可以与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;

}

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

    public boolean checkRooted() {

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

答案 7 :(得分:18)

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

如果您不想使用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;
    }

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

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

<强> 实施例

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

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

答案 8 :(得分:12)

您可以使用isAccessGiven()代替使用isRootAvailable()。直接来自RootTools wiki

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

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

Reference

答案 9 :(得分:11)

一些修改后的版本用于为此目的设置system property 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]

另一方面,运行1.5图像的1.5 SDK中的模拟器也有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]

至于零售版本,我手头没有,但site:xda-developers.com下的各种搜索都是提供信息的。这是一个G1 in the Netherlands,您可以看到ro.build.tags没有test-keys,我认为这可能是最可靠的属性。

答案 10 :(得分:8)

RootBeer是Scott和Matthew的根检查Android库。 它使用各种检查来指示设备是否已植根。

  

Java检查

     
      
  • CheckRootManagementApps

  •   
  • CheckPotentiallyDangerousAppss

  •   
  • CheckRootCloakingApps

  •   
  • CheckTestKeys

  •   
  • checkForDangerousProps

  •   
  • checkForBusyBoxBinary

  •   
  • checkForSuBinary

  •   
  • checkSuExists

  •   
  • checkForRWSystem

  •   
     

原生支票

     

我们打电话到我们的原生根检查器来运行它自己的一些   检查。本机检查通常更难掩盖,因此有些根   斗篷应用程序只是阻止加载包含的本机库   某些关键词。

     
      
  • checkForSuBinary
  •   

答案 11 :(得分:7)

这是我的代码基于这里的一些答案:

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

答案 12 :(得分:6)

继@kevins回答之后,我最近在使用他的系统时发现,Nexus 7.1为所有三种方法返回false - 没有which命令,没有{{1} } test-keys中未安装SuperSU

我补充说:

/system/app

在某些情况下(如果您需要保证root访问权限),这有点 有用,因为SuperSU完全可以安装在没有SU访问权限的设备上

但是,由于可以在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; } } 目录中安装并运行SuperSU但,这个额外的情况将根除(哈哈)这种情况。

答案 13 :(得分: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);
        }

答案 14 :(得分:4)

另外两个想法,如果你想从你的应用程序检查设备是否具有root功能:

  1. 检查'su'二进制文件的存在:从Runtime.getRuntime().exec()
  2. 运行“which su”
  3. /system/app/Superuser.apk位置
  4. 中查找SuperUser.apk

答案 15 :(得分:4)

我建议使用本机代码进行根检测。 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();
}

常量:

// 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
};
来自本机代码的

根检测:

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

答案 16 :(得分:3)

将C ++与ndk一起使用是检测root的最佳方法,即使用户正在使用隐藏其根目录的应用程序(如RootCloak)。我使用RootCloak测试了这段代码,即使用户试图隐藏它,我也能够检测到它。 所以你的cpp文件想:

#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;
}

您将从您的Java代码中调用该函数,如下所示

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

答案 17 :(得分:1)

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

答案 18 :(得分:1)

确实这是一个有趣的问题,到目前为止还没有人值得奖励。我使用以下代码:

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

代码肯定不是防弹的,因为网络可能不可用,所以你得到一个例外。如果这个方法返回true,那么99%你可以肯定,否则只有50%没有。网络许可也可能破坏解决方案。

答案 19 :(得分:1)

Google play服务 Safety Net Attestation API ,我们可以通过该API评估设备并确定其是否被扎根/篡改。

请仔细回答我的问题,以处理植根设备:
https://stackoverflow.com/a/58304556/3908895

答案 20 :(得分:1)

忘记所有检测根应用程序和su二进制文件的功能。检查根守护进程。这可以从终端完成,您可以在应用程序中运行终端命令。试试这种单线。

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

您也不需要root权限即可实现。

答案 21 :(得分:1)

根据这里的一些答案,我合并了它们,并检查是否安装了一些已知的 root-manager 应用程序:

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
}

清单:

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

当然,这仍然是一个猜测,就像所有其他解决方案一样。 例如,用户无需植根设备即可安装 Magisk。

答案 22 :(得分:1)

使用 google SafetyNet Attestation API,您可以轻松检查您的设备是否已植根:

  1. 在 build.gradle(:app) 中添加依赖

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

  2. 获取 Api 密钥并使用 link

    启用 Android 设备验证 API
  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. 如果 ctsProfileMatch 和 basicIntegrity 都为 false,则检查您的日志以获取 decodedPayload,这意味着您的设备已 root。证明 API 返回一个 JWS 响应,如下所示:

{ "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 欲了解更多info,请查看此链接

答案 23 :(得分:0)

您可以通过以下代码进行操作:

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

您也可以检查此库RootBeer.

答案 24 :(得分:0)

如果您不想使用任何第三方库或任何随机解决方案,则只需使用google lib进行检测即可。

Android设备验证

响应:

{
  "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如果设备已植根,则为false。

参考链接: [1]:https://developer.android.com/training/safetynet/attestation

答案 25 :(得分:0)

截至 2021 年(今天),似乎没有任何可靠的方法或方法来检测根目录,尤其是在启用了强大的隐藏工具(例如 MagiskHide)时。这里的大多数答案不再相关,所以不要在生产中使用它。依靠像 SafetyNet 这样经过验证的检查,而不是花额外的时间来检测 root,我建议在两个运行时保护您的应用,例如防止调试器/检测并确保使用混淆。

答案 26 :(得分:-1)

rootbox使用我的图书馆,非常简单。请检查以下所需的代码:

    //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.");