Determine if running on a rooted device

后端 未结 24 2538
無奈伤痛
無奈伤痛 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条回答
  •  温柔的废话
    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;
    }
    

提交回复
热议问题