Определите, работает ли на рутированном устройстве

292

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

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

miracle2k
источник
11
Нет надежного способа сделать это; Ответы ниже проверяют общие характеристики, но данное устройство не может быть внедрено обычным способом. Если проверка на root становится распространенной, корневые решения, вероятно, начнут пытаться скрыться. Так как они могут изменять поведение операционной системы, у них есть множество вариантов для этого.
Крис Страттон
Возможно, было бы лучше указать, что функция недоступна из-за отсутствия корневых возможностей, предоставляющих пользователю больше информации, а не скрывающих возможности вашего приложения, добавляющих двусмысленность к общему опыту.
Ник Фокс
Работают ли ответы ниже для Systemless Root ?
Пиюш Кукадия

Ответы:

260

Вот класс, который будет проверять 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();
        }
    }
}
Кевин Паркер
источник
8
Если два вопроса требуют одинаковых ответов, то они дублируют 99% времени, поэтому помечайте их как дубликаты, а не размещайте одинаковые ответы на обоих. Спасибо.
Кев
2
Это может быть так, однако я просто сообщаю вам, что сообщество отметило точные повторяющиеся ответы . Вы должны адаптировать свои ответы и рассмотреть особенности проблемы ОП. Копирование и вставка ответов находятся под угрозой привлечения негативных голосов.
Кев
9
-1, этот метод нежизнеспособен, потому что некоторые телефоны включают suбинарный файл, но без рута.
neevek
12
Просто хочу сообщить, что приложение Fox Digital Copy (Beta) использует ваш код почти дословно, включая классы Root и ExecShell, а также методы checkRootMethod1 / 2/3. Было очень забавно.
Мэтт Джозеф
8
Я могу подать в суд на них, как Фокс подал в суд на бесчисленное множество других?
Кевин Паркер
58

Если вы уже используете 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();
        }
    }
}
кингстон
источник
Лучший ответ. Пожалуйста, используйте это в любой библиотеке, на китайских устройствах работает много ложных срабатываний.
Педро Пауло Аморим
Есть ли ложный положительный результат в этом методе?
Машхади
Я проверил это на Nexus 5 с download.chainfire.eu/363/CF-Root/CF-Auto-Root/… , это не точно.
Джеффри Лю
54

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

RootTools.isRootAvailable()

Ссылка

Intrications
источник
10
isRootAvailable () просто проверяет наличие su в пути и некоторых других жестко закодированных каталогах. Я слышал, что некоторые инструменты рутирования оставят su там, так что это даст ложный положительный результат.
Боб Вайтман
13
RootTools.isAccessGiven () будет не только проверять наличие root, но и запрашивать разрешение root; таким образом, unrooted устройство всегда будет возвращать false с этим методом.
aggregate1166877
2
@ aggregate1166877, вы правы, но этого недостаточно, что делать, если мне не требуется разрешение root, когда я спрашиваю? Я просто хочу знать, рутирован ли он, но мне сейчас не нужно разрешение root.
neevek
4
isAccessGiven () возвращает false, когда пользователь отказывает в разрешении, даже если устройство было корневым.
subair_a
Это единственный ответ, который я считаю ценным. Посмотрите мой ответ ниже, если вы хотите что-то похожее, просто скопируйте вставку, или хотели бы получить более подробную информацию
rsimp
52

В моем приложении я проверял, является ли устройство рутованным или нет, выполнив команду «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 () вызывает ту же проблему.

Devrim
источник
5
Это очень тревожно. У меня был укоренившийся класс обнаружения устройств, который делал то же самое - прочитав это, я подтвердил то, что подробно описано выше. Случайные зомби-процессы, оставленные позади, замедление работы устройств и т. Д.
AWT
1
Я подтверждаю проблему с RootTools 3.4 на андроиде GT-S5830i 2.3.6. Большая часть зомби получила выделенную память, и проблема носит систематический характер. Мне нужно перезагрузить устройство после 3-4 теста. Я рекомендую сохранить результат теста в общий доступ.
Христос
2
Google теперь рекомендует использовать ProcessBuilder () и команду start ().
EntangledLoops
1
@NickS Интересно, а какую команду ты запускал? У меня нет такой проблемы с выдачей команд на многочисленные телефоны Android с различным уровнем API от 9 до 23.
EntangledLoops
1
@EntangledLoops. Спасибо. Я запускаю свой собственный бинарный файл и взаимодействую с ним через stdin / stdout. Я снова проверил, как я остановил это и обнаружил, что я пропустил Process.destroy () в одном из случаев. Так что, без зомби.
Ник С
36

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

  • Проверка на наличие тестовых ключей связана с корневым доступом, но не обязательно гарантирует
  • Каталоги «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 неправильно распоряжается процессами, которые пытаются запустить отсутствующие команды. Эти побочные процессы могут увеличивать потребление памяти с течением времени.

