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

有办法吗?

评论

没有可靠的方法可以这样做。以下答案检查了共同的特征,但是给定的设备可能无法以共同的方式扎根。如果检查根目录变得普遍,则根目录解决方案可能会开始努力隐藏自己。由于他们可以修改操作系统的行为,因此有很多选择。

最好指出该功能不可用是因为缺乏根功能,无法为用户提供更多信息,而不是隐藏应用程序的功能,从而给整体体验带来歧义。

以下答案对无系统根有用吗?

#1 楼

这是一个将检查根的三种方法之一。

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


评论


如果两个问题的答案相同,那么它们有99%的时间是重复的,因此请将其标记为重复,而不要在两个问题上都张贴相同的答案。谢谢。

–凯夫
2011年11月11日23:27

-1,此方法不可行,因为某些手机在没有root用户的情况下包含su二进制文件。

–neevek
13年6月27日在6:43

只是想让您知道,Fox Digital Copy(Beta)应用程序几乎逐字使用您的代码,包括Root和ExecShell类以及checkRootMethod1 / 2/3方法。发现它非常有趣。

–马特·约瑟夫
2013年12月15日23:35

我可以像福克斯起诉无数其他人一样起诉他们?

–凯文·帕克(Kevin Parker)
13年12月16日在8:38

@PedroPauloAmorim不是作者的错,而是AIB的工程和质量检查团队,他们没有在各种设备和环境上测试脚本行为。再一次,没有可靠的方法来确定设备是否已植根。此方法与其他方法一样容易出错。

–爱多伦
19-09-30在8:45



#2 楼

如果您已经在使用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();
        }
    }
}


评论


有史以来最好的答案。请在任何库中使用此方法,因为在中文设备上有很多误报。

– Pedro Paulo Amorim
18年8月7日在14:53

这种方法有误报吗?

– Ehsan Mashhadi
19年6月16日在9:28

我在nexus 5上使用download.chainfire.eu/363/CF-Root/CF-Auto-Root/…进行了测试,这是不准确的。

–刘Jeff
5月5日18:37

#3 楼

RootTools库提供了用于检查根目录的简单方法:

评论


isRootAvailable()仅检查路径和其他一些硬编码目录中是否存在su。我听说有些无根的工具会将su留在这里,所以这会带来误报。

–鲍勃·怀特曼(Bob Whiteman)
2011年11月2日,19:24

RootTools.isAccessGiven()不仅会检查root用户,还会请求root用户权限;因此,无根设备将始终使用此方法返回false。

–aggregate116​​6877
13年4月17日在9:40

@ aggregate116​​6877,您是对的,但这还不够好,如果我在询问时不需要root权限怎么办?我只想知道它是否已植根,但目前不需要root权限。

–neevek
2013年6月27日下午6:46

当用户拒绝该权限时,即使该设备是root用户,isAccessGiven()也会返回false。

–subair_a
14年4月16日在9:38

这是我认为值得投票的唯一答案。如果您想要类似于复制粘贴的内容,或者想要更多详细信息,请在下面查看我的答案

–rsimp
16-09-22在20:24

#4 楼

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

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

有人抱怨我的应用程序使设备运行缓慢(当然,我认为那是不对的)。我试图找出原因。因此,我使用MAT进行堆转储和分析,一切似乎都很完美。但是,在多次重新启动我的应用程序后,我意识到设备确实变慢了,停止应用程序并没有使其变得更快(除非我重新启动设备)。我在设备非常慢的情况下再次分析了转储文件。但是对于转储文件来说,一切仍然是完美的。
然后,我做了必须先做的事情。我列出了进程。

我的应用程序有很多流程(清单中有我的应用程序的流程标签)。其中一些是僵尸,有些不是僵尸。

通过一个具有单个Activity并仅执行“ su”命令的示例应用程序,我意识到,每次启动应用程序时都会创建一个僵尸进程。最初,这些僵尸分配了0KB,但没有发生什么事,僵尸进程与我的应用程序的主进程持有几乎相同的KB,它们成为标准进程。 com:http://bugs.sun.com/view_bug.do?bug_id=6474073这说明了如果找不到命令,将使用exec()方法创建僵尸。但是我仍然不明白为什么以及如何使其成为标准流程并拥有大量知识库。 (这并非一直发生)

如果需要,可以尝试以下代码示例;

$ adb shell ps


简单的命令执行方法;

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


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

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

评论


那太令人担忧了。我有一个扎根的设备检测类,它做同样的事情-阅读此书后,我确认了上面详细介绍的爱琴海。偶尔出现僵尸进程,设备运行缓慢等...

– AWT
2013年9月3日13:49

我在GT-S5830i android 2.3.6上确认了RootTools 3.4的问题。大多数僵尸都分配了内存,这个问题是系统的。 3-4次测试后,我需要重新启动设备。我建议将测试结果保存为共享首选项。

–基督
2014年5月27日晚上8:03

Google现在建议使用ProcessBuilder()和start()命令。

–纠缠循环
2015年9月23日19:05



@NickS有趣,但是您启动了什么命令?我在从9到23的许多具有不同API级别的Android手机上发布命令时没有相同的问题。

–纠缠循环
16年4月9日在14:02

@EntangledLoops。谢谢。我启动自己的二进制文件并通过stdin / stdout与之交互。我再次检查了如何停止它,发现在一种情况下错过了Process.destroy()。因此,没有僵尸。

–尼克S
16年4月11日在19:52

#5 楼

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


检查测试键与root用户访问相关,但不一定保证它应该得到
“ PATH”目录从实际的“ PATH”环境变量中获取而不是进行硬编码,并且应该尽可能让系统解析其路径
,因为SuperUser应用已安装在设备上并不意味着该设备具有root访问权限。

Stericson的RootTools库似乎更合法地检查根。它还有很多额外的工具和实用程序,因此我强烈推荐它。但是,没有关于它是如何专门检查root的解释,它可能比大多数应用程序真正需要的要重。图书馆。如果您只想检查设备上是否有“ su”可执行文件,则可以使用以下方法: “ PATH”环境变量,并检查其中是否存在“ su”文件。

为了真正检查root用户访问权限,实际上必须运行“ su”命令。如果安装了诸如SuperUser之类的应用,则此时它可能会要求进行根访问,或者可能已经显示了是否已被授予/拒绝了祝酒词,指示是否已授予/拒绝访问。一个很好的命令是“ id”,以便您可以验证用户ID实际上是否为0(root)。 >
public static boolean isRootAvailable(){
    for(String pathDir : System.getenv("PATH").split(":")){
        if(new File(pathDir, "su").exists()) {
            return true;
        }
    }
    return false;
}


实际测试运行“ su”命令很重要,因为某些仿真器已预安装了“ su”可执行文件,但仅允许某些用户像adb shell一样访问它。

在尝试运行“ su”可执行文件之前,检查其是否存在也很重要,因为众所周知,android无法正确处理试图运行丢失命令的进程。这些虚幻进程会随着时间的流逝消耗内存。

评论


isRootAvailable()方法效果很好,谢谢。我建议不要在主线程上运行此命令,但是要避免ANR,例如来自AsyncTask的调用

–雷电
16年11月8日在20:32



我认为这是想要确保root不可用和想要确保root不可用之间的区别。如果要确保设备没有植根,建议的检查是好的。您会得到误报,但是当您不担心在受感染设备上运行代码时没关系。

–杰弗里·布拉特曼(Jeffrey Blattman)
17年11月9日在17:19

@ DAC84我不确定我是否理解您的问题。如果在生根应用程序上运行isRootGiven并拒绝,则它应返回false。那不是正在发生的事吗?如果要避免出现警报,可以只使用isRootAvailable,也可以将其命名为didSUExist。您也可以尝试将根应用程序配置为自由分发根,而不对其进行管理。

–rsimp
18-2-15在21:04



