MokaByte
Numero 07 - Aprile 1997
|
|||
|
|
||
Piero Campanelli |
|||
E' Java un
maturo linguaggio di programmazione? E' possibile sviluppare progetti complessi?
Quali problemi sorgono nella creazione di applicazioni? Queste sono domande
che vengono frequentemente poste tra gli sviluppatori e nelle grandi aziende
di software. Da quanto visto alla "Italian Java Conference" e da quanto
riportato dalle riviste del settore, sembra che i programmatori stiano
convergendo verso questa nuova tecnologia, non solo per semplici e banali
applets, ma anche per medio-grandi applicazioni.
Tuttavia
esiste un problema che grava su chi sviluppa in Java, a cui poco spazio
é stato riservato dai media e che, per il momento, é lontano
da una soluzione efficace: il problema della decompilazione.
La
decompilazione
E' possibile
ottenere il file sorgente di una classe java, dal rispettivo formato bytecode
di quella classe, o in parole piú semplici, dato un file .class
é possibile derivare, con l'ausilio di particolari programmi, il
rispettivo .java. Piú precisamente non si ottiene l'esatta copia
dell'originale, bensí una ricostruzione il piú possibile
fedele del file sorgente originario.
Questo articolo
tenterá di evidenziare le cause di questo problema, cercando di
non trattare la questione da un punto di vista formale, bensí con
un linguaggio piú semplice e talvolta impreciso, con l'unico scopo
di focalizzare al meglio il caso trattato.
Il problema del
reverse-engineering in campo informatico é in un certo senso limitato,
o piú precisamente il rischio arriva da persone abili, dotate di
una grande pazienza e, soprattutto, di molto tempo. (persone di non facile
reperibilitá)
Generalmente,
indipendentemente dal tipo di piattaforma su cui si opera, da un file eseguibile,
generato con uno dei linguaggi di programmazione diffusi al momento, possiamo
ottenere, tramite un programma chiamato disassemblatore, le istruzioni
assembler che compongono il file in questione. E' evidente che l'interpretazione
di un listato assembler (in linea di massima esiste una corrispondeza uno
a uno tra istruzioni assembler ed istruzioni del microprocessore) non puó
esser fatta a cuor leggero e richiede ore se non settimane di duro lavoro,
che magari non sempre portano al risultato atteso. Per Java, attualmente,
la vicenda é contraria: da un decompilatore non si ottiene il listato
del programma in assembler, bensí si puó ottenere il codice
ad alto livello, appunto in linguaggio Java. E' immediato rendersi conto
di quanto sia piú semplice e fattibile interpretare un programma
scritto in un linguaggio ad alto livello nei confronti di uno scritto a
basso, come appunto in assembler.
Si vuole far
presente che il reverse-engineering é vietato dalle leggi internazionali
sulla tutela del copyright del software. Sebbene la maggior parte dei programmatori
del mondo sia onesta e non si "degraderebbe" a lavori di questo genere,
risulta comunque pericoloso lasciare a persone disoneste la chance di vanificare
o sfruttare il frutto di mesi o anni di sodo lavoro.
Perché
é possibile la decompilazione?
I class files
di Java contengono una sequenza di byte, chiamata bytecode, ovvero il set
di istruzioni della Java Virtual Machine. Questi bytecodes viaggiano grazie
alla rete Internet fino ad arrivare alle JVM di tutto il mondo. Un class
file contiene le informazioni necessarie ad una macchina virtuale Java
per eseguire la classe contenuta nel file. Purtroppo sfruttando queste
informazioni non solo si é in grado di eseguire applicazioni Java,
ma anche di decompilare in codice sorgente i programmi in questione.
Senza specificare
i dettagli di questo file, per il cui approfondimento é consigliabile
la lettura di [1], viene sotto riportata la struttura, evidenziando i campi
critici, ovvero i campi portatori di informazioni utili alla decompilazione.
I byte del class
file, memorizzati in big-endian, sono organizzati secondo un particolare
formato, in modo tale che la JVM sia in grado di riconoscere ed interpretare
in modo corretto i vari campi che formano la struttura.
Per alcuni di
questi campi la lunghezza non é costante, bensí variabile
(varia a seconda del contenuto della classe): di conseguenza, prima di
questi campi sono presenti dei bytes che specificano la lunghezza dei campi
successivi.
Classfile {
4 byte magic;
2 byte minor_version;
2 byte major_version;
}
I primi 4 byte contengono il seguente numero 0xCAFEBABE. Grazie a questo codice, la JVM é in grado di capire che la sequenza di bit, data in input, é un class file. Quindi major e minor version identificano a quale versione il class file aderisce.
2 byte constant_pool_count;
cp_info constant_pool[constant_pool_count - 1];
Constant pool
é un array avente constant_pool_count elementi che contiene le costanti
associate ad una classe, ovvero: strighe, nome della classe, nomi e tipi
delle variabili (variabili statiche e variabili istanziate, ma non variabili
locali), nomi e tipi dei metodi definiti nella classe.
Per ogni elemento
dell'array il primo byte indica il tipo di elemento contenuto in quella
posizione. In base a questo byte chiamato "tag", la JVM é in grado
di interpretare ció che segue. Ad esempio se il "tag" indica un
Integer (codice 0x3) i successivi 4 byte conterranno il valore Integer.
Ovviamente esistono tag che identificano una classe, un metodo, una variabile
e cosí via….
Costant_pool
é di lunghezza variabile (infatti le costanzi associate ad una classe,
variano a seconda del contenuto della classe stessa), quindi, come spiegato
sopra, si necessita di una serie di byte che identifichino la lunghezza
di constant_pool. In questo caso si usano 2 byte e sono chiamati constant_pool_count.
2 byte access_flags;
2 byte this_class;
2 byte super_class;
2 byte interfaces_count;
2 byte interfaces[interfaces_count];
2 byte fields_count;
field_info fields[fields_count];
Tralasciando
alcuni campi, si arriva alla tabella "fields" (di lunghezza fields_count)
che riporta informazioni sui fields della classe, ovvero delle variabili
istanziate o delle variabili di classe. Per ogni variabile viene memorizzato
il nome, il tipo, il valore se é una costante. Questi non sono memorizzati
in questa tabella, ma nel constant_pool. Di conseguenza, la tabella conterrá
dei puntatori alle varie locazioni del constant_pool.
Per esempio
il valore di una costante di tipo intero sará referenziato con un
puntatore ad una particolare locazione del constant_pool, ad esempio alla
locazione dove é memorizzato il valore integer sopra descritto.
2 byte methods_count;
method_info
methods[methods_count];
methods é una tabella simile alla tabella dei fields, ma con riferimento ai metodi della classe.
2 byte attributes_count;
attribute_info attributes[attribute_count];
Anche se visto
in modo abbastanza approssimativo, (una trattazione esaustiva richiederebbe
pagine e pagine di spiegazioni ed esempi…) risulta chiaro che le informazioni
trasportate da un class file, sono vitali ad un ambiente java runtime per
l'esecuzione della classe e quindi dell'applicazione o dell'applet e sarebbe
quindi impensabile eliminare questo tipo di informazioni.
Quali
sono i software che permettono la decompilazione?
Non esistono
molti software che permettono la decompilazione (fortunatamente), tuttavia
é stato possibile provare la versione demo di un prodotto della
WingSoft (http://www.wingsoft.com):
"JavaDis V2.02"
La limitazione
di questa versione free riguarda il numero dei metodi di una classe che
possono essere decompilati, infatti il programma esegue il reverse-engineering
solo dei primi tre metodi. Tuttavia per pochi dollari, direttamente dal
sito, é possibile comprare la versione completa.
Il primo sorgente riportato é un esempio scritto dal programmatore, mentre il secondo esempio é l'output ottenuto dalla decompilazione della classe "provadecompilazione". Si puó notare che le variabili locali non sono decompilate in modo corretto, in quanto, come detto precedentemente, il loro nome non viene memorizzato nel classfile. Si puó inoltre constatare che il sorgente ottenuto non é una copia fedele dell'originale, ma una sua ricostruzione che logicamente ne conserva la semantica.
// Classe di esempioEcco il risultato della decompilazioneimport java.io.*;
class provadecompilazione {
static int variabile_1;
int variabile_2;
String str=new String("Prova!!");
public static void main (String args[]) {
int variabile_locale_1=0;
int variabile_locale_2;
variabile_locale_1=variabile_locale_1+10;
variabile_1=1000;
variabile_locale_2=variabile_locale_1;
}
int metodo_prova (int parametro_1,String parametro_2) {
return parametro_1;
}
}
// This program is genearted by Javadis 2.02, a product from WingSoft// For more information about WingSoft, please visit http://www.wingsoft.com
synchronized class provadecompilazione {
static int variabile_1;
int variabile_2;
String str;
public static void main(
String[] L0) {
L1= 0;
L1= ( (L1 + 10);
variabile_1= 1000;
L2= L1;
return;
}
int metodo_prova(
int L1,
String L2) {
return L1;
}
provadecompilazione() {
super();
str= new String("Prova!!");
return;
}
}
Javadis
non é l'unico decompilatore che si puó trovare nella rete.
Ricordo che esiste un altro programma, anzi il primo decompilatore java,
chiamato "mocha". Purtroppo non é stata possibile una prova di questo
prodotto, in quanto sembra che sia in giro solo una versione beta che si
é rivelata abbastanza instabile nelle prove.
Quali
sono i possibili rimedi?
Non é
facile trovare dei rimedi a questo problema, tantoché nemmeno Sunsoft
ha trovato una possibile soluzione nel JDK 1.1.
Tuttavia, come
é stato confermato da alcuni membri di Sunsoft, non é un
problema dimenticato, ma si sta studiando una possibile soluzione.
Esistono, comunque,
dei tools che garantiscono una minima forma di difesa da questo tipo di
minaccia il cui funzionamento consiste "nell'offuscare" il class file,
ovvero di complicarlo. Sostitutendo, ad esempio, tutti i nomi di variabili
e di metodi con nomi complicati e senza senso in modo da rendere un un
po' piú ardua la comprensione del sorgente.
Bibliografia
[1] - Tim Lindholm,
Frank Yellin "THE JAVA VIRTUAL MACHINE SPECIFICATION", Addison-Wesley http://www.aw.com/cp/lindholm-yellin.html
Una versione
ridotta si trova si trova a http://java.sun.com/1.0alpha3/doc/vmspec/vmspec_1.html
Note
sull'autore
Piero Campanelli é studente di informatica presso l'universitá statale di Milano. "Vivo a Varese e mi occupo di sistemi operativi, di reti e di algoritmi. Nel tempo libero mi interesso di astronomia e televisione via satellite. Posso essere contattato tramite posta elettronica all'indirizzo campanel@tin.it oppure se siete radioamatori, via packet-radio a iw2lkg@ik1msl.ipie.ita.eu" |
|
||
|
||
MokaByte ricerca
nuovi collaboratori
|
||
|