Determina se in esecuzione su un dispositivo rooted


292

La mia app ha una certa funzionalità che funzionerà solo su un dispositivo in cui è disponibile root. Invece di avere questa funzione non riuscita quando viene utilizzata (e quindi mostrare un messaggio di errore appropriato all'utente), preferirei la possibilità di controllare silenziosamente se il root è disponibile per primo e, in caso contrario, nascondere le rispettive opzioni in primo luogo .

C'è un modo per fare questo?


11
Non esiste un modo affidabile per farlo; le risposte seguenti controllano le caratteristiche comuni ma un determinato dispositivo potrebbe non essere rootato in modo comune. Se il controllo della radice diventa prevalente, le soluzioni di root probabilmente inizieranno a cercare di nascondersi. Dal momento che possono modificare il comportamento del sistema operativo hanno molte opzioni per farlo.
Chris Stratton,

Potrebbe essere meglio indicare che la funzione non è disponibile a causa della mancanza di capacità di root che fornisce maggiori informazioni all'utente anziché nascondere le capacità della tua app aggiungendo ambiguità all'esperienza complessiva.
Nick Fox,

Le risposte seguenti funzionano per Systemless Root ?
Piyush Kukadiya,

Risposte:


260

Ecco una classe che controllerà la radice in tre modi.

/** @author Kevin Kowalewski */
public class RootUtil {
    public static boolean isDeviceRooted() {
        return checkRootMethod1() || checkRootMethod2() || checkRootMethod3();
    }

    private static boolean checkRootMethod1() {
        String buildTags = android.os.Build.TAGS;
        return buildTags != null && buildTags.contains("test-keys");
    }

    private static boolean checkRootMethod2() {
        String[] paths = { "/system/app/Superuser.apk", "/sbin/su", "/system/bin/su", "/system/xbin/su", "/data/local/xbin/su", "/data/local/bin/su", "/system/sd/xbin/su",
                "/system/bin/failsafe/su", "/data/local/su", "/su/bin/su"};
        for (String path : paths) {
            if (new File(path).exists()) return true;
        }
        return false;
    }

    private static boolean checkRootMethod3() {
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(new String[] { "/system/xbin/which", "su" });
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            if (in.readLine() != null) return true;
            return false;
        } catch (Throwable t) {
            return false;
        } finally {
            if (process != null) process.destroy();
        }
    }
}

8
Se due domande richiedono risposte identiche, sono il 99% delle volte duplicate, quindi contrassegna come duplicati invece di pubblicare la stessa risposta su entrambi. Grazie.
Kev

2
Potrebbe essere così, tuttavia ti sto solo facendo sapere che le risposte duplicate esatte sono contrassegnate dalla community. È necessario personalizzare le risposte e affrontare le specifiche del problema del PO. Copia e incolla le risposte sono a rischio di attrarre i voti negativi.
Kev

9
-1, questo metodo non è praticabile, poiché alcuni telefoni includono il subinario mentre non è rootato.
Neevek,

12
Volevo solo farti sapere, l'app Fox Digital Copy (Beta) utilizza il tuo codice quasi alla lettera, comprese le classi Root ed ExecShell, nonché i metodi checkRootMethod1 / 2/3. L'ho trovato molto divertente.
Matt Joseph,

8
Posso denunciarli come Fox ha citato innumerevoli altri?
Kevin Parker,

58

Se stai già utilizzando Fabric / Firebase Crashlytics puoi chiamare

CommonUtils.isRooted(context)

Questa è l'attuale implementazione di quel metodo:

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

La migliore risposta di sempre. Si prega di utilizzare questo su qualsiasi libreria, ci sono molti falsi positivi in ​​esecuzione su dispositivi cinesi.
Pedro Paulo Amorim,

C'è qualche falso positivo in questo metodo?
Ehsan Mashhadi,

Ho provato questo su Nexus 5 con download.chainfire.eu/363/CF-Root/CF-Auto-Root/… , questo non è accurato.
Jeffrey Liu,

54

La libreria RootTools offre metodi semplici per verificare la radice:

RootTools.isRootAvailable()

Riferimento


10
isRootAvailable () controlla solo l'esistenza di su nel percorso e alcune altre directory hardcoded. Ho sentito che alcuni strumenti di sradicamento lasceranno su lì, quindi questo darà un falso positivo.
Bob Whiteman,

13
RootTools.isAccessGiven () non solo controlla la radice, ma richiede anche il permesso di root; quindi un dispositivo non root restituirà sempre false con questo metodo.
aggregate1166877,

2
@ aggregate1166877, hai ragione, ma non è abbastanza buono, e se non avessi bisogno del permesso di root quando chiedo? Voglio solo sapere se è rootato, ma al momento non ho bisogno del permesso di root.
Neevek,

4
isAccessGiven () restituisce false quando l'utente nega l'autorizzazione anche se il dispositivo era rootato.
subair_a

Questa è l'unica risposta che trovo degna di essere votata. Controlla la mia risposta qui sotto se vuoi qualcosa di simile al semplice copia e incolla o desideri maggiori dettagli
rsimp

52

Nella mia applicazione stavo verificando se il dispositivo è rootato o meno eseguendo il comando "su". Ma oggi ho rimosso questa parte del mio codice. Perché?

Perché la mia applicazione è diventata un killer di memoria. Come? Lascia che ti racconti la mia storia.

Ci sono state alcune lamentele sul fatto che la mia applicazione stava rallentando i dispositivi (ovviamente ho pensato che non potesse essere vero). Ho provato a capire perché. Quindi ho usato MAT per ottenere discariche di mucchio e analizzare, e tutto sembrava perfetto. Ma dopo aver riavviato la mia app molte volte mi sono reso conto che il dispositivo sta davvero rallentando e l'arresto della mia applicazione non l'ha resa più veloce (a meno che non riavvio il dispositivo). Ho analizzato nuovamente i file di dump mentre il dispositivo è molto lento. Ma tutto era ancora perfetto per il file di dump. Poi ho fatto quello che doveva essere fatto all'inizio. Ho elencato i processi.

$ adb shell ps

Surprize; c'erano molti processi per la mia applicazione (con il tag di processo della mia applicazione su manifest). Alcuni erano zombi, altri no.

Con un'applicazione di esempio che ha un'unica attività ed esegue solo il comando "su", mi sono reso conto che un processo zombi veniva creato ad ogni avvio dell'applicazione. Inizialmente questi zombi allocano 0 KB, ma poi succede qualcosa e i processi di zombi contengono quasi gli stessi KB del processo principale della mia applicazione e sono diventati processi standard.

C'è una segnalazione di bug per lo stesso problema su bugs.sun.com: http://bugs.sun.com/view_bug.do?bug_id=6474073 questo spiega se il comando non viene trovato, gli zombi verranno creati con il metodo exec () . Ma ancora non capisco perché e come possano diventare processi standard e contenere KB significativi. (Questo non succede sempre)

Puoi provare se vuoi con l'esempio di codice qui sotto;

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

Metodo di esecuzione del comando semplice;

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) {
            }
        }
    }
}

Per riassumere; Non ho consigli per voi per determinare se il dispositivo è rootato o meno. Ma se fossi in te non userei Runtime.getRuntime (). Exec ().

A proposito; RootTools.isRootAvailable () causa lo stesso problema.


5
Questo è molto preoccupante. Avevo una classe di rilevamento dei dispositivi rooted che faceva la stessa cosa - dopo aver letto questo ho confermato ciò che l'egean ha descritto sopra. Occasionali processi di zombi lasciati alle spalle, rallentamenti del dispositivo, ecc ...
AWT

1
Confermo il problema con RootTools 3.4 su un Android 2.3.6 GT-S5830i. Alla maggior parte degli zombi è stata assegnata memoria e il problema è sistematico. Devo riavviare il dispositivo dopo 3-4 test. Consiglio di salvare il risultato del test nelle preferenze condivise.
Cristo,

2
Google ora consiglia di utilizzare ProcessBuilder () e il comando start ().
EntangledLoops,

1
@NickS Interessante, ma quale comando hai lanciato? Non ho lo stesso problema qui inviando comandi su numerosi telefoni Android di diverso livello API dal 9 al 23.
EntangledLoops

1
@EntangledLoops. Grazie. Lancio il mio binario e interagisco con esso tramite stdin / stdout. Ho ricontrollato come l'ho fermato e ho scoperto che in uno dei casi mi mancava Process.destroy (). Quindi, niente zombi.
Nick S

36

Molte delle risposte elencate qui hanno problemi inerenti:

  • Il controllo delle chiavi di test è correlato all'accesso root ma non lo garantisce necessariamente
  • Le directory "PATH" dovrebbero essere derivate dall'attuale variabile d'ambiente "PATH" anziché essere codificate
  • L'esistenza dell'eseguibile "su" non significa necessariamente che il dispositivo sia stato rootato
  • L'eseguibile "quale" può o non può essere installato e, se possibile, è necessario consentire al sistema di risolverne il percorso
  • Solo perché l'app SuperUser è installata sul dispositivo non significa che il dispositivo abbia ancora accesso root

