Определите, работает ли на рутированном устройстве - PullRequest
265 голосов
/ 09 июля 2009

Мое приложение имеет определенную функциональность, которая будет работать только на устройстве, где доступен root. Вместо того, чтобы эта функция не работала при ее использовании (а затем показывать соответствующее сообщение об ошибке пользователю), я бы предпочел возможность молча проверить, доступен ли сначала root, а если нет, сперва скрыть соответствующие параметры .

Есть ли способ сделать это?

Ответы [ 20 ]

245 голосов
/ 11 ноября 2011

Вот класс, который будет проверять 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();
        }
    }
}
54 голосов
/ 03 июля 2011

Библиотека RootTools предлагает простые методы проверки на наличие root:

RootTools.isRootAvailable()

Ссылка

47 голосов
/ 18 марта 2013

В своем приложении я проверял, является ли устройство рутованным или нет, выполнив команду "su". Но сегодня я удалил эту часть своего кода. Зачем?

Потому что мое приложение стало убийцей памяти. Как? Позвольте мне рассказать вам мою историю.

Были некоторые жалобы, что мое приложение замедляло работу устройств (конечно, я думал, что это не может быть правдой). Я пытался выяснить, почему. Поэтому я использовал MAT, чтобы получить кучу дампов и проанализировать, и все казалось идеальным. Но после многократного перезапуска приложения я понял, что устройство действительно работает медленнее, и остановка моего приложения не делала его быстрее (если я не перезагружаю устройство). Я снова проанализировал файлы дампа, пока устройство работает очень медленно. Но все было идеально для файла дампа. Тогда я сделал то, что должно быть сделано сначала. Я перечислил процессы.

$ adb shell ps

Surprize; для моего приложения было много процессов (с меткой процесса моего приложения в manifest). Некоторые из них были зомби, некоторые нет.

С примером приложения, которое имеет единственное действие и выполняет только команду su, я понял, что процесс зомби создается при каждом запуске приложения. Сначала эти зомби выделяют 0 КБ, но потом что-то происходит, и процессы зомби держат почти те же КБ, что и основной процесс моего приложения, и они стали стандартными процессами.

На bugs.sun.com есть отчет об ошибке для той же проблемы: http://bugs.sun.com/view_bug.do?bug_id=6474073 это объясняет, если команда не найдена, зомби будут созданы методом exec (). Но я до сих пор не понимаю, почему и как они могут стать стандартными процессами и иметь значительные КБ. (Это не происходит все время)

Вы можете попробовать, если хотите, с примером кода ниже;

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 () вызывает ту же проблему.

40 голосов
/ 25 февраля 2016

Если вы уже используете 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();
        }
    }
}
32 голосов
/ 28 июля 2017

Обновление 2017

Вы можете сделать это сейчас с помощью Google Safetynet API . API SafetyNet предоставляет 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,
}

Анализ этого ответа может помочь вам определить, является ли устройство рутованным или нет.

Кажется, что корневые устройства вызывают ctsProfileMatch = false.

Вы можете сделать это на стороне клиента, но рекомендуется проанализировать ответ на стороне сервера. Базовая архитектура клиент-сервер с API защитной сети будет выглядеть так: -

enter image description here

32 голосов
/ 10 сентября 2016

Многие из перечисленных здесь ответов имеют присущие проблемы:

  • Проверка на наличие тестовых ключей связана с корневым доступом, но не обязательно гарантирует это
  • Каталоги "PATH" должны быть получены из фактической переменной среды "PATH", а не жестко закодированы
  • Наличие исполняемого файла su не обязательно означает, что устройство было рутировано
  • Исполняемый файл «which» может быть установлен или не установлен, и вы должны позволить системе разрешить свой путь, если это возможно
  • Тот факт, что приложение SuperUser установлено на устройстве, еще не означает, что устройство имеет root-доступ

Библиотека RootTools от Stericson, похоже, более законно проверяет наличие root. Он также имеет много дополнительных инструментов и утилит, поэтому я очень рекомендую его. Тем не менее, нет объяснения того, как именно он проверяет наличие 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», так что вы можете проверить, что идентификатор пользователя на самом деле равен 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.

Также важно проверить наличие исполняемого файла "su", прежде чем пытаться его запустить, поскольку известно, что android неправильно распоряжается процессами, которые пытаются запустить отсутствующие команды. Эти побочные процессы могут со временем потреблять память.

31 голосов
/ 15 мая 2016

Root-проверка на уровне Java не является безопасным решением. Если ваше приложение имеет проблемы безопасности для запуска на устройстве с рутовым доступом, используйте это решение.

Ответ Кевина работает, если только в телефоне нет приложения, такого как RootCloak. Такие приложения имеют API-интерфейсы Handle over Java после рутирования телефона, и они высмеивают эти 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;
     }
18 голосов
/ 30 мая 2014

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");
}
13 голосов
/ 13 марта 2013

Вместо использования isRootAvailable () вы можете использовать isAccessGiven (). Напрямую от RootTools wiki :

if (RootTools.isAccessGiven()) {
    // your app has been granted root access
}

RootTools.isAccessGiven () не только проверяет, что устройство рутировано, оно также вызывает su для вашего приложения, запрашивает разрешение и возвращает true, если вашему приложению были успешно предоставлены права root. Это можно использовать как первая проверка в вашем приложении, чтобы убедиться, что вы получите доступ, когда вам это нужно.

Ссылка

11 голосов
/ 09 июля 2009

Некоторые модифицированные сборки используются для установки системного свойства 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]

С другой стороны, эмулятор из SDK 1.5, на котором запущен образ 1.5, также имеет 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, и я думаю, что это, вероятно, самое надежное свойство для использования.

...