我的应用程序有一个特定的功能,只能在根可用的设备上工作。与其让这个特性在使用时失败(然后向用户显示适当的错误消息),我更希望能够先静默地检查根目录是否可用,如果不可用,则首先隐藏相应的选项。

有办法做到这一点吗?


当前回答

如果你已经在使用Fabric/Firebase 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();
        }
    }
}

public static boolean isEmulator(Context context) {
    String androidId = Secure.getString(context.getContentResolver(), "android_id");
    return "sdk".equals(Build.PRODUCT) || "google_sdk".equals(Build.PRODUCT) || androidId == null;
}

其他回答

在2021年最后一个季度的今天,我试图使用SafetyNet来回答@HimanshiThakur的答案。但我有个问题,在这里提出了一个问题。还是没有回答。

所以我决定使用RootBeer。它工作得很好,但当Magisk隐藏根时,它就不起作用了。

如果你不在乎这种情况(许多银行应用程序也不能解决这个问题),你可以使用以下步骤:

添加到Gradle:

implementation 'com.scottyab:rootbeer-lib:0.1.0'

用这些句子:

RootBeer rootBeer = new RootBeer(context);
if (rootBeer.isRooted()) {
    //we found indication of root
} else {
    //we didn't find indication of root
}

Java级别的根检查不是一个安全的解决方案。如果您的应用程序在root设备上运行有安全问题,那么请使用此解决方案。

凯文的答案是有效的,除非手机也有像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代码中,需要创建包装器类RootUtils来进行本机调用

    public boolean checkRooted() {

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

一些用于设置系统属性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 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下的各种搜索提供了大量信息。这是荷兰的G1, ro。build。tags没有test-key,我认为这可能是最可靠的属性。

使用谷歌SafetyNet认证API,您可以轻松检查您的设备是否已根:

Add dependency in build.gradle(:app) implementation 'com.google.android.gms:play-services-safetynet:17.0.0' Get Api key and enable Android Device Verification API using link public static void sendSafetyNetRequest(Activity context) { if(GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context, 13000000) == ConnectionResult.SUCCESS) { Log.e(TAG, "The SafetyNet Attestation API is available"); // TODO(developer): Change the nonce generation to include your own, used once value, // ideally from your remote server. String nonceData = "Safety Net Sample: " + System.currentTimeMillis(); ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); Random mRandom = new SecureRandom(); byte[] bytes = new byte[24]; mRandom.nextBytes(bytes); try { byteStream.write(bytes); byteStream.write(nonceData.getBytes()); } catch (IOException e) { e.printStackTrace(); } byte[] nonce = byteStream.toByteArray(); SafetyNetClient client = SafetyNet.getClient(context); Task<SafetyNetApi.AttestationResponse> task = client.attest(nonce, API_KEY_FROM_STEP_2_LINK); task.addOnSuccessListener(context, attestationResponse -> { /* TODO(developer): Forward this result to your server together with the nonce for verification. You can also parse the JwsResult locally to confirm that the API returned a response by checking for an 'error' field first and before retrying the request with an exponential backoff. NOTE: Do NOT rely on a local, client-side only check for security, you must verify the response on a remote server! */ String jwsResult = attestationResponse.getJwsResult(); Log.e(TAG, "Success! SafetyNet result:\n" + jwsResult + "\n"); if (jwsResult == null) { Log.e(TAG, "jwsResult Null"); } final String[] jwtParts = jwsResult.split("\\."); if (jwtParts.length == 3) { String decodedPayload = new String(Base64.decode(jwtParts[1], Base64.DEFAULT)); Log.e(TAG, "decodedPayload : " + decodedPayload); } }); task.addOnFailureListener(context, e -> { // An error occurred while communicating with the service. String mResult = null; if (e instanceof ApiException) { // An error with the Google Play Services API contains some additional details. ApiException apiException = (ApiException) e; Util.showLog(TAG, "Error: " + CommonStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": " + apiException.getStatusMessage()); } else { // A different, unknown type of error occurred. Log.e(TAG, "ERROR! " + e.getMessage()); } }); } else { Log.e(TAG, "Prompt user to update Google Play services."; } } ` Check your logs for decodedPayload if ctsProfileMatch and basicIntegrity both are false it means your device is rooted . The Attestation API returns a JWS response which looks like:

{"nonce": "6pLrr9zWyl6TNzj+kpbR4LZcfPY3U2FmZXR5IE5ldCBTYW1wbGU6IDE2MTQ2NzkwMTIzNjc=", " timestamms ": 9860437986543, "apkPackageName": "你的包名将显示在这里","ctsProfileMatch": true, "apkDigestSha256": ["base64编码,用于签名请求应用程序的证书的SHA-256哈希值"],"basicIntegrity": true, "evaluationType": "BASIC"}

欲了解更多信息,请查看此链接。

另外两个方法,如果你想检查一个设备在你的应用程序中是否具有根功能:

检查是否存在“su”二进制文件:从Runtime.getRuntime().exec()运行"which su" 在/system/app/ SuperUser.apk目录下查找SuperUser.apk