La libreria RootTools di Stericson sembra controllare legittimamente il root. Ha anche molti strumenti e utilità extra, quindi lo consiglio vivamente. Tuttavia, non vi è alcuna spiegazione di come verifichi specificamente il root e potrebbe essere un po 'più pesante di quanto la maggior parte delle app abbia realmente bisogno.

Ho creato un paio di metodi di utilità vagamente basati sulla libreria RootTools. Se vuoi semplicemente verificare se l'eseguibile "su" è sul dispositivo, puoi utilizzare il seguente metodo:

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

Questo metodo scorre semplicemente le directory elencate nella variabile d'ambiente "PATH" e controlla se esiste un file "su" in una di esse.

Per verificare veramente l'accesso alla radice, il comando "su" deve essere effettivamente eseguito. Se è installata un'app come SuperUser, a questo punto potrebbe richiedere l'accesso come root, oppure se è già stato concesso / negato un toast può essere mostrato indicando se l'accesso è stato concesso / negato. Un buon comando da eseguire è "id" in modo da poter verificare che l'id utente sia in realtà 0 (root).

Ecco un metodo di esempio per determinare se è stato concesso l'accesso 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;
}

È importante testare effettivamente l'esecuzione del comando "su" perché alcuni emulatori hanno l'eseguibile "su" preinstallato, ma consentono solo ad alcuni utenti di accedervi come la shell adb.

È anche importante verificare l'esistenza dell'eseguibile "su" prima di provare a eseguirlo, perché è noto che Android non dispone correttamente di processi che tentano di eseguire comandi mancanti. Questi processi fantasma possono aumentare il consumo di memoria nel tempo.


Il metodo isRootAvailable () funziona alla grande, grazie. Consiglio comunque di non eseguirlo sul thread principale per evitare un ANR, come una chiamata da un AsyncTask
Thunderstick,

1
Penso che sia la differenza tra voler garantire che root non sia disponibile e voler assicurarsi che lo sia. Se vuoi assicurarti che un dispositivo non sia rootato, i controlli suggeriti sono buoni. Otterrai falsi positivi ma va bene quando non esegui il codice su un dispositivo compromesso è la tua principale preoccupazione.
Jeffrey Blattman,

1
@ DAC84 Non sono sicuro di aver capito la tua domanda. Se esegui isRootGiven e neghi sulla tua applicazione di root, dovrebbe restituire false. Non è quello che sta succedendo? Se vuoi evitare l'avviso, puoi semplicemente usare isRootAvailable che potrebbe anche essere chiamato doSUExist. Potresti anche provare a configurare la tua applicazione root per distribuire il root liberamente e non gestirlo.
rsimp,

1
@BeeingJk no non proprio, anche se è davvero il massimo che puoi controllare senza eseguire su, che è il vero test. Tuttavia, è necessario verificare la presenza di su in PATH prima di provare a eseguirlo. Tuttavia, l'esecuzione di su spesso genera un messaggio di brindisi o un'interazione con un'app di gestione radice che potrebbe non essere quella desiderata. Per la tua logica potresti ritenere la mera esistenza di su sufficiente. Ciò può comunque dare falsi positivi in ​​alcuni emulatori che possono contenere un eseguibile su ma bloccarne l'accesso.
rsimp

1
@BeeingJk isRootAvailable è probabilmente tutto ciò di cui hai bisogno, ma il punto che sto cercando di fare è che un nome del genere o addirittura SUExist fornisce una semantica migliore di un nome di metodo come isDeviceRooted che non è del tutto corretto. Se hai davvero bisogno di verificare l'accesso root completo prima di procedere, devi provare a eseguire un comando con su come quello codificato in isRootGiven
rsimp

35

Aggiornamento 2017

Puoi farlo ora con l' API di Google Safetynet . L'API SafetyNet fornisce l'API di attestazione che ti aiuta a valutare la sicurezza e la compatibilità degli ambienti Android in cui vengono eseguite le tue app.

Questa attestazione può aiutare a determinare se il particolare dispositivo è stato manomesso o modificato in altro modo.

L'API di attestazione restituisce una risposta JWS come questa

{
  "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'analisi di questa risposta può aiutarti a determinare se il dispositivo è rootato o meno

I dispositivi rooted sembrano causare ctsProfileMatch = false.

Puoi farlo sul lato client ma è consigliabile analizzare la risposta sul lato server. Un archtecture del server client di base con API della rete di sicurezza sarà simile al seguente: -

inserisci qui la descrizione dell'immagine


3
Informazioni eccellenti e in un contesto diverso credo che questa sarebbe la risposta corretta. Sfortunatamente la domanda dei PO non riguarda la difesa della sua app da ambienti non sicuri, ma il rilevamento di root per abilitare le funzionalità solo root nella sua app. Ai fini previsti dai PO questo processo sembra eccessivamente complesso.
rsimp,

31

Il controllo di root a livello di Java non è una soluzione sicura. Se la tua app ha problemi di sicurezza da eseguire su un dispositivo rootato, utilizza questa soluzione.

La risposta di Kevin funziona a meno che il telefono non abbia anche un'app come RootCloak. Tali app hanno un handle su API Java una volta che il telefono è rootato e prendono in giro queste API per restituire il telefono non è rootato.

Ho scritto un codice di livello nativo basato sulla risposta di Kevin, funziona anche con RootCloak! Inoltre non causa problemi di perdita di memoria.

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

}