rsimp
источник
Метод isRootAvailable () прекрасно работает, спасибо. Однако я рекомендую не запускать это в основном потоке, чтобы избежать ANR, например, вызова из AsyncTask
Thunderstick
1
Я думаю, что разница между желанием гарантировать, что root недоступен, и желанием убедиться, что он есть. Если вы хотите убедиться, что устройство не является рутированным, предложенные проверки хороши. Вы получите ложные срабатывания, но это нормально, если ваша основная задача - не запускать код на скомпрометированном устройстве.
Джеффри Блатман
1
@ DAC84 Я не уверен, что понимаю ваш вопрос. Если вы запустите isRootGiven и откажетесь от своего рутирующего приложения, тогда оно должно вернуть false. Разве это не то, что происходит? Если вы хотите избежать предупреждения, вы можете просто использовать isRootAvailable, который также может называться doSUExist. Вы также можете попытаться настроить свое корневое приложение так, чтобы оно свободно выдавало root, а не управляло им.
rsimp
1
@BeeingJk нет, не совсем, хотя это действительно самое лучшее, что вы можете проверить без запуска su, который является настоящим тестом. Вы должны проверить su в PATH, прежде чем пытаться выполнить его. Однако фактическое выполнение su часто приводит к появлению всплывающего сообщения или взаимодействию с управляющим приложением root, которое может не соответствовать вашим ожиданиям. Для вашей собственной логики вы можете считать, что простого существования su достаточно. Это все еще может давать ложные срабатывания в некоторых эмуляторах, которые могут содержать исполняемый файл su, но блокировать доступ к нему.
rsimp
1
@BeeingJk isRootAvailable - это, вероятно, все, что вам нужно, но я хочу подчеркнуть, что подобное имя или дажеSuxxist обеспечивает лучшую семантику, чем имя метода, такое как isDeviceRooted, что не совсем правильно. Если вам действительно нужно проверить полный root-доступ, прежде чем продолжить, попробуйте запустить команду с su, наподобие той, что написана в isRootGiven
rsimp
35

Обновление 2017

Вы можете сделать это сейчас с Google Safetynet API . API SafetyNet предоставляет API-интерфейс аттестации, который помогает оценить безопасность и совместимость сред Android, в которых работают ваши приложения.

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

Аттестационный 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 защитной сети будет выглядеть так:

введите описание изображения здесь

Хите саху
источник
3
Отличная информация, и в другом контексте я считаю, что это будет правильный ответ. К сожалению, вопрос OPs заключается не в защите его приложения от небезопасных сред, а в том, чтобы обнаружить root, чтобы включить в своем приложении только функции root. Для предполагаемой цели ОП этот процесс кажется слишком сложным.
rsimp
31

Корневая проверка на уровне 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;
     }
Алок Кулькарни
источник
1
Я думаю, что обнаружение root делится на две категории, включающие зависимые от root функции, а затем меры, основанные на безопасности, чтобы попытаться смягчить проблемы безопасности с рутированными телефонами. Что касается корневых функций, я нахожу ответ Кевина довольно скудным. В контексте этого ответа эти методы имеют больше смысла. Хотя я бы переписал метод 2, чтобы не использовать который, и вместо этого перебрал бы переменную среды PATH, чтобы найти «su». «который» не гарантированно будет на телефоне.
rsimp
Можете ли вы привести пример того, как использовать этот код C в Java?
17
@mrid Пожалуйста, проверьте, как делать вызовы JNI из Java на Android.
Алок
Этот метод предотвращает обнаружение root с помощью приложения RootCloak. Существуют ли какие-либо известные способы обхода root-ов, которые не справляются с этим методом?
Нидин
20

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");
}
noobProgrammer
источник
Спасибо! Я использую это как checkRootMethod4()с Ответом Кевина .
Шехарьяр
1
Никогда не добавляйте == trueв логическое значение, оно ничего не добавляет и выглядит не очень хорошо.
Minipif
2
@smoothBlue С чего бы это? Это не порождает никаких процессов, как решение DevrimTuncer.
FD_
1
Лучшей
1
Используй, if (isRooted())проверь вместо явного написания true. Лучше следовать шаблонам написания кода
blueware
13

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

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

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

Ссылка

saulobrito
источник
но пользователь должен дать права root-доступа? поэтому, если моей целью было остановить запуск моего приложения, если устройство
рутировано,
11

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

Крис Бойл
источник
Это выглядит интересно, но: хотя эмулятор (и ADP) разрешают сам root, они не позволяют приложениям использовать его, то есть: $ su app_29 $ su su: uid 10029 не разрешено su
miracle2k
Ах, я полагаю, что они не ... вы могли бы объединить это с проверкой ro.build.host (not), заканчивающейся на google.com, тогда, если они единственные, у которых есть тестовые ключи, но блокировать su без спрашиваю пользователя. Зависит от того, что является хостом сборки для новых устройств, вещей, которые не являются телефонами ... не просто.
Крис Бойл
11

RootBeer - это библиотека Android, проверяющая root, созданная Скоттом и Мэтью. Он использует различные проверки, чтобы указать, является ли устройство рутованным или нет.

Java проверяет

  • CheckRootManagementApps

  • CheckPotentiallyDangerousAppss

  • CheckRootCloakingApps

  • CheckTestKeys

  • checkForDangerousProps

  • checkForBusyBoxBinary

  • checkForSuBinary

  • checkSuExists

  • checkForRWSystem

Родные чеки

Мы обращаемся к нашему собственному корневому контролеру, чтобы выполнить некоторые его собственные проверки. Собственные проверки, как правило, труднее скрыть, поэтому некоторые приложения для корневых оболочек просто блокируют загрузку собственных библиотек, содержащих определенные ключевые слова.

  • checkForSuBinary
Android-разработчик
источник
8

Я предлагаю использовать нативный код для обнаружения рутов. Вот полный рабочий пример .

введите описание изображения здесь

Обертка 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;
}
Дима Кожевин
источник
4
Офигенный инструмент, Дима. Большое спасибо. Он даже ловит магиск.
эксперт
Это реальная сделка.
Вахид Амири
@klutch есть ссылка на рабочий пример (github) в первой строке моего поста
Дима Кожевин
7

Вот мой код, основанный на некоторых ответах здесь:

 /**
   * 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;
  }
peceps
источник
7

В дополнение к ответу @Kevins, я недавно обнаружил при использовании его системы, что Nexus 7.1 возвращался falseдля всех трех методов - Нет whichкоманды, нет test-keysи SuperSUне был установлен в /system/app.

Я добавил это:

public static boolean checkRootMethod4(Context context) {
    return isPackageInstalled("eu.chainfire.supersu", context);     
}

private static boolean isPackageInstalled(String packagename, Context context) {
    PackageManager pm = context.getPackageManager();
    try {
        pm.getPackageInfo(packagename, PackageManager.GET_ACTIVITIES);
        return true;
    } catch (NameNotFoundException e) {
        return false;
    }
}

Это немного менее полезно в некоторых ситуациях (если вам нужен гарантированный root-доступ), поскольку вполне возможно, что SuperSU будет установлен на устройствах, которые не имеют доступа SU.

Однако, поскольку SuperSU может быть установлен и работать, но не в /system/appкаталоге, этот дополнительный случай вычеркнет (ха-ха) такие случаи.

Graeme
источник
Это не очень хороший ответ, так как у вас есть другие корневые пакеты, которые могут быть установлены на вашем устройстве. Жесткое программирование других пакетов приложений будет резким, поскольку вы не можете ожидать, и перечислите все из них
blueware
5
    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);
        }
Квант
источник
4

Две дополнительные идеи, если вы хотите проверить, поддерживает ли ваше устройство права root в вашем приложении:

  1. Проверьте наличие двоичного файла 'su': запустите "which su" из Runtime.getRuntime().exec()
  2. Ищите SuperUser.apk в /system/app/Superuser.apkместоположении
Guardian Project
источник
3

Использование C ++ с ndk - лучший подход для обнаружения root, даже если пользователь использует приложения, скрывающие его root, такие как RootCloak. Я протестировал этот код с помощью RootCloak и смог обнаружить рут, даже если пользователь пытается его скрыть. Итак, ваш файл cpp хотел бы:

#include <jni.h>
#include <string>


/**
 *
 * function that checks for the su binary files and operates even if 
 * root cloak is installed
 * @return integer 1: device is rooted, 0: device is not 
 *rooted
*/
extern "C"
JNIEXPORT int JNICALL


Java_com_example_user_root_1native_rootFunction(JNIEnv *env,jobject thiz){
const char *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"};

int counter =0;
while (counter<9){
    if(FILE *file = fopen(paths[counter],"r")){
        fclose(file);
        return 1;
    }
    counter++;
}
return 0;
}

И вы будете вызывать функцию из вашего кода Java следующим образом

public class Root_detect {



   /**
    *
    * function that calls a native function to check if the device is 
    *rooted or not
    * @return boolean: true if the device is rooted, false if the 
    *device is not rooted
   */
   public boolean check_rooted(){

        int checker = rootFunction();

        if(checker==1){
           return true;
        }else {
           return false;
        }
   }
   static {
    System.loadLibrary("cpp-root-lib");//name of your cpp file
   }

   public native int rootFunction();
}
Сами Канафани
источник
1
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
ESSPEE
источник
1

Существует Safety Net Аттестационная API из услуг в Google Play , с помощью которого мы можем оценить устройство и определить , если она коренится / подделана.

Пожалуйста, пройдите мой ответ, чтобы разобраться с рутированными устройствами:
https://stackoverflow.com/a/58304556/3908895

Калпеш Вадекар
источник
1

Забудьте все, что обнаруживает корневые приложения и su-файлы. Проверьте процесс корневого демона. Это можно сделать из терминала, и вы можете запускать команды терминала в приложении. Попробуйте эту однострочную.

if [ ! -z "$(/system/bin/ps -A | grep -v grep | grep -c daemonsu)" ]; then echo "device is rooted"; else echo "device is not rooted"; fi

Для этого вам также не нужно разрешение root.

5p0ng3b0b
источник
0

Действительно, это интересный вопрос, и пока никто не заслужил награду. Я использую следующий код:

  boolean isRooted() {
      try {
                ServerSocket ss = new ServerSocket(81);
                ss.close();
                                    return true;
            } catch (Exception e) {
                // not sure
            }
    return false;
  }

Код, конечно, не пуленепробиваемый, потому что сеть может быть недоступна, поэтому вы получите исключение. Если этот метод возвращает true, тогда вы можете быть уверены, что 99%, иначе только 50%, что нет. Сетевое разрешение также может испортить решение.

Singagirl
источник
Я проверил это, и это не возвращает истину с моим рутованным устройством.
Трикнология
Интересно посмотреть, какое исключение вы получите. Вы можете получить исключение, связанное с портом, однако, если вы не можете создать порт сервера в диапазоне меньше 1024, это уменьшает значение root, так как у вас все еще есть определенные ограничения.
Singagirl
-1

Используя мою библиотеку в rootbox , это довольно просто. Проверьте необходимый код ниже:

    //Pass true to <Shell>.start(...) call to run as superuser
    Shell shell = null;
    try {
            shell = Shell.start(true);
    } catch (IOException exception) {
            exception.printStackTrace();
    }
    if (shell == null)
            // We failed to execute su binary
            return;
    if (shell.isRoot()) {
            // Verified running as uid 0 (root), can continue with commands
            ...
    } else
            throw Exception("Unable to gain root access. Make sure you pressed Allow/Grant in superuser prompt.");
ВПЗ
источник