Root Detection è un meccanismo di sicurezza che protegge le applicazioni Android dall'esecuzione su dispositivi con privilegi di superutente. Le applicazioni bancarie, di pagamento e aziendali bloccano o limitano le funzionalità sui dispositivi rootati, perché l'accesso root rimuove le restrizioni della sandbox Android e apre la possibilità di intercettare il traffico, leggere la memoria dei processi e manomettere i dati. Secondo OWASP Mobile Top 10 (2024), l'assenza di Root Detection rientra nella categoria M8 (Security Decisions via Untrusted Inputs). Root Detection si basa su una combinazione di controlli statici del file system e analisi dinamica del comportamento a runtime.
Punti chiave
Root Detection è un meccanismo software che rileva la presenza di accesso root su un dispositivo Android. L'accesso root fornisce il controllo completo sul sistema operativo, consentendo ad applicazioni e script di eseguire comandi con UID 0. Su un dispositivo rootato, l'isolamento delle applicazioni (Android Sandbox) viene perso, rendendo possibile intercettare l'input della tastiera, leggere i database SQLite di altre applicazioni, iniettare codice nei processi e sostituire i certificati SSL nell'archivio fidato.
Per le applicazioni finanziarie e aziendali, operare su un dispositivo rootato presenta un rischio inaccettabile: un aggressore ottiene accesso a token, chiavi di sessione e dati personali. Gli enti regolatori, incluso il PCI Security Standards Council, richiedono che le applicazioni di pagamento rilevino e rispondano all'accesso root. In risposta, gli sviluppatori Android integrano Root Detection come parte di una strategia di protezione proattiva.
Esistono due approcci al rilevamento: statico, che analizza il file system e i pacchetti installati, e dinamico, che esegue controlli a runtime. L'approccio combinato è considerato il più affidabile, poiché copre diversi vettori di elusione. Secondo uno studio di NowSecure (2025), il 76% delle applicazioni bancarie nella top 100 di Google Play contiene qualche forma di Root Detection.
I metodi statici vengono eseguiti all'avvio dell'applicazione e verificano i segni di accesso root lasciati dagli strumenti di rooting nel file system. Questi metodi non richiedono l'esecuzione di comandi privilegiati e funzionano nel contesto di un'applicazione normale.
Il principale indicatore di accesso root è la presenza del file eseguibile su nei percorsi standard: /system/bin/su, /system/xbin/su, /sbin/su, /su/bin/su. L'applicazione verifica l'esistenza del file tramite File.exists() o un'implementazione nativa di access() da libc. Inoltre, si può tentare di eseguire su --version o su -c id e verificare il codice di uscita.
Applicazioni tipiche per gestire l'accesso root: Superuser, SuperSU, Magisk Manager, KingRoot. La loro presenza viene verificata tramite PackageManager.getPackageInfo() o leggendo la directory /data/app/. Pacchetti da verificare: com.topjohnwu.magisk, eu.chainfire.supersu, com.noshufou.android.su, com.thirdparty.superuser, com.koushikdutta.superuser, com.zacharee1.systemuituner.
Android memorizza le informazioni sullo stato del sistema nelle proprietà di sistema, accessibili tramite System.getProperty e Build.TAGS. Se Build.TAGS contiene test-keys invece di release-keys, questo indica un firmware personalizzato, spesso con accesso root. Inoltre, ro.build.tags, ro.debuggable e ro.secure vengono verificati leggendo /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;
}
}
I metodi dinamici vengono eseguiti durante il funzionamento dell'applicazione e analizzano l'ambiente di esecuzione. A differenza dei metodi statici, possono rilevare il rooting nascosto tramite Magisk Hide o Zygisk, poiché verificano il comportamento del sistema e non solo la struttura dei file.
Con l'accesso root, alcune partizioni di sistema vengono montate con il flag rw (lettura-scrittura) invece di ro (sola lettura). L'applicazione legge /proc/mounts e verifica che /system sia montato come ro. Se /system è montato come rw, questo indica un sistema modificato. Inoltre, viene verificata la presenza del montaggio di /su tramite Magisk.
La modalità provvisoria di Android disabilita le applicazioni di terze parti, inclusi i gestori root. Un'implementazione corretta di Root Detection può verificare se il dispositivo è in esecuzione in modalità provvisoria. Se l'applicazione rileva che i gestori root non sono visibili ma il binario su esiste, questo è un indicatore di Magisk Hide.
Tentare di eseguire su -c id tramite ProcessBuilder o Runtime.exec è un test diretto dell'accesso root. Tuttavia, Magisk può intercettare questa chiamata. Un approccio più affidabile è la verifica tramite codice nativo: aprire /proc/1/limits o /proc/self/maps e analizzare l'UID dei processi in esecuzione. Se l'applicazione può ottenere UID 0 o leggere file accessibili solo a root, il dispositivo è compromesso.
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 implementato in Java viene facilmente aggirato tramite moduli Xposed o Frida, che intercettano i metodi Java e sostituiscono i valori di ritorno. L'implementazione nativa in C++ tramite JNI è significativamente più resistente: gli strumenti di analisi dinamica che operano a livello Java non vedono le chiamate native libc come 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;
}
La verifica nativa non utilizza l'API Java, rendendola invisibile agli strumenti di elusione che operano a livello Dalvik/ART. Per una protezione aggiuntiva, si consiglia di non memorizzare le costanti (elenco dei percorsi) in una sezione di sola lettura, ma di calcolarle tramite semplici funzioni reversibili. La chiamata stat da libc accede direttamente al kernel Linux, bypassando i wrapper Java, e non può essere intercettata tramite Xposed.
Gli sviluppatori di protezione devono comprendere i metodi di elusione esistenti per costruire un sistema di rilevamento robusto. Ogni metodo di elusione richiede una contromisura al livello appropriato.
Magisk è lo strumento di rooting più popolare su Android 9–14. Magisk Hide nasconde la presenza di su da /proc e falsifica i risultati della verifica dei percorsi. Magisk opera a livello di kernel e intercetta stat() e access() prima che l'applicazione li veda. Contromisura: verificare la presenza di Magisk stesso tramite l'esistenza di /sbin/.magisk o verificare tramite la lettura delle proprie mappe dell'applicazione — Magisk inietta la sua libreria in ogni processo.
Frida è uno strumento di strumentazione dinamica che può intercettare funzioni native tramite Ptrace o Dobby. Frida sostituisce il valore di ritorno di qualsiasi verifica, falsificando il risultato di stat in ENOENT. Contromisura: verificare l'integrità delle funzioni native calcolando un checksum delle istruzioni in memoria e rilevare Frida tramite l'analisi di /proc/self/maps per la presenza di frida-agent.so o frida-helper.
Root Detection implementato in Java viene rimosso in 2–3 minuti: l'APK viene decompilato tramite apktool, il valore di ritorno del metodo viene modificato in false nel codice smali, l'APK viene ricostruito e firmato. Contromisura: trasferire la logica critica nel codice nativo e verificare la firma digitale dell'applicazione in fase di runtime tramite l'API di Signature o confrontare l'hash dell'APK con un riferimento sul server.
Un Root Detection efficace si basa su un'architettura multilivello. Nessun singolo metodo fornisce una protezione sufficiente da solo. La combinazione di controlli statici e dinamici, codice nativo e verifica lato server offre la massima resistenza.
Non affidarsi esclusivamente alla verifica lato client. Inviare i risultati di Root Detection al server insieme a un token di sessione monouso. Il server decide se bloccare o limitare le funzionalità. Questo previene gli attacchi a livello API, dove l'applicazione client può essere modificata mentre il server rimane una parte fidata.
Il codice di Root Detection deve essere offuscato. Se un aggressore vede una sequenza chiara di controlli dei percorsi su in jadx, l'elusione richiederà minuti. Utilizzare ProGuard o DexGuard per offuscare il flusso di controllo e crittografare le stringhe. L'offuscamento aumenta il tempo di analisi del codice di protezione da minuti a ore.
L'elenco dei percorsi, dei pacchetti e degli indicatori controllati deve essere aggiornato con ogni versione dell'applicazione. Nuovi strumenti di rooting e elusione appaiono mensilmente. Un elenco statico che non è cambiato in un anno non rileverà i metodi moderni. Si consiglia di caricare le firme attuali dal server all'avvio dell'applicazione prima di eseguire i controlli.
Domande frequenti
Root Detection protegge dall'esecuzione di un'applicazione su un dispositivo in cui la sandbox Android è disabilitata. Su un dispositivo rootato, qualsiasi applicazione può leggere i dati di altre applicazioni. Le applicazioni bancarie e di pagamento sono obbligate a bloccare il funzionamento su dispositivi rootati secondo i requisiti PCI DSS e le raccomandazioni OWASP Mobile Security.
Magisk Hide utilizza un meccanismo di namespace di montaggio (mount namespace). Per ogni processo nell'elenco di esclusione, Magisk crea un namespace isolato in cui il binario su è invisibile. Le chiamate di sistema stat, access e open in questo namespace non vedono i file di Magisk. Magisk può essere rilevato verificando la presenza di /proc/self/maps e cercando dump magisk.
Sì, se l'applicazione non verifica l'integrità del suo codice. Tramite Frida, è possibile intercettare il metodo Java di verifica e forzarlo a restituire false. La contromisura è un'implementazione nativa della logica critica in C++ e la verifica dell'integrità tramite hash del file DEX. Senza offuscamento, qualsiasi Root Detection in Java viene aggirato in 5–10 minuti.
SafetyNet (obsoleto) e Play Integrity API sono controlli lato server di Google che confermano l'integrità del dispositivo. Includono la verifica del bootloader, della firma di sistema e dello stato root. Play Integrity API è il sostituto raccomandato per SafetyNet, che fornisce tre livelli: BASIC, DEVICE e STRONG. Root Detection lato client integra l'attestazione lato server.
Installare l'applicazione su un dispositivo rootato reale (ad esempio, un Pixel con Magisk). Verificare se il blocco si attiva. Quindi provare a nascondere il root tramite Magisk Hide per la propria applicazione e riavviare il test. Per un test approfondito, utilizzare Frida per intercettare i metodi target e assicurarsi che la protezione nativa non possa essere aggirata.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche