Déterminer s’il est exécuté sur un périphérique enraciné

Mon application possède une certaine fonctionnalité qui ne fonctionnera que sur un périphérique sur lequel root est disponible. Plutôt que de faire échouer cette fonctionnalité lorsqu’elle est utilisée (puis d’afficher un message d’erreur approprié à l’utilisateur), je préférerais pouvoir vérifier si la racine est disponible en premier et, dans le cas contraire, masquer les options respectives en premier lieu. .

Y a-t-il un moyen de faire cela?

Voici une classe qui vérifiera la racine de trois façons.

/** @author Kevin Kowalewski */ public class RootUtil { public static boolean isDeviceRooted() { return checkRootMethod1() || checkRootMethod2() || checkRootMethod3(); } private static boolean checkRootMethod1() { Ssortingng buildTags = android.os.Build.TAGS; return buildTags != null && buildTags.contains("test-keys"); } private static boolean checkRootMethod2() { Ssortingng[] 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 (Ssortingng path : paths) { if (new File(path).exists()) return true; } return false; } private static boolean checkRootMethod3() { Process process = null; try { process = Runtime.getRuntime().exec(new Ssortingng[] { "/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(); } } } 

La bibliothèque RootTools propose des méthodes simples pour vérifier la racine:

 RootTools.isRootAvailable() 

Référence

Dans mon application, je vérifiais si le périphérique était enraciné ou non en exécutant la commande “su”. Mais aujourd’hui, j’ai supprimé cette partie de mon code. Pourquoi?

Parce que mon application est devenue un tueur de mémoire. Comment? Laissez-moi vous raconter mon histoire.

Il y a eu des plaintes selon lesquelles ma demande ralentissait les appareils (bien sûr, je pensais que cela ne pouvait pas être vrai). J’ai essayé de comprendre pourquoi. J’ai donc utilisé MAT pour obtenir des décharges de tas et les parsingr, et tout semblait parfait. Mais après avoir relancé mon application plusieurs fois, je me suis rendu compte que le périphérique ralentissait vraiment et que l’arrêt de mon application ne le rendait pas plus rapide (à moins que je ne redémarre le périphérique). J’ai analysé de nouveau les fichiers de vidage alors que le périphérique est très lent. Mais tout était encore parfait pour dump file. Ensuite, j’ai fait ce qui devait être fait au début. J’ai listé les processus.

 $ adb shell ps 

Surprendre; il y avait beaucoup de processus pour mon application (avec la balise de processus de mon application au manifeste). Certains d’entre eux étaient des zombies, d’autres non.

Avec un exemple d’application qui a une seule activité et qui exécute uniquement la commande “su”, j’ai réalisé qu’un processus de zombies est créé à chaque lancement de l’application. Au début, ces zombies allouent 0 Ko mais, quelque chose arrive et les processus zombies contiennent presque les mêmes Ko que le processus principal de mon application et ils sont devenus des processus standard.

Il y a un rapport de bogue pour le même problème sur bugs.sun.com: http://bugs.sun.com/view_bug.do?bug_id=6474073 Cela explique si la commande est introuvable Les zombies vont être créés avec la méthode exec () . Mais je ne comprends toujours pas pourquoi et comment peuvent-ils devenir des processus standard et détenir des KB importants. (Cela ne se produit pas tout le temps)

Vous pouvez essayer si vous voulez avec un exemple de code ci-dessous;

 Ssortingng commandToExecute = "su"; executeShellCommand(commandToExecute); 

Méthode d’exécution de commande simple;

 private boolean executeShellCommand(Ssortingng 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) { } } } } 

Pour résumer; Je n’ai aucun conseil pour vous pour déterminer si l’appareil est enraciné ou non. Mais si j’étais toi, je n’utiliserais pas Runtime.getRuntime (). Exec ().

Au fait; RootTools.isRootAvailable () provoque le même problème.

Bon nombre des réponses énumérées ici ont des problèmes inhérents:

  • La vérification des clés de test est corrélée à l’access root mais ne le garantit pas nécessairement
  • Les répertoires “PATH” doivent être dérivés de la variable d’environnement “PATH” au lieu d’être codés en dur
  • L’existence de l’exécutable “su” ne signifie pas nécessairement que l’appareil est enraciné
  • L’exécutable “qui” peut ou peut ne pas être installé, et vous devriez laisser le système résoudre son chemin si possible
  • Ce n’est pas parce que l’application SuperUser est installée sur l’appareil que le périphérique dispose d’un access root

La bibliothèque RootTools de Stericson semble rechercher plus légitimement root. Il a également beaucoup d’outils et d’utilitaires supplémentaires, donc je le recommande fortement. Cependant, il n’y a aucune explication sur la façon dont il vérifie spécifiquement la racine, et il peut être un peu plus lourd que la plupart des applications en ont réellement besoin.

J’ai fait quelques méthodes utilitaires qui sont basées sur la bibliothèque RootTools. Si vous voulez simplement vérifier si l’exécutable “su” se trouve sur l’appareil, vous pouvez utiliser la méthode suivante:

 public static boolean isRootAvailable(){ for(Ssortingng pathDir : System.getenv("PATH").split(":")){ if(new File(pathDir, "su").exists()) { return true; } } return false; } 

Cette méthode consiste simplement à parcourir les répertoires répertoriés dans la variable d’environnement “PATH” et à vérifier si un fichier “su” existe dans l’un d’eux.

Afin de vérifier réellement l’access root, la commande “su” doit être exécutée. Si une application comme SuperUser est installée, alors, à ce stade, elle peut demander un access root ou si un toast peut déjà être accordé / refusé, indiquant si l’access a été accordé / refusé. Une bonne commande à exécuter est “id” afin que vous puissiez vérifier que l’ID utilisateur est bien 0 (root).

Voici un exemple de méthode pour déterminer si l’access root a été accordé:

 public static boolean isRootGiven(){ if (isRootAvailable()) { Process process = null; try { process = Runtime.getRuntime().exec(new Ssortingng[]{"su", "-c", "id"}); BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())); Ssortingng 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; } 

Il est important de tester l’exécution de la commande “su” car certains exécuteurs ont le programme “su” pré-installé, mais permettent seulement à certains utilisateurs d’y accéder comme le shell adb.

Il est également important de vérifier l’existence de l’exécutable “su” avant d’essayer de l’exécuter, car Android est connu pour ne pas éliminer correctement les processus qui tentent d’exécuter les commandes manquantes. Ces processus fantômes peuvent augmenter la consommation de mémoire au fil du temps.

La vérification de racine au niveau de Java n’est pas une solution sûre. Si votre application a des problèmes de sécurité à exécuter sur un périphérique racine, veuillez utiliser cette solution.

La réponse de Kevin fonctionne à moins que le téléphone ne dispose également d’une application comme RootCloak. Une fois que le téléphone est enraciné, ces applications ont une poignée sur les API Java et elles se moquent de ces API pour que le téléphone ne soit pas connecté.

J’ai écrit un code de niveau natif basé sur la réponse de Kevin, cela fonctionne même avec RootCloak! En outre, il ne provoque aucun problème de fuite de mémoire.

 #include  #include  #include  #include  #include  #include "android_log.h" #include  #include  #include  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 . 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"which su"; FILE* pipe = popen("which su", "r"); if (!pipe) return -1; char buffer[128]; std::ssortingng 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 . 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; } 

Dans votre code Java, vous devez créer la classe wrapper RootUtils pour effectuer les appels natifs

  public boolean checkRooted() { if( rootUtils.checkRootAccessMethod3() == 0 || rootUtils.checkRootAccessMethod1() == 0 || rootUtils.checkRootAccessMethod2() == 0 ) return true; return false; } 

Si vous utilisez déjà Fabric / Crashlytics, vous pouvez appeler

 CommonUtils.isRooted(context) 

C’est l’implémentation actuelle de cette méthode:

 public static boolean isRooted(Context context) { boolean isEmulator = isEmulator(context); Ssortingng 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(); } } } 