Nel tuo codice Java, devi creare la classe wrapper RootUtils per effettuare le chiamate native

    public boolean checkRooted() {

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

1
Penso che il rilevamento della radice rientri in due categorie, abilitando le funzionalità dipendenti dalla radice e quindi le misure basate sulla sicurezza per cercare di mitigare i problemi di sicurezza con i telefoni rootati. Per le funzionalità dipendenti dalla radice trovo che la risposta di Kevin sia piuttosto scarsa. Nel contesto di questa risposta questi metodi hanno più senso. Anche se riscriverei il metodo 2 per non usare quale e invece ripeterei la variabile d'ambiente PATH per cercare "su". "quale" non è garantito per essere al telefono.
rsimp,

puoi per favore fornire un esempio di come usare questo codice c in java?
mrid,

@mrid Controlla come effettuare chiamate JNI da Java su Android.
Alok Kulkarni,

Questo metodo impedisce il byepass di rilevamento root tramite l'app RootCloak. Esistono tecniche di byepass di root note che non superano questo metodo?
Nidhin,

20

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

Se non si desidera utilizzare il file jar, utilizzare semplicemente il codice:

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

Il programma proverà a trovare la cartella su:

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

Esempio:

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

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

Grazie! Sto usando questo come checkRootMethod4()con la risposta di Kevin .
Sheharyar,

1
Non aggiungere mai == truea un valore booleano, non aggiunge nulla e non ha un bell'aspetto.
minipif,

2
@smoothBlue Perché dovrebbe? Non sta generando alcun processo come lo è la soluzione di DevrimTuncer.
FD_

1
Un'idea migliore sarebbe quella di iterare su PATH, invece di codificare hard directory tipiche PATH
rsimp

1
Usa, if (isRooted())controlla invece di scrivere esplicitamente vero. È meglio seguire i modelli di scrittura del codice
programma

13

Invece di usare isRootAvailable () puoi usare isAccessGiven (). Direttamente dal wiki di RootTools :

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

RootTools.isAccessGiven () non solo controlla che un dispositivo sia rootato, ma chiama anche su per la tua app, richiede l'autorizzazione e restituisce true se l'app ha ottenuto correttamente le autorizzazioni di root. Questo può essere usato come primo controllo nella tua app per assicurarti che ti verrà concesso l'accesso quando ne hai bisogno.

Riferimento


ma l'utente deve dare l'accesso alla radice giusto? quindi se il mio obiettivo era fermare l'esecuzione della mia app se il dispositivo è
rootato,

11

Alcune build modificate utilizzate per impostare la proprietà di sistema ro.modversion per questo scopo. Le cose sembrano andare avanti; la mia build da TheDude alcuni mesi fa ha questo:

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'emulatore dall'SDK 1.5 invece, che esegue l'immagine 1.5, ha anche il root, è probabilmente simile all'Android Dev Phone 1 (che presumibilmente vuoi consentire) e ha questo:

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]

Per quanto riguarda le build al dettaglio, non ne ho una a portata di mano, ma varie ricerche sotto site:xda-developers.comsono informative. Ecco un G1 nei Paesi Bassi , puoi vedere che ro.build.tagsnon ha test-keys, e penso che sia probabilmente la proprietà più affidabile da usare.


Sembra interessante, ma: Sebbene l'emulatore (e ADP) consentano il root di per sé, non consentono alle applicazioni di usarlo, vale a dire: $ su app_29 $ su su: uid 10029 non consentito su
miracle2k

Ah, suppongo che non lo farebbero ... potresti combinarlo con un controllo per ro.build.host (non) che termina con google.com, quindi, se sono gli unici che hanno chiavi di prova ma bloccano su senza chiedendo all'utente. Dipende dall'host di build per i dispositivi più recenti, cose che non sono telefoni ... non è facile.
Chris Boyle,

11

RootBeer è una libreria Android per il controllo di root di Scott e Matthew. Utilizza vari controlli per indicare se il dispositivo è rootato o meno.

