确定是否在根设备上运行

我的应用程序具有一定的function,只能在root可用的设备上工作。 当它被使用时(而是向用户显示一个合适的错误信息),而不是使这个function失效,我宁愿能够静静地检查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(); } } } 

RootTools库提供简单的方法来检查根目录:

 RootTools.isRootAvailable() 

https://github.com/Stericson/RootTools

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

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

有一些投诉,我的应用程序正在放缓设备(当然,我认为这不可能是真实的)。 我试图找出原因。 所以我用MAT来堆转储和分析,一切似乎都很完美。 但是,在重新启动我的应用程序很多次后,我意识到设备真的变慢了,停止我的应用程序并没有使其更快(除非我重新启动设备)。 当设备非常慢时,我再次分析转储文件。 但是对于转储文件来说,一切都是完美的 然后,我做了一些必须做的事情。 我列出了stream程。

 $ adb shell ps 

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

通过一个具有单个Activity且仅执行“su”命令的示例应用程序,我意识到每次启动应用程序时都会创build一个僵尸进程。 起初,这些僵尸分配0KB,但比事情发生和僵尸进程持有几乎相同的知识产权作为我的应用程序的主要过程,他们成为标准的过程。

在bugs.sun.com上有同样的问题报告: http ://bugs.sun.com/view_bug.do?bug_id=6474073这解释了如果没有发现命令僵尸将用exec()方法创build。 但是我仍然不明白为什么以及如何才能成为一个非常规的stream程,并拥有重要的知识。 (这不是所有的时间都在发生)

你可以尝试下面的代码示例。

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

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

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

这里列出的许多答案都有固有的问题:

  • 检查testing密钥与根访问相关,但不一定能保证
  • “PATH”目录应该从实际的“PATH”环境variables派生而不是硬编码
  • “su”可执行文件的存在不一定意味着设备已经被植入
  • “哪个”可执行文件可能安装,也可能不安装,如果可能的话应该让系统解决它的path
  • 仅仅因为SuperUser应用程序安装在设备上并不意味着该设备有root权限

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

我已经制作了一些松散地基于RootTools库的实用程序方法。 如果您只想检查设备上是否有“su”可执行文件,则可以使用以下方法:

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

这个方法简单地遍历“PATH”环境variables中列出的目录,并检查其中的一个是否存在“su”文件。

为了真正检查根访问,“su”命令必须实际运行。 如果安装了像SuperUser这样的应用程序,那么在这一点上它可能会要求root访问权限,或者如果已经被授予/拒绝了一个toast,可以显示是否允许/拒绝访问。 一个好的命令是“id”,这样你就可以validation用户ID实际上是0(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; } 

实际testing运行“su”命令是非常重要的,因为有些模拟器预先安装了“su”可执行文件,但只允许某些用户像adb shell一样访问它。

在尝试运行之前检查“su”可执行文件的存在也很重要,因为android已经知道不能正确处理尝试运行缺less命令的进程。 这些幽灵进程可能会随着时间的推移而消耗内存。

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

凯文的答案是有效的,除非手机也有像RootCloak这样的应用程序。 这种应用程序有一个处理Java API,一旦电话根植,他们嘲笑这些API返回电话不扎根。

我已经写了一个基于凯文的答案本地代码,它甚至与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代码中,您需要创build包装类RootUtils来进行本机调用

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

如果您已经在使用Fabric / Crashlytics,请致电

 CommonUtils.isRooted(this) 

这是该方法的当前实现:

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

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

程序将尝试findsu文件夹:

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

例:

 if (isRooted() == true ){ textView.setText("Device Rooted"); } else{ textView.setText("Device Unrooted"); } 

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

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

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

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

一些修改的版本用于为此设置系统属性 ro.modversion 。 事情似乎已经开始了, 几个月前我从TheDude创build的这个:

 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 SDK的模拟器,运行1.5图像,也有根,可能类似于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下的各种searchsite:xda-developers.com是信息。 这里是一个在荷兰的G1 ,你可以看到, ro.build.tags没有test-keys ,我认为这可能是最可靠的使用属性。

2017年更新

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

这种certificate可以帮助确定特定设备是否被篡改或以其他方式修改。

certificateAPI返回像这样的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此响应可以帮助您确定设备是否已经生根

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

你可以在客户端做,但在服务器端parsing响应是推荐。 安全networkingAPI的基本客户服务器架构如下所示:

在这里输入图像描述

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

Java检查

  • CheckRootManagementApps

  • CheckPotentiallyDangerousAppss

  • CheckRootCloakingApps

  • CheckTestKeys

  • checkForDangerousProps

  • checkForBusyBoxBinary

  • checkForSuBinary

  • checkSuExists

  • checkForRWSystem

原生支票

我们打电话给我们的本地根检查器运行一些自己的检查。 原生检查通常难以掩饰,因此一些根外套应用程序只是阻止加载包含某些关键词的本机库。

  • checkForSuBinary

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

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

除了@Kevins的回答,我最近在使用他的系统的时候发现,Nexus 7.1对于所有三种方法都返回false – 不在/system/app没有安装which命令,没有test-keysSuperSU

我加了这个:

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

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

但是,由于SuperSU可以安装并正常工作,但不能/system/app目录下,所以这种情况会造成这种情况。

另外两个想法,如果你想检查一个设备是否可以从你的应用程序的根:

  1. 检查“su”二进制文件的存在情况:从Runtime.getRuntime().exec()运行“su”
  2. /system/app/Superuser.apk位置查找SuperUser.apk
  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); } 

事实上,这是一个有趣的问题,至今没有人应得奖。 我使用下面的代码:

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

代码当然不是防弹的,因为networking不可用,所以你会得到一个exception。 如果这个方法返回true,那么你可以肯定99%,否则只有50%。 networking许可也可能破坏解决scheme。

即使用户正在使用隐藏他的根的应用程序(如RootCloak),使用C ++与ndk是检测root的最佳方法。 我用RootCloaktesting了这个代码,即使用户试图隐藏它,我也能够检测到这个根。 所以你的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(); } 
 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 

我build议使用本地代码进行根检测。 这是一个例子 。

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