android kingo - Determine se está sendo executado em um dispositivo com raiz




root apk (17)

Meu aplicativo tem uma certa funcionalidade que funcionará somente em um dispositivo em que o root esteja disponível. Em vez de ter esse recurso falha quando é usado (e depois mostrar uma mensagem de erro apropriada para o usuário), eu preferiria uma capacidade de verificar silenciosamente se o root está disponível primeiro e, se não, ocultar as respectivas opções em primeiro lugar .

Existe uma maneira de fazer isso?


Answers

Atualizar 2017

Você pode fazer isso agora com a Google Safetynet API . A API SafetyNet fornece a API de Atestado, que ajuda a avaliar a segurança e a compatibilidade dos ambientes Android nos quais seus aplicativos são executados.

Este atestado pode ajudar a determinar se o dispositivo em particular foi adulterado ou modificado de alguma outra forma.

A API de Atestado retorna uma resposta do JWS como esta

{
  "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,
}

Analisar esta resposta pode ajudá-lo a determinar se o dispositivo está enraizado ou não

Dispositivos enraizados parecem causar ctsProfileMatch = false.

Você pode fazê-lo no lado do cliente, mas é recomendável a análise de resposta no lado do servidor. Uma arquitetura básica do servidor do cliente com a API de rede de segurança será semelhante a esta:


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

A verificação de raiz no nível de Java não é uma solução segura. Se seu aplicativo tiver problemas de segurança para ser executado em um dispositivo com raíz, use esta solução.

A resposta de Kevin funciona a menos que o telefone também tenha um aplicativo como o RootCloak. Esses aplicativos têm um identificador sobre Java APIs, uma vez que o telefone está enraizado e eles zombam dessas APIs para retornar o telefone não está enraizado.

Eu escrevi um código de nível nativo baseado na resposta de Kevin, funciona mesmo com o RootCloak! Também não causa problemas de vazamento de memória.

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

}

Em seu código Java, você precisa criar uma classe de wrapper RootUtils para fazer as chamadas nativas

    public boolean checkRooted() {

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

Aqui está o meu código baseado em algumas respostas aqui:

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

Muitas das respostas listadas aqui têm problemas inerentes:

  • A verificação das chaves de teste está correlacionada com o acesso root, mas não garante necessariamente
  • Os diretórios "PATH" devem ser derivados da variável de ambiente "PATH" real, em vez de serem codificados permanentemente
  • A existência do executável "su" não significa necessariamente que o dispositivo foi enraizado
  • O executável "which" pode ou não ser instalado, e você deve deixar o sistema resolver seu caminho se possível
  • Só porque o aplicativo SuperUser está instalado no dispositivo não significa que o dispositivo tenha acesso root ainda

A biblioteca Reference da Stericson parece estar verificando a raiz mais legitimamente. Ele também tem muitas ferramentas extras e utilitários, então eu recomendo fortemente. No entanto, não há explicação de como ele verifica especificamente o root e pode ser um pouco mais pesado do que a maioria dos aplicativos realmente precisa.

Eu fiz um par de métodos utilitários que são vagamente baseados na biblioteca RootTools. Se você quiser apenas verificar se o executável "su" está no dispositivo, use o seguinte método:

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

Esse método simplesmente percorre os diretórios listados na variável de ambiente "PATH" e verifica se existe um arquivo "su" em um deles.

Para realmente verificar o acesso root, o comando "su" deve ser executado. Se um aplicativo como o SuperUser estiver instalado, então, neste momento, ele poderá solicitar acesso root ou, caso já tenha sido concedido / negado, um brinde pode ser exibido indicando se o acesso foi concedido / negado. Um bom comando para executar é "id" para que você possa verificar se o ID do usuário é de fato 0 (root).

Aqui está um método de amostra para determinar se o acesso root foi concedido:

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 testar realmente a execução do comando "su" porque alguns emuladores têm o executável "su" pré-instalado, mas permitem que apenas determinados usuários o acessem como o shell adb.

Também é importante verificar a existência do executável "su" antes de tentar executá-lo, porque o Android é conhecido por não descartar adequadamente os processos que tentam executar comandos ausentes. Esses processos fantasmas podem aumentar o consumo de memória ao longo do tempo.


Se você já estiver usando o Fabric / Firebase Crashlytics, ligue para

CommonUtils.isRooted(context)

Esta é a implementação atual desse método:

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

Em vez de usar isRootAvailable (), você pode usar isAccessGiven (). Direto do wiki do RootTools:

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

O RootTools.isAccessGiven () não só verifica se um dispositivo está enraizado, como também chama o su para o seu aplicativo, solicita permissão e retorna true se o aplicativo tiver recebido permissões de root com êxito. Isso pode ser usado como a primeira verificação em seu aplicativo para garantir que você receba acesso quando precisar.

wiki


Aqui está uma classe que irá verificar a raiz de uma das três maneiras.

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

A biblioteca RootTools oferece métodos simples para verificar a raiz:

RootTools.isRootAvailable()

Reference


Using my library at rootbox , it is pretty easy. Check the required code below:

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

Algumas construções modificadas são usadas para definir a propriedade do sistema ro.modversion para essa finalidade. As coisas parecem ter mudado; minha compilação do TheDude há alguns meses tem isso:

[email protected]:~$ 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]