@BeeingJk并非完全不是,尽管这实际上是您可以在不运行su的情况下进行的最多检查,这是真正的测试。不过,您需要先在PATH中检查su,然后再尝试执行它。但是,实际上执行su通常会导致吐司消息或与root管理应用程序的交互,这可能不是您想要的。根据您自己的逻辑,您可能认为su足够存在。在某些可能包含su可执行文件但对其具有锁定访问权限的仿真器中,这仍然可能产生误报。

–rsimp
18年8月3日在22:20

@BeeingJk isRootAvailable可能就是您所需要的,但是我要说明的是,像这样的名称甚至dosSUExist提供的语义要比isDeviceRooted这样的方法名称更好,语义并不完全正确。如果您确实需要在继续之前验证完全根访问权限,则需要尝试使用su运行命令,例如isRootGiven中编码的命令

–rsimp
18年8月3日在22:32

#6 楼

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

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

已生根的设备似乎导致ctsProfileMatch = false。

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


评论


优秀的信息,在不同的背景下,我相信这将是正确的答案。不幸的是,OP的问题不是关于在不安全的环境中保护其应用程序,而是检测root用户以在其应用程序中启用仅root用户功能。对于OP而言,此过程似乎过于复杂。

–rsimp
17年7月29日在6:31

#7 楼

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

除非手机还具有RootCloak之类的应用程序,否则凯文的答案会起作用。这样的应用程序一旦植根了电话,便具有Handle over Java API,并且它们模拟了这些API来返回植根的植根电话。此外,它也不会引起任何内存泄漏问题。 />
#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;

}


评论


我认为根检测分为两类,启用根相关的功能,然后再采用基于安全的措施来尝试缓解根电话的安全问题。对于依赖根的功能,我发现Kevin的答案很差。在此答案的上下文中,这些方法更有意义。尽管我会重写方法2而不使用它,而是遍历PATH环境变量来搜索“ su”。不能保证在电话中显示“哪个”。

–rsimp
17年5月22日在19:36

您能提供一个如何在Java中使用此C代码的示例吗?

– rid
17年8月18日在17:46

@mrid请检查如何从Android上的Java进行JNI调用。

– Alok Kulkarni
17年8月22日在9:05

此方法可防止使用RootCloak应用程序绕过根检测。是否有任何已知的绕过这些方法的方法?

– Nidhin
18 Mar 22 '18 at 12:33

#8 楼

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文件夹:

评论


谢谢!我将它用作带有Kevin's Answer的checkRootMethod4()。

– Sheharyar
2015年1月29日在3:38



永远不要在布尔值中添加== true,它什么也不添加,看起来也不好。

– minipif
2015年11月18日,0:29



@smoothBlue为什么会这样?它没有像DevrimTuncer的解决方案那样产生任何进程。

– FD_
16 Mar 24 '16 at 16:27

一个更好的主意是遍历PATH,而不是对典型的PATH目录进行硬编码

–rsimp
16-09-22在20:22

使用if(isRooted())检查,而不是显式写入true。最好遵循代码编写模式

– blueware
17年1月22日在13:31

#9 楼

可以使用isAccessGiven()代替使用isRootAvailable()。直接从RootTools Wiki:

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



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


参考

评论


但是用户必须授予root访问权限?因此,如果我的目标是在设备已扎根的情况下停止运行我的应用,那么我的选择确实很有限

– Nasz Njoka Sr.
16年5月6日在13:30

#10 楼

RootBeer是Scott和Matthew进行的根检查Android库。
它使用各种检查来指示设备是否已root。 >
CheckRootManagementApps
CheckPotentiallyDangerousAppss
CheckRootCloakingApps
CheckTestKeys
checkForDangerousProps
checkForBusyBoxBinary
checkForSuBinary
checkSuExists
checkForRWSystems
本机检查

我们调用本机根检查器来运行一些它自己的
检查。通常,本机检查很难进行伪装,因此某些root
伪装应用仅阻止加载包含某些关键词的本机库。


