L’attaccante analizza il file con strumenti statici. Qui contano nomi, stringhe, grafo di controllo e pattern riconoscibili.
Offuscare codice non significa renderlo inviolabile.
Significa aumentare il costo dell’analisi. A volte basta far perdere tempo. A volte serve proteggere una parte precisa del flusso. In ogni caso, va progettato con un modello di minaccia reale, non come decorazione.
Il punto di partenza
Un obfuscator prende un programma e ne produce uno equivalente dal punto di vista osservabile, ma più difficile da capire, modificare o ricostruire. Questo non crea sicurezza assoluta: se un segreto deve comparire in memoria, un attaccante abbastanza motivato può arrivarci.
Il valore pratico sta nel compromesso: rendere costosa l’analisi statica, disturbare quella dinamica e ridurre la probabilità che un attaccante scelga proprio quel binario come bersaglio facile.
Prima domanda: contro chi?
L’attaccante può eseguire, debuggare, tracciare, usare hook o emulatori. L’offuscamento diventa solo uno strato.
È lo scenario più comune: non devi fermare tutti, devi rendere poco conveniente l’analisi rispetto al valore ottenuto.
Tecniche che hanno senso
1. Stringhe e dati immediatamente riconoscibili
URL, nomi di endpoint, messaggi interni e token non dovrebbero stare in chiaro nel binario. Codificarli e decodificarli solo al bisogno è una protezione semplice, ma utile contro analisi superficiali.
fn decrypt_xor(data: &[u8], key: u8) -> String {
let bytes: Vec<u8> = data.iter().map(|b| b ^ key).collect();
String::from_utf8_lossy(&bytes).into_owned()
}
fn main() {
let encoded = [0x2a, 0x27, 0x30, 0x30];
let value = decrypt_xor(&encoded, 0x42);
println!("{}", value);
}
Da sola non basta: una chiave statica si recupera. Meglio derivare chiavi a runtime, spezzare dati sensibili e ridurre il tempo in cui restano in chiaro.
2. Control-flow flattening
Il flattening rende meno leggibile il grafo di controllo, trasformando una logica lineare in un dispatcher. È utile contro decompilatori e lettura rapida, ma può peggiorare performance e manutenzione.
3. Virtualizzazione
La virtualizzazione traduce porzioni di programma in bytecode custom, eseguito da un interprete interno. È più pesante, ma spesso più efficace sulle funzioni davvero sensibili.
fn exec(code: &[u8]) -> i64 {
let mut pc = 0usize;
let mut stack = Vec::new();
while pc < code.len() {
match code[pc] {
0x01 => { pc += 1; stack.push(code[pc] as i64); }
0x02 => {
let b = stack.pop().unwrap_or(0);
let a = stack.pop().unwrap_or(0);
stack.push(a + b);
}
0xff => break,
_ => break,
}
pc += 1;
}
stack.pop().unwrap_or(0)
}
4. Controlli anti-debug e anti-tamper
Controlli su debugger, timing, checksum delle sezioni eseguibili o integrità dei file possono rallentare l’analisi dinamica. Non vanno trattati come muri, ma come attrito.
fn being_traced() -> bool {
let Ok(status) = std::fs::read_to_string("/proc/self/status") else {
return false;
};
status
.lines()
.find(|line| line.starts_with("TracerPid:"))
.and_then(|line| line.split_whitespace().last())
.is_some_and(|pid| pid != "0")
}
Come capire se serve davvero
- Misura il tempo necessario a recuperare una stringa, una chiave o una funzione critica.
- Confronta output di Ghidra, IDA o altri decompilatori prima e dopo la trasformazione.
- Controlla l’impatto su performance, dimensione binaria, crash rate e debugging interno.
La baseline non offuscata deve restare disponibile per audit, test e manutenzione. L’offuscamento va applicato alla release, non al modo in cui il team ragiona sul codice.
Regole pratiche
- Proteggi poche parti ad alto valore, non tutto il programma senza criterio.
- Non spedire segreti statici quando puoi derivarli o recuperarli da un canale controllato.
- Automatizza test di equivalenza dopo ogni trasformazione.
- Prevedi rotazione, revoca e aggiornamento: prima o poi qualcosa verrà estratto.
L’offuscamento è utile quando è mirato, misurabile e inserito in una strategia più ampia. Non sostituisce sicurezza, design corretto o gestione seria dei segreti. Aggiunge costo. E in molti casi è esattamente quello che serve.