Визначте, чи працює на вкоріненому пристрої


292

У моєму додатку є певна функціональність, яка працюватиме лише на пристрої, де доступний root. Замість того, щоб ця функція не працювала під час її використання (а потім показувала користувачеві відповідне повідомлення про помилку), я вважаю за краще можливість спочатку мовчки перевірити, чи доступний root, а якщо ні, то сховайте відповідні параметри в першу чергу .

Чи є спосіб це зробити?


11
Не існує надійного способу зробити це; наведені нижче відповіді перевіряють загальні характеристики, але даний пристрій може не вкорінюватися загальноприйнятим чином. Якщо перевірка на наявність кореня стає поширеною, кореневі рішення, ймовірно, почнуть намагатися приховати себе. Оскільки вони можуть змінювати поведінку операційної системи, у них є маса варіантів.
Кріс Страттон

Можливо, краще вказати, що функція недоступна через відсутність можливостей 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 пропонує прості методи перевірки кореня:

RootTools.isRootAvailable()

Довідково


10
isRootAvailable () просто перевіряє наявність su в шляху та деяких інших жорстко кодованих каталогах. Я чув, що деякі інструменти для розкорінення залишать там су, тому це дасть помилковий позитив.
Боб Вітмен

13
RootTools.isAccessGiven () не тільки перевірить на root, але й запитуватиме дозвіл root; тому не вкорінене пристрій завжди повертає помилковий за допомогою цього методу.
агрегат1166877

2
@ agregate1166877, ви праві, але це недостатньо добре, що робити, якщо мені не потрібен дозвіл root, коли я запитую? Я просто хочу знати, чи він вкорінений, але на даний момент мені не потрібен дозвіл кореня.
neevek

4
isAccessGiven () повертає помилку, коли користувач відмовляє у дозволі, навіть якщо пристрій був укоріненим.
subair_a

Це єдина відповідь, яку я вважаю гідною для голосування. Ознайомтеся з моєю відповіддю нижче, якщо ви хочете щось схоже на те, щоб просто скопіювати пасту, або хочете отримати більше деталей
rsimp

52

У своїй програмі я перевіряв, чи кореняється пристрій чи ні, виконуючи команду "su". Але сьогодні я видалив цю частину свого коду. Чому?

Тому що моя заявка стала вбивцею пам’яті. Як? Дозвольте розповісти вам свою історію.

Були деякі скарги на те, що моя програма сповільнювала роботи пристроїв (Звичайно, я вважав, що це не може бути правдою). Я спробував розібратися, чому. Тому я використовував MAT, щоб отримати кучу відвалів та аналізувати, і все здавалося ідеальним. Але багато разів перезапустивши додаток, я зрозумів, що пристрій дійсно повільніше, і зупинка моєї програми не зробила її швидшою (якщо я не перезавантажую пристрій). Я знову проаналізував дамп-файли, поки пристрій дуже повільний. Але все ще було ідеально для дамп-файлу. Тоді я зробив те, що треба зробити спочатку. Я перерахував процеси.

$ adb shell ps

Сюрприз; для моєї програми було багато процесів (з тегом процесу мого додатка на маніфесті). Деякі з них були зомбі, деякі - ні.

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


5
Це дуже хвилює. У мене був укорінений клас виявлення пристроїв, який робив те саме - прочитавши це, я підтвердив, що детально описано вище. Випадкові зомбі-процеси залишаються позаду, сповільнюється пристрій тощо ...
AWT

1
Я підтверджую проблему з RootTools 3.4 на GT-S5830i android 2.3.6. Більшість зомбі отримали виділену пам’ять і проблема є систематичною. Мені потрібно перезапустити пристрій після 3-4 тесту. Рекомендую зберегти результат тесту до спільних уподобань.
Христос

2
Зараз Google рекомендує використовувати ProcessBuilder () та команду start ().
EntangledLoops

1
@NickS Цікаво, але яку команду ви запустили? У мене немає такого ж питання, як випускати команди на численні телефони Android різного рівня API з 9 по 23
EntangledLoops

1
@EntangledLoops. Дякую. Я запускаю власний бінарний і взаємодію з ним через stdin / stdout. Я ще раз перевірив, як я його зупиняю, і дізнався, що я пропустив Process.destroy () в одному з випадків. Отже, жодних зомбі.
Nick S

36

Багато перелічених тут відповідей мають притаманні питання:

  • Перевірка тестових ключів співвідноситься з кореневим доступом, але не обов'язково гарантує це
  • "PATH" каталоги повинні бути отримані з фактичної змінної середовища "PATH", а не з жорстким кодуванням
  • Наявність виконуваного файлу "su" не обов'язково означає, що пристрій вкорінюється
  • Виконавчий файл "який" може бути встановлений або не може бути встановлений, і ви повинні дозволити системі вирішити свій шлях, якщо це можливо
  • Тільки тому, що на пристрої встановлено додаток SuperUser, це не означає, що пристрій ще не має кореневого доступу