checkForSuBinary



#11 楼

为此,使用了一些经过修改的版本来设置系统属性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(您可能希望允许它使用),它具有以下功能: ,但在site:xda-developers.com下进行的各种搜索是有益的。这是荷兰的G1,您可以看到ro.build.tags没有test-keys,我认为这可能是最可靠的属性。

评论


看起来很有趣,但是:虽然仿真器(和ADP)确实允许root用户使用,但它们不允许应用程序使用root,即:$ su app_29 $ su su:uid 10029 not su

– miiracle2k
09年9月9日在10:08

嗯,我想他们不会...如果可以,将它们与ro.build.host(不是)以google.com结尾的检查结合起来,如果它们是唯一具有测试键但阻止su却没有问用户。取决于构建主机是什么用于较新的设备,不是电话的东西……并不容易。

–克里斯·博伊尔(Chris Boyle)
09年7月9日在10:27

#12 楼

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

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


#13 楼

我建议使用本机代码进行根检测。
这是一个完整的示例。



JAVA包装器:

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包装器(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] = 'q4312078q';
            e->mnt_dir = &buf[dir0];
            buf[dir1] = 'q4312078q';
            e->mnt_type = &buf[type0];
            buf[type1] = 'q4312078q';
            e->mnt_opts = &buf[opts0];
            buf[opts1] = 'q4312078q';
            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 == 'q4312078q' || *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;
}


评论


真棒工具,Dima。非常感谢。它甚至可以捕捉到魔力。

–专家
18年3月2日在10:24

这是实打实的。

–Vahid Amiri
18/12/22在5:49

@klutch在我的文章的第一行有指向工作示例(github)的链接

–迪马·科热文(Dima Kozhevin)
6月4日10:07



Stil magisk正在检测,它不是magisk安全的:(

– Arjun T Raj
7月20日7:11

#14 楼

除了@Kevins答案之外,我最近在使用他的系统时发现Nexus 7.1为所有三种方法都返回false-which中未安装test-keys命令,SuperSU/system/app。我添加了以下内容:

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


在某些情况下(如果您需要保证root用户访问权限)这用处不大,因为SuperSU可以完全安装在没有没有SU访问权限。

评论


这不是一个好答案,因为您可以在设备上安装其他根软件包。硬编码其他应用程序包会很困难,因为您无法期望并列出所有程序包

– blueware
17年1月22日在13:29

#15 楼

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


#16 楼

如果您想检查您的应用程序是否支持root设备,还有另外两个想法:


检查是否存在'su'二进制文件:从Runtime.getRuntime().exec()

/system/app/Superuser.apk位置中查找SuperUser.apk


#17 楼

将C ++与ndk一起使用是检测root的最佳方法,即使用户使用的是隐藏RootCloak之类的应用程序。我使用RootCloak测试了此代码,即使用户试图隐藏它,我也能够检测到根。
因此,您的cpp文件将需要: br />

#18 楼

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


#19 楼

有Google Play服务的Safety Net Attestation API,我们可以通过该API评估设备并确定其是否已被根目录/被篡改。 .com / a / 58304556/3908895

#20 楼

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

#21 楼

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

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


该代码肯定不是防弹的,因为网络不可用,所以您会遇到异常。如果此方法返回true,则可以确定99%,否则可以确定只有50%。网络权限也会破坏解决方案。

评论


我对此进行了测试,但对于我的已植根设备,它不会返回true。

–技术
2014年10月6日在21:26

有趣的是,看到了哪种异常。您可能会获得端口已绑定异常,但是,如果您不能在1024以下的范围内创建服务器端口,则会降低rooting的值,因为您仍然有某些限制。

–Singagirl
2014年10月7日,0:20

#22 楼

如果您不想使用任何第三方库或任何随机解决方案,则只需使用google lib对其进行检测即可。如果设备已植根,则为false。
参考链接:
[1]:https://developer.android.com/training/safetynet/attestation

#23 楼

您可以通过以下代码进行操作:
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 楼

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