Mise à jour 2017

Vous pouvez le faire maintenant avec l’ API Google Safetynet . L’API SafetyNet fournit une API d’attestation qui vous aide à évaluer la sécurité et la compatibilité des environnements Android dans lesquels vos applications s’exécutent.

Cette attestation peut aider à déterminer si le périphérique particulier a été altéré ou modifié.

L’API d’attestation renvoie une réponse JWS comme celle-ci

 { "nonce": "R2Rra24fVm5xa2Mg", "timestampMs": 9860437986543, "apkPackageName": "com.package.name.of.requesting.app", "apkCertificateDigestSha256": ["base64 encoded, SHA-256 hash of the certificatee used to sign requesting app"], "apkDigestSha256": "base64 encoded, SHA-256 hash of the app's APK", "ctsProfileMatch": true, "basicIntegrity": true, } 

L’parsing de cette réponse peut vous aider à déterminer si le périphérique est enraciné ou non

Les périphériques enracinés semblent provoquer ctsProfileMatch = false.

Vous pouvez le faire côté client, mais il est recommandé d’parsingr la réponse côté serveur. Une architecture de base de serveur client avec une API de réseau de sécurité ressemblera à ceci: –

entrer la description de l'image ici

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

Si vous ne souhaitez pas utiliser le fichier jar, utilisez simplement le code:

 public static boolean findBinary(Ssortingng binaryName) { boolean found = false; if (!found) { Ssortingng[] places = { "/sbin/", "/system/bin/", "/system/xbin/", "/data/local/xbin/", "/data/local/bin/", "/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/" }; for (Ssortingng where : places) { if (new File(where + binaryName).exists()) { found = true; break; } } } return found; } 

Le programme va essayer de trouver le dossier su:

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

Exemple:

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

Au lieu d’utiliser isRootAvailable (), vous pouvez utiliser isAccessGiven (). Direct à partir du wiki RootTools:

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

