Pagina principale di REC | Guida utente | English Version | CG's Home page | E-mail : caprino@netcom.com
Il decompilatore REC puo' essere usato in molti modi diversi, a seconda del tipo del file eseguibile che deve essere decompilato.
In questo esempio, considereremo un file binario, sul quale non si hanno molte informazioni. Anche se si ha un file in uno dei formati riconosciuti, talvolta si ottengono migliori risultati seguendo l'approccio descritto di seguito.
Il primo passo e' dire a REC come caricare il file. Se il file e' in uno dei formati riconosciuti (COFF, Windows PE, ELF, AOUT), REC determinera' automaticamente quali porzioni del file contengono codice, e quali contengono dati. D'altra parte, se il formato del file non e' riconosciuto, REC stampera' il seguente messaggio prima di uscire:
file.exe: file error or wrong format file
Per forzare REC ad accettare il file, dobbiamo usare un file comandi (.cmd). Percio', assumendo che si voglia leggere il file chiamato file.exe, dovremo prima scrivere il file file.cmd contenente i seguenti comandi:
#!wrec
file: file.exe
cpu: mips
region: 0x0 0x2000 0 data
Qui assumiamo di sapere che il file eseguibile contiene istruzioni per il processore MIPS R3000. Altri possibili valori per il comando cpu: sono: i386, 68000 e ppc. Assumiamo inoltre che il file sia lungo 0x2000 bytes. Dopo aver scritto il file comandi, dovremmo essere in grado di leggere il file eseguibile fornendo il seguente comando:
rec +interactive file.cmd
REC leggera' il file e mostrera' il menu' principale. Da questo menu', l'unica opzione che ha senso usare e' il visore esadecimale, attivato dalla lettera 'D'. Questa opzione mostrera' il contenuto del file in esadecimale sullo schermo. Da una ispezione visuale dell'esadecimale, dovremmo essere in grado di isolare certe aree del file binario che sembrano contenere dati. Per esempio, tutte le sequenze di byte che sono troppo regolari (per esempio tutti zeri) sono quasi certamente dati. Anche sequenze che si ripetono ogni quattro byte (per un processore a 32 bit) sono molto probabilmente dati, in particolare indirizzi. E' anche molto importante sapere dov'e' il punto di ingresso del programma, cioe' la prima istruzione eseguita quando il programma e' caricato. Sapendo queste informazioni, possiamo creare una lista di regioni, e specificare ogni regione nel file .cmd. Per esempio, un file .cmd rivisto potrebbe essere:
#!wrec
file: file.exe
cpu: mips
region: 0x80000400 0x80001600 0x400 text
region: 0x80001600 0x80002000 0x1600 data
In questo esempio assumiamo che i primi 0x400 byte sono l'header del file, che il codice inizia all'offset 0x400 e che il primo indirizzo dell'area codice e' 0x80000400, che la porzione dati inizia all'offset 0x1600 e che il suo indirizzo di caricamento in memoria e' 0x80001600. Si noti che non abbiamo specificato i byte tra gli offset 0 e 0x3FF. Questo significa che tale porzione del file sara' ignorata, poiche' probabilmente contiene informazioni necessarie solo al sistema operativo per eseguire il file. Similmente, possiamo non specificare le porzioni del file che contengono rilocazioni o informazioni simboliche.
Si ricordi che questo processo e' automatizzato per i formati oggetto riconosciuti da REC.
Una volta che abbiamo partizionato il file in aree codice, dati e informazioni ausiliarie, possiamo uscire da REC e ri-eseguirlo fornendo il nuovo file.cmd. A questo punto, poiche' REC conosce quali porzioni del file sono codice, esso cerchera' di localizzare il punto di inizio di ogni procedura del programma. Per far cio', ogni regione testo viene disassemblata per costruire una lista di label, salti e chiamate a funzione. Queste liste possono essere ispezionate con i comandi interattivi 'l' e 'b'. Se vediamo che ci sono salti o label al di fuori delle regioni di memoria specificate come codice, e' probabile che tali salti sono stati causati dall'interpretazione da parte di REC di un'area dati come contenente codice. Possiamo quindi modificare la lista delle regioni fino a che tutti i salti sono contenuti all'interno dei limiti delle regioni testo.
Un'altro comando molto utile e' 'p : show procedures', che mostra una lista delle procedure trovate. La lista (cosi' come ogni altra lista) puo' essere ispezionata usando i tasti 'k' e 'j' (su e giu' di una linea, rispettivamente) o i tasti Ctrl-B e Ctrl-F (su e giu' una pagina, rispettivamente).
Si puo' ora disassemblare la procedura indicata dal cursore premendo il tasto 'd'. Questo mostrera' il codice disassemblato tra l'indirizzo iniziale e finale della procedura. Se il disassemblato sembra ragionevole, possiamo uscire dal disassemblatore con il tasto ESC, e cercare di decompilare la procedura premendo prima il tasto 'c' e poi il tasto 'x'. Questo mostrera' una rappresentazione C della procedura disassemblata.
Eseguire questo processo per tutte le procedure e' molto noioso. Puo' essere piu' facile disassemblare l'intero programma e quindi esaminare il disassemblato. Per far cio', si esca REC e si fornisca la seguente linea di comando:
rec +disasmonly file.cmd
Questo comando produrra' un file chiamato file.dis, che e' il disassemblato di tutte le regioni codice. Usando questo file possiamo ulteriormente determinare quali regioni hanno codice e quali dati.
Dobbiamo continuare a raffinare il nostro file .cmd fino a che siamo sicuri che tutte le porzioni dati sono state identificate. Quindi, possiamo chiedere a REC di decompilare l'intero file eseguibile, con la seguente linea di comando:
rec -interactive file.cmd
Questo dice a REC di produrre il file chiamato file.rec con la rappresentazione C di tutte le procedure. Si noti che alla fine del file e' presente una lista di regioni. Questa lista puo' essere piu' lunga di quella fornita nel file .cmd, poiche' REC puo' avere deciso che alcune regioni specificate come codice contengono in realta' dei dati, per esempio perche' contengono una tabella di salti o una stringa ASCII.
Guardando il file C fianco a fianco con il file disassemblato possiamo cercare di capire la funzione di ogni procedura. Quando abbiamo determinato che una procedura per esempio apre un file (perche' chiama qualche funzione di sistema di cui conosciamo il comportamento), possiamo fornire un nome e un tipo alla funzione e ai suoi parametri. Per esempio, possiamo aggiungerre la seguente linea al file .cmd:
symbol: 0x80001000, 0x80001100 T OpenFile(char *filename)
Questo dice a REC di sostituire tutti i riferimenti all'indirizzo 0x80001000 con il nome simbolico OpenFile. Inoltre, indica che la procedura OpenFile ha un parametro di tipo char *. Quando ri-eseguiamo REC con la versione estesa del file .cmd, il file di uscita diventera' via via piu' leggibile e con informazioni piu' accurate.
Un'altro modo di fornire le informazioni di tipo, e' di scrivere un file di soli simboli. Questo e' un file ELF o AOUT compilato con il compilatore GNU gcc. Questo file contiene informazioni simboliche sul programma da decompilare. Per esempio, possiamo ottenere lo stesso risultato ottenuto in precedenza con la seguente linea nel file .cmd:
types: file.o
symbol: 0x80001000, 0x80001100 T OpenFile()
Con questi comandi, REC leggera' le informazioni simboliche dal file file.o, e le applichera' alle funzioni specificare dal comando symbol:. Nel nostro esempio, file.o puo' essere prodotto compilando il seguente file sorgente file.c con il comando gcc -g -c file.c:
/* file.c - symbolic info for file.exe */
int OpenFile(char *file_name)
{
}
Solo le informazioni simboliche sono usate. Il codice prodotto da gcc e' ignorato. Questo puo' anche essere usato per fornire informazioni su informazioni di libreria, il cui comportamento e' specificato dal linguaggio C. File simboli precompilati per alcune librerie C sono disponibili sul sito web di REC, e nelle destribuzioni binarie di REC.
Questo secondo metodo e' preferito al primo per due motivi: primo, permette di specificare strutture dati molto piu' complesse, come strutture e unioni; secondo, i file tipi possono essere usati con molti eseguibili diversi, cioe' non e' necessario cambiare tutti i file .cmd quando si trova il significato di una nuova porzione di codice. Si puo' semplicemente fornire il nome della procedura, e REC trovera' i suoi parametri dai file dei tipi.
REC ha anche un'altra caratteristica che puo' essere usata per velocizzare ulteriormente il miglioramento del file decompilato. Questa caratteristica e' utile principalmente quando sappiamo che un insieme di file eseguibili e' stato compilato con lo stesso compilatore e linkato con le stesse librerie. In questo caso, possiamo chiedere a REC di determinare dove sono nelle regioni di codice le procedure di una libreria. Per far cio' dobbiamo prima riconoscere dove inizia ogni procedura e dobbiamo dire a REC i primi byte della procedura stessa. Per esempio, se sappiamo che le procedure open() ed lseek() iniziano sempre con gli stessi byte, possiamo scrivere il seguente file di pattern (signature), file.sig:
open(char *name, int mode) size: 16
A0 00 0A 24 08 00 40 01
00 00 09 24 00 00 00 00
;
lseek() size: 16
A0 00 0A 24 08 00 40 01 01 00 09 24 00 00 00 00
;Quindi, possiamo aggiungere il comando pattern: al nostro file .cmd:
types: file.o
pattern: file.sig
symbol: 0x80001000, 0x80001100 T OpenFile()
Ora, in aggiunta a trattare i riferimenti all'indirizzo 0x80001000 come chiamate alla procedura OpenFile(), REC trovera' anche dove si trovano le procedure open() ed lseek() nel file eseguibile, e trattera i riferimenti a tali indirizzi in maniera simbolica.
Si noti ancora che le informazioni sul tipo possono essere fornite nel file di pattern, sebbene sia meglio fornire tali informazioni simboliche tramite il comando type:.
Ci sono anche molte altre opzioni. Alcune sono solo significative quando si debugga REC. Altre possono migliorare la qualita' del file di uscita. La Guida Utente fornisce una lista delle opzioni principali.
Pagina principale di REC | Guida utente | English Version | CG's Home page | E-mail : caprino@netcom.com
Copyright (C) 1998 Backer Street Software -- Tutti i diritti riservati.