Assegni Java

  • CheckRootManagementApps

  • CheckPotentiallyDangerousAppss

  • CheckRootCloakingApps

  • CheckTestKeys

  • checkForDangerousProps

  • checkForBusyBoxBinary

  • checkForSuBinary

  • checkSuExists

  • checkForRWSystem

Controlli nativi

Chiamiamo il nostro correttore root nativo per eseguire alcuni dei suoi controlli. I controlli nativi sono in genere più difficili da mascherare, quindi alcune app di mantelli di root bloccano il caricamento di librerie native che contengono determinate parole chiave.

  • checkForSuBinary

8

Suggerisco di usare il codice nativo per il rilevamento di root. Ecco un esempio di lavoro completo .

inserisci qui la descrizione dell'immagine

Involucro JAVA :

package com.kozhevin.rootchecks.util;


import android.support.annotation.NonNull;

import com.kozhevin.rootchecks.BuildConfig;

public class MeatGrinder {
    private final static String LIB_NAME = "native-lib";
    private static boolean isLoaded;
    private static boolean isUnderTest = false;

    private MeatGrinder() {

    }

    public boolean isLibraryLoaded() {
        if (isLoaded) {
            return true;
        }
        try {
            if(isUnderTest) {
                throw new UnsatisfiedLinkError("under test");
            }
            System.loadLibrary(LIB_NAME);
            isLoaded = true;
        } catch (UnsatisfiedLinkError e) {
            if (BuildConfig.DEBUG) {
                e.printStackTrace();
            }
        }
        return isLoaded;
    }

    public native boolean isDetectedDevKeys();

    public native boolean isDetectedTestKeys();

    public native boolean isNotFoundReleaseKeys();

    public native boolean isFoundDangerousProps();

    public native boolean isPermissiveSelinux();

    public native boolean isSuExists();

    public native boolean isAccessedSuperuserApk();

    public native boolean isFoundSuBinary();

    public native boolean isFoundBusyboxBinary();

    public native boolean isFoundXposed();

    public native boolean isFoundResetprop();

    public native boolean isFoundWrongPathPermission();

    public native boolean isFoundHooks();

    @NonNull
    public static MeatGrinder getInstance() {
        return InstanceHolder.INSTANCE;
    }

    private static class InstanceHolder {
        private static final MeatGrinder INSTANCE = new MeatGrinder();
    }
}

Wrapper JNI (native-lib.c) :

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isDetectedTestKeys(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isDetectedTestKeys();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isDetectedDevKeys(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isDetectedDevKeys();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isNotFoundReleaseKeys(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isNotFoundReleaseKeys();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundDangerousProps(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundDangerousProps();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isPermissiveSelinux(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isPermissiveSelinux();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isSuExists(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isSuExists();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isAccessedSuperuserApk(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isAccessedSuperuserApk();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundSuBinary(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundSuBinary();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundBusyboxBinary(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundBusyboxBinary();
}


JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundXposed(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundXposed();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundResetprop(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundResetprop();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundWrongPathPermission(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundWrongPathPermission();
}

JNIEXPORT jboolean JNICALL
Java_com_kozhevin_rootchecks_util_MeatGrinder_isFoundHooks(
        JNIEnv *env,
        jobject  this ) {

    return (jboolean) isFoundHooks();
}

costanti:

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

rilevamenti di root dal codice nativo:

struct mntent *getMntent(FILE *fp, struct mntent *e, char *buf, int buf_len) {

    while (fgets(buf, buf_len, fp) != NULL) {
        // Entries look like "/dev/block/vda /system ext4 ro,seclabel,relatime,data=ordered 0 0".
        // That is: mnt_fsname mnt_dir mnt_type mnt_opts mnt_freq mnt_passno.
        int fsname0, fsname1, dir0, dir1, type0, type1, opts0, opts1;
        if (sscanf(buf, " %n%*s%n %n%*s%n %n%*s%n %n%*s%n %d %d",
                   &fsname0, &fsname1, &dir0, &dir1, &type0, &type1, &opts0, &opts1,
                   &e->mnt_freq, &e->mnt_passno) == 2) {
            e->mnt_fsname = &buf[fsname0];
            buf[fsname1] = '\0';
            e->mnt_dir = &buf[dir0];
            buf[dir1] = '\0';
            e->mnt_type = &buf[type0];
            buf[type1] = '\0';
            e->mnt_opts = &buf[opts0];
            buf[opts1] = '\0';
            return e;
        }
    }
    return NULL;
}


bool isPresentMntOpt(const struct mntent *pMnt, const char *pOpt) {
    char *token = pMnt->mnt_opts;
    const char *end = pMnt->mnt_opts + strlen(pMnt->mnt_opts);
    const size_t optLen = strlen(pOpt);
    while (token != NULL) {
        const char *tokenEnd = token + optLen;
        if (tokenEnd > end) break;
        if (memcmp(token, pOpt, optLen) == 0 &&
            (*tokenEnd == '\0' || *tokenEnd == ',' || *tokenEnd == '=')) {
            return true;
        }
        token = strchr(token, ',');
        if (token != NULL) {
            token++;
        }
    }
    return false;
}

static char *concat2str(const char *pString1, const char *pString2) {
    char *result;
    size_t lengthBuffer = 0;

    lengthBuffer = strlen(pString1) +
                   strlen(pString2) + 1;
    result = malloc(lengthBuffer);
    if (result == NULL) {
        GR_LOGW("malloc failed\n");
        return NULL;
    }
    memset(result, 0, lengthBuffer);
    strcpy(result, pString1);
    strcat(result, pString2);
    return result;
}

static bool
isBadPropertyState(const char *key, const char *badValue, bool isObligatoryProperty, bool isExact) {
    if (badValue == NULL) {
        GR_LOGE("badValue may not be NULL");
        return false;
    }
    if (key == NULL) {
        GR_LOGE("key may not be NULL");
        return false;
    }
    char value[PROP_VALUE_MAX + 1];
    int length = __system_property_get(key, value);
    bool result = false;
    /* A length 0 value indicates that the property is not defined */
    if (length > 0) {
        GR_LOGI("property:[%s]==[%s]", key, value);
        if (isExact) {
            if (strcmp(value, badValue) == 0) {
                GR_LOGW("bad value[%s] equals to [%s] in the property [%s]", value, badValue, key);
                result = true;
            }
        } else {
            if (strlen(value) >= strlen(badValue) && strstr(value, badValue) != NULL) {
                GR_LOGW("bad value[%s] found in [%s] in the property [%s]", value, badValue, key);
                result = true;
            }
        }
    } else {
        GR_LOGI("[%s] property not found", key);
        if (isObligatoryProperty) {
            result = true;
        }
    }
    return result;
}

bool isDetectedTestKeys() {
    const char *TEST_KEYS_VALUE = "test-keys";
    return isBadPropertyState(ANDROID_OS_BUILD_TAGS, TEST_KEYS_VALUE, true, false);
}

bool isDetectedDevKeys() {
    const char *DEV_KEYS_VALUE = "dev-keys";
    return isBadPropertyState(ANDROID_OS_BUILD_TAGS, DEV_KEYS_VALUE, true, false);
}

bool isNotFoundReleaseKeys() {
    const char *RELEASE_KEYS_VALUE = "release-keys";
    return !isBadPropertyState(ANDROID_OS_BUILD_TAGS, RELEASE_KEYS_VALUE, false, true);
}

bool isFoundWrongPathPermission() {

    bool result = false;
    FILE *file = fopen("/proc/mounts", "r");
    char mntent_strings[BUFSIZ];
    if (file == NULL) {
        GR_LOGE("setmntent");
        return result;
    }

    struct mntent ent = {0};
    while (NULL != getMntent(file, &ent, mntent_strings, sizeof(mntent_strings))) {
        for (size_t i = 0; MG_READ_ONLY_PATH[i]; i++) {
            if (strcmp((&ent)->mnt_dir, MG_READ_ONLY_PATH[i]) == 0 &&
                isPresentMntOpt(&ent, "rw")) {
                GR_LOGI("%s %s %s %s\n", (&ent)->mnt_fsname, (&ent)->mnt_dir, (&ent)->mnt_opts,
                        (&ent)->mnt_type);
                result = true;
                break;
            }
        }
        memset(&ent, 0, sizeof(ent));
    }
    fclose(file);
    return result;
}


bool isFoundDangerousProps() {
    const char *BAD_DEBUGGABLE_VALUE = "1";
    const char *BAD_SECURE_VALUE = "0";
    const char *BAD_SYS_INITD_VALUE = "1";
    const char *BAD_SERVICE_ADB_ROOT_VALUE = "1";

    bool result = isBadPropertyState(ANDROID_OS_DEBUGGABLE, BAD_DEBUGGABLE_VALUE, true, true) ||
                  isBadPropertyState(SERVICE_ADB_ROOT, BAD_SERVICE_ADB_ROOT_VALUE, false, true) ||
                  isBadPropertyState(ANDROID_OS_SECURE, BAD_SECURE_VALUE, true, true) ||
                  isBadPropertyState(ANDROID_OS_SYS_INITD, BAD_SYS_INITD_VALUE, false, true);

    return result;
}

bool isPermissiveSelinux() {
    const char *BAD_VALUE = "0";
    return isBadPropertyState(ANDROID_OS_BUILD_SELINUX, BAD_VALUE, false, false);
}

bool isSuExists() {
    char buf[BUFSIZ];
    char *str = NULL;
    char *temp = NULL;
    size_t size = 1;  // start with size of 1 to make room for null terminator
    size_t strlength;

    FILE *pipe = popen("which su", "r");
    if (pipe == NULL) {
        GR_LOGI("pipe is null");
        return false;
    }

    while (fgets(buf, sizeof(buf), pipe) != NULL) {
        strlength = strlen(buf);
        temp = realloc(str, size + strlength);  // allocate room for the buf that gets appended
        if (temp == NULL) {
            // allocation error
            GR_LOGE("Error (re)allocating memory");
            pclose(pipe);
            if (str != NULL) {
                free(str);
            }
            return false;
        } else {
            str = temp;
        }
        strcpy(str + size - 1, buf);
        size += strlength;
    }
    pclose(pipe);
    GR_LOGW("A size of the result from pipe is [%zu], result:\n [%s] ", size, str);
    if (str != NULL) {
        free(str);
    }
    return size > 1 ? true : false;
}

static bool isAccessedFile(const char *path) {
    int result = access(path, F_OK);
    GR_LOGV("[%s] has been accessed with result: [%d]", path, result);
    return result == 0 ? true : false;
}

static bool isFoundBinaryFromArray(const char *const *array, const char *binary) {
    for (size_t i = 0; array[i]; ++i) {
        char *checkedPath = concat2str(array[i], binary);
        if (checkedPath == NULL) { // malloc failed
            return false;
        }
        bool result = isAccessedFile(checkedPath);
        free(checkedPath);
        if (result) {
            return result;
        }
    }
    return false;
}

bool isAccessedSuperuserApk() {
    return isAccessedFile("/system/app/Superuser.apk");
}

bool isFoundResetprop() {
    return isAccessedFile("/data/magisk/resetprop");
}

bool isFoundSuBinary() {
    return isFoundBinaryFromArray(MG_SU_PATH, "su");
}

bool isFoundBusyboxBinary() {
    return isFoundBinaryFromArray(MG_SU_PATH, "busybox");
}

bool isFoundXposed() {
    for (size_t i = 0; MG_EXPOSED_FILES[i]; ++i) {
        bool result = isAccessedFile(MG_EXPOSED_FILES[i]);
        if (result) {
            return result;
        }
    }
    return false;
}

bool isFoundHooks() {
    bool result = false;
    pid_t pid = getpid();
    char maps_file_name[512];
    sprintf(maps_file_name, "/proc/%d/maps", pid);
    GR_LOGI("try to open [%s]", maps_file_name);
    const size_t line_size = BUFSIZ;
    char *line = malloc(line_size);
    if (line == NULL) {
        return result;
    }
    FILE *fp = fopen(maps_file_name, "r");
    if (fp == NULL) {
        free(line);
        return result;
    }
    memset(line, 0, line_size);
    const char *substrate = "com.saurik.substrate";
    const char *xposed = "XposedBridge.jar";
    while (fgets(line, line_size, fp) != NULL) {
        const size_t real_line_size = strlen(line);
        if ((real_line_size >= strlen(substrate) && strstr(line, substrate) != NULL) ||
            (real_line_size >= strlen(xposed) && strstr(line, xposed) != NULL)) {
            GR_LOGI("found in [%s]: [%s]", maps_file_name, line);
            result = true;
            break;
        }
    }
    free(line);
    fclose(fp);
    return result;
}

4
Strumento fantastico, Dima. Molte grazie. Cattura persino magisk.
esperto

Questo è il vero affare.
Vahid Amiri,

@klutch c'è il link all'esempio funzionante (github) nella prima riga del mio post
Dima Kozhevin

7

Ecco il mio codice basato su alcune risposte qui:

 /**
   * Checks if the phone is rooted.
   * 
   * @return <code>true</code> if the phone is rooted, <code>false</code>
   * otherwise.
   */
  public static boolean isPhoneRooted() {

    // get from build info
    String buildTags = android.os.Build.TAGS;
    if (buildTags != null && buildTags.contains("test-keys")) {
      return true;
    }

    // check if /system/app/Superuser.apk is present
    try {
      File file = new File("/system/app/Superuser.apk");
      if (file.exists()) {
        return true;
      }
    } catch (Throwable e1) {
      // ignore
    }

    return false;
  }

7

In seguito alla risposta di @Kevins, recentemente ho scoperto che durante l'utilizzo del suo sistema il Nexus 7.1 stava tornando falseper tutti e tre i metodi - Nessun whichcomando, no test-keyse SuperSUnon era installato in/system/app .

Ho aggiunto questo:

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

Ciò è leggermente meno utile in alcune situazioni (se è necessario l'accesso root garantito) in quanto è possibile installare SuperSU su dispositivi che non dispongono dell'accesso SU.

Tuttavia, poiché è possibile avere SuperSU installato e funzionante ma non nella /system/appdirectory, questo caso in più eliminerà tali casi.


Questa non è una buona risposta in quanto hai altri pacchetti root che possono essere installati sul tuo dispositivo. La codifica
hardware di

5
    public static boolean isRootAvailable(){
            Process p = null;
            try{
               p = Runtime.getRuntime().exec(new String[] {"su"});
               writeCommandToConsole(p,"exit 0");
               int result = p.waitFor();
               if(result != 0)
                   throw new Exception("Root check result with exit command " + result);
               return true;
            } catch (IOException e) {
                Log.e(LOG_TAG, "Su executable is not available ", e);
            } catch (Exception e) {
                Log.e(LOG_TAG, "Root is unavailable ", e);
            }finally {
                if(p != null)
                    p.destroy();
            }
            return false;
        }
 private static String writeCommandToConsole(Process proc, String command, boolean ignoreError) throws Exception{
            byte[] tmpArray = new byte[1024];
            proc.getOutputStream().write((command + "\n").getBytes());
            proc.getOutputStream().flush();
            int bytesRead = 0;
            if(proc.getErrorStream().available() > 0){
                if((bytesRead = proc.getErrorStream().read(tmpArray)) > 1){
                    Log.e(LOG_TAG,new String(tmpArray,0,bytesRead));
                    if(!ignoreError)
                        throw new Exception(new String(tmpArray,0,bytesRead));
                }
            }
            if(proc.getInputStream().available() > 0){
                bytesRead = proc.getInputStream().read(tmpArray);
                Log.i(LOG_TAG, new String(tmpArray,0,bytesRead));
            }
            return new String(tmpArray);
        }

4

Due idee aggiuntive, se si desidera verificare se un dispositivo è in grado di eseguire il root dalla propria app:

  1. Controlla l'esistenza del binario 'su': esegui "da cui" Runtime.getRuntime().exec()
  2. Cerca il SuperUser.apk nella /system/app/Superuser.apkposizione

3

L'uso di C ++ con ndk è l'approccio migliore per rilevare root anche se l'utente utilizza applicazioni che nascondono la propria radice come RootCloak. Ho testato questo codice con RootCloak e sono stato in grado di rilevare il root anche se l'utente sta cercando di nasconderlo. Quindi il tuo file cpp vorrebbe:

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

E chiamerai la funzione dal tuo codice java come segue

public class Root_detect {



   /**
    *
    * function that calls a native function to check if the device is 
    *rooted or not
    * @return boolean: true if the device is rooted, false if the 
    *device is not rooted
   */
   public boolean check_rooted(){

        int checker = rootFunction();

        if(checker==1){
           return true;
        }else {
           return false;
        }
   }
   static {
    System.loadLibrary("cpp-root-lib");//name of your cpp file
   }

   public native int rootFunction();
}

1
if [[ "`adb shell which su | grep -io "permission denied"`" != "permission denied" ]]; then
   echo "Yes. Rooted device."
 else
   echo "No. Device not rooted. Only limited tasks can be performed. Done."
    zenity --warning --title="Device Not Rooted" --text="The connected Android Device is <b>NOT ROOTED</b>. Only limited tasks can be performed." --no-wrap
fi


1

Dimentica tutto ciò che rileva app di root e su binari. Controlla il processo del demone root. Questo può essere fatto dal terminale ed è possibile eseguire i comandi del terminale all'interno di un'app. Prova questo one-liner.

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

Non è necessario nemmeno il permesso di root per raggiungere questo obiettivo.


0

In effetti è una domanda interessante e finora nessuno ha meritato il premio. Io uso il seguente codice:

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

Il codice non è certamente a prova di proiettile, perché la rete non può essere disponibile, quindi si ottiene un'eccezione. Se questo metodo restituisce true, il 99% può esserne sicuro, altrimenti solo il 50%. L'autorizzazione di rete può anche rovinare la soluzione.


Ho provato questo e non ritorna vero con il mio dispositivo rooted.
tricknology,

È interessante vedere che tipo di eccezione ottieni. È possibile che la porta sia già associata a un'eccezione, tuttavia se non è possibile creare la porta del server nell'intervallo inferiore a 1024, diminuisce il valore del rooting, poiché esistono ancora alcune limitazioni.
Singagirl,

-1

Usando la mia libreria su rootbox , è abbastanza facile. Controlla il codice richiesto di seguito:

    //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.");
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.