Root Detection é um mecanismo de segurança que protege aplicativos Android de serem executados em dispositivos com privilégios de superusuário. Aplicativos bancários, de pagamento e corporativos bloqueiam ou restringem a funcionalidade em dispositivos com root, pois o acesso root remove as restrições da sandbox do Android e abre a possibilidade de interceptação de tráfego, leitura de memória de processos e adulteração de dados. De acordo com OWASP Mobile Top 10 (2024), a ausência de Root Detection se enquadra na categoria M8 (Security Decisions via Untrusted Inputs). Root Detection é construído em uma combinação de verificações estáticas do sistema de arquivos e análise dinâmica do comportamento em tempo de execução.
Principais pontos
Root Detection é um mecanismo de software que detecta a presença de acesso root em um dispositivo Android. O acesso root fornece controle total sobre o sistema operacional, permitindo que aplicativos e scripts executem comandos com UID 0. Em um dispositivo com root, o isolamento de aplicativos (Android Sandbox) é perdido, tornando possível interceptar entrada do teclado, ler bancos de dados SQLite de outros aplicativos, injetar código em processos e substituir certificados SSL no armazenamento confiável.
Para aplicativos financeiros e corporativos, operar em um dispositivo com root apresenta um risco inaceitável: um invasor obtém acesso a tokens, chaves de sessão e dados pessoais. Reguladores, incluindo o PCI Security Standards Council, exigem que aplicativos de pagamento detectem e respondam ao acesso root. Em resposta, desenvolvedores Android incorporam Root Detection como parte de uma estratégia de proteção proativa.
Existem duas abordagens para detecção: estática, que analisa o sistema de arquivos e pacotes instalados, e dinâmica, que realiza verificações em tempo de execução. A abordagem combinada é considerada a mais confiável, pois cobre diferentes vetores de bypass. De acordo com um estudo da NowSecure (2025), 76% dos aplicativos bancários no top 100 do Google Play contêm alguma forma de Root Detection.
Os métodos estáticos são executados na inicialização do aplicativo e verificam sinais de acesso root deixados por ferramentas de root no sistema de arquivos. Esses métodos não exigem execução de comandos privilegiados e funcionam no contexto de um aplicativo normal.
O principal indicador de acesso root é a presença do arquivo executável su em caminhos padrão: /system/bin/su, /system/xbin/su, /sbin/su, /su/bin/su. O aplicativo verifica a existência do arquivo via File.exists() ou implementação nativa de access() da libc. Adicionalmente, pode-se tentar executar su --version ou su -c id e verificar o código de saída.
Aplicativos típicos para gerenciar acesso root: Superuser, SuperSU, Magisk Manager, KingRoot. Sua presença é verificada via PackageManager.getPackageInfo() ou leitura do diretório /data/app/. Pacotes para verificar: com.topjohnwu.magisk, eu.chainfire.supersu, com.noshufou.android.su, com.thirdparty.superuser, com.koushikdutta.superuser, com.zacharee1.systemuituner.
O Android armazena informações de estado do sistema em propriedades do sistema, acessíveis via System.getProperty e Build.TAGS. Se Build.TAGS contém test-keys em vez de release-keys, isso indica firmware personalizado, frequentemente com acesso root. Adicionalmente, ro.build.tags, ro.debuggable e ro.secure são verificados lendo /system/build.prop.
public class RootDetectionChecker {
private static final String[] SU_PATHS = {
"/system/bin/su",
"/system/xbin/su",
"/sbin/su",
"/su/bin/su",
"/system/sd/xbin/su"
};
public boolean checkRootByFiles() {
for (String path : SU_PATHS) {
if (new File(path).exists()) {
return true;
}
}
return false;
}
public boolean checkRootByPackages(Context ctx) {
String[] packages = {
"com.topjohnwu.magisk",
"eu.chainfire.supersu",
"com.noshufou.android.su",
"com.koushikdutta.superuser"
};
for (String pkg : packages) {
try {
ctx.getPackageManager().getPackageInfo(pkg, 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
// package not found
}
}
return false;
}
}
Os métodos dinâmicos são executados durante a operação do aplicativo e analisam o ambiente de execução. Ao contrário dos estáticos, podem detectar root oculto via Magisk Hide ou Zygisk, pois verificam o comportamento do sistema, não apenas a estrutura de arquivos.
Com acesso root, algumas partições do sistema são montadas com a flag rw (read-write) em vez de ro (read-only). O aplicativo lê /proc/mounts e verifica se /system está montado como ro. Se /system está montado como rw, isso indica um sistema modificado. Adicionalmente, verifica-se a presença de montagem /su via Magisk.
O Modo Seguro do Android desativa aplicativos de terceiros, incluindo gerenciadores de root. Uma implementação correta de Root Detection pode verificar se o dispositivo está em modo seguro. Se o aplicativo detecta que os gerenciadores de root não estão visíveis, mas o binário su existe, isso é um indicador de Magisk Hide.
Tentar executar su -c id via ProcessBuilder ou Runtime.exec é um teste direto de acesso root. No entanto, o Magisk pode interceptar essa chamada. Uma abordagem mais confiável é a verificação via código nativo: abrir /proc/1/limits ou /proc/self/maps e analisar o UID dos processos em execução. Se o aplicativo puder obter UID 0 ou ler arquivos acessíveis apenas ao root, o dispositivo está comprometido.
public boolean checkRootDynamically() {
// Build flags check
String buildTags = Build.TAGS;
if (buildTags != null && buildTags.contains("test-keys")) {
return true;
}
// Checking /system mount
try {
BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream("/proc/mounts"))
);
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("/system")
&& line.contains("rw")) {
reader.close();
return true;
}
}
reader.close();
} catch (IOException e) {
// error reading mounts
}
return false;
}
Root Detection implementado em Java é facilmente contornado via módulos Xposed ou Frida, que interceptam métodos Java e substituem valores de retorno. A implementação nativa em C++ via JNI é significativamente mais resistente: ferramentas de análise dinâmica que operam no nível Java não veem chamadas nativas da libc como stat, access, popen e dlopen.
#include <unistd.h>
#include <sys/stat.h>
#include <cstring>
#include <vector>
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_checker_RootCheck_nativeCheck(
JNIEnv* env, jobject instance) {
std::vector<const char*> paths = {
"/system/bin/su",
"/system/xbin/su",
"/sbin/su",
"/data/local/su"
};
struct stat st;
for (const char* path : paths) {
if (stat(path, &st) == 0) {
return JNI_TRUE;
}
}
return JNI_FALSE;
}
A verificação nativa não usa a API Java, tornando-a invisível para ferramentas de bypass que operam no nível Dalvik/ART. Para proteção adicional, recomenda-se armazenar constantes (lista de caminhos) não em seção somente leitura, mas calculá-las através de funções reversíveis simples. A chamada stat da libc acessa diretamente o kernel Linux, ignorando wrappers Java, e não pode ser interceptada via Xposed.
Desenvolvedores de proteção precisam entender os métodos de bypass existentes para construir um sistema de detecção robusto. Cada método de bypass requer uma contramedida no nível apropriado.
Magisk é a ferramenta de root mais popular no Android 9–14. O Magisk Hide oculta a presença de su do /proc e falsifica resultados de verificação de caminhos. O Magisk opera no nível do kernel e intercepta stat() e access() antes que o aplicativo os veja. Contramedida: verificar a presença do próprio Magisk através da existência de /sbin/.magisk ou verificar através da leitura dos próprios maps do aplicativo — o Magisk injeta sua biblioteca em cada processo.
Frida é uma ferramenta de instrumentação dinâmica que pode interceptar funções nativas via Ptrace ou Dobby. A Frida substitui o valor de retorno de qualquer verificação, falsificando o resultado de stat para ENOENT. Contramedida: verificar a integridade de funções nativas calculando a soma de verificação das instruções na memória e detectar Frida através da análise de /proc/self/maps pela presença de frida-agent.so ou frida-helper.
Root Detection implementado em Java é removido em 2–3 minutos: o APK é descompilado via apktool, o valor de retorno do método é alterado para false no código smali, o APK é reconstruído e assinado. Contramedida: transferir a lógica crítica para código nativo e verificar a assinatura digital do aplicativo em tempo de execução via API de Signature ou comparar o hash do APK com uma referência no servidor.
Root Detection eficaz é construído sobre uma arquitetura em múltiplas camadas. Nenhum método isolado fornece proteção suficiente. A combinação de verificações estáticas e dinâmicas, código nativo e verificação no lado do servidor fornece a máxima resistência.
Não confie apenas na verificação do lado do cliente. Envie os resultados do Root Detection para o servidor junto com um token de sessão de uso único. O servidor decide se bloqueia ou restringe a funcionalidade. Isso previne ataques no nível da API, onde o aplicativo cliente pode ser modificado enquanto o servidor permanece uma parte confiável.
O código do Root Detection deve ser ofuscado. Se um invasor vir uma sequência clara de verificações de caminhos su no jadx, o bypass levará minutos. Use ProGuard ou DexGuard para ofuscar o fluxo de controle e criptografar strings. A ofuscação aumenta o tempo de análise do código de proteção de minutos para horas.
A lista de caminhos, pacotes e indicadores verificados deve ser atualizada a cada versão do aplicativo. Novas ferramentas de root e bypass aparecem mensalmente. Uma lista estática que não mudou em um ano não detectará métodos modernos. Recomenda-se carregar assinaturas atuais do servidor na inicialização do aplicativo antes de realizar as verificações.
Perguntas frequentes
Root Detection protege contra a execução de um aplicativo em um dispositivo onde a sandbox do Android está desabilitada. Em um dispositivo com root, qualquer aplicativo pode ler dados de outros aplicativos. Aplicativos bancários e de pagamento são obrigados a bloquear a operação em dispositivos com root de acordo com os requisitos PCI DSS e recomendações OWASP Mobile Security.
Magisk Hide usa um mecanismo de montagem de namespace (mount namespace). Para cada processo na lista de exclusão, o Magisk cria um namespace isolado onde o binário su é invisível. As chamadas de sistema stat, access e open neste namespace não veem os arquivos do Magisk. O Magisk pode ser detectado verificando a presença de /proc/self/maps e procurando por dumps do magisk.
Sim, se o aplicativo não verificar a integridade do seu código. Através do Frida, é possível interceptar o método Java de verificação e forçá-lo a retornar false. A contramedida é uma implementação nativa da lógica crítica em C++ e verificação de integridade via hash do arquivo DEX. Sem ofuscação, qualquer Root Detection em Java é contornado em 5–10 minutos.
SafetyNet (obsoleto) e Play Integrity API são verificações do lado do servidor do Google que confirmam a integridade do dispositivo. Elas incluem verificação do bootloader, assinatura do sistema e status de root. Play Integrity API é a substituição recomendada para SafetyNet, fornecendo três níveis: BASIC, DEVICE e STRONG. O Root Detection no cliente complementa a atestação do servidor.
Instale o aplicativo em um dispositivo com root real (por exemplo, um Pixel com Magisk). Verifique se o bloqueio é ativado. Em seguida, tente ocultar o root via Magisk Hide para seu aplicativo e reinicie o teste. Para teste aprofundado, use Frida para interceptar os métodos alvo e certifique-se de que a proteção nativa não pode ser contornada.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também