Бібліотека RootTools від Stericson, здається, перевіряє кореневі права більш законно. Він також має багато додаткових інструментів та утиліт, тому я дуже рекомендую його. Однак немає пояснень того, як він спеціально перевіряє кореневу систему, і це може бути трохи важче, ніж дійсно потрібно більшості програм.

Я створив пару корисних методів, які грунтуються на бібліотеці 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" в одному з них.

Для того, щоб по-справжньому перевірити кореневий доступ, команду "su" потрібно запустити. Якщо встановлено такий додаток, як SuperUser, то в цей момент він може попросити кореневий доступ або якщо його вже надано / заборонено, тост може вказувати, чи був доступ наданий / заборонений. Гарною командою для запуску є "id", щоб ви могли перевірити, що ідентифікатор користувача насправді 0 (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", перш ніж намагатися запустити його, тому що андроїд, як відомо, не належним чином розпоряджається процесами, які намагаються запустити відсутні команди. Ці привидні процеси можуть з часом збільшити споживання пам'яті.


Метод isRootAvailable () відмінно працює, дякую. Я рекомендую не запускати це на основній темі, але щоб уникнути ANR, наприклад, дзвінок з AsyncTask
Thunderstick

1
Я думаю, що це різниця між бажанням переконатися, що root не доступний, і бажаючим переконатися, що він є. Якщо ви хочете переконатися, що пристрій не вкорінюється, пропоновані перевірки хороші. Ви отримаєте помилкові позитиви, але це нормально, якщо не запускати код на компрометованому пристрої - ваша головна проблема.
Джефрі Блатман

1
@ DAC84 Я не впевнений, що розумію ваше запитання. Якщо ви запускаєте isRootGiven і відмовляєте в застосуванні для вкорінення, то він повинен повернути помилкове. Хіба це не те, що відбувається? Якщо ви хочете уникнути сповіщення, ви можете просто використовувати isRootAvailable, який також може бути названий doSUExist. Ви також можете спробувати налаштувати свою кореневу програму, щоб вільно видавати корінь і не керувати ним.
rsimp

1
@BeeingJk не дуже, хоча це справді найбільше, що можна перевірити, не запускаючи su, що є справжнім тестом. Вам потрібно перевірити наявність su в PATH, перш ніж намагатися його виконати. Однак фактично виконання su часто призводить до отримання тостів або взаємодії з кореневим додатком, який може бути не тим, що ви хочете. Для вашої власної логіки ви можете вважати достатньо простим існування. Це все ще може дати помилкові позитиви в деяких емуляторах, які можуть містити доступний виконуваний, але доступ до блокування.
rsimp

1
@BeeingJk isRootAvailable - це, мабуть, все, що вам потрібно, але те, що я намагаюся зробити, - це те, що таке ім’я або навіть doesSUExist забезпечує кращу семантику, ніж ім'я методу, як isDeviceRooted, що не зовсім правильно. Якщо вам дійсно потрібно перевірити повний кореневий доступ, перш ніж продовжувати, вам потрібно спробувати запустити команду з su, як кодована в isRootGiven
rsimp

35

Оновлення 2017 року

Ви можете це зробити зараз за допомогою API безпечної мережі Google . API SafeNet пропонує API Attestation, який допомагає оцінити безпеку та сумісність середовищ 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
Відмінна інформація, і в іншому контексті я вважаю, що це була б правильна відповідь. На жаль, питання щодо ОП полягає не в тому, щоб захистити його додаток від небезпечних середовищ, а виявити root, щоб увімкнути функції, що мають лише корінь. Для запланованих цілей ОП цей процес видається надто складним.
rsimp

31

Перевірка коренів на рівні Java не є безпечним рішенням. Якщо у вашій програмі є проблеми з безпекою для роботи на пристрої з укоріненими коштами, скористайтеся цим рішенням.

Відповідь Кевіна працює, якщо телефон також не має додатка, наприклад 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;
     }

1
Я думаю, що виявлення коренів поділяється на дві категорії, що дозволяє використовувати функції, що залежать від кореня, а потім заходи, що базуються на безпеці, щоб спробувати пом'якшити проблеми безпеки з укоріненими телефонами. Що стосується кореневих функцій, я вважаю, що відповідь Кевіна досить погана. У контексті цієї відповіді ці методи мають більше сенсу. Хоча я б переписав метод 2, щоб не використовувати який, а замість цього перебирати змінну середовища PATH для пошуку "su". "який" не гарантовано знаходиться по телефону.
rsimp

Ви можете надати приклад, як використовувати цей код c в Java?
1717

@mrid Перевірте, як здійснювати дзвінки JNI з Java на Android.
Алок Кулкарні

Цей спосіб запобігає викоріненню виявлення коренів за допомогою програми RootCloak. Чи є якісь відомі методи кореневого потоку, які не вдаються до цього методу?
Нідхін

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;
    }

Програма спробує знайти су папку:

private static boolean isRooted() {
        return findBinary("su");
    }

Приклад:

if (isRooted()) {
   textView.setText("Device Rooted");

} else {
   textView.setText("Device Unrooted");
}

Дякую! Я використовую це як checkRootMethod4()у відповіді Кевіна .
Шехаряр

1
Ніколи не додайте == trueбулева, вона нічого не додає і не виглядає добре.
minipif

2
@smoothBlue Навіщо це? Це не породжує жодних процесів, як рішення DevrimTuncer.
FD_

1
Кращою ідеєю було б перебрати PATH замість жорсткого кодування типових каталогів PATH
rsimp

1
Використовуйте, if (isRooted())перевірте замість того, щоб явно писати правду. Краще дотримуватися шаблонів написання коду
блювер

13

Замість використання isRootAvailable () ви можете використовувати isAccessGiven (). Прямо з вікі RootTools :

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

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

Довідково


але користувач повинен надати права доступу root? тож якщо моя мета полягала в тому, щоб призупинити роботу мого додатка, якщо пристрій вкоренився, то мої варіанти дійсно обмежені
Nasz Njoka Sr.

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]

Емулятор з 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-keys, і я думаю, це, мабуть, найнадійніша властивість для використання.


Це виглядає цікаво, але: Хоча емулятор (і ADP) дозволяють використовувати root сам по собі, вони не дозволяють програмам його використовувати, тобто: $ su app_29 $ su su: uid 10029 не дозволено su
miracle2k

Ах, гадаю, вони цього не зробили ... ви можете поєднати це з чеком на ro.build.host (не), що закінчується на google.com, якщо вони єдині, у кого є тестові ключі, але блокують без запитуючи користувача. Залежить, що хост побудови для нових пристроїв, те, що не телефони ... непросте.
Кріс Бойл

11

RootBeer - це коренева бібліотека Android, котра перевіряє Скотта та Метью. Він використовує різні перевірки, щоб вказати, вкорінено пристрій чи ні.

Java перевіряє

  • CheckRootManagementApps

  • CheckPotenicallyDangerousAppss

  • CheckRootCloakingApps

  • CheckTestKeys

  • checkForDangerousProps

  • checkForBusyBoxBinary

  • checkForSuBinary

  • checkSuExists

  • checkForRWSystem

Рідні чеки

Ми зателефонуємо до нашої кореневої перевірки для запуску деяких власних перевірок. Рідні чеки, як правило, важче закрити, тому деякі програми кореневих пластів просто блокують завантаження рідних бібліотек, які містять певні ключові слова.

  • checkForSuBinary

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;
  }

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;
    }
}

Це дещо менш корисно в деяких ситуаціях (якщо вам потрібен гарантований доступ до кореня), оскільки це можливо повністю для установки SuperSU на пристрої, які не мають доступу до SU.

Однак, оскільки можливо встановити і працювати SuperSU, але не в /system/appкаталозі, цей додатковий випадок викорінить (ха-ха) такі випадки.


Це не є гарною відповіддю, оскільки у вас є інші кореневі пакети, які можна встановити на вашому пристрої. Напружене кодування інших пакетів програм було б суворим, оскільки ви не можете розраховувати та перелічити їх усі
блювер

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

Дві додаткові ідеї, якщо ви хочете перевірити, чи пристрій працює з вашим додатком:

  1. Перевірте, чи існує бінарний файл 'su': запустіть з «su su» Runtime.getRuntime().exec()
  2. Шукайте /system/app/Superuser.apkмісце розташування SuperUser.apk

3

Використання C ++ за допомогою ndk є найкращим підходом до виявлення 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

1

Існує API безпечної перевірки служб Google Play, за допомогою якого ми можемо оцінити пристрій та визначити, чи він укорінений / підроблений.

Будь ласка, перегляньте мою відповідь, щоб розібратися з укоріненими пристроями:
https://stackoverflow.com/a/58304556/3908895


1

Забудьте про все, що виявляє додатки root та su binaries. Перевірте процес кореневого демона. Це можна зробити з терміналу, і ви можете запускати команди терміналу в додатку. Спробуйте цей однолінійний.

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

Для цього вам також не потрібен кореневий дозвіл.


0

Дійсно цікаве питання, і поки що ніхто не заслужив нагороди. Я використовую такий код:

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

Код, звичайно, не захищений від куль, оскільки мережа може бути недоступною, тому ви отримуєте виняток. Якщо цей метод повертає значення true, то 99% ви можете бути впевнені, інакше лише 50% це не так. Дозвіл на мережу також може зіпсувати рішення.


Я перевірив це, і це не повертає правду з моїм укоріненим пристроєм.
фокуси

Цікаво подивитися, який виняток ви отримуєте. Ви можете отримати порт вже пов'язаний виняток, однак якщо ви не можете створити порт сервера в діапазоні менше 1024, це зменшує значення вкорінення, оскільки все-таки у вас є певні обмеження.
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.");
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.