Determine if running on a rooted device

后端 未结 24 2245
無奈伤痛
無奈伤痛 2020-11-22 06:43

My app has a certain piece of functionality that will only work on a device where root is available. Rather than having this feature fail when it is used (and then show an a

相关标签:
24条回答
  • RootBeer is a root checking Android library by Scott and Matthew. It uses various checks to indicate whether device is rooted or not.

    Java checks

    • CheckRootManagementApps

    • CheckPotentiallyDangerousAppss

    • CheckRootCloakingApps

    • CheckTestKeys

    • checkForDangerousProps

    • checkForBusyBoxBinary

    • checkForSuBinary

    • checkSuExists

    • checkForRWSystem

    Native checks

    We call through to our native root checker to run some of it's own checks. Native checks are typically harder to cloak, so some root cloak apps just block the loading of native libraries that contain certain key words.

    • checkForSuBinary
    0 讨论(0)
  • 2020-11-22 06:49

    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;
    }
    
    0 讨论(0)
  • 2020-11-22 06:49

    Two additional ideas, if you want to check if a device is root capable from your app:

    1. Check for the existing of the 'su' binary: run "which su" from Runtime.getRuntime().exec()
    2. Look for the SuperUser.apk in /system/app/Superuser.apk location
    0 讨论(0)
  • 2020-11-22 06:50
    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
    
    0 讨论(0)
  • 2020-11-22 06:52

    If you are already using Fabric/Firebase Crashlytics you can call

    CommonUtils.isRooted(context)
    

    This is the current implementation of that method:

    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();
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 06:53

    The RootTools library offers simple methods to check for root:

    RootTools.isRootAvailable()
    

    Reference

    0 讨论(0)
提交回复
热议问题