Determinar se está sendo executado em um dispositivo raiz


292

Meu aplicativo tem uma certa funcionalidade que só funciona em um dispositivo em que a raiz está disponível. Em vez de fazer com que esse recurso falhe quando é usado (e depois mostre uma mensagem de erro apropriada para o usuário), prefiro verificar silenciosamente se o root está disponível primeiro e, se não, ocultar as opções respectivas em primeiro lugar .

Existe uma maneira de fazer isso?


11
Não há maneira confiável de fazer isso; as respostas abaixo verificam características comuns, mas um determinado dispositivo pode não estar enraizado de maneira comum. Se a verificação de raiz se tornar predominante, as soluções raiz provavelmente começarão a se esforçar para se esconder. Como eles podem modificar o comportamento do sistema operacional, têm várias opções para fazê-lo.
22612 Chris Stratton

Talvez seja melhor indicar que a função não está disponível devido à falta de recursos raiz, fornecendo mais informações ao usuário, em vez de ocultar os recursos do seu aplicativo, adicionando ambiguidade à experiência geral.
Nick Fox

As respostas abaixo funcionam para a raiz sem sistema ?
Piyush Kukadiya

Respostas:


260

Aqui está uma classe que verificará a raiz de 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();
        }
    }
}

8
Se duas perguntas exigem respostas idênticas, elas são duplicadas em 99% do tempo, então marque como dupes em vez de postar a mesma resposta em ambas. Obrigado.
Kev

2
Pode ser que sim, no entanto, estou apenas informando que respostas duplicadas exatas são sinalizadas pela comunidade. Você deve adaptar suas respostas e abordar as especificidades do problema do OP. As respostas de copiar e colar correm o risco de atrair votos negativos.
Kev

9
-1, esse método não é viável, porque alguns telefones incluem o subinário enquanto não estão enraizados.
neevek

12
Só queria que você soubesse que o aplicativo Fox Digital Copy (Beta) usa seu código quase literalmente, incluindo as classes Root e ExecShell, bem como os métodos checkRootMethod1 / 2/3. Achei muito divertido.
Matt Joseph

8
Posso processá-los como a Fox processou inúmeros outros?
Kevin Parker

58

Se você já estiver usando o Fabric / Firebase Crashlytics, poderá ligar

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

Melhor resposta de sempre. Por favor, use isso em qualquer biblioteca, pois há muitos falsos positivos em execução em dispositivos chineses.
Pedro Paulo Amorim

Existe algum falso positivo nesse método?
Ehsan Mashhadi

Testei isso no nexus 5 com download.chainfire.eu/363/CF-Root/CF-Auto-Root/… , este não é preciso.
Jeffrey Liu

54

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

RootTools.isRootAvailable()

Referência


10
isRootAvailable () apenas verifica a existência de su no caminho e em alguns outros diretórios codificados. Ouvi dizer que algumas ferramentas de remoção de raiz deixarão su lá, então isso dará um falso positivo.
11119 Bob Marman

13
RootTools.isAccessGiven () não apenas verifica a raiz, mas também solicita permissão de raiz; portanto, um dispositivo não raiz sempre retornará falso com esse método.
aggregate1166877

2
@ agregate1166877, você está certo, mas não é bom o suficiente, e se eu não precisar de permissão root quando pedir? Eu só quero saber se está enraizada, mas não preciso de permissão de raiz no momento.
neevek

4
isAccessGiven () retorna false quando o usuário nega a permissão, mesmo que o dispositivo tenha sido rooteado.
subair_a

Esta é a única resposta que acho que vale a pena votar. Confira abaixo a minha resposta se você quiser algo semelhante ao Basta copiar colar, ou gostaria de mais alguns detalhes
rsimp

52

No meu aplicativo, eu estava verificando se o dispositivo está enraizado ou não, executando o comando "su". Mas hoje eu removi essa parte do meu código. Por quê?

Porque meu aplicativo se tornou um assassino de memória. Quão? Deixa-me contar-te a minha historia.

Houve algumas reclamações de que meu aplicativo estava desacelerando os dispositivos (é claro que pensei que isso não pode ser verdade). Eu tentei descobrir o porquê. Então, eu usei o MAT para obter despejos de pilha e analisar, e tudo parecia perfeito. Mas, após reiniciar o aplicativo muitas vezes, percebi que o dispositivo estava realmente ficando mais lento e a interrupção do aplicativo não o tornava mais rápido (a menos que eu reiniciasse o dispositivo). Analisei os arquivos de despejo novamente enquanto o dispositivo está muito lento. Mas tudo ainda estava perfeito para o arquivo de despejo. Então eu fiz o que deve ser feito primeiro. Eu listei processos.

$ adb shell ps

Surpresa; havia muitos processos para meu aplicativo (com a tag de processo do aplicativo no manifesto). Alguns deles eram zumbis, outros não.

Com um aplicativo de amostra que possui uma única atividade e executa apenas o comando "su", percebi que um processo zumbi está sendo criado em cada inicialização do aplicativo. A princípio, esses zumbis alocam 0 KB, mas isso acontece e os processos zumbis mantêm quase os mesmos KBs que o processo principal do meu aplicativo e eles se tornaram processos padrão.

Existe um relatório de bug para o mesmo problema no bugs.sun.com: http://bugs.sun.com/view_bug.do?bug_id=6474073 isso explica se o comando não foi encontrado Os zumbis serão criados com o método exec () . Mas ainda não entendo por que e como eles podem se tornar processos padrão e conter KBs significativos. (Isso não está acontecendo o tempo todo)

Você pode tentar se quiser com o exemplo de código abaixo;

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

Método simples de execução de comandos;

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

Resumindo; Não tenho conselhos para você determinar se o dispositivo está enraizado ou não. Mas se eu fosse você, não usaria Runtime.getRuntime (). Exec ().

A propósito; RootTools.isRootAvailable () causa o mesmo problema.


5
Isso é muito preocupante. Eu tinha uma classe de detecção de dispositivo enraizada que fazia a mesma coisa - depois de ler isso, confirmei o que o egeu detalhou acima. Processos zombie ocasionais ser deixado para trás, lentidão de dispositivos, etc ...
AWT

1
Confirmo o problema com o RootTools 3.4 em um Android 2.3.6 GT-S5830i. A maioria dos zumbis recebeu memória alocada e o problema é sistemático. Preciso reiniciar o dispositivo após o teste 3-4. Eu recomendo salvar o resultado do teste na preferência compartilhada.
Cristo

2
O Google agora recomenda usar ProcessBuilder () e o comando start ().
EntangledLoops

1
@NickS Interessante, mas que comando você lançou? Eu não tenho o mesmo problema aqui emitir comandos em numerosas telefones Android de nível API diferente de 9 - 23.
EntangledLoops

1
@EntangledLoops. Obrigado. Eu inicio meu próprio binário e interajo com ele através do stdin / stdout. Verifiquei novamente como o interrompi e descobri que perdi Process.destroy () em um dos casos. Então, não há zumbis.
Nick S

36

Muitas das respostas listadas aqui têm problemas inerentes:

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

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

Eu criei alguns métodos utilitários que são vagamente baseados na biblioteca RootTools. Se você simplesmente deseja verificar se o executável "su" está no dispositivo, você pode usar 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 realmente executado. Se um aplicativo como o SuperUser estiver instalado, nesse momento, ele poderá solicitar acesso root ou se já foi concedido / negado um brinde, poderá ser mostrado 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 (raiz).

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

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


O método isRootAvailable () funciona muito bem, obrigado. Eu não recomendo executar este no segmento principal no entanto, para evitar uma ANR, como chamada de um AsyncTask
Thunderstick

1
Eu acho que é a diferença entre querer garantir que o root não esteja disponível e querer garantir que ele esteja. Se você deseja garantir que um dispositivo não esteja enraizado, as verificações sugeridas são boas. Você obterá falsos positivos, mas tudo bem quando não executar seu código em um dispositivo comprometido é sua principal preocupação.
precisa saber é o seguinte

1
@ DAC84 Não sei se entendi sua pergunta. Se você executar isRootGiven e negar no seu aplicativo de root, ele deverá retornar false. Não é isso que está acontecendo? Se você deseja evitar o alerta, basta usar isRootAvailable, que também pode ser chamado de doesSUExist. Você também pode tentar configurar seu aplicativo raiz para distribuí-lo livremente e não gerenciá-lo.
rsimp

1
@BeeingJk não, não realmente, embora seja realmente o máximo que você pode verificar sem executar o su, que é o teste real. Você precisa verificar se há su no PATH antes de tentar executá-lo. No entanto, na verdade, executar su geralmente resulta em uma mensagem do sistema ou em uma interação com um aplicativo de gerenciamento raiz, que pode não ser o que você deseja. Para sua própria lógica, você pode considerar a mera existência de su suficiente. Isso ainda pode dar falsos positivos em alguns emuladores que podem conter um acesso executável su, mas bloqueado.
rsimp

1
@BeeingJk isRootAvailable provavelmente é tudo o que você precisa, mas o que estou tentando fazer é que um nome como esse ou mesmo doesSUExist forneça melhor semântica do que um nome de método como isDeviceRooted, o que não está certo. Se você realmente precisa para verificar o acesso root completo antes de prosseguir você precisa tentar executar um comando com su como o codificado em isRootGiven
rsimp

35

Atualização 2017

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

Esse atestado pode ajudar a determinar se o dispositivo em particular foi violado ou modificado.

A API de atestado retorna uma resposta 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,
}

A análise desta resposta pode ajudá-lo a determinar se o dispositivo está enraizado ou não

Dispositivos enraizados parecem causar ctsProfileMatch = false.

Você pode fazer isso no lado do cliente, mas é recomendável analisar a resposta no lado do servidor. Uma arquitetura básica de servidor cliente com API de rede de segurança terá a seguinte aparência: -

insira a descrição da imagem aqui


3
Informações excelentes e, em um contexto diferente, acredito que essa seria a resposta correta. Infelizmente, a questão dos OPs não é defender seu aplicativo de ambientes inseguros, mas detectar o root para ativar recursos somente raiz no aplicativo. Para os POs pretendidos, esse processo parece excessivamente complexo.
rsimp

31

A verificação da raiz no nível do Java não é uma solução segura. Se o seu aplicativo tiver preocupações de segurança para executar em um dispositivo raiz, 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 APIs de manipulação sobre Java, 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 com base na resposta de Kevin, ele 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;

}

No seu código Java, você precisa criar a 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;
     }

1
Penso que a detecção de raiz se enquadra em duas categorias, permitindo recursos dependentes de raiz e, em seguida, medidas baseadas em segurança para tentar mitigar problemas de segurança com telefones com raiz. Para recursos dependentes da raiz, acho a resposta de Kevin bastante ruim. No contexto desta resposta, esses métodos fazem mais sentido. Embora eu reescrevesse o método 2 para não usar qual e, em vez disso, itere sobre a variável de ambiente PATH para procurar por "su". "qual" não é garantido que esteja no telefone.
Rsimp 22/17 /

você pode fornecer um exemplo de como usar esse código c em java?
Mrid

@mrid Verifique como fazer chamadas JNI do Java no Android.
Alok Kulkarni

Esse método evita o desvio de detecção de raiz usando o aplicativo RootCloak. Existe alguma técnica conhecida de byepass raiz que falhe nesse método?
Nidhin 22/0318

20

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

Se você não quiser usar o arquivo jar, use o código:

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

O programa tentará encontrar a pasta su:

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

Exemplo:

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

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

Obrigado! Eu estou usando isso como checkRootMethod4()com Resposta de Kevin .
Sheharyar

1
Nunca adicione == truea um booleano, ele não adiciona nada e não parece bom.
Minipif

2
@smoothBlue Por que seria? Não está gerando nenhum processo, como é a solução do DevrimTuncer.
FD_ 24/03/16

1
Uma idéia melhor seria para iterar PATH, em vez de codificar diretórios caminho típico
rsimp

1
Use, if (isRooted())verifique em vez de escrever explicitamente true. O seu melhor para seguir padrões de escrita de código
blueware

13

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

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

RootTools.isAccessGiven () não apenas verifica se um dispositivo está enraizado, mas também chama su para o seu aplicativo, solicita permissão e retorna true se o seu aplicativo recebeu permissões de root. Isso pode ser usado como a primeira verificação no seu aplicativo para garantir que você tenha acesso concedido quando precisar.

Referência


mas o usuário tem que dar acesso root certo? por isso, se meu objetivo era parar meu aplicativo seja executado se o dispositivo está enraizada em seguida, minhas opções são realmente limitado
Nasz Njoka Sr.

11

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

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]

O emulador do SDK 1.5, por outro lado, executando a imagem 1.5, também possui raiz, provavelmente é semelhante ao Android Dev Phone 1 (que você provavelmente deseja permitir) e tem o seguinte:

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]

Quanto às versões de varejo, não tenho uma em mãos, mas várias pesquisas abaixo site:xda-developers.comsão informativas. Aqui está um G1 na Holanda , você pode ver que ro.build.tagsnão tem test-keys, e acho que essa é provavelmente a propriedade mais confiável a ser usada.


Que parece interessante, mas: Enquanto o emulador (e ADP) permitem raiz, por si só, eles não permitem que aplicativos para usá-lo, ou seja: $ su app_29 $ su su: uid 10029 não tem permissão para su
miracle2k

Ah, suponho que eles não ... você poderia combiná-lo com uma verificação de ro.build.host (não) terminando no google.com, se eles são os únicos que têm chaves de teste, mas bloqueiam su sem perguntando ao usuário. Depende do host do build para dispositivos mais recentes, coisas que não são telefones ... não é fácil.
9309 Chris Boyle

11

RootBeer é uma biblioteca Android de verificação de raiz de Scott e Matthew. Ele usa várias verificações para indicar se o dispositivo está enraizado ou não.

Verificações Java

  • CheckRootManagementApps

  • CheckPotentiallyDangerousAppss

  • CheckRootCloakingApps

  • CheckTestKeys

  • checkForDangerousProps

  • checkForBusyBoxBinary

  • checkForSuBinary

  • checkSuExists

  • checkForRWSystem

Verificações nativas

Chamamos nosso verificador raiz nativo para executar algumas de suas próprias verificações. As verificações nativas são geralmente mais difíceis de ocultar, portanto, alguns aplicativos de capa raiz apenas bloqueiam o carregamento de bibliotecas nativas que contêm determinadas palavras-chave.

  • checkForSuBinary

8

Sugiro usar código nativo para detecção de raiz. Aqui está um exemplo completo de trabalho .

insira a descrição da imagem aqui

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

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

detecções de raiz do código 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
Ferramenta incrível, Dima. Muito obrigado. Até pega magisk.
expert

Este é o negócio real.
Vahid Amiri

@klutch existe o link para o exemplo de trabalho (github) na primeira linha do meu post
Dima Kozhevin

7

Aqui está o meu código com base 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;
  }

7

Após a resposta do @Kevins, descobri recentemente, enquanto usava o sistema, que o Nexus 7.1 estava retornando falsepara todos os três métodos - Sem whichcomando, não test-keyse SuperSUnão foi instalado /system/app.

Eu adicionei isso:

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), pois é completamente possível que o SuperSU seja instalado em dispositivos que não têm acesso à SU.

No entanto, como é possível ter o SuperSU instalado e funcionando, mas não no /system/appdiretório, esse caso extra eliminará (haha) esses casos.


Esta não é uma boa resposta, pois você tem outros pacotes raiz que podem ser instalados no seu dispositivo. Codificar com
firmeza

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

Duas idéias adicionais, se você quiser verificar se um dispositivo é compatível com raiz do seu aplicativo:

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

3

Usar C ++ com o ndk é a melhor abordagem para detectar raiz, mesmo que o usuário esteja usando aplicativos que ocultam sua raiz, como o RootCloak. Testei esse código com o RootCloak e consegui detectar a raiz mesmo que o usuário estivesse tentando ocultá-la. Portanto, seu arquivo cpp gostaria de:

#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 você chamará a função do seu código java da seguinte maneira

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

Esqueça tudo o que detecta aplicativos raiz e binários. Verifique o processo do daemon raiz. Isso pode ser feito no terminal e você pode executar comandos do terminal em um aplicativo. Experimente este 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

Você também não precisa de permissão root para conseguir isso.


0

Na verdade, é uma pergunta interessante e até agora ninguém mereceu prêmio. Eu uso o seguinte código:

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

O código certamente não é à prova de balas, porque a rede pode não estar disponível e você recebe uma exceção. Se esse método retornar true, então 99% pode ter certeza, caso contrário, apenas 50% não. A permissão de rede também pode estragar a solução.


Eu testei isso e ele não retorna verdadeiro com o meu dispositivo enraizado.
Tricknology # 6/14

É interessante ver que tipo de exceção você recebe. Você pode obter uma exceção de porta já vinculada, no entanto, se você não pode criar uma porta de servidor no intervalo abaixo de 1024, isso diminui o valor do root, pois ainda existem algumas limitações.
Singagirl

-1

Usando minha biblioteca no rootbox , é bem fácil. Verifique o código necessário abaixo:

    //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.");
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.