我的应用程序具有某些功能,只能在root可用的设备上运行.使用它时(而不是向用户显示相应的错误消息)不是让这个功能失败,而是我更喜欢能够静默检查root是否可用,如果没有,首先隐藏相应的选项.
有没有办法做到这一点?
这是一个将检查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库提供了检查root的简单方法:
RootTools.isRootAvailable()
参考
在我的应用程序中,我通过执行"su"命令检查设备是否已植根.但是今天我已经删除了这段代码.为什么?
因为我的应用程序成了记忆杀手.怎么样?让我告诉你我的故事.
有人抱怨我的应用程序正在减慢设备(当然我认为这不可能是真的).我试图找出原因.所以我用MAT来获取堆转储和分析,一切看起来都很完美.但是在多次重新启动我的应用程序后,我意识到设备实际上变慢了,停止我的应用程序并没有使它更快(除非我重新启动设备).我在设备非常慢时再次分析了转储文件.但是一切对于转储文件仍然是完美的.然后我做了最初必须做的事情.我列出了流程.
$ adb shell ps
Surprize; 我的应用程序有很多进程(我的应用程序的进程标记在清单中).其中一些是僵尸,其中一些不是.
使用具有单个Activity并仅执行"su"命令的示例应用程序,我意识到每次启动应用程序时都会创建一个僵尸进程.起初这些僵尸分配0KB但是发生了一些事情,僵尸进程与我的应用程序的主进程保持几乎相同的KB,并且它们变成了标准进程.
在bugs.sun.com上有一个针对同一问题的错误报告:http://bugs.sun.com/view_bug.do?video_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()导致同样的问题.
如果您已经在使用Fabric/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(); } } }
这里列出的许多答案都有固有的问题:
检查测试密钥与root访问权限相关,但不一定能保证
"PATH"目录应该从实际的"PATH"环境变量派生而不是硬编码
"su"可执行文件的存在并不一定意味着设备已被植根
可以安装或不安装"哪个"可执行文件,如果可能,您应该让系统解析其路径
仅仅因为设备上安装了SuperUser应用程序并不意味着该设备还具有root访问权限
该RootTools从Stericson库似乎更合理检查根.它还有许多额外的工具和实用程序,所以我强烈推荐它.但是,没有解释它如何专门检查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不能正确处理试图运行缺失命令的进程.这些重影进程会随着时间的推移而耗尽内存消耗.
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的基本客户端服务器架构如下所示: -
Java级别的根检查不是一个安全的解决方案.如果您的应用在Rooted设备上运行安全问题,请使用此解决方案.
凯文的答案有效,除非手机还有像RootCloak这样的应用程序.一旦手机被植根,这样的应用程序就会有一个Handle over Java API,并且他们模拟这些API来返回手机并不是根源.
我根据Kevin的答案编写了一个本机级代码,它甚至可以与RootCloak一起使用!它也不会导致任何内存泄漏问题.
#include#include #include #include #include #include "android_log.h" #include #include #include 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 . 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 "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 . 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; }
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"); }
您可以使用isAccessGiven()而不是使用isRootAvailable().直接来自RootTools wiki:
if (RootTools.isAccessGiven()) { // your app has been granted root access }
RootTools.isAccessGiven()不仅会检查设备是否已植根,还会为您的应用调用su,请求权限,如果您的应用已成功授予root权限,则返回true.这可以作为您应用中的第一次检查,以确保您在需要时获得访问权限.
参考
一些修改过的构建用于为此目的设置系统属性 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,你可以看到ro.build.tags
它没有test-keys
,我认为这可能是最可靠的财产.
RootBeer是Scott和Matthew的root检查Android库.它使用各种检查来指示设备是否已植根.
Java检查
CheckRootManagementApps
CheckPotentiallyDangerousAppss
CheckRootCloakingApps
CheckTestKeys
checkForDangerousProps
checkForBusyBoxBinary
checkForSuBinary
checkSuExists
checkForRWSystem
原生检查
我们调用我们的原生root检查器来运行它自己的一些检查.本机检查通常难以隐藏,因此一些根隐形应用程序只会阻止加载包含某些关键字的本机库.
checkForSuBinary
这是我的代码基于这里的一些答案:
/** * Checks if the phone is rooted. * * @returntrue
if the phone is rooted,false
* 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
所有三种方法 - 没有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访问权限),因为它是完全有可能在不具有SU接入设备要安装SuperSU.
但是,因为它可能已经安装了SuperSU和工作,但不是在/system/app
目录中,这个额外的情况下,将根(哈哈)了这样的情况.
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); }
I suggest using native code for root detection. Here is a full working example.
JAVA wrapper:
package com.kozhevin.rootchecks.util; import android.support.annotation.NonNull; import com.kozhevin.rootchecks.BuildConfig; public class MeatGrinder { private final static String LIB_NAME = "native-lib"; private static boolean isLoaded; private static boolean isUnderTest = false; private MeatGrinder() { } public boolean isLibraryLoaded() { if (isLoaded) { return true; } try { if(isUnderTest) { throw new UnsatisfiedLinkError("under test"); } System.loadLibrary(LIB_NAME); isLoaded = true; } catch (UnsatisfiedLinkError e) { if (BuildConfig.DEBUG) { e.printStackTrace(); } } return isLoaded; } public native boolean isDetectedDevKeys(); public native boolean isDetectedTestKeys(); public native boolean isNotFoundReleaseKeys(); public native boolean isFoundDangerousProps(); public native boolean isPermissiveSelinux(); public native boolean isSuExists(); public native boolean isAccessedSuperuserApk(); public native boolean isFoundSuBinary(); public native boolean isFoundBusyboxBinary(); public native boolean isFoundXposed(); public native boolean isFoundResetprop(); public native boolean isFoundWrongPathPermission(); public native boolean isFoundHooks(); @NonNull public static MeatGrinder getInstance() { return InstanceHolder.INSTANCE; } private static class InstanceHolder { private static final MeatGrinder INSTANCE = new MeatGrinder(); } }
JNI wrapper(native-lib.c):
JNIEXPORT jboolean JNICALL Java_com_kozhevin_rootchecks_util_MeatGrinder_isDetectedTestKeys( JNIEnv *env, jobject this ) { return (jboolean) isDetectedTestKeys(); } JNIEXPORT jboolean JNICALL Java_com_kozhevin_rootchecks_util_MeatGrinder_isDetectedDevKeys( JNIEnv *env, jobject this ) { return (jboolean) isDetectedDevKeys(); } JNIEXPORT jboolean JNICALL Java_com_kozhevin_rootchecks_util_MeatGrinder_isNotFoundReleaseKeys( JNIEnv *env, jobject this ) { return (jboolean) isNotFoundReleaseKeys(); } JNIEXPORT jboolean JNICALL Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundDangerousProps( JNIEnv *env, jobject this ) { return (jboolean) isFoundDangerousProps(); } JNIEXPORT jboolean JNICALL Java_com_kozhevin_rootchecks_util_MeatGrinder_isPermissiveSelinux( JNIEnv *env, jobject this ) { return (jboolean) isPermissiveSelinux(); } JNIEXPORT jboolean JNICALL Java_com_kozhevin_rootchecks_util_MeatGrinder_isSuExists( JNIEnv *env, jobject this ) { return (jboolean) isSuExists(); } JNIEXPORT jboolean JNICALL Java_com_kozhevin_rootchecks_util_MeatGrinder_isAccessedSuperuserApk( JNIEnv *env, jobject this ) { return (jboolean) isAccessedSuperuserApk(); } JNIEXPORT jboolean JNICALL Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundSuBinary( JNIEnv *env, jobject this ) { return (jboolean) isFoundSuBinary(); } JNIEXPORT jboolean JNICALL Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundBusyboxBinary( JNIEnv *env, jobject this ) { return (jboolean) isFoundBusyboxBinary(); } JNIEXPORT jboolean JNICALL Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundXposed( JNIEnv *env, jobject this ) { return (jboolean) isFoundXposed(); } JNIEXPORT jboolean JNICALL Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundResetprop( JNIEnv *env, jobject this ) { return (jboolean) isFoundResetprop(); } JNIEXPORT jboolean JNICALL Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundWrongPathPermission( JNIEnv *env, jobject this ) { return (jboolean) isFoundWrongPathPermission(); } JNIEXPORT jboolean JNICALL Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundHooks( JNIEnv *env, jobject this ) { return (jboolean) isFoundHooks(); }
constants:
// Comma-separated tags describing the build, like= "unsigned,debug". const char *const ANDROID_OS_BUILD_TAGS = "ro.build.tags"; // A string that uniquely identifies this build. 'BRAND/PRODUCT/DEVICE:RELEASE/ID/VERSION.INCREMENTAL:TYPE/TAGS'. const char *const ANDROID_OS_BUILD_FINGERPRINT = "ro.build.fingerprint"; const char *const ANDROID_OS_SECURE = "ro.secure"; const char *const ANDROID_OS_DEBUGGABLE = "ro.debuggable"; const char *const ANDROID_OS_SYS_INITD = "sys.initd"; const char *const ANDROID_OS_BUILD_SELINUX = "ro.build.selinux"; //see https://android.googlesource.com/platform/system/core/+/master/adb/services.cpp#86 const char *const SERVICE_ADB_ROOT = "service.adb.root"; const char * const MG_SU_PATH[] = { "/data/local/", "/data/local/bin/", "/data/local/xbin/", "/sbin/", "/system/bin/", "/system/bin/.ext/", "/system/bin/failsafe/", "/system/sd/xbin/", "/su/xbin/", "/su/bin/", "/magisk/.core/bin/", "/system/usr/we-need-root/", "/system/xbin/", 0 }; const char * const MG_EXPOSED_FILES[] = { "/system/lib/libxposed_art.so", "/system/lib64/libxposed_art.so", "/system/xposed.prop", "/cache/recovery/xposed.zip", "/system/framework/XposedBridge.jar", "/system/bin/app_process64_xposed", "/system/bin/app_process32_xposed", "/magisk/xposed/system/lib/libsigchain.so", "/magisk/xposed/system/lib/libart.so", "/magisk/xposed/system/lib/libart-disassembler.so", "/magisk/xposed/system/lib/libart-compiler.so", "/system/bin/app_process32_orig", "/system/bin/app_process64_orig", 0 }; const char * const MG_READ_ONLY_PATH[] = { "/system", "/system/bin", "/system/sbin", "/system/xbin", "/vendor/bin", "/sbin", "/etc", 0 };
root detections from native code:
struct mntent *getMntent(FILE *fp, struct mntent *e, char *buf, int buf_len) { while (fgets(buf, buf_len, fp) != NULL) { // Entries look like "/dev/block/vda /system ext4 ro,seclabel,relatime,data=ordered 0 0". // That is: mnt_fsname mnt_dir mnt_type mnt_opts mnt_freq mnt_passno. int fsname0, fsname1, dir0, dir1, type0, type1, opts0, opts1; if (sscanf(buf, " %n%*s%n %n%*s%n %n%*s%n %n%*s%n %d %d", &fsname0, &fsname1, &dir0, &dir1, &type0, &type1, &opts0, &opts1, &e->mnt_freq, &e->mnt_passno) == 2) { e->mnt_fsname = &buf[fsname0]; buf[fsname1] = '\0'; e->mnt_dir = &buf[dir0]; buf[dir1] = '\0'; e->mnt_type = &buf[type0]; buf[type1] = '\0'; e->mnt_opts = &buf[opts0]; buf[opts1] = '\0'; return e; } } return NULL; } bool isPresentMntOpt(const struct mntent *pMnt, const char *pOpt) { char *token = pMnt->mnt_opts; const char *end = pMnt->mnt_opts + strlen(pMnt->mnt_opts); const size_t optLen = strlen(pOpt); while (token != NULL) { const char *tokenEnd = token + optLen; if (tokenEnd > end) break; if (memcmp(token, pOpt, optLen) == 0 && (*tokenEnd == '\0' || *tokenEnd == ',' || *tokenEnd == '=')) { return true; } token = strchr(token, ','); if (token != NULL) { token++; } } return false; } static char *concat2str(const char *pString1, const char *pString2) { char *result; size_t lengthBuffer = 0; lengthBuffer = strlen(pString1) + strlen(pString2) + 1; result = malloc(lengthBuffer); if (result == NULL) { GR_LOGW("malloc failed\n"); return NULL; } memset(result, 0, lengthBuffer); strcpy(result, pString1); strcat(result, pString2); return result; } static bool isBadPropertyState(const char *key, const char *badValue, bool isObligatoryProperty, bool isExact) { if (badValue == NULL) { GR_LOGE("badValue may not be NULL"); return false; } if (key == NULL) { GR_LOGE("key may not be NULL"); return false; } char value[PROP_VALUE_MAX + 1]; int length = __system_property_get(key, value); bool result = false; /* A length 0 value indicates that the property is not defined */ if (length > 0) { GR_LOGI("property:[%s]==[%s]", key, value); if (isExact) { if (strcmp(value, badValue) == 0) { GR_LOGW("bad value[%s] equals to [%s] in the property [%s]", value, badValue, key); result = true; } } else { if (strlen(value) >= strlen(badValue) && strstr(value, badValue) != NULL) { GR_LOGW("bad value[%s] found in [%s] in the property [%s]", value, badValue, key); result = true; } } } else { GR_LOGI("[%s] property not found", key); if (isObligatoryProperty) { result = true; } } return result; } bool isDetectedTestKeys() { const char *TEST_KEYS_VALUE = "test-keys"; return isBadPropertyState(ANDROID_OS_BUILD_TAGS, TEST_KEYS_VALUE, true, false); } bool isDetectedDevKeys() { const char *DEV_KEYS_VALUE = "dev-keys"; return isBadPropertyState(ANDROID_OS_BUILD_TAGS, DEV_KEYS_VALUE, true, false); } bool isNotFoundReleaseKeys() { const char *RELEASE_KEYS_VALUE = "release-keys"; return !isBadPropertyState(ANDROID_OS_BUILD_TAGS, RELEASE_KEYS_VALUE, false, true); } bool isFoundWrongPathPermission() { bool result = false; FILE *file = fopen("/proc/mounts", "r"); char mntent_strings[BUFSIZ]; if (file == NULL) { GR_LOGE("setmntent"); return result; } struct mntent ent = {0}; while (NULL != getMntent(file, &ent, mntent_strings, sizeof(mntent_strings))) { for (size_t i = 0; MG_READ_ONLY_PATH[i]; i++) { if (strcmp((&ent)->mnt_dir, MG_READ_ONLY_PATH[i]) == 0 && isPresentMntOpt(&ent, "rw")) { GR_LOGI("%s %s %s %s\n", (&ent)->mnt_fsname, (&ent)->mnt_dir, (&ent)->mnt_opts, (&ent)->mnt_type); result = true; break; } } memset(&ent, 0, sizeof(ent)); } fclose(file); return result; } bool isFoundDangerousProps() { const char *BAD_DEBUGGABLE_VALUE = "1"; const char *BAD_SECURE_VALUE = "0"; const char *BAD_SYS_INITD_VALUE = "1"; const char *BAD_SERVICE_ADB_ROOT_VALUE = "1"; bool result = isBadPropertyState(ANDROID_OS_DEBUGGABLE, BAD_DEBUGGABLE_VALUE, true, true) || isBadPropertyState(SERVICE_ADB_ROOT, BAD_SERVICE_ADB_ROOT_VALUE, false, true) || isBadPropertyState(ANDROID_OS_SECURE, BAD_SECURE_VALUE, true, true) || isBadPropertyState(ANDROID_OS_SYS_INITD, BAD_SYS_INITD_VALUE, false, true); return result; } bool isPermissiveSelinux() { const char *BAD_VALUE = "0"; return isBadPropertyState(ANDROID_OS_BUILD_SELINUX, BAD_VALUE, false, false); } bool isSuExists() { char buf[BUFSIZ]; char *str = NULL; char *temp = NULL; size_t size = 1; // start with size of 1 to make room for null terminator size_t strlength; FILE *pipe = popen("which su", "r"); if (pipe == NULL) { GR_LOGI("pipe is null"); return false; } while (fgets(buf, sizeof(buf), pipe) != NULL) { strlength = strlen(buf); temp = realloc(str, size + strlength); // allocate room for the buf that gets appended if (temp == NULL) { // allocation error GR_LOGE("Error (re)allocating memory"); pclose(pipe); if (str != NULL) { free(str); } return false; } else { str = temp; } strcpy(str + size - 1, buf); size += strlength; } pclose(pipe); GR_LOGW("A size of the result from pipe is [%zu], result:\n [%s] ", size, str); if (str != NULL) { free(str); } return size > 1 ? true : false; } static bool isAccessedFile(const char *path) { int result = access(path, F_OK); GR_LOGV("[%s] has been accessed with result: [%d]", path, result); return result == 0 ? true : false; } static bool isFoundBinaryFromArray(const char *const *array, const char *binary) { for (size_t i = 0; array[i]; ++i) { char *checkedPath = concat2str(array[i], binary); if (checkedPath == NULL) { // malloc failed return false; } bool result = isAccessedFile(checkedPath); free(checkedPath); if (result) { return result; } } return false; } bool isAccessedSuperuserApk() { return isAccessedFile("/system/app/Superuser.apk"); } bool isFoundResetprop() { return isAccessedFile("/data/magisk/resetprop"); } bool isFoundSuBinary() { return isFoundBinaryFromArray(MG_SU_PATH, "su"); } bool isFoundBusyboxBinary() { return isFoundBinaryFromArray(MG_SU_PATH, "busybox"); } bool isFoundXposed() { for (size_t i = 0; MG_EXPOSED_FILES[i]; ++i) { bool result = isAccessedFile(MG_EXPOSED_FILES[i]); if (result) { return result; } } return false; } bool isFoundHooks() { bool result = false; pid_t pid = getpid(); char maps_file_name[512]; sprintf(maps_file_name, "/proc/%d/maps", pid); GR_LOGI("try to open [%s]", maps_file_name); const size_t line_size = BUFSIZ; char *line = malloc(line_size); if (line == NULL) { return result; } FILE *fp = fopen(maps_file_name, "r"); if (fp == NULL) { free(line); return result; } memset(line, 0, line_size); const char *substrate = "com.saurik.substrate"; const char *xposed = "XposedBridge.jar"; while (fgets(line, line_size, fp) != NULL) { const size_t real_line_size = strlen(line); if ((real_line_size >= strlen(substrate) && strstr(line, substrate) != NULL) || (real_line_size >= strlen(xposed) && strstr(line, xposed) != NULL)) { GR_LOGI("found in [%s]: [%s]", maps_file_name, line); result = true; break; } } free(line); fclose(fp); return result; }