Claudio Brotto

[APPTXT] Obfuscation: Considerazioni

Questo è di gran lunga l'argomento più gettonato tra i miei appunti.

E pensare che in *nessun caso* era a programma una discussione esplicita riguardo l'obfuscation degli assembly.

Le cose saltano fuori, per così dire, in maniera anche casuale e a volte ripetitiva: in *ogni caso* ho utilizzato almeno una volta Reflector, magari per illustrare concetti anche molto lontani tra loro.

Tempo massimo per la domanda direi 10 secondi: "Ma quindi stiamo guardando i sorgenti di .NET ?", così come "Ma allora i clienti vedono il nostro codice ?".

Qui scrivo le mie considerazioni su queste tematiche, anche se rimando comunque ad un paio di risorse (ce ne saranno migliaia) che possono affrontare il discorso in maniera più esaustiva.

Premessa (mooooolto breve e incompleta).

Il codice sorgente .NET (C#, VB.NET e *tutti* gli altri linguaggi .NET) all'atto della compilazione viene tradotto in un "componente" (d'ora in poi: assembly) che contiene fondamentalmente due cose: metadati e IL.

IL = Intermediate Language = il linguaggio della macchina virtuale .NET. Detta in altri termini, IL è il set di istruzioni che il Common Language Runtime comprende e traduce, in fase di esecuzione, in istruzioni native della piattaforma all'interno della quale l'applicazione gira.

I metadati descrivono l'assembly a cui si riferiscono in termini di tipi contenuti (campi, proprietà, metodi, ...) e di altri assembly ai quali questo fa riferimento. I metadati sono tutto tranne che opzionali ! Di conseguenza ogni assembly, ne possiamo essere certi, è auto-descrittivo.

Problema.

Il problema nasce nel momento in cui le informazioni di cui sopra (metadati e stream IL), oltre che dal CLR per l'esecuzione del componente, vengono utilizzate da qualche strumento per l'indagine del contenuto del componente.

Quale tool ?

Ne indico un paio, con questa considerazione: la class library del .NET Framework espone funzionalità di ispezione degli assembly tramite Reflection, che già potrebbe essere sufficiente per scrivere un tool del genere. Se le funzionalità sono troppo limitate e ci vogliamo divertire, possiamo utilizzare le interfacce unmanaged, che consentono un livello di esame più basso (quindi più complesso e più potente).

I tool in questione sono ILDASM e Reflector.

ILDASM (IL Disassembler) è uno strumento scritto da Microsoft e parte del .NETFX, funzionante sia a linea di comando che con interfaccia grafica, che dà accesso in lettura alle informazioni sui metadati (anche in forma "grezza" a livello di struttura interna degli stessi) e al codice IL, che viene disassemblato e visualizzato come file di testo.

Reflector, il miglior amico di molti sviluppatori .NET, è un eccellente tool scritto da Lutz Roeder, disponibile gratuitamente e aggiornato di frequente. Oltre ad essere più ... carino dal punto di vista grafico, ha "un po'" di funzionalità aggiuntive niente male. Una su tutte la decompilazione del codice IL in un linguaggio di alto livello ... a scelta (meccanismo aumentabile tramite plugin) ! 

Non mi dilungo. Provare per credere !

Il falso problema.

Torno quindi alle domande.

E metto subito in chiaro il punto che a mio giudizio è fondamentale.

Se distribuiamo un assembly .NET, *non distribuiamo i sorgenti*. Punto.

Non facciamo nulla di differente rispetto a prima: diamo ai nostri clienti del codice "eseguibile". Perchè lo devono eseguire e non "leggere".

Se prendiamo un eseguibile Win32, al di là delle questioni legali che non sono il tema di questo post, nulla ci vieta di aprirlo con un editor binario e "leggere" il codice macchina. Così come nulla ci vieta di utilizzare qualche disassemblatore per codice x86. Invece di una serie apparentemente incomprensibile di zeri e uno (rappresentati magari in base esadecimale o con i corrispondenti caratteri ASCII, ma poco cambia) abbiamo la traduzione di quelle istruzioni in assembler.

E poi ... e poi ci fermiamo perchè ...

Perchè è troppo difficile !

Non impossibile. Solo molto difficile. Tanto per noi uomini, quanto per le macchine e i decompilatori.

Tutto qui: il nodo della questione è la complessità del codice che andiamo a leggere.

Complessità che, di per sè, non è un problema, e lo diventa ovviamente quando la variabile tempo viene presa in considerazione. Se abbiamo 2000 anni a disposizione, siamo in gamba (molto) e ci facciamo dare una mano da qualche strumento, il codice sorgente di, che so, Word forse possiamo anche riuscire a dedurlo (non uguale, simile !).

Se l'applicazione è scritta in .NET il tutto diventa, obiettivamente, molto più agevole.

I tempi si riducono, le competenze richieste anche, la cosa diventa ... facile (in proporzione, si intende) !

Un rimedio al problema ?

Vista in questa ottica, se la pensiamo in termini di protezione della nostra proprietà intellettuale (ho scritto un algoritmo segretissimo e ultraottimizzato e il mio concorrente non lo deve avere) la strada che dobbiamo seguire è indicata: aumentare la complessità.

Aumentare la complessità, in ultima analisi, può anche voler dire rinunciare alla piattaforma ed adottarne una che sia intrinsecamente più complessa (da questo punto di vista). Insomma, back to C++.

Ma siamo proprio sicuri che quello che otteniamo, in termini di "protezione", sia realmente un motivo per rinunciare a quel centinaio di migliaia di milioni di triliardilioni di vantaggi che ci sono nell'adottare .NET come piattaforma di sviluppo ?

Mmhhhh ... forse ho un po' condizionato la risposta, vero ?

Obfuscation.

Ed eccoci quindi al punto.

Possiamo rendere più complessa la decifrazione dei nostri segreti, pur mantenendo la piattaforma che abbiamo scelto ?

Quando ho parlato di obfuscation ho sempre visto espressioni del tipo "ah sì, ne ho sentito parlare". E in effetti se ne parla dagli albori della piattaforma .NET (evidentemente il problema alla radice era, ed è, ben chiaro e molto sentito).

Trovo molto bella la definizione di obfuscation su Wikipedia:

  1. The concept of concealing the meaning of communication by making it more confusing and harder to interpret.
  2. ...
  3. (computing) The option to alter computer code, preserving its behavior but concealing its structure and intent.

Nascondere il significato. Mantenere il funzionamento.

Nella pratica esistono diverse tecniche, anche complementari, di obfuscation di assembly .NET.

Comunemente, vengono modificati i nomi (di classi, metodi, ...) nei metadati, in modo da renderli meno chiari.

La classe AuthenticationManager esponde un metodo VerifyCredentials ? Bene, offusco il codice. La classe h8hsaqisqw1uhsshasa espone un metodo ijsdsc9mx4312co2c2 !

In realtà molto spesso vengono usati nomi del tutto simili l'uno all'altro (per esempio differenti per un carattere), in modo tale che per un uomo la lettura diventi quantomeno scomoda !

Addirittura, è molto comune dare lo stesso nome a membri logicamente disgiunti: viene sfruttata la funzionalità di overloading e si parla in questo caso di "overload induction". Provate ad aprire con Reflector un assembly offuscato e vi ritroverete, nella maggior parte dei casi, di fronte ad un insieme di metodi, costanti, proprietà che si chiamano tutti "a".

In questo caso il funzionamento (funzionalità ma anche prestazioni) non viene minimamente alterato (potrebbe teoricamente anche esserci un miglioramento delle prestazioni, discorso per un altro post). D'altra parte il contenuto dei metodi rimane tale e quale a prima.

Altro livello di obfuscation è quello, più invasivo, in cui vengono generati flussi di codice IL intricati e "inconcludenti". E qui ci si capisce sempre meno, cambia il contenuto dei metodi, a volte cambiano anche le prestazioni.

Questa tecnica peraltro è un buon metodo per ingannare i decompilatori.

Ad esempio: i cicli sono costrutti sintattici non presenti in IL (in fin dei conti, è sempre intermediate !) e i compilatori generalmente emettono sequenze di istruzioni IL piuttosto standard per implementarli. Se la sequenza non è più standard per via di codice iniettato o modificato da un obfuscator è probabile che il decompilatore non riesca a tradurre in maniera corretta.

Conclusioni e Riferimenti.

Questo (lungo) APPTXT numero 1 non voleva essere (e vi assicuro che non è) una guida di riferimento alle tecniche di Obfuscation.

Esprimere un giudizio ... beh quello forse era il mio intento, sì.

Il mio punto di vista personale, come probabilmente avrete avuto modo di intuire se siete riusciti ad arrivare fino qui, è questo.

Fatto saldo che la facilità di reverse engineering su codice .NET *non è* un difetto della piattaforma ma una sua caratteristica intrinseca, e che esistono comunque tecniche per ridurre tale "facilità", nella realtà credo che il problema sia molto sopravvalutato e, talvolta, utilizzato come "capro espiatorio".

Addurre la semplicità di decompilazione come fattore determinante nella scelta di NON passare a .NET è, a mio parere, decisamente sbagliato. Ne ho sentiti diversi, di discorsi del genere, e in quei casi la paura di effettuare un upgrade della tecnologia (con tutte le problematiche di gestione che ne derivano) era semplicemente ...

... offuscata dietro all'obfuscation !!!

Se vi interessano questi argomenti, comunque, vi posso consigliare:

Posted: gen 28 2007, 05:08 by devlizard | with 1 comment(s)
Filed under: ,

Comments

Claudio Brotto said:

Ok, discorso anche questo vecchio come il mondo. Il tutto, usando come riferimento le domande che mi

# febbraio 3, 2007 5:37