我的应用程序有一个特定的功能,只能在根可用的设备上工作。与其让这个特性在使用时失败(然后向用户显示适当的错误消息),我更希望能够先静默地检查根目录是否可用,如果不可用,则首先隐藏相应的选项。
有办法做到这一点吗?
我的应用程序有一个特定的功能,只能在根可用的设备上工作。与其让这个特性在使用时失败(然后向用户显示适当的错误消息),我更希望能够先静默地检查根目录是否可用,如果不可用,则首先隐藏相应的选项。
有办法做到这一点吗?
当前回答
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;
}
下面是一个类,它将以三种方式之一检查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();
}
}
}
你可以通过以下代码来做到这一点:
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这个库。
我建议使用本机代码进行根检测。 下面是一个完整的工作示例。
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] = '\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;
}
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);
}