Mon application possède certaines fonctionnalités qui ne fonctionneront que sur un appareil où la racine est disponible. Plutôt que de laisser cette fonctionnalité échouer lors de son utilisation (et d'afficher ensuite un message d'erreur approprié à l'utilisateur), je préférerais une possibilité de vérifier en mode silencieux si la racine est disponible en premier, sinon de masquer les options respectives en premier lieu. .
Y a-t-il un moyen de faire cela?
Voici une classe qui va vérifier pour Root l’une des trois manières suivantes:.
/** @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();
}
}
}
La bibliothèque RootTools offre des méthodes simples pour vérifier la racine:
RootTools.isRootAvailable()
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? Laisse-moi te raconter mon histoire.
Il y avait quelques plaintes que mon application 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 analyser et analyser des tas de données, et tout semblait parfait. Mais après avoir relancé mon application plusieurs fois, je me suis rendu compte que le périphérique devenait vraiment plus lent 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é à nouveau les fichiers de vidage alors que l'appareil est très lent. Mais tout était encore parfait pour le fichier de vidage. Ensuite, j'ai fait ce qu'il fallait faire au début. J'ai énuméré les processus.
$ adb Shell ps
Surprize; il y avait beaucoup de processus pour mon application (avec l'étiquette de processus de mon application au manifeste). Certains d'entre eux étaient des zombies, d'autres pas.
Avec un exemple d'application comportant une seule activité et exécutant simplement la commande "su", j'ai compris qu'un processus zombie était en cours de création à chaque lancement d'application. Au début, ces zombies allouent 0 Ko, mais il se passe quelque chose. Les processus zombies contiennent presque les mêmes bases de connaissances 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=647407 Ceci explique si la commande n'est pas trouvée, les zombies vont être créé avec la méthode exec (). Mais je ne comprends toujours pas pourquoi et comment peuvent-ils devenir des processus standard et contenir des bases de données significatives. (Cela n'arrive pas tout le temps)
Vous pouvez essayer si vous voulez avec un exemple de code ci-dessous;
String commandToExecute = "su";
executeShellCommand(commandToExecute);
Méthode d'exécution de commande simple;
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) {
}
}
}
}
Pour résumer; Je n'ai aucun conseil pour vous de déterminer si le périphérique est enraciné ou non. Mais si j'étais vous, je n'utiliserais pas Runtime.getRuntime (). Exec ().
Au fait; RootTools.isRootAvailable () provoque le même problème.
Si vous utilisez déjà Fabric/Firebase Crashlytics, vous pouvez appeler
CommonUtils.isRooted(context)
Voici l'implémentation actuelle de cette méthode:
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();
}
}
}
Mise à jour 2017
Vous pouvez le faire maintenant avec 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é falsifié ou autrement 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
certificate used to sign requesting app"],
"apkDigestSha256": "base64 encoded, SHA-256 hash of the app's APK",
"ctsProfileMatch": true,
"basicIntegrity": true,
}
L'analyse 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 causer ctsProfileMatch = false.
Vous pouvez le faire côté client, mais l’analyse de la réponse côté serveur est recommandée. Une architecture de serveur client de base avec une API de réseau de sécurité ressemblera à ceci: -
Bon nombre des réponses énumérées ici ont des problèmes inhérents:
La bibliothèque RootTools de Stericson semble rechercher la racine de manière plus légitime. Il comporte également de nombreux outils et utilitaires supplémentaires, je le recommande donc vivement. Cependant, rien n’explique comment il vérifie spécifiquement la racine, et cela peut être un peu plus lourd que la plupart des applications n’en ont réellement besoin.
J'ai créé quelques méthodes utilitaires basées sur la bibliothèque RootTools. Si vous voulez simplement vérifier si le fichier "su" est sur le périphérique, vous pouvez utiliser la méthode suivante:
public static boolean isRootAvailable(){
for(String pathDir : System.getenv("PATH").split(":")){
if(new File(pathDir, "su").exists()) {
return true;
}
}
return false;
}
Cette méthode parcourt simplement les répertoires répertoriés dans la variable d’environnement "PATH" et vérifie si un fichier "su" existe dans l’un d’eux.
Afin de vérifier réellement l’accès root, la commande "su" doit en réalité être exécutée. Si une application telle que SuperUser est installée, elle peut à ce stade demander un accès root ou, si son application a déjà été accordée/refusée, un toast peut être affiché pour indiquer si l'accès a été accordé/refusé. Une bonne commande à exécuter est "id" afin que vous puissiez vérifier que l'identifiant de l'utilisateur est bien 0 (racine).
Voici un exemple de méthode permettant de déterminer si l'accès root a été accordé:
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;
}
Il est important de tester réellement l'exécution de la commande "su" car certains émulateurs ont l'exécutable "su" pré-installé, mais n'autorisent que certains utilisateurs à 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 il est connu que Android ne dispose pas correctement des processus qui essaient d'exécuter des commandes manquantes. Ces processus fantômes peuvent augmenter la consommation de mémoire au fil du temps.
Le contrôle de la racine au niveau 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 root, utilisez cette solution.
La réponse de Kevin ne fonctionne que si le téléphone dispose également d'une application telle que RootCloak. Ces applications ont un Handle over Java API lorsque le téléphone est enraciné et elles se moquent de ces API pour retourner le téléphone n'est pas enraciné.
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 pas de problèmes de fuite de mémoire.
#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;
}
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;
}
http://code.google.com/p/roottools/
Si vous ne voulez pas utiliser le fichier jar , utilisez simplement le code:
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;
}
Le programme essaiera 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 (). Directement à partir de RootTools wiki :
if (RootTools.isAccessGiven()) {
// your app has been granted root access
}
RootTools.isAccessGiven () non seulement vérifie qu'un périphérique est enraciné, il appelle également su pour votre application, demande une autorisation et renvoie la valeur true si des autorisations root ont été attribuées à votre application. Cela peut être utilisé comme premier contrôle de votre application pour vous assurer que l'accès vous sera accordé lorsque vous en aurez besoin.
Certaines versions modifiées utilisées pour définir propriété systèmero.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 SDK 1.5, d’autre part, qui exécute l’image 1.5, a également une racine, est probablement similaire au Android Dev Phone 1 (que vous voulez probablement autoriser) et présente les caractéristiques suivantes:
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]
Pour ce qui est des versions commerciales, je n’en ai pas, mais plusieurs recherches sous site:xda-developers.com
sont informatives. Voici un G1 aux Pays-Bas , vous pouvez voir que ro.build.tags
n'a pas test-keys
, et je pense que c'est probablement la propriété la plus fiable à utiliser.
RootBeer est une bibliothèque vérifiant la bibliothèque Android de Scott et Matthew. Il utilise diverses vérifications pour indiquer si le périphérique est enraciné ou non.
Contrôles Java
CheckRootManagementApps
CheckPotentiallyDangerousAppss
CheckRootCloakingApps
CheckTestKeys
checkForDangerousProps
checkForBusyBoxBinary
checkForSuBinary
checkSuExists
checkForRWSystem
Contrôles natifs
Nous appelons notre vérificateur de racine natif pour exécuter certaines de ses propres vérifications. Les vérifications natives sont généralement plus difficiles à masquer. Par conséquent, certaines applications de ce type ne bloquent que le chargement des 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 <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;
}
Suite à la réponse de @Kevins, lors de l’utilisation de son système, j’ai récemment découvert que le Nexus 7.1 renvoyait false
pour les trois méthodes - No which
commande, no test-keys
et SuperSU
n'a pas été installé dans /system/app
.
J'ai ajouté ceci:
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;
}
}
C'est légèrement moins utile dans certaines situations (si vous avez besoin d'un accès root garanti) car il est tout à fait possible que SuperSU soit installé sur des périphériques qui ne possèdent pas Accès SU.
Cependant, comme il est possible d’installer SuperSU et de fonctionner, mais pas dans le répertoire /system/app
, ce cas supplémentaire extraira (haha) de tels cas.
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);
}
Je suggère d'utiliser du code natif pour la détection de la racine. Voici un exemple de travail complet .
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();
}
}
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();
}
constantes:
// 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
};
détections de racine à partir du code natif:
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;
}
Deux idées supplémentaires, si vous voulez vérifier si un appareil est capable de root avec votre application:
Runtime.getRuntime().exec()
/system/app/Superuser.apk
Utiliser C++ avec le ndk est la meilleure approche pour détecter la racine même si l'utilisateur utilise des applications qui cachent sa racine, telles que RootCloak. J'ai testé ce code avec RootCloak et j'ai pu détecter la racine même si l'utilisateur tente de le masquer. Donc, votre fichier cpp voudrait:
#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;
}
Et vous appellerez la fonction à partir 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();
}
En effet c'est une question intéressante et jusqu'à présent personne n'a mérité de 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 renvoie true, vous pouvez en être sûr à 99%, sinon 50% seulement. Une 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 <b>NOT ROOTED</b>. Only limited tasks can be performed." --no-wrap
fi