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

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

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

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

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

 RootTools.isRootAvailable() 

https://github.com/Stericson/RootTools

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

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

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

 $ adb shell ps 

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

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

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

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

Библиотека 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 для одного из них.

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

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() == true ){ textView.setText("Device Rooted"); } else{ textView.setText("Device Unrooted"); } 

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

Ответ Кевина работает, если у телефона нет приложения вроде RootCloak. Такие приложения имеют API-интерфейс для обработки 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; } 

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

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

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

https://code.google.com/p/roottools/

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

Если вы уже используете Fabric / Crashlytics, вы вызываете

 CommonUtils.isRooted(this) 

Это текущая реализация этого метода:

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

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

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

В дополнение к ответу @Kevins, который я недавно обнаружил при использовании его системы, Nexus 7.1 возвращал false для всех трех методов: никакой команды, никаких 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 , этот дополнительный случай будет root (haha) из таких случаев.

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

Проверка Java

  • CheckRootManagementApps

  • CheckPotentiallyDangerousAppss

  • CheckRootCloakingApps

  • CheckTestKeys

  • checkForDangerousProps

  • checkForBusyBoxBinary

  • checkForSuBinary

  • checkSuExists

  • checkForRWSystem

Индивидуальные проверки

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

  • checkForSuBinary

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

  1. Проверьте, существует ли Runtime.getRuntime().exec() 'su': run "su" из Runtime.getRuntime().exec()
  2. Найдите SuperUser.apk в /system/app/Superuser.apk
  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); } на  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); } 

Обновление 2017

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

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

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

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

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

Использование C ++ с ndk – лучший подход для обнаружения корня, даже если пользователь использует приложения, которые скрывают его корень, такой как 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(); } 
 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 

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

  //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.");