xiv INTRODUZIONE
“firma” che permetta all’autore di dimostrare la paternita` del programma
stesso. Ovviamente, questa firma deve essere costruita in modo da non esse-
re facilmente riconoscibile, altrimenti sarebbe facile per il nostro avversario
rimuoverla.
Nel Capitolo 1 viene introdotto FLOWer, cioe` un tool, scritto in Java,
che, preso in input una classe Java Bytecode (cioe` un file .class), estrae
il Control Flow Graph (CFG) e il Data Dependence Graph (DDG) di ogni
metodo della classe. Oltre all’utilita` in se` che l’estrazione di questi grafi puo`
avere in vari campi (ottimizzazione, program slicing, misura della comples-
sita`, debug, ecc.), FLOWer contribuira` a rendere piu` chiari alcuni concetti
espressi nei Capitoli successivi, generando le immagini dei CFG di alcuni
programmi.
Nel Capitolo 2 viene chiarito meglio il problema della segretezza della
proprieta` intellettuale, e vengono illustrate alcune tecniche per raggiungere
questo scopo. In particolare, queste tecniche mirano a contrastare il Reverse
Engineering, cioe` l’analisi dettagliata di un programma finalizzata a com-
prenderne la logica e la struttura, e la Software Piracy, cioe` l’atto illegale di
copiare e rivendere un software.
Nel Capitolo 3 viene introdotta una tecnica di offuscamento di codice
per programmi Java Bytecode che sfrutta un bug presente in tutti i decom-
pilatori di Java testati (Jad, Jode, SourceAgain e Soot) e rende obsoleto,
cioe` non ricompilabile oppure con una semantica diversa rispetto a quella
originaria, il codice sorgente ricostruito da questi. Questo offuscamento, che
abbiamo chiamato T , e` in realta` la composizione di due sotto-offuscamenti:
OLCMP , che sfrutta l’appena menzionato bug dei decompilatori, i quali han-
no molta difficolta` a tradurre ad alto livello un particolare tipo di istruzioni
Java Bytecode, e OOP , che introduce all’interno del programma il classico
offuscamento dei predicati opachi. E` particolarmente interessante il caso di
Soot, uno dei piu` efficienti decompilatori esistenti, il quale, in presenza di un
programma Java Bytecode offuscato con la nostra tecnica, genera un codice
ad alto livello perfettamente ricompilabile ma che presenta una semantica
diversa rispetto a quella originaria. Questo e` un enorme vantaggio, e anche
molto subdolo, in quanto un eventuale avversario che, avendo decompilato
un nostro eseguibile, ritenesse di averci rubato l’idea, si troverebbe tra le
mani, senza saperlo, un programma non corretto.
Nel Capitolo 4, infine, ci serviremo di due tecniche di offuscamento:
OLCMP , gia` introdotto nel Capitolo 3, e OIF , che introduce all’interno del
programma una sorta di predicato opaco “mascherato”, e sfrutteremo mag-
giormente l’arma della composizione di offuscamenti, che ci portera` a dei
risultati molto interessanti: oltre a fornirci un metodo efficace per nascon-
dere il nostro codice ai malintenzionati, infatti, ci dara` anche la possibilita` di
inserire tramite di essa un watermark all’interno di un programma offuscato.
Capitolo 1
FLOWer
La comprensione di alcune caratteristiche e dei vincoli di dipendenza di vario
genere presenti all’interno di un programma e` fondamentale per intraprende-
re una buona analisi del programma stesso. Le operazioni di ottimizzazione,
di program slicing, di misura della complessita`, di debug, ecc. si basano
sempre su una rappresentazione astratta e piu` intuitiva rispetto al semplice
codice del programma su cui ci si sta concentrando.
FLOWer e` un tool che analizza programmi scritti in Java Bytecode, e
da essi estrae due tra le piu` famose e utilizzate rappresentazioni astratte di
cui stavamo parlando: il Control Flow Graph e il Data Dependence Graph.
Un programma Java Bytecode e` composto da un insieme di classi, ognu-
na delle quali a sua volta e` composta da un insieme di campi e da un insieme
di metodi. I metodi, in particolare, sono le componenti attive di un program-
ma, in quanto sono quelle che contengono le istruzioni. Durante l’esecuzione
di un programma, il controllo passa da un metodo ad un altro fino a che
l’esecuzione non termina. Noi ci preoccuperemo di andare a vedere cosa ac-
cade all’interno di ogni singolo metodo, che considereremo come un oggetto
a se stante.
FLOWer prende come input una classe Java (solitamente rappresentata
da un file .class) contenente n metodi e ritorna in output:
• Un file <nome classe>_code.j che contiene il codice Java Bytecode
della classe;
• n file <nome metodo i>_CFG.dot che contengono ognuno la rappresen-
tazione nel linguaggio dot del Control Flow Graph dell’i-esimo metodo
della classe;
• n file <nome metodo i>_DDG.dot che contengono ognuno la rappre-
sentazione nel linguaggio dot del Data Dependence Graph dell’i-esimo
metodo della classe.
1
2 CAPITOLO 1. FLOWER
Per la manipolazione delle istruzioni Java Bytecode, FLOWer fa largo
uso delle librerie BCEL. Per il download e la documentazione si veda [2].
Il linguaggio dot e` molto utile per disegnare grafi in modo automatico.
I file scritti in dot sono interpretati dal software Graphviz. Per il download
e la documentazione si veda [3].
1.1 Control Flow Graph (CFG)
Un CFG e` una rappresentazione astratta, sotto forma di grafo, dei vari flussi
di controllo che esistono tra le istruzioni all’interno di un programma. In
altre parole, esso rappresenta tutte le possibili esecuzioni di un programma.
Prima di entrare nei dettagli e andare a vedere come viene costruito
un CFG e` necessario fare alcune precisazioni. Come e` noto, durante l’ese-
cuzione di un programma puo` capitare che venga lanciata un’eccezione a
causa di qualche evento non previsto o non corretto, come ad esempio una
divisione per zero. Il linguaggio Java, e di conseguenza il linguaggio Java
Bytecode, incorpora un meccanismo di gestione delle eccezioni che funziona
in questo modo (vedi [22]): se durante l’esecuzione di un’istruzione accade
qualcosa di anomalo, il flusso normale di esecuzione del metodo si blocca
e viene lanciata (thrown) un’eccezione, cioe` viene creato un oggetto della
classe java.lang.Exception, o di una sua sottoclasse. Questa eccezione
puo` essere catturata (caught), cioe` gestita opportunamente da alcune istru-
zioni speciali all’interno del metodo in cui e` stata generata. In questo caso
il flusso di esecuzione passa ad un exception handler, che e` la prima delle
istruzioni speciali di cui si e` appena detto. Oppure, se cos`ı non accade, essa
viene rimbalzata al metodo chiamante, il quale puo` a sua volta catturarla o
rimbalzarla al suo chiamante. Se continuando a rimbalzare questa eccezione
si arriva fino al metodo main, e neppure esso la cattura, l’esecuzione del
programma termina e viene dato un messaggio di errore.
Ora, esistono due modi in cui puo` essere lanciata un’eccezione: esplicita-
mente, quando l’eccezione viene lanciata consapevolmente dal programma-
tore attraverso l’istruzione Java throw, che viene tradotta in Java Bytecode
con il bytecode athrow, oppure implicitamente, quando durante l’esecuzio-
ne di una qualsiasi altra istruzione avviene un comportamento anomalo. In
entrambi i casi, il sopraggiungere dell’eccezione altera il normale flusso di
esecuzione del programma, percio`, nel caso in cui non si escluda a priori la
possibilita` di eccezioni, la costruzione del CFG di un metodo deve tenerne
conto in qualche modo.
In questa Sezione ci occuperemo della costruzione del CFG di metodi
durante l’esecuzione dei quali si assume che non possa mai sussistere la
possibilita` che venga lanciata un’eccezione implicitamente, mentre ne sara`
permesso il lancio esplicito. Nella Sezione 1.2 ci occuperemo di CFG che
tengano conto anche della possibilita` di eccezioni non previste. I concetti
1.1. CONTROL FLOW GRAPH (CFG) 3
e le definizioni che verranno presentati in questa Sezione ricalcano in larga
parte [27].
1.1.1 Definizioni preliminari
Diamo innanzitutto qualche definizione che ci aiutera` a capire cio` di cui si
parlera` in seguito.
Definizione 1 (Basic Block) Un basic block e` una sequenza di istruzioni
con un singolo entry point e un singolo exit point: l’esecuzione di un basic
block puo` iniziare solo al suo entry point e il controllo non viene mai ceduto
ad un altro basic block prima del suo exit point. Percio`, se il controllo entra
in un basic block, ogni istruzione di quel basic block sara` eseguita.
Definizione 2 (Control Flow Graph) Un control flow graph (CFG) di
un metodo Java Bytecode M e` una quadrupla (V,A,s,t), dove (V,A) e` un
grafo diretto tale che V e` un insieme di nodi che rappresentano i basic block
di M e A ⊂ V × V e` un insieme di archi che rappresentano i possibili flussi
di controllo tra i basic block di M. s ∈ V e` un nodo, chiamato START, che
rappresenta l’entry point di M, tale che in-degree(s) = 0. t ∈ V e` un nodo
chiamato END che rappresenta l’exit point di M, tale che out-degree(t) = 0
e t 6= s.
1.1.2 Costruzione del CFG
Per costruire il CFG di un metodo, la prima cosa da fare e` dividere le
istruzioni nei vari basic block. Per fare cio`, e` necessario identificare tutte
quelle istruzioni che iniziano un nuovo basic block, cioe` i cosiddetti leader.
L’identificazione dei leader e` un’operazione piuttosto semplice, in quanto
basta seguire questa semplice regola:
Definizione 3 (Leader) Un’istruzione e` un leader se:
• E` la prima istruzione del metodo, oppure
• E` la prima istruzione di un exception handler, oppure
• E` il target di un branch incondizionale (goto, goto_w, jsr, jsr_w,
ret), oppure
• E` il target di un branch condizionale (if_icmpeq, if_icmpne, if_icmplt,
if_icmpgt, if_icmple, if_icmpge, if_acmpeq, if_acmpne, ifeq,
iflt, ifle, ifne, ifgt, ifge, ifnull, ifnonnull), oppure
• E` uno dei target di uno switch (lookupswitch o tableswitch), oppure
4 CAPITOLO 1. FLOWER
Figura 1.1: Un metodo Java Bytecode e i corrispondenti basic block
• Segue immediatamente un branch condizionale, un branch incondizio-
nale o uno switch, oppure
• Segue immediatamente un’istruzione di ritorno (ireturn, lreturn,
freturn, dreturn, areturn, return), oppure
• Segue immediatamente un’istruzione athrow, oppure
• E` un’invocazione a metodo (invokeinterface, invokespecial, invokestatic,
invokevirtual), oppure
• Segue immediatamente un’invocazione a metodo.
Ogni leader da` vita ad un nuovo basic block, e ogni basic block e` compo-
sto dalle istruzioni che vanno dal suo leader all’istruzione immediatamente
precedente il leader successivo.
Si noti che, a causa della definizione di leader, ogni istruzione di invoca-
zione a metodo e` sempre la sola del proprio basic block. Questo ha un senso,
in quanto questo tipo di istruzioni passa il controllo ad un altro metodo, du-
rante l’esecuzione del quale potrebbe accadere qualsiasi cosa, compresa la
terminazione del programma, a causa di un’eccezione o di una scelta del
programmatore, e questo impedirebbe di portare a termine l’esecuzione del-
l’istruzione di invocazione stessa e di tutte le eventuali istruzioni successive
del basic block. Percio`, dato che la definizione di basic block asserisce che se
viene eseguita la prima istruzione allora devono essere eseguite anche tutte
le altre, non si puo` fare altro che lasciare da sole le istruzioni di invocazione
a metodo. In figura 1.1 vediamo il codice di un metodo Java Bytecode e
i suoi corrispondenti basic block. Per completare l’insieme V dei nodi del
CFG, si aggiungono infine i due basic block “speciali” START e END.
Dopo aver diviso il codice del metodo nei vari basic block, e aver quindi
creato i nodi del CFG, e` necessario estrarre gli archi del grafo, e cioe` i vari
1.2. EXTENDED CONTROL FLOW GRAPH (ECFG) 5
flussi di controllo che possono intercorrere tra i vari basic block. Anche nel
caso della definizione degli archi le regole da seguire sono piuttosto semplici:
Definizione 4 (Arco del CFG) Siano u ∈ V e v ∈ V due basic block.
Allora l’arco (u, v) viene aggiunto ad A se:
• La prima istruzione contenuta in v segue immediatamente l’ultima
istruzione contenuta in u nel codice del metodo e l’ultima istruzione
contenuta in u non e` un branch incondizionale, oppure
• L’ultima istruzione contenuta in u e` un branch condizionale o incondi-
zionale (diverso da ret) e ha come target la prima istruzione contenuta
in v, oppure
• L’ultima istruzione contenuta in u e` uno switch e ha come uno dei
target la prima istruzione contenuta in v, oppure
• L’ultima istruzione contenuta in u e` un ret e la prima istruzione
contenuta in v segue immediatamente nel codice il jsr o il jsr_w cor-
rispondente (le istruzioni jsr e ret infatti non compaiono mai se non
insieme), oppure
• L’ultima istruzione contenuta in u e` athrow e la prima istruzione con-
tenuta in v e` l’exception handler che cattura quella eccezione, oppure
• u e` il basic block START e la prima istruzione contenuta in v e` anche
la prima istruzione del metodo, oppure
• L’ultima istruzione contenuta in u e` un’istruzione di ritorno e v e` il
basic block END.
In figura 1.2 vediamo il CFG completo del metodo precedente.
1.2 Extended Control Flow Graph (eCFG)
Ovviamente non e` sempre possibile o consigliabile assumere che durante
l’esecuzione di un programma non possano mai essere lanciate eccezioni
implicite inaspettate (mai dire mai! ), percio` in certe occasioni puo` essere
piu` utile considerare CFG che tengano conto anche di questa possibilita`.
Anche in questa Sezione, i concetti e le definizioni che verranno presentati
sono in larga parte ripresi da [27].
1.2.1 Definizioni preliminari
Diamo allora la nuova definizione di Extended Control Flow Graph: