Определите, работает ли на рутированном устройстве
Мое приложение имеет определенную функциональность, которая будет работать только на устройстве, где доступен root. Вместо того, чтобы эта функция не работала при ее использовании (а затем показывать соответствующее сообщение об ошибке пользователю), я бы предпочел возможность молча проверять, доступен ли сначала root, и, если нет, сначала скрывать соответствующие параметры.,
Есть ли способ сделать это?
29 ответов
Вот класс, который будет проверять 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();
}
}
}
Если вы уже используете Fabric/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();
}
}
}
В моем приложении я проверял, является ли устройство рутованным или нет, выполнив команду "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() вызывает ту же проблему.
Библиотека RootTools предлагает простые методы для проверки на наличие root:
RootTools.isRootAvailable()
Обновление 2017
Вы можете сделать это сейчас с помощью Google Safetynet API. API SafetyNet предоставляет 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 безопасности будет выглядеть так:
Многие из перечисленных здесь ответов имеют присущие проблемы:
- Проверка на наличие тестовых ключей связана с корневым доступом, но не обязательно гарантирует
- Каталоги "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 неправильно распределяет процессы, которые пытаются запустить пропущенные команды. Эти побочные процессы могут увеличивать потребление памяти с течением времени.
Корневая проверка на уровне 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;
}
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");
}
RootBeer - это библиотека Android для проверки root, созданная Скоттом и Мэтью. Он использует различные проверки, чтобы указать, является ли устройство рутованным или нет.
Java проверяет
CheckRootManagementApps
CheckPotentiallyDangerousAppss
CheckRootCloakingApps
CheckTestKeys
checkForDangerousProps
checkForBusyBoxBinary
checkForSuBinary
checkSuExists
checkForRWSystem
Родные чеки
Мы обращаемся к нашему собственному корневому контролеру, чтобы выполнить некоторые его собственные проверки. Собственные проверки, как правило, труднее скрыть, поэтому некоторые приложения для корневых оболочек просто блокируют загрузку собственных библиотек, содержащих определенные ключевые слова.
- checkForSuBinary
Вместо использования isRootAvailable() вы можете использовать isAccessGiven(). Напрямую из вики RootTools:
if (RootTools.isAccessGiven()) {
// your app has been granted root access
}
RootTools.isAccessGiven () не только проверяет, является ли устройство рутованным, но также вызывает su для вашего приложения, запрашивает разрешение и возвращает true, если вашему приложению были успешно предоставлены права root. Это можно использовать в качестве первой проверки в вашем приложении, чтобы убедиться, что вам будет предоставлен доступ, когда вам это нужно.
Некоторые модифицированные сборки, используемые для установки системного свойства 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
и я думаю, что это, пожалуй, самое надежное свойство для использования.
Я предлагаю использовать нативный код для обнаружения root. Вот пример.
Вот мой код, основанный на некоторых ответах здесь:
/**
* 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
для всех трех методов - нет 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
каталог, этот дополнительный случай будет рутировать (хаха) такие случаи.
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);
}
Две дополнительные идеи, если вы хотите проверить, поддерживает ли ваше устройство права root в вашем приложении:
- Проверьте наличие двоичного файла 'su': запустите "which su" из
Runtime.getRuntime().exec()
- Ищите SuperUser.apk в
/system/app/Superuser.apk
место нахождения
Использование 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();
}
Сегодня в последнем квартале 2021 года я попытался использовать SafetyNet в отношении ответа @HimanshiThakur. Но у меня вопрос , и открыл вопрос здесь . По-прежнему нет ответа.
Поэтому я решил использовать RootBeer. Он работает отлично, но когда Magisk скрывает корень, он не работает.
Если вас не волнует этот случай (и многие банковские приложения тоже не могут решить эту проблему), вы можете использовать следующие шаги:
- Добавьте это в Gradle:
implementation 'com.scottyab:rootbeer-lib:0.1.0'
- Используйте эти строки:
RootBeer rootBeer = new RootBeer(context);
if (rootBeer.isRooted()) {
//we found indication of root
} else {
//we didn't find indication of root
}
Основываясь на некоторых ответах здесь, я думаю, что это хорошее решение:
@JvmStatic
fun isProbablyRooted(): Boolean {
return try {
findBinary("su")
} catch (e: Exception) {
e.printStackTrace()
false
}
}
private fun findBinary(binaryName: String): Boolean {
val paths = System.getenv("PATH")
if (!paths.isNullOrBlank()) {
val systemPlaces: List<String> = paths.split(":")
return systemPlaces.firstOrNull { File(it, binaryName).exists() } != null
}
val places = arrayOf("/sbin/", "/system/bin/", "/system/xbin/", "/data/local/xbin/", "/data/local/bin/",
"/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/")
return places.firstOrNull { File(it, binaryName).exists() } != null
}
Вы также можете добавить проверку, установлены ли некоторые популярные приложения, связанные с root (например, Magisk Manager, у которого имя пакета «com.topjohnwu.magisk»), но, как и все решения здесь, это всего лишь предположение.
Забудьте об обнаружении корневых приложений и 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.
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
Используя API аттестации Google SafetyNet, вы можете легко проверить, рутировано ли ваше устройство или нет.
Добавить зависимость в build.gradle(:app)
реализация 'com.google.android.gms:play-services-safetynet:17.0.0'
Получите ключ Api и включите API проверки устройств Android по ссылке
public static void sendSafetyNetRequest(контекст действия) {
if(GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context, 13000000) == ConnectionResult.SUCCESS) { Log.e(TAG, "The SafetyNet Attestation API is available"); // TODO(developer): Change the nonce generation to include your own, used once value, // ideally from your remote server. String nonceData = "Safety Net Sample: " + System.currentTimeMillis(); ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); Random mRandom = new SecureRandom(); byte[] bytes = new byte[24]; mRandom.nextBytes(bytes); try { byteStream.write(bytes); byteStream.write(nonceData.getBytes()); } catch (IOException e) { e.printStackTrace(); } byte[] nonce = byteStream.toByteArray(); SafetyNetClient client = SafetyNet.getClient(context); Task<SafetyNetApi.AttestationResponse> task = client.attest(nonce, API_KEY_FROM_STEP_2_LINK); task.addOnSuccessListener(context, attestationResponse -> { /* TODO(developer): Forward this result to your server together with the nonce for verification. You can also parse the JwsResult locally to confirm that the API returned a response by checking for an 'error' field first and before retrying the request with an exponential backoff. NOTE: Do NOT rely on a local, client-side only check for security, you must verify the response on a remote server! */ String jwsResult = attestationResponse.getJwsResult(); Log.e(TAG, "Success! SafetyNet result:\n" + jwsResult + "\n"); if (jwsResult == null) { Log.e(TAG, "jwsResult Null"); } final String[] jwtParts = jwsResult.split("\\."); if (jwtParts.length == 3) { String decodedPayload = new String(Base64.decode(jwtParts[1], Base64.DEFAULT)); Log.e(TAG, "decodedPayload : " + decodedPayload); } }); task.addOnFailureListener(context, e -> { // An error occurred while communicating with the service. String mResult = null; if (e instanceof ApiException) { // An error with the Google Play Services API contains some additional details. ApiException apiException = (ApiException) e; Util.showLog(TAG, "Error: " + CommonStatusCodes.getStatusCodeString(apiException.getStatusCode()) + ": " + apiException.getStatusMessage()); } else { // A different, unknown type of error occurred. Log.e(TAG, "ERROR! " + e.getMessage()); } }); } else { Log.e(TAG, "Prompt user to update Google Play services."; }
} `
Проверьте свои журналы на наличие decodedPayload, если ctsProfileMatch и basicIntegrity неверны, это означает, что ваше устройство внедрено. API аттестации возвращает ответ JWS, который выглядит следующим образом:
{ "nonce": "6pLrr9zWyl6TNzj+kpbR4LZcfPY3U2FmZXR5IE5ldCBTYW1wbGU6IDE2MTQ2NzkwMTIzNjc=", "timestampMs": 9860437986543, "apkPackageName": " your package name will be displayed here", "ctsProfileMatch": true, "apkDigestSha256": [ "base64 encoded, SHA-256 hash of the certificate used to sign requesting app" ], "basicIntegrity": true, "evaluationType": "BASIC" }
Для получения дополнительной информации
Вы можете сделать это с помощью следующего кода:
public boolean getRootInfo() {
if (checkRootFiles() || checkTags()) {
return true;
}
return false;
}
private boolean checkRootFiles() {
boolean root = false;
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) {
root = new File(path).exists();
if (root)
break;
}
return root;
}
private boolean checkTags() {
String tag = Build.TAGS;
return tag != null && tag.trim().contains("test-keys");
}
Вы также можете проверить эту библиотеку RootBeer.
Если вы не хотите использовать какую-либо стороннюю библиотеку или какое-либо случайное решение, просто используйте google lib для его обнаружения.
Проверка устройства Android
ответ:
{
"timestampMs": 9860437986543,
"nonce": "R2Rra24fVm5xa2Mg",
"apkPackageName": "com.package.name.of.requesting.app",
"apkCertificateDigestSha256": ["base64 encoded, SHA-256 hash of the
certificate used to sign requesting app"],
"ctsProfileMatch": true,
"basicIntegrity": true,
}
ctsProfileMatch дает false, если устройство рутировано.
ссылка: [1]: https://developer.android.com/training/safetynet/attestation
По состоянию на 2021 год (сегодня), похоже, нет надежного способа или метода для обнаружения root, особенно когда включен мощный инструмент скрытия, такой как MagiskHide. Большинство ответов здесь больше не актуальны, поэтому не используйте их в продакшене. Положитесь на проверенную проверку, такую как SafetyNet, и вместо того, чтобы тратить лишние мили на обнаружение root, я предлагаю защитить ваше приложение во время выполнения, например, предотвратить отладчик / инструментарий и обязательно использовать обфускацию.
Существует Safety Net Аттестационная API из услуг в Google Play, с помощью которого мы можем оценить устройство и определить, если она коренится / подделана.
Пожалуйста, прочтите мой ответ, чтобы разобраться с корневыми устройствами:
/questions/15769359/kak-najti-rutovannoe-ustrojstvo-programmno/55211600#55211600
Действительно, это интересный вопрос, и пока никто не заслужил награду. Я использую следующий код:
boolean isRooted() {
try {
ServerSocket ss = new ServerSocket(81);
ss.close();
return true;
} catch (Exception e) {
// not sure
}
return false;
}
Код, конечно, не пуленепробиваемый, потому что сеть может быть недоступна, поэтому вы получите исключение. Если этот метод возвращает true, тогда вы можете быть уверены, что 99%, иначе только 50%, что нет. Сетевое разрешение также может испортить решение.
fun isDeviceRooted(): Boolean {
val buildTags = android.os.Build.TAGS
if (buildTags != null && buildTags.contains("test-keys")) {
return true
}
// Check for the existence of known superuser APKs
val superUserApkPaths = arrayOf(
"/system/app/Superuser.apk",
"/system/app/SuperSU.apk",
"/system/app/MagiskManager.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"
)
for (path in superUserApkPaths) {
if (File(path).exists()) {
return true
}
}
// Check for the presence of the "su" binary
try {
val process = Runtime.getRuntime().exec(arrayOf("/system/xbin/which", "su"))
val bufferedReader = BufferedReader(InputStreamReader(process.inputStream))
val line = bufferedReader.readLine()
bufferedReader.close()
if (line != null) {
return true
}
} catch (e: Exception) {
// Exception occurred, indicating "su" is not available
}
return false
}
Используя мою библиотеку в 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.");