O emulador do 1.5 SDK, por outro lado, rodando a imagem 1.5, também tem root, é provavelmente similar ao Android Dev Phone 1 (que você presumivelmente quer permitir) e tem isso:

[email protected]:~$ 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]

Quanto às construções de varejo, eu não tenho uma mão, mas várias pesquisas no site:xda-developers.com são informativas. Aqui está um G1 na Holanda , você pode ver que o ro.build.tags não tem test-keys , e eu acho que é provavelmente a propriedade mais confiável para se usar.


Using C++ with the ndk is the best approach to detect root even if the user is using applications that hide his root such as RootCloak. I tested this code with RootCloak and I was able to detect the root even if the user is trying to hide it. So your cpp file would like:

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

And you will call the function from your java code as follows

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

Duas ideias adicionais, se você quiser verificar se um dispositivo é compatível com root no seu aplicativo:

  1. Verifique a existência do binário 'su': execute "which su" em Runtime.getRuntime().exec()
  2. Procure o SuperUser.apk no local /system/app/Superuser.apk

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

RootBeer é um root verificando a biblioteca do Android por Scott e Matthew. Ele usa várias verificações para indicar se o dispositivo está com raiz ou não.

Verificações de Java

  • CheckRootManagementApps

  • CheckPotentiallyDangerousAppss

  • CheckRootCloakingApps

  • CheckTestKeys

  • checkForDangerousProps

  • checkForBusyBoxBinary

  • checkForSuBinary

  • checkSuists

  • checkForRWSystem

Cheques nativos

Chamamos nosso verificador de raiz nativa para executar alguns dos seus próprios cheques. Normalmente, as verificações nativas são mais difíceis de encobrir, portanto, alguns aplicativos de capa de raiz apenas bloqueiam o carregamento de bibliotecas nativas que contêm determinadas palavras-chave.

  • checkForSuBinary

Além da resposta do @Kevins, recentemente descobri, ao usar o sistema dele, que o Nexus 7.1 estava retornando false para todos os três métodos - Não, comando, test-keys e SuperSU não estavam instalados em /system/app .

Eu adicionei isto:

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

Isso é um pouco menos útil em algumas situações (se você precisar de acesso root garantido), já que é completamente possível que o SuperSU seja instalado em dispositivos que não possuem acesso SU.

No entanto, uma vez que é possível ter o SuperSU instalado e funcionando, mas não no diretório /system/app , esse caso extra vai arrancar (haha) esses casos.


Pode haver vários serviços com o mesmo nome de classe.

Acabei de criar dois aplicativos. O nome do pacote do primeiro aplicativo é com.example.mock . Eu criei um subpacote chamado lorem no aplicativo e um serviço chamado Mock2Service . Portanto, seu nome totalmente qualificado é com.example.mock.lorem.Mock2Service .

Então criei o segundo aplicativo e um serviço chamado Mock2Service . O nome do pacote do segundo aplicativo é com.example.mock.lorem . O nome totalmente qualificado do serviço também é com.example.mock.lorem.Mock2Service .

Aqui está minha saída do logcat.

03-27 12:02:19.985: D/TAG(32155): Mock-01: com.example.mock.lorem.Mock2Service
03-27 12:02:33.755: D/TAG(32277): Mock-02: com.example.mock.lorem.Mock2Service

Uma idéia melhor é comparar as instâncias de ComponentName porque equals() de ComponentName compara nomes de pacotes e nomes de classes. E não pode haver dois aplicativos com o mesmo nome de pacote instalado em um dispositivo.

O método equals () de ComponentName .

@Override
public boolean equals(Object obj) {
    try {
        if (obj != null) {
            ComponentName other = (ComponentName)obj;
            // Note: no null checks, because mPackage and mClass can
            // never be null.
            return mPackage.equals(other.mPackage)
                    && mClass.equals(other.mClass);
        }
    } catch (ClassCastException e) {
    }
    return false;
}

ComponentName





android root