RootTools.isAccessGiven () vérifie non seulement qu’un périphérique est enraciné, mais appelle également su pour votre application, demande l’autorisation et renvoie true si les permissions root de votre application ont été obtenues. Cela peut être utilisé comme première vérification dans votre application pour vous assurer que vous serez autorisé à y accéder lorsque vous en aurez besoin.

Référence

Certaines versions modifiées ont été utilisées pour définir la propriété système ro.modversion à cette fin. Les choses semblent avoir évolué. ma version de TheDude il y a quelques mois a ceci:

 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] 

L’émulateur du 1.5 SDK, en revanche, exécutant l’image 1.5, a également une racine, est probablement similaire à l’ Android Dev Phone 1 (que vous souhaitez probablement autoriser) et a ceci:

 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] 

En ce qui concerne les versions commerciales, je n’en ai pas une, mais diverses recherches sous le site:xda-developers.com sont instructives. Voici un G1 aux Pays-Bas , vous pouvez voir que ro.build.tags n’a pas de test-keys , et je pense que c’est probablement la propriété la plus fiable à utiliser.

RootBeer est une racine vérifiant la bibliothèque Android par Scott et Matthew. Il utilise différentes vérifications pour indiquer si l’appareil est enraciné ou non.

Vérifications Java

  • CheckRootManagementApps

  • CheckPotentiallyDangerousAppss

  • CheckRootCloakingApps

  • CheckTestKeys

  • vérifierPourDangerousProps

  • checkForBusyBoxBinary

  • checkForSuBinary

  • checkSuExists

  • checkForRWSystem

Vérifications natives

Nous appelons notre vérificateur racine natif pour exécuter certaines de ses propres vérifications. Les vérifications natives sont généralement plus difficiles à masquer, de sorte que certaines applications de masquage racine ne font que bloquer le chargement de bibliothèques natives contenant certains mots-clés.

  • checkForSuBinary

Voici mon code basé sur quelques réponses ici:

  /** * Checks if the phone is rooted. * * @return true if the phone is rooted, false * otherwise. */ public static boolean isPhoneRooted() { // get from build info Ssortingng 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; } 

Suite à la réponse de @Kevins, j’ai récemment découvert, lors de l’utilisation de son système, que le Nexus 7.1 renvoyait false pour les trois méthodes – Aucune commande, aucune test-keys et SuperSU n’était installé dans /system/app .

J’ai ajouté ceci:

 public static boolean checkRootMethod4(Context context) { return isPackageInstalled("eu.chainfire.supersu", context); } private static boolean isPackageInstalled(Ssortingng packagename, Context context) { PackageManager pm = context.getPackageManager(); try { pm.getPackageInfo(packagename, PackageManager.GET_ACTIVITIES); return true; } catch (NameNotFoundException e) { return false; } } 

Ceci est légèrement moins utile dans certaines situations (si vous avez besoin d’un access root garanti) car il est tout à fait possible d’installer SuperSU sur des périphériques qui ne disposent pas d’un access SU.

Cependant, comme il est possible d’installer SuperSU et de le faire fonctionner, mais pas dans le répertoire /system/app , ce cas supplémentaire détruira (haha) ces cas.

  public static boolean isRootAvailable(){ Process p = null; try{ p = Runtime.getRuntime().exec(new Ssortingng[] {"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 Ssortingng writeCommandToConsole(Process proc, Ssortingng 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 Ssortingng(tmpArray,0,bytesRead)); if(!ignoreError) throw new Exception(new Ssortingng(tmpArray,0,bytesRead)); } } if(proc.getInputStream().available() > 0){ bytesRead = proc.getInputStream().read(tmpArray); Log.i(LOG_TAG, new Ssortingng(tmpArray,0,bytesRead)); } return new Ssortingng(tmpArray); } 

Deux idées supplémentaires, si vous souhaitez vérifier si un périphérique est compatible avec votre application:

  1. Vérifiez l’existence du binary ‘su’: lancez “quel su” de Runtime.getRuntime().exec()
  2. Recherchez le fichier SuperUser.apk dans l’emplacement /system/app/Superuser.apk

L’utilisation de C ++ avec le ndk est la meilleure approche pour détecter le root même si l’utilisateur utilise des applications qui cachent sa racine, comme RootCloak. J’ai testé ce code avec RootCloak et j’ai pu détecter la racine même si l’utilisateur essayait de la cacher. Donc, votre fichier cpp aimerait:

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

Et vous appelez la fonction de votre code Java comme suit

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

Je suggère d’utiliser un code natif pour la détection des racines. Voici un exemple .

En effet, c’est une question intéressante et jusqu’à présent, personne n’a mérité une récompense. J’utilise le code suivant:

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

Le code n’est certainement pas à l’épreuve des balles, car le réseau peut ne pas être disponible, vous obtenez donc une exception. Si cette méthode retourne vrai, alors vous pouvez être sûr de 99%, sinon 50% non. L’autorisation de mise en réseau peut également gâcher la solution.

 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 NOT ROOTED. Only limited tasks can be performed." --no-wrap fi 

En utilisant ma bibliothèque sur rootbox , c’est assez facile. Vérifiez le code requirejs ci-dessous:

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