Obfuscator è uno strumento che trasforma il codice sorgente in una forma difficile da leggere senza modificarne la funzionalità. Obfuscator viene utilizzato per proteggere la proprietà intellettuale, complicare l'analisi del codice e prevenire il reverse engineering. Secondo la Documentazione di Android Developers, l'offuscamento tramite R8 e ProGuard è un passaggio standard nella build di produzione delle applicazioni Android.
Punti chiave
Obfuscator è un programma che esegue l'offuscamento: trasformare codice leggibile in codice funzionalmente equivalente ma illeggibile per l'uomo. I compiti principali di un offuscatore sono il mangling degli identificatori, la rimozione delle informazioni di debug, l'offuscamento del flusso di controllo e la crittografia delle stringhe.
L'offuscamento non è crittografia. Il codice crittografato non può essere eseguito senza decrittografia. Il codice offuscato viene eseguito direttamente su JVM, ART o un motore JavaScript, ma è estremamente difficile da comprendere per l'uomo. L'offuscamento non fornisce una protezione assoluta — uno specialista determinato può sempre recuperare la logica tramite un deoffuscatore o il debug in fase di esecuzione.
Il primo offuscatore commerciale ProGuard è apparso nel 2002 come strumento per applet Java. Con la crescita di Android (2008), ProGuard è diventato lo standard per lo sviluppo mobile. Nel 2018, Google ha rilasciato R8 come sostituto di ProGuard per Android Gradle Plugin 3.4. R8 è 2–3 volte più veloce di ProGuard e genera bytecode più compatto grazie all'ottimizzazione approfondita a livello SSA (Static Single Assignment) — un formato di rappresentazione intermedia che consente l'analisi del flusso di dati.
Nello sviluppo web, l'offuscamento si è evoluto da semplici minifier (YUI Compressor, 2007) a complessi trasformatori AST (Obfuscator.io, 2016). Gli offuscatori JavaScript moderni utilizzano il control flow flattening, predicati opachi (condizioni sempre vere o false ma non ovvie per l'analizzatore) e la crittografia delle stringhe con auto-decrittografia in fase di esecuzione. Jscrambler (2012) integra l'offuscamento con la protezione del debugger e meccanismi DRM.
L'ambito di applicazione dell'offuscamento è ampio. Nello sviluppo mobile, gli offuscatori proteggono il codice dal furto tramite decompilatori APK (jadx, APKTool, dex2jar). Nello sviluppo web, l'offuscamento JavaScript protegge algoritmi, chiavi API e logica di business lato client. Nelle librerie e SDK, l'offuscamento impedisce ai concorrenti di utilizzare il codice.
| Tecnica | Prima dell'offuscamento | Dopo l'offuscamento |
|---|---|---|
| Rinominazione classi | NetworkManager | a |
| Rinominazione metodi | sendRequest() | b() |
| Crittografia stringhe | "API_KEY" | decrypt("x9fK2p") |
| Offuscamento condizioni | if (a > b) | if (a > b ? true : false) |
Identifier Mangling è il metodo più comune. I nomi di classi, metodi, campi e variabili vengono sostituiti con stringhe corte e non informative: a, b, c, aa, ab. Ciò rende difficile comprendere lo scopo di ogni elemento del codice. ProGuard e R8 utilizzano nomi identici per tipi diversi (classe A, campo A, metodo A), complicando ulteriormente l'analisi.
Control Flow Obfuscation modifica la struttura del codice in modo che la sequenza lineare diventi non ovvia. Vengono aggiunti rami morti, le condizioni vengono invertite (if (!a) invece di if (a)), vengono inseriti operatori simili a goto (break/continue con etichette). Ciò rende l'analisi tramite decompilatore e debugger estremamente dispendiosa in termini di tempo.
// Codice sorgente
function authenticate(token) {
const url = "https://api.example.com/auth";
const headers = { Authorization: "Bearer " + token };
return fetch(url, { method: "POST", headers });
}// Dopo Obfuscator.io in modalità alta
const _0x4f2e = ["https://api.example.com/auth",
"Authorization", "Bearer ", "POST"];
(function(_0x5a3b, _0x4f2e) {
const _0x1c2d = function(_0x3e4f) {
while (--_0x3e4f) {
_0x5a3b["push"](_0x5a3b["shift"]());
}
};
_0x1c2d(++_0x4f2e);
}(_0x4f2e, _0x1c2d));
function _0x1c2d(_0x5a3b, _0x4f2e) {
return _0x4f2e[_0x5a3b];
}
function _0x3e4f(_0x1c2d) {
const _0x5a3b = _0x1c2d(0, "https://api.example.com/auth");
const _0x4f2e = { Authorization: "Bearer " + _0x1c2d };
return fetch(_0x5a3b, { method: "POST", headers: _0x4f2e });
}Obfuscator.io ha aggiunto array di stringhe, una funzione autoinvocante per mescolare l'array, rinominato tutti gli identificatori e sostituito le stringhe con indici dell'array. Le 5 righe di codice originali sono diventate 20+ righe illeggibili, ma la funzionalità authenticate(token) è completamente preservata. Il deoffuscamento è possibile tramite analisi AST, ma richiede tempo.
ProGuard è un offuscatore classico per Java e Android, in uso dal 2002. ProGuard esegue tre compiti: compressione (rimozione di classi e metodi inutilizzati), ottimizzazione (ottimizzazione del bytecode) e offuscamento (rinominazione degli identificatori). ProGuard è integrato in Android Gradle Plugin tramite il file proguard-rules.pro con regole di esclusione per le librerie.
R8 è un offuscatore più moderno incluso in Android Gradle Plugin da AGP 3.4. R8 esegue le stesse funzioni di ProGuard ma è più veloce (scritto in Kotlin da zero) e più efficiente (migliore ottimizzazione del bytecode per ART Runtime). R8 viene configurato utilizzando gli stessi file proguard-rules.pro di ProGuard. Per abilitare R8, è sufficiente impostare minifyEnabled true in build.gradle.
Il file di mapping è l'output di R8/ProGuard che contiene la corrispondenza tra nomi originali e offuscati di classi, metodi e campi. Il file di mapping è fondamentale per analizzare i crash report: senza di esso, una traccia dello stack conterrà a.a.b invece di com.example.app.MainActivity.onCreate. Firebase Crashlytics e Sentry caricano automaticamente i file di mapping e ripristinano i nomi originali nei report.
I file di mapping devono essere caricati su Firebase o Sentry a ogni nuova versione dell'applicazione. Se il file di mapping viene perso o non caricato, tutti i crash report dopo l'offuscamento diventano illeggibili. Android Gradle Plugin salva automaticamente il file di mapping in build/outputs/mapping/release/mapping.txt. Firebase utilizza Crashlytics Gradle Plugin, che carica il mapping durante la build di release.
// app/build.gradle — offuscamento tramite R8
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(
"proguard-android-optimize.txt"),
"proguard-rules.pro"
}
}
}# proguard-rules.pro — regole di conservazione
# Conservare il modello dati per Gson
-keep class com.example.model.** { *; }
# Conservare le classi per le interfacce Retrofit
-keep,allowobfuscation interface com.example.api.*
# Non offuscare le attività pubbliche
-keep class * extends android.app.Activity {
public protected *;
}
# Rimuovere i log in produzione
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(...);
public static int v(...);
public static int d(...);
}Le regole -keep in proguard-rules.pro sono critiche — senza di esse, R8 rimuoverà o rinominerà classi e metodi utilizzati tramite reflection (Gson, Retrofit, Room). assumentSideEffects rimuove le chiamate Log.v e Log.d dal codice di produzione. Librerie come Gson, Retrofit e OkHttp forniscono regole pronte in proguard.txt all'interno dell'AAR.
Obfuscator.io è l'offuscatore JavaScript open source più popolare che supporta la rinominazione degli identificatori, la crittografia delle stringhe, il control flow flattening e la protezione del debugger. La configurazione viene effettuata tramite JSON config o CLI. La versione gratuita supporta i metodi di base; la versione Enterprise aggiunge codice polimorfico e autoprotezione.
Jscrambler è un offuscatore JavaScript commerciale con protezione avanzata: trasformazioni polimorfiche (ogni esecuzione genera nuovo codice offuscato), protezione del debugger (rilevamento di DevTools), protezione dagli screenshot (self-defending) e meccanismi di scadenza (il codice smette di funzionare dopo una data specifica). Jscrambler viene utilizzato in applicazioni bancarie e sistemi DRM.
// obfuscate.js — configurazione Obfuscator.io
const JavaScriptObfuscator = require("javascript-obfuscator");
const fs = require("fs");
const code = fs.readFileSync("app.js", "utf8");
const result = JavaScriptObfuscator.obfuscate(code, {
compact: true,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 0.75,
numbersToExpressions: true,
simplify: false,
stringArray: true,
stringArrayThreshold: 0.8,
debugProtection: true,
disableConsoleOutput: true,
});
fs.writeFileSync("app.obfuscated.js", result.code);Parametri di Obfuscator.io: controlFlowFlattening: 0.75 offusca il flusso di controllo nel 75% dei blocchi; stringArray: true sposta le stringhe in un array; debugProtection impedisce l'apertura di DevTools; disableConsoleOutput rimuove console.log. Più alti sono i threshold, maggiore è il tempo di offuscamento e la dimensione del codice, ma più difficile è l'analisi.
L'offuscamento non protegge dall'analisi in fase di esecuzione. Un attaccante può eseguire l'applicazione in un debugger (Frida, Objection, Xposed) e intercettare i metodi in tempo reale. L'offuscamento protegge dall'analisi statica (decompilazione APK, lettura del bytecode) ma non dall'analisi dinamica. Per la protezione in fase di esecuzione sono necessarie misure aggiuntive: SSL Pinning, Root Detection, Integrity Verification.
La dimensione dell'applicazione può aumentare del 20–50% dopo l'offuscamento. Il control flow obfuscation aggiunge rami morti e duplica le condizioni — ciò aumenta la dimensione del bytecode. La crittografia delle stringhe sostituisce i letterali di stringa brevi con chiamate decrypt(), il che aumenta anche la dimensione. Per le applicazioni mobili questo è critico, poiché la dimensione dell'APK influisce direttamente sulla conversione in Google Play.
Le prestazioni sono anche influenzate. Il control flow obfuscation aggiunge controlli e ramificazioni aggiuntive, aumentando il tempo di esecuzione dei metodi del 5–15%. La crittografia delle stringhe aggiunge una chiamata decrypt a ogni accesso a una stringa. Per le funzioni critiche per le prestazioni (onDraw in Android, render in React), l'offuscamento dovrebbe essere disabilitato tramite regole -keep.
Domande frequenti
La crittografia rende il codice non eseguibile senza decrittografia — per l'esecuzione è necessario un decrittatore. L'offuscamento rende il codice illeggibile ma direttamente eseguibile. La crittografia fornisce una protezione più forte ma richiede un caricatore di decrittografia, che a sua volta può essere analizzato.
Il deoffuscamento è possibile ma laborioso. Strumenti come jadx, JEB Decompiler e UnConfuser ripristinano il bytecode con deoffuscamento parziale. Il ripristino completo del codice sorgente originale con i nomi originali è impossibile — i nomi vengono persi irreversibilmente. Gli offuscatori moderni (R8, ProGuard) sono resistenti al deoffuscamento automatico.
Google Play non richiede l'offuscamento ma lo raccomanda vivamente tramite minifyEnabled in build.gradle. Le applicazioni senza offuscamento vengono facilmente decompilate tramite APKTool e jadx, rendendole vulnerabili al furto di chiavi API, alla modifica e alla pirateria. La maggior parte delle grandi applicazioni utilizza R8 o ProGuard.
I crash report dopo l'offuscamento contengono nomi offuscati (a.b.c invece di com.example.app.MainActivity). I file di mapping generati da R8/ProGuard vengono utilizzati per il ripristino. Il file di mapping deve essere caricato su Firebase Crashlytics o Sentry per il deoffuscamento automatico delle tracce dello stack.
String Encryption sostituisce i letterali di stringa (chiavi API, URL, messaggi) con dati crittografati e una chiamata alla funzione decrypt in fase di esecuzione. Ciò protegge le stringhe sensibili dalla lettura tramite semplice ricerca nel codice decompilato. R8/ProGuard supportano la crittografia delle stringhe tramite la regola -encryptstrings.
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