Saggio sul PEditor 1.6 |
|
|
02/11/2000 |
by "AndreaGeddon" |
|
|
Published by Quequero |
|
Qualcuno dovrebbe fare un tute... |
Certo che dovevamo fare a botte ma quando? E non mi dovevi portare la bottiglia di grappa? E la cioccolata? E poi non dire che sono io a non mantenere le promesse :))) Torniamo a noi: codesto brutto animal (tale andrewgheddone o simile) cerca in un modo piuttosto prolisso di farci capire cosa sia il PE dandoci istruzioni su come usare un programma che a LUI sembra buono....Ma come si sa, le sue idee sono opinabili. In pratica questa persona che io NON conosco vuole insegnarci a coltivare le patate facendoci andare in giro col trattore...Vabbu�, se lo dice lui io poco ci posso fare, e neanche me la sento di obiettare visto che codesta persona potrebbe risentirsi, ad ogni modo, se volete confondervi ancora di pi� le idee potete leggere questo pessimo (IMHO) tutorial, formattato anche male....:)))))) Bravo Andreucolo :)))) |
..su come usare questo maledetto form |
UIC's form |
|
UIC's form |
Difficolt� |
( )NewBies ( )Intermedio (x)Avanzato (x)Master |
In questo testo verr� spiegato tutto il formato PE. Ogni spiegazione sar� corredata da esempi pratici (per esempio sulla Import Table) per capire a fondo come funziona e come � fatto questo benedetto PE.
Saggio sul PEditor 1.6
Spieghiamo TUTTO il formato PE
Written by AndreaGeddon
Introduzione |
Tools usati |
Un editor esadecimale (per andare ad esaminare i bytes nell'eseguibile)
Il PEditor (dato che questo testo � ispirato a tale programma)
Notepad (io ho quello del win98 prima edizione) Winzip 7 - Usati come cavie
URL o FTP del programma |
Notizie sul programma |
Dopo la chiusura ufficiale del progetto ProcDump ecco che arriva un degno sostituto. Ho notato alcune diversit� nei dump effettuati dal ProcDump e dal PEditor, sono arrivato alla conclusione che il ProcDump � superiore in certe cose, il PEditor in altre. Non credo che si possa in definitiva dire quindi quale � il migliore, di sicuro averli entrambi vi garantir� un ottimo supporto nelle vostre unpacking wars.
Essay |
Vi riporto il testo che ho scritto origiariamente per OndaQuadra, la e-zine degli hackmaniaci (le tabelle le ho rifatte visto che da ascii ad
html non vengono bene):
-
-------------------------------------------------------------------------------------------------------+ -o DISCLAIMER -------------------------------------------------------------------------------------------------------+Le informazioni presenti in questo testo sono a scopo puramente didattico. L'autore e l'E-Zine non si
ritengono responsabili dell'uso che farete di queste informazioni (e francamente se ne fregano).
-------------------------------------------------------------------------------------------------------+ -o OVERVIEW -------------------------------------------------------------------------------------------------------+Ho trovato questo bellissimo programma, che � una sorta di ProcDump ma pi� evoluto e con molte pi�
funzioni. Ho pensato quindi di descriverne tuuutte le funzioni e di spiegare tutti i campi che ci
permette di modificare in un eseguibile. Ovviamente verranno spiegate anche la teoria del Manual
Unpacking e il formato PE, senza i quali la spiegazione del prog non starebbe in piedi. Man mano che
tratter� vari punti di questo programma, ne spiegher� anche la relativa teoria. Il seguente documento
inoltre pu� essere usato come riferimento per tutti i programmi di PE-Editing che volete.
+-- INTRO -- Informazioni sull'uso del programma e sua reperibilit�, accenni al formato PE.
|
+-- CENNI DI TEORIA SUL MEMORY MAPPING E SUL FORMATO PE
|
+-- LA SCHERMATA INIZIALE -- Commento degli header Optional & File e funzioni principali
|
+-- LA SEZIONE "SECTIONS" -- Vediamo le sezioni da vicino
| |
| +-- Relativo Men� del tasto destrto
|
+-- LA BARRA DEI COMANDI SULLA DESTRA
|
+-- LA SEZIONE "DIRECTORY" -- Accurata descrizione dell'import/export table etc...
|
+-- NOTE
|
+-- LIBRARY, THANKS, LINKS AND E-MAIL
-------------------------------------------------------------------------------------------------------+ -o INTRO -------------------------------------------------------------------------------------------------------+A cosa serve il PEditor? Serve per manipolare file eseguibili del formato standard PE (Portable
Executable) della piattaforma Win32, ma � anche un potente Memory Tool che permette di dumpare i
processi che volete, ottenerne informazioni, killarli, etc. In pratica, molti dei maggiori sistemi di
protezione utilizzano la tecnica del Crypting (o Packing), che consiste nel criptare il file eseguibile
in modo da impedire agli smanettoni di andare a ficcare il naso nel programma per modificarlo a proprio
piacimento. Questi programmi si autodecriptano all'esecuzione, quindi si troveranno in memoria gi� belli
che decriptati, ed � la che arriviamo noi col PEditor: non dobbiamo fare altro che prendere il file
dalla memoria e salvarlo su disco, per ottenere una copia del file eseguibile gi� decriptata e
funzionante e pronta per essere manipolata da noi. Peccato che in realt� non � mai cos� semplice, visto
che spesso c'� bisogno di rimettere a posto alcune cosette per far partire il programma. Il PEditor se
ben usato ci permette di fare tutto questo.
Il programma lo trovate su Player's Tools:
http://playtools.cjb.net
gli autori sono M.O.D. e Yoda.
-------------------------------------------------------------------------------------------------------+ -o CENNI DI TEORIA SUL MEMORY MAPPING E SUL FORMATO PE -------------------------------------------------------------------------------------------------------+Ogni file eseguibile ha una sua struttura ben precisa. Tale struttra varia a seconda della piattaforma
per la quale � stato scritto il software. Quella di cui ci interessemo qui � la struttra del PE, il
COFF (Common Object File Format) degli eseguibili standard della piattaforma win32. Tale struttura ci
servir� anche per capire come il programma viene caricato in memoria. Vediamola in breve:
DOS Header (MZ) |
PE Header (PE) |
Section Table |
Sezione |
Sezione n |
Gli header contengono le informazioni sul file eseguibile stesso, la section table contiene le
informazioni delle varie sezioni, e le sezioni sono i veri e propri blocchi di dati e codice accomunati
dalle stesse propriet�. Di solito ci sono delle sezioni standard, tipo:
.text o .CODE - Sezione di codice eseguibile
.data - Sezione di data
.rsrc - Sezione delle risorse, tipo bitmap, dialogs etc.
.reloc - Sezione della relocation table
e altre un p� meno comuni. Dare uno sguardo alle sezioni � sempre utile, non solo per farci un'idea del
programma, ma anche per ottenere preziose informazioni: insomma, se trovate la sezione .Shrink non avete
difficolt� a capire che avete a che fare con uno Shrinker :-).
Ora abbiamo una idea di come � fatto un file eseguibile PE, vediamo brevemente come viene caricato,
eseguito e mappato in memoria. Quando eseguiamo un file, il PE loader legge gli header (MZ e PE), da l�
legge le varie informazioni sul file che ci si sta apprestando a caricare (informazioni che ritroveremo
e spiegheremo in seguito), quindi le varie sezioni vengono mappate in memoria. In genere l'indirizzo di
inizio per la mappatura � di 00401000 per un eseguibile (cio� la sua image-base + CodeBase, vedi dopo).
Gli eseguibili protetti da packers o crypters sono leggermente diversi: il file fisico contiene le
sezioni criptate, quindi non possiamo andarle a disassemblare o modificare, a meno che non conosciamo
a priori l'algoritmo di crypting. Durante l'esecuzione il file si auto decripta e si mappa in memoria
gi� decriptato e funzionante. L'idea � quindi quella di copiare il contenuto della memoria (relativo al
file eseguibile decriptato) e salvarlo su disco. Otterremmo cos� senza sforzo il nostro eseguibile
decriptato. Come immaginate la cosa non � cos� semplice! Ci sono vari problemi che si incontrano nel
fare questa operazione (che viene chiamata "Dumping"): alcuni dovuti al SO, altri dovuti alla
bastardaggine dei programmatori che l'hanno implementato. Per esempio, il Windox usa il meccanismo della
paginazione per gestire la memoria, cio� le sezioni del file eseguibile vengono mappate a pagine di 4kb.
Il problema � che per risparmiare memoria non sempre sono presenti TUTTE le pagine del programma che
vogliamo dumpare (ad esempio alcune pagine sono state swappate sul disco), quindi usando il comando PAGE
del softice dovremmo andare a vedere la lista delle pagine con i relativi attributi Present/Not Present,
poi con il comando PAGEIN dovremmo caricare fisicamente in memoria le pagine non presenti. A quel punto
abbiamo in memoria il file completo e pronto per essere dumpato. Una volta dumpato poi il file ancora
non funzia, quindi dobbiamo andare a vedere cosa c'� che non va, e qui succedono i casini, perch�
bisogna vedere come hanno implementato i programmatori il sistema di Crypting: ad esempio una cosa molto
comune � la manomissione della Import Table. Addirittura si potrebbero anche incontrare problemi quando
si va a fare il PAGEIN. Ecco perch� andremo a studiarci il PEditor: conoscendo a fondo tutte ci� che ci
permette di fare il programma possiamo riuscire ad attaccare e risolvere i vari crypter.
-------------------------------------------------------------------------------------------------------+ -o LA SCHERMATA INIZIALE -------------------------------------------------------------------------------------------------------+Quando avviate il PEditor vi appare la schermata iniziale con molti campi che corrispondono ognuno ad
un valore specifico. Caricate un programma eseguibile (io per esempio carico il WinZip32 v7.00) e
vedrete gli edit box riempirsi di numerelli :-). Commentiamoli tutti!
Piccola precisazione:
RVA = Relative Virtual Address, cio� indirizzo RELATIVO all'image base (del tipo 00001234)
VA = Virtual Address, cio� indirizzo virtuale che troviamo in memoria (del tipo 00401234)
Tenete Presente che in ogni campo del PEditor troveremo quasi sempre RVA, e questi valori non indicano
direttamente la posizione dell'oggetto nel file fisico. Per ottenere il vero offset bisogna calcolarselo
attraverso il FLC. In questo testo per comodit� ho scelto programmi in cui l'rva coincide sempre con
l'offset fisico (fenomeno spiegato in seguito), per� non dimenticate mai di considerare l'rva come un
valore che deve sempre essere convertito in file offset.
-- Entry Point --Questo valore ci d� l'indirizzo fisico dell'Entry Point, cio� dell'indirizzo da cui il programma inizia
l'esecuzione. Nel mio caso � 00055C20. Ho detto indirizzo FISICO in quanto questo valore corrisponde
alla locazione nel file eseguibile, cio� al 55C20 = 351264-esimo byte ci sar� il punto dove il programma
deve iniziare l'esecuzione. L'indirizzo di memoria corrispondente sar� ottenuto sommando 55C20 al valore
datoci dall'Image Base (per� non � sempre cos�), di cui parleremo fra poco.
-- Image Base --Nel mio caso vale 00400000 ma praticamente quasi tutti gli eseguibili hanno questo valore. L'image base
non � altro che il valore da cui viene iniziata la mappatura del file eseguibile, cio� � l'indirizzo a
partire dal quale viene messo il file in memoria. Se debuggate spesso non avrete potuto fare a meno di
notare che nei file eseguibili le istruzioni che seguite si trovano sempre ad indirizzi tipo 004xxxxx.
Appunto questo valore ve lo d� l'image base. Tale valore cambia a seconda del formato dell'eseguibile,
cio� se EXE o DLL... etc. L'image base per le DLL per esempio � pi� vario, ma generalmente l'indirizzo �
del tipo X0000000, per i tipi SYS in genere � 00010000 etc.
-- Base of Code --Qui trovo il valore 00001000. Anche questo � quasi sempre uguale per tutti gli eseguibili. La sezione di
codice di un eseguibile inizia all'indirizzo 00401000, che appunto � dato da Image Base + Base of Code.
-- Base of Data --68000. Questo valore invece cambia di volta in volta. Infatti � dato dalla grandezza delle sezioni di
codice precedenti: nel caso del winzip abbiamo le sezioni .text e INIT_TEX che finiscono appunto
all'indirizzo 68000. Da qui in poi ci sono sezioni di dati.
-- Size of Image --La grandezza dell'immagine, cio� la grandezza che verr� mappata in memoria. Nel mio caso � FA000, che
corrisponde a 1'024'000. Il size fisico dell'eseguibile � invece di 983'040 bytes. Questa differenza �
data dal fatto che gli eseguibili devono rispettare dei valori di allineamento (che vederemo dopo), sia
per gli indirizzi VA (in memoria), sia per l'allineamento FISICO nel file (vedi Section Alignment e File
Alignment). Tali valori di allineamento significano che una sezione pu� iniziare e finire solo ad un
indirizzo che sia un multiplo del valore di allineamento.
-- Size of Headers --Come intuibile, ci indica la grandezza degli headers (MZ+PE) e della Object Table (la table che descrive
le sezioni). Se questo valore � = a 1000 avremo un allineamento perfetto tra Virtual Offset e Raw
Offset, cio� VA - Image Base = Offset fisico (perch� la mappatura delle sezioni in memoria inizia da
00401000!). Se invece il size � diverso da 1000, allora per calcolarci l'offset fisico dobbiamo usare
la formula: VA - Image Base - Base of Code + Size of Headers = Offset Fisico.
-- Section Alignment --Nel mio caso (ma molto spesso) 1000. Le sezioni vengono mappate in memoria secondo multipli di questo
valore. Per esempio, se una sezione � grande 1234, in memoria non verr� mappata uno spazio di 1234 ma
uno spazio di 2000 (primo multiplo di 1000 dopo 1234). Quando arrivo alla sezione sulle sezioni (sorry)
far� alcuni esempi e vedrete che capirete al volo.
-- File Alignment --Come prima, solo che questo allineamento � relativo al file fisico. Anche nel file fisico le sezioni
vengono scritte a multipli di questo valore (che di solito � 200, ma varia parecchio). Ovviamente se
mettiamo un valore di allineamento troppo grande, sprecheremo dello spazio nel file fisico.
-- SubSystem --Il sistema richiesto dal programma. Nel mio caso 0002. Questo campo pu� avere i seguenti valori:
0000h __Unknown
0001h __Native
0002h __Windows GUI
0003h __Windows Character
0005h __OS/2 Character
0007h __Posix Character
-- Machine Type --
Il tipo di macchina per cui � stato scritto il sw. Nel mio caso 14C. I valori di questo campo sono:
0000h __sconosciuto
014Ch __80386
014Dh __80486
014Eh __80586
0162h __MIPS Mark I (R2000, R3000)
0163h __MIPS Mark II (R6000)
0166h __MIPS Mark III (R4000)
quindi il programma in esame girer� su tutte le macchine 386+.
-- Number of Sections --Indovinate ???????
-- Time Date Stamp --Data e ora dalla creazione del file (creazione fatta dal linker).
-- Pointer To Symbol Table --Usato per il debugging.
-- Number of symbols --Usato per il debugging.
-- Size of Optional Header --L'optional header � quello contiene le informazioni di Base of code, Size of Image etc. Questo campo vi
dice il suo size (nota che l'optional header per� non contiene solo i campi che vediamo dal PEditor)
-- Characteristcs --Contiene vari flag, ad esempio per specificare se il PE considerato � una DLL o un EXE.
-------------------------------------------------------------------------------------------------------+ -o LA SEZIONE DELLE SEZIONI -------------------------------------------------------------------------------------------------------+Nella schermata iniziale c'� un pulsante etichettato "Sections". Premetelo e vi aprir� la finestra con
la lista e descrizione delle sezioni. Io ad esempio carico il 5� corso newbies di Quequero, e mi si
presenta la seguente tabella:
Section | Virtual Size | Virtual Offset | Raw Size | Raw Offset | Characteristics |
.text | 0000322B | 00001000 | 00003400 | 00000400 | 60000020 |
.rdata | 000007F6 | 000007F6 | 00000800 | 00003800 | 40000040 |
.data | 000008E4 | 00006000 | 00000800 | 00004000 | C0000040 |
.idata | 000009BA | 00007000 | 00000A00 | 00004800 | C0000040 |
.rsrc | 00000854 | 00008000 | 00000A00 | 00005200 | 40000040 |
.reloc | 00000430 | 00009000 | 00000600 | 00005C00 | 42000040 |
Bella vero? Qui troviamo tutte le informazioni sulle sezioni.
-- Section --IL primo campo (Section) ci dice il nome
delle varie sezioni. Di solito iniziano con un ".", ma non � sempre cos�. Il nome delle sezioni spesso
� anche relativo al contenuto: "text" indica una sezione di codice (anche .CODE), .data o simili indicano
sezioni di dati, .rsrc indica la sezione delle risorse (bitmap, icone, dialogs etc...), .reloc indica la
sezione delle Rilocazioni (� questa la traduzione di Relocations?). Qui � il caso di spendere due parole
sulle rilocazioni. Premetto che in un file eseguibile questa sezione � completamente inutili, visto che
nel 99% dei casi un eseguibile non subisce MAI una rilocazione. Ma cos'� una rilocazione? E' un fenomeno
che troviamo spesso nelle DLL. Quando un processo carica una dll, il codice della dll viene mappato su
un certo indirizzo xxx. Se per� il programma carica diverse dll, si trover� a mappare di nuovo al tale
indirizzo xxx, dove � gi� presente una dll. Quindi il sistema operativo deve caricare questa dll in un
altro indirizzo yyy. A causa di questa rilocazione anche tutti i puntatori della dll vengono spostati!
Per esempio se normalmente avete una definizione di una stringa, in asm vedrete che il puntatore a questa
stringa sar� 00401234. Quando la dll verr� mappata, all'indirizzo 00401234 troverete la stringa. Se per�
la dll � stata rilocata, anche questo indirizzo verr� cambiato per puntare alla stringa mappata nel nuovo
spazio yyy. Bene, la sezione .reloc contiene tutti questi indirizzi fissi, cos� quando la dll viene
caricata, il sistema operativo controlla questi indirizzi; se sono gi� occupati in memoria (da una dll
precedente) allora li alloca da un'altra parte. Detto cos� pu� sembrare un discorso campato in aria, ma
quando vi trovate a dover patchare delle dll le rilocazioni vi provocano problemini non indifferenti!
-- Virtual Size --Questi valori indicano la grandezza reale della sezione in memoria. Ad esempio la sezione .text risulta
grande 322B bytes. Nota che questi valori sono REALI, e non soddisfano nessun allineamento.
-- Virtual Offset --Indica l'RVA a partire dal quale sar� mappata in memoria la sezione. Per la sezione .text il valore � di
1000 e non zero. Perch�? Perch� questo 1000 � il famoso "Base of code". Quindi la prima sezione (.text)
partir� dal VA 00401000. Adesso ritorniamo al discorso degli allineamenti. Per la sezione .text io parto
da 00401000 e mappo 322B bytes. Quindi essendo le sezioni contigue, dovrei aspettarmi la prossima
sezione mappata da 00401000 + 322B = 0040422B. Il valore di allineamento delle sezioni, per�, qui � di
1000, quindi la prossima sezione comincer� dal primo multiplo di 1000 disponibile dopo 0040422B, cio�
al VA 00405000 (RVA = 5000). E cos� via tutte le sezioni. L'ultima sezione comincia all'RVA 9000 ed �
lunga 430 bytes. Quindi in tutto l'ultimo byte sar� il 9430�. Sempre per l'allineamento, la fine di
questa sezione va a finire a A000, che � il valore del Size Of Image.
-- Raw Size --La grandezza che le sezioni occupano nel file fisico. Anche questo valore � correlato ad un allineamento,
quello dato da File Alignment, che di solito � pari a 200. In effetti tutti i size sono multipli di 200.
La prima sezione ad esempio abbiamo visto che VIRTUALMENTE � grande 322B. Il primo multiplo di 200
disponibile dopo tale valore � 3400, quello del raw size. Lo spazio che va da 322B a 3400 viene riempito
con taanti zeri (469 decimale per l'esattezza). Bene, immaginate che questo si ripete per tutte le sezioni
di tutti gli eseguibili (e tutti i file con formato PE) che avete sull'HD! Gi�! Avete un hard disk pieno
di inutili zeri! Che ci volete fare...
-- Raw Offset --L'indirizzo fisico dal quale � mappata la sezione nel file. Nel nostro caso per la sezione .text abbiamo
un offset di 400 e non di zero. 400 infatti � il valore che ritroviamo in "Size of Headers". Quindi gli
header MZ+PE sono grandi 400 bytes, e da qui inizieranno le sezioni. La raw size di .text � 3400, quindi
la prossima sezione sar� a 3400+400 = 3800. Come vedete qui tutto combacia con l'allineamento a 200.
A questo punto prendiamo il raw offset dell'ultima sezione (5C00), aggiungiamogli il raw size dell'ultima
sezione (600). Risultato: 6200. Aggiungiamo ancora 200 (del file alignment) e otteniamo 6400, che in
decimale vale 25600, esattamente il valore della grandezza del file fisico.
-- Characteristcs --Questo campo descrive il tipo di sezione (dati, eseguibile, etc.). I valori possono essere i seguenti
000000020h Sezione di codice
000000040h Sezione di dati inizializzati
000000080h Sezione di dati non inizializzati
040000000h La sezione non deve essere messa in cache
080000000h La sezione non � paginabile
100000000h La sezione � condivisa
200000000h Sezione eseguibile
400000000h Sezione in lettura
800000000h Sezione in scrittura
e possono essere combinati. Ad esempio la sezione .text ha il valore 60000020, che � una combinazione di
000000020 + 40000000 + 20000000, cio� Sezione di codice, eseguibile e leggibile. La sezione .data,
invece, � una combinazione di 80000000 + 40000000 + 00000040, cio� dati inizializzati, lettura e
scrittura. Questi attributi sono molto importanti perch� ci danno un idea precisa delle sezioni che
abbiamo davanti.
--== IL MENU' DEL TASTO DESTRO ==--Se cliccate col destro su una sezione avrete il segente men�:
>>Edit SectionVi aprir� un nuovo DialogBox con tutti i campi della sezione, e ve li potrete modificare a vostro
piacimento. Notate il tasto Char Wizard: questo apre una apposia finestra che vi permette di modificare
automaticamente le flag di "Characteristcs".
>>Add a section in the PE HeaderPer aggiungere una sezione.
>>Delete the section in the PE HeaderPer cancellare la sezione selezionata.
>>Copy the section to HDSalva la sezione in un file su HD.
>>Move the section to HDSalva la sezione su HD e poi la cancella dall'eseguibile.
>>Copy a section from HD to EOFPrende una sezione salvata su disco e la inserisce alla fine del file (dopo le altre sezioni).
>>DumpFixer (RS=VS & RO=VO)Dopo aver unpackato un file eseguibile molto probabilmente dovrete rimettere a posto i valori Size e
Offset sia Virtuali che Raw. Prima ce lo facevamo a parte col procdump, ora con il PEditor � automatico!
Questa operazione si chiama "Riallineamento del PE", e a questo punto dovrebbe esservi chiaro perch�
dobbiamo farla: dopo aver dumpato un programma � probabile che le sezioni contengano dei dati non giusti
sulla Raw Size; infatti dopo che avete unpackato il file, le sezioni risultano pi� grandi di quanto lo
erano prima nel file fisico compresso, quindi dobbiamo cambiare i relativi descrittori.
>>Set the characteristics to E0000020Setta automaticamente le flag della sezione a CODICE, ESEGUIBILE, LETTURA, SCRITTURA.
>>Truncate at the start of this sectionCancella tutti i byte dalla sezione selezionata in poi.
>>Custom truncateCancella tutti i byte da un offset specificato in poi.
-------------------------------------------------------------------------------------------------------+ -o LA BARRA DEI COMANDI SULLA DESTRA -------------------------------------------------------------------------------------------------------+Descriviamo ora i comandi (quelli in colonna a destra).
-- Browse --Se questo non l'avete capito da soli spegnete il pc e dategli fuoco.
-- Tasks --L'elenco dei task in esecuzione. Per ogni task avete i dati, cio� Process ID, Image Base, Size of Image
e Owner. Premendo su un task con il tasto destro avrete il seguente men�:
dump (full) dump (partial) dump (Sections) |
view process infos load into PEditor |
terminate process |
refresh |
>> dump full: vi permette di dumpare l'intero processo in automatico
>> dump partial: vi permette di dumpare una area di memoria a piacimento, scegliendo gli offset iniziale
e finale del dump.
>> dump sections: apre un dialogbox in cui avete una lista delle sezioni che potrete dumpare
separatamente, compreso l'header PE.
>> view process infos: apre un dialog che contiene i seguenti campi:
Usage
Th32ProcessId
Th32DefaultHeapId
Th32ModuleId
CntThreads
Th32ParenProcessId
PcPriClassBase
>> load into PEditor: attenzione per gli assuefatti al ProcDump! Questa opzione carica solo il file exe
relativo al processo, e non i dati relativi al processo stesso! Cio�, in procdump voi con process
infos caricavate le info sulle sezioni a runtime, con PEditor invece non potete.
>> terminate process: killa un processo
>> refresh: refresha tutti i valori
-- ! --Refresha tutti i valori di tutti i campi.
-- Section Split --Dumpa in automatico tutte le sezioni separate, ognuna in un file diverso che verr� messo nella dir
corrente. Attenzione perch� non vi viene chiesta nessuna conferma!
-- Break'n Enter --Questa funzione serve per breakare su un indirizzo qualsiasi di un programma. Come funziona? Per esempio
prima andavamo a scriverci a mano un CCh all'indirizzo per trapparlo con softice tramite BPINT 3, ora con
break'n enter settiamo un BPINT 3 da softice, premiamo il tasto RUN e popperemo direttamente sullo
indirizzo. Non solo! In sice avremo nella prompt window un riquadro che ci dice che byte dobbiamo
ripristinare all'indirizzo al posto del CC! Non � stramitico ??!!
-- FLC --Sta per File Location Calculator. Praticamente vi richiede un valore che pu� essere uno dei seguenti
campi: VA, RVA, Offset esadecimale, Offset decimale; tale valore verr� convertito negli altri campi
e vi vengono pure indicati i primi bytes che si trovano all'offset in quel file.
-- Checksum --Calcola il checksum corretto del file e vi dice quale � invece il checksum attualmente memorizzato nel
file (di solito 0). C'� anche la possibilit� di correggereil checksum in automatico tramite l'apposito
tasto "correct it".
-- Rebuilder --Il rebuilder serve per ricostruire (o meglio, provare a ricostruire) la import table. Spesso dopo il
dump un programma pu� avere la Import Table (IT) compromessa, quindi per far funzionare l'eseguibile
dumpato avremo bisogno di ricreargli la IT. Questa cosa si pu� fare in diversi modi, e il Rebuilder �
uno di questi! La comodit� del rebuilder � che lo fa in automatico, per� non sempre riesce (anzi, io
finora non sono mai riuscito a ricostruire una IT semplicemente col rebuilder). Cmq vediamo come funzia:
O-O Wipe Relocation SectionQuesta opzione permette di eliminare la Relocation Section, permettendo cos� direcuperare un p� di
spazio. La sezione Relocation negli eseguibili, come abbiamo visto, � praticamente inutile, visto che
gli eseguibili non subiscono mai le rilocazioni.
O-O Realign File (Only Win 9x)Normal: Riduce il valore di allineamento del file, in modo da guadagnare spazio.
HardCore: Elimina anche il Dos Stub (una parte dell'header MZ, in genere quella che contiene il
messaggio "Questo prog ha bisogno di winzozz...) per guadagnare ancora altro spazio.
Notate che la riduzione di spazio ottenuta con il riallineamento non � sempre significativa.
O-O Rebuild Import TableFast: Cerca nell'import table le stringhe delle funzioni importate.
Carefully: Cerca nell'intero file le stringhe delle funzioni importate.
Force functionsearching: Forza la ricerca nell'intero file delle stringhe delle funzioni importate
anche se c'� gi� l'OriginalFirstThunk.
Nota-- Il discorso del table rebuilding � abbastanza complesso, qui ho dato solo la semplice
descrizione delle opzioni, i dettagli della Import Table verranno approfonditi pi� avanti nella
Sezione Directory/Imports.
Make PE Header Win NT/2k compatible: cerca di rendere il file compatibile con il Win NT controllando
il Size of Image, il File Size, Null Sections e fa il Section Overlap (boh!).
ecco fatto. Ora scegliete le opzioni pi� opportune e provate a ricostruire tutte le IT che volete. Vi
ricordo che se possibile le IT � meglio ricavarle in fase di dumping, o con il ProcDump (Rebuild New IT)
o ve la dumpate a mano. E' possibile che vi ritroverete ad aver aggiustato una IT, e scoprirete che il
file dumpato e riparato funzioner� solo sul vostro PC. Anche questo spiacevole inconveniente verr�
approfondito pi� avanti quando parleremo della IT.
-- Apply changes --Se avete modificato un valore, con questo pulsante potete fare l'update.
-- About --La finestra di about :-) Notate tra i ringraziamenti il nome del nostro caro Kill3xx!
-- Exit --Indovinate ?
-------------------------------------------------------------------------------------------------------+ -o LA SEZIONE DIRECTORY -------------------------------------------------------------------------------------------------------+Nella schermata principale avete il pulsante "Directory". Se ci cliccate verr� aperto un nuovo dialog
pieno di nuove funzioni e valori da commentare (gasp! ma quando finisce 'sto testo??). Questa sar� la
parte pi� difficile e importante, visto che parleremo della Import/Export Table e dei problemi connessi.
Cominciamo dal trafiletto di dwords: per ogni campo abbiamo sia RVA che SIZE. RVA ci d� l'indirizzo di
mappatura dell'oggetto in considerazione, SIZE ci d� la grandezza dell'oggetto stesso. In questa ultima
versione del PEditor hanno anche aggiunto i tips, tenete il mouse su un campo e vi dir� la posizione
fisica all'interno del file. Per esempio nel WinZip 8, vado sul campo Import Table e mi appare il tip:
"position of this entry: 00000170h (e_lfanew + 80h) related to: .rdata"
dove e_lfanew � un membro della struttura IMAGE_DOS_HEADER, membro che contiene il puntatore all'header
PE. 80h � la posizione del puntatore all'IT all'interno della struttura Directory, e related to .rdata
vuol dire che il VA si trova all'interno della sezione rdata (ehm.. questi tips stanno anche nella
schermata principale, avevo dimenticato di menzionarli :-)).
-- Export Table --Nel programma in esame (WinZip 8) questo campo ha valori nulli. Di solito un exeguibile non ha delle
funzioni esportate. Per avere un esempio sottomano prendiamo il file Wz32.dll dalla dir del winzip.
Ora per la Export Table avremo l'RVA 0002FC60 e il SIZE 0000000149. Siccome siamo curiosi prendiamo un
hex-editor e andiamo a vedere con i nostri occhi questa esprt table. Carichiamo il wz32.dll, quindi
prendiamo l'rva 0002FC60 e col FLC lo convertiamo in raw offset. In questo caso il raw offset coincide
con 2FC60, a causa del perfetto allineamento. Ecco cosa troviamo:
02FC60 00 00 00 00 - E4 EE FD 38 - 00 00 00 00 - 0A FD 02 00 ................
02FC70 01 00 00 00 - 0D 00 00 00 - 0D 00 00 00 - 88 FC 02 00 ................
02FC80 BC FC 02 00 - F0 FC 02 00 - C0 83 01 00 - 60 83 01 00 ................
02FC90 50 83 01 00 - 46 E9 00 00 - E0 E8 00 00 - 10 72 01 00 ................
02FCA0 70 71 01 00 - A0 9C 01 00 - 80 97 01 00 - 60 C9 01 00 ................
02FCB0 00 C9 01 00 - 10 BF 01 00 - B0 C9 01 00 - 13 FD 02 00 ................
02FCC0 22 FD 02 00 - 32 FD 02 00 - 37 FD 02 00 - 42 FD 02 00 ................
02FCD0 52 FD 02 00 - 58 FD 02 00 - 63 FD 02 00 - 67 FD 02 00 ................
02FCE0 70 FD 02 00 - 81 FD 02 00 - 90 FD 02 00 - 99 FD 02 00 ................
02FCF0 00 00 01 00 - 02 00 03 00 - 04 00 05 00 - 06 00 07 00 ................
02FD00 08 00 09 00 - 0A 00 0B 00 - 0C 00 77 7A - 33 32 2E 64 ..........wz32.d
02FD10 6C 6C 00 44 - 72 61 67 41 - 70 70 65 6E - 64 46 69 6C ll.DragAppendFil
02FD20 65 00 44 72 - 61 67 43 72 - 65 61 74 65 - 46 69 6C 65 e.DragCreateFile
02FD30 73 00 57 5A - 35 36 00 75 - 6E 63 6F 6D - 70 72 65 73 s.WZ56.uncompres
02FD40 73 00 75 6E - 63 6F 6D 70 - 72 65 73 73 - 5F 69 6E 69 s.uncompress_ini
02FD50 74 00 75 6E - 7A 69 70 00 - 75 6E 7A 69 - 70 5F 69 6E t.unzip.unzip_in
02FD60 69 74 00 7A - 69 70 00 7A - 69 70 5F 69 - 6E 69 74 00 it.zip.zip_init.
02FD70 7A 69 70 6C - 61 62 65 6C - 44 69 73 6B - 65 74 74 65 ziplabelDiskette
02FD80 00 7A 69 70 - 6D 65 6D 63 - 6F 6D 70 72 - 65 73 73 00 .zipmemcompress.
02FD90 7A 69 70 73 - 70 6C 69 74 - 00 7A 69 70 - 77 69 70 65 zipsplit.zipwipe
02FDA0 44 69 73 6B - 65 74 74 65 - 00 Diskette.
(nota che sono riportati nella notazione intel, quindi ad esempio la seconda dword non � E4EEFD38 ma
il suo contrario 38FDEEE4)
questi sono i 329 Bytes della Export Table (149h, il Size). Come vedete la seconda met� � piena di
quelli che sembrano i nomi delle funzioni. Okei, diamo un significato a questa manciata di bytes.
La Export Table � strutturata nel modo seguente:
1. Directory Table (informazioni sulla ET stessa) (prime 10 dwords)
2. Address Table (puntatori alle funzioni nella dll)
3. Name Pointer Table (puntatori ai nomi delle funzioni)
4. Ordinal Table (struttura ordinale delle funzioni)
5. Name Strings (nomi delle funzioni)
bene, abbiamo tutti i dati per interpretare la nostra ET. Partiamo dalla Directory Table, che � formata
dalle prime 10 dwords, che identificano la struttra seguente:
VALORE | DWORD NELLA ET DI ESEMPIO |
EXPORT FLAGS | 00 00 00 00 1� |
TIME/DATE STAMP | E4 EE FD 38 2� |
MAJOR VERSION | MINOR VERSION | 00 00 | 00 00 3� |
NAME RVA | 0A FD 02 00 4� |
ORDINAL BASE | 01 00 00 00 5� |
# EAT ENTRIES | 0D 00 00 00 6� |
# NAME PTRS | 0D 00 00 00 7� |
ADDRESS TABLE RVA | 88 FC 02 00 8� |
NAME PTR TABLE RVA | BC FC 02 00 9� |
ORDINAL TABLE RVA | F0 FC 02 00 10� |
()- Export Flags: attualmente questo valore non � mai usato, sta sempre settato a zero.
()- Time/Date Stamp: data e ora di creazione dell'export.
()- Major/Minor version: due words che raramente sono usate.
()- Name RVA: punta alla stringa che rappresenta il nome della DLL
Nel nostro caso punta a 0002FD0A, che � l'indirizzo della stringa wz32.dll (contare per credere).
()- Ordinal Base: indica il numero di partenza della EAT (export address table), normalmente � a 1.
()- # EAT Entries: il numero delle funzioni esportate (nel nostro caso 0D = 13dec).
()- # Name Ptrs: indica il numero di funzioni nella Name Ptr Table, cio� abbiamo 13 nomi di funzioni.
Non sempre questo valore coincide con il numero dell funzioni effettivamente presenti nella ET.
Indica solo il numero dei NOMI delle funzioni esportate.
()- Address Table RVA: indica la struttra che contiene i puntatori al codice delle funzioni.
Nel nostro caso punta alla seguente struttura:
02FC88 C0 83 01 00 - 60 83 01 00 ................
02FC90 50 83 01 00 - 46 E9 00 00 - E0 E8 00 00 - 10 72 01 00 ................
02FCA0 70 71 01 00 - A0 9C 01 00 - 80 97 01 00 - 60 C9 01 00 ................
02FCB0 00 C9 01 00 - 10 BF 01 00 - B0 C9 01 00
................
Come vedete sono tutti puntatori (RVA) a zone di codice precedenti a quella della ET. Ad esempio
avremo che la prima dword 000183C0 identifica l'RVA del codice che effettivamente contiene la
prima funzione esportata, la seconda dword 00018360 identifica il puntatore alla seconda funzione
effettiva di codice e cos� via.
()- Name ptr table RVA: indica la struttura che contiene i nomi delle funzioni importate.
Nel nostro caso punta a 0002FCBC, che � la seguente struttura:
02FCBC 13 FD 02 00 ................
02FCC0 22 FD 02 00 - 32 FD 02 00 - 37 FD 02 00 - 42 FD 02 00 ................
02FCD0 52 FD 02 00 - 58 FD 02 00 - 63 FD 02 00 - 67 FD 02 00 ................
02FCE0 70 FD 02 00 - 81 FD 02 00 - 90 FD 02 00 - 99 FD 02 00 ................
Anche questi sono puntatori (sempre RVA) ai nomi delle funzioni esportate, prendiamo la prima
dword: 0002FD13 punta alla stringa "DragAppendFile" che � il nome della prima funzione esportata,
la seconda dword punta a 0002FD22, cio� la stringa "DragCreateFiles", e cos� via per tutte e 13
le funzioni esportate.
()- Ordinal Table RVA: indica la struttura degli Ordinali (li vedremo fra poco).
Di nuovo vediamo che punta a 02FCF0, ecco cosa ci trovate:
02FCF0 00 00 01 00 - 02 00 03 00 - 04 00 05 00 - 06 00 07 00 ................
02FD00 08 00 09 00 - 0A 00 0B 00 - 0C 00 ................
i numeri da 0000 a 000C (gli ordinali delle 13 funzioni). Beh, prima vi ho tenuto in sospeso sul
fatto di questi ordinali, ora � il momento di fare luce! Le funzioni possono essere esportate
tramite i nomi, come in questo caso, o anche soltanto tramite i loro numeri ordinali, senza dover
usare i nomi. Sicuramente vi sar� capitato di debuggare qualche programma che nella sua Import
Table non ha nomi di funzioni carini tipo "CheckTheSerial" o "CheckIfCdIsCopied", ma avrete solo
quei noiosissimi "Ordinal 0160h" e cos� via. Questo � dovuto proprio al fatto che il programma fa
riferimento a una libreria che ha le export in Ordinal Only. Il programma identifica le funzioni
da richiamare semplicemente dal numerello a 16 bit (assegnato univocamente a ogni funzione),
numero che viene appunto usato da GetProcAddress per ricavarsi l'indirizzo della funzione voluta.
-- Import Table --Se fin qui gli argomeni trattati vi sono sembrati difficili, spegnete il computer e andate a giocare con
le macchinine (o con le bambole), perch� stiamo per affrontare l'argomento pi� ostico di tutto questo
testo, e francamente anche il pi� importante, quello che vi ritroverete spesso a dover fronteggiare
quando ingaggerete battaglie disperate con crypters assurdi. Cmq fin qui � stato tutto facile, giusto?
Vedrete che alla fine se spiegato bene anche questo argomento diventer� facile come gli altri.
Tanto per cominciare mi serve una cavia adatta: qual'� un file con una IT semplice che abbiamo tutti?
il Notepad.exe! Andiamo nella Directory Table e per la IT troviamo:
Import table: RVA: 00006000 SIZE: 8C
quindi abbiamo indirizzo e grandezza. Ora 6000 � un RVA, quindi col FLC andiamocelo a convertire in raw
offset, ed otteniamo... 6000 (allineamento perfetto).
B0 61 00 00 - ED 6C 45 37 - FF FF FF FF - 8A 65 00 00
F0 63 00 00 - 18 61 00 00 - B3 C2 1F 37 - FF FF FF FF
9C 67 00 00 - 58 63 00 00 - CC 61 00 00 - EB 6C 45 37
FF FF FF FF - 92 6B 00 00 - 0C 64 00 00 - B8 60 00 00
72 C2 3E 35 - FF FF FF FF - F6 6C 00 00 - F8 62 00 00
C0 62 00 00 - ED 6C 45 37 - FF FF FF FF - 7A 6D 00 00
00 65 00 00 - A0 60 00 00 - EB 6C 45 37 - FF FF FF FF
DA 6D 00 00 - E0 62 00 00 - 00 00 00 00 - 00 00 00 00
00 00 00 00 - 00 00 00 00 - 00 00 00 00
questi sono i 140 (8Ch) bytes. Per ora non compaiono nomi di funzioni o dll.
Partiamo dalla Directory Table, che come al solito contiene le informazioni sulla IT stessa. Dobbiamo
prendere in considerazione le prime 5 dwords, che ci identificano i seguenti valori:
VALORE: | DWORD NELLA IT DI ESEMPIO |
Union: IMPORT FLAGS or OriginalFirstThunk | B0 61 00 00 |
TIME/DATE STAMP | ED 6C 45 37 |
FORWARDER CHAIN | FF FF FF FF |
NAME RVA | 8A 65 00 00 |
IMPORT ADDRESS TABLE RVA (FirstThunk) | F0 63 00 00 |
anche le dword successive vanno considerate a gruppi di 5, compreso l'ultimo gruppo di 5 dwords che
serve per chiudere la Directory dell'IT.
()- Import Flags
Questo dovrebbe rientrare nel campo "characteristics", ma in realt� la dword � una union tra il valore
Characteristics e OriginalFirstThunk. Essendo il campo characteristics sempre a zero, la dword contiene
il valore dell'OriginalFirstThunk, 000061B0, che punta alla struttura degli indirizzi ai nomi delle
funzioni importate:
7A 65 00 00 - 68 65 00 00 - 20 65 00 00 - 4E 65 00 00
3C 65 00 00 - 2E 65 00 00 - 00 00 00 00
questi valori dunque puntano ai nomi delle funzioni (la NULL indica il termine della struttura), e
questi indirizzi di nomi faranno parte degli OriginalFirstThunk (ne parleremo dopo).
E le Characteristics? La union � come una struttura, solo che i membri di tale struttura possono essere
utilizzati solo uno alla volta. Quindi il la union in questione pu� contenere O le characteristics O
OriginalFirstThunk. Notate che sono 6 funzioni importate, e che l'ordine dei loro nomi non coincide con
l'ordine dei loro puntatori (cosa che noterete se andate a vedere l'IT dal PEditor).
In realt� questi indirizzi sono dei puntatori ad altre strutture, cio�:
Struttura IMAGE_IMPORT_BY_NAME:
word HINT
byte NAME1
infatti prendiamo ad esempio il primo puntatore, 0000657A, punta alla stringa:
0000657A 6C 00 53 68 65 6C 6C 45 78 65 63 75 74 65 41 00 l.ShellExecuteA
in cui i primi due byte sono la word HINT (006C) che rappresenta l'indice della funzione nell'ET della
dll da cui stiamo importando la funzione, e poi il nome vero e proprio, ShellExecuteA in forma di array
di byte terminati dallo 0.
()- Time Date Stamp
La solita dword con la data e ora di creazione della IT.
()- Forwarder Chain
Nel nostro caso � settato a -1, e quindi non ci sono forwarders. Cos'� un forwarder? Una dll pu�
esportare un simbolo che non � definito nella dll stessa, ma � importato da un'altra dll. In tal caso
il simbolo si dice Forwarded. Ora sorge il problema che nel FirstThunk di un Forwarded noi abbiamo
l'entry point della funzione, per� il loader non ha modo di verificare se questo entry point � corretto
dato che non si trova realmente nella dll da cui stiamo importando. Quindi l'entry point dell forwarded
deve essere sempre fixato, e tale fix pu� avvenire tramite il ForwarderChain. Lo troviamo come indice
nella lista dei thunks, in cui la funzione alla posizione indicizzata � una funzione esportata in
forward, e il FirstThunk a tale posizione � l'indice della prossima funzione importata. La catena
continua fino a trovare il valore -1, che indica che non ci sono pi� forwarders.
()- Name RVA
Questo punta al nome della dll importata. Abbiamo un bel 0000658A. Andiamo a cercare anche questo rva
nel file (non serve la conversione in raw offset perch� come abbiamo visto l'allineamento � perfetto) e
ci troviamo la stringa:
0000658A 53 48 45 4C 4C 33 32 2E 64 6C 6C 00 SHELL32.dll
()- Import Address Table RVA
Abbiamo il valore 000063F0. Questo valore punta ad un array di dwords, l'ultima delle quali � nulla ed
indica la fine della tabella.
Nel nostro caso a 000063F0 troviamo:
FF 6A D1 7F - 0F 28 CD 7F - 66 16 CE 7F - 7C 84 CB 7F
EB C6 CC 7F - 7B 42 D1 7F - 00 00 00 00
6 dowrd pi� l'ultima NULL. Queste dwords ci danno gli indirizzi delle funzioni importate per ogni dll,
per� non le import all'interno del file (OriginalFirstThunk), ma delle import functions vere e proprie
(FirstThunk).
Ho come l'impressione di non essere stato molto chiaro. Allora continuaimo l'esempio pratico, tanto per
eliminare tutti i dubbi. Sempre nella Import Table che abbiamo appena esaminato, torniamo all'indirizzo
della IT e troviamo:
6000 B0 61 00 00 - ED 6C 45 37 - FF FF FF FF - 8A 65 00 00 Primo Modulo
F0 63 00 00
18 61 00 00 - B3 C2 1F 37 - FF FF FF FF Secondo Modulo
9C 67 00 00 - 58 63 00 00
- CC 61 00 00 - EB 6C 45 37 Terzo Modulo
FF FF FF FF - 92 6B 00 00 - 0C 64 00 00
- B8 60 00 00 Quarto Modulo
72 C2 3E 35 - FF FF FF FF - F6 6C 00 00 - F8 62 00 00
C0 62 00 00 - ED 6C 45 37 - FF FF FF FF - 7A 6D 00 00 Quinto Modulo
00 65 00 00
- A0 60 00 00 - EB 6C 45 37 - FF FF FF FF Sesto Modulo
DA 6D 00 00 - E0 62 00 00
- 00 00 00 00 - 00 00 00 00 NULL
00 00 00 00 - 00 00 00 00 - 00 00 00 00
bene, cos� divisi sono pi� leggibili. Ogni 5 dword abbiamo la struttura IMAGE_IMPORT_DESCRIPTOR, questa
struttura contiene le informazioni sulla dll importata e le sue relative funzioni. Il primo modulo lo
abbiamo gi� visto, era SHELL32.DLL, ora passiamo al secondo. Andiamo subito alla terza dword, otteniamo
l'indirizzo 0000679C. Andiamo a cercare nel file l'offset e troviamo la stringa KERNEL32.dll, quindi
abbiamo ricavato il nome della dll. Prendiamo la prima dword del secondo modulo e abbiamo il puntatore
00006118, troviamo il seguente array:
. EE 66 00 00 - FC 66 00 00
06 67 00 00 - E4 66 00 00 - CC 66 00 00 - 1A 67 00 00
24 67 00 00 - BE 66 00 00 - DA 66 00 00 - 4E 67 00 00
5C 67 00 00 - 6C 67 00 00 - 7E 67 00 00 - 90 67 00 00
B8 65 00 00 - A4 65 00 00 - 96 65 00 00 - 96 66 00 00
B2 66 00 00 - A2 66 00 00 - 66 66 00 00 - 86 66 00 00
7A 66 00 00 - 3C 66 00 00 - 5A 66 00 00 - 48 66 00 00
12 66 00 00 - 30 66 00 00 - 20 66 00 00 - EA 65 00 00
08 66 00 00 - FC 65 00 00 - 30 67 00 00 - DC 65 00 00
CA 65 00 00 - 10 67 00 00 - 40 67 00 00 - 00 00 00 00
questi sono tutti i puntatori a strutture HINT, cio� strutture formate come gi� visto da una dword che
indica il numero ordinale della funzione e da una stringa. Quindi i puntatori avranno il significato:
000066EE 5F 00 44 65 6C 65 74 65 46 69 6C 65 41 00 _.DeleteFileA
indice ordinale: 5F nome: DeleteFileA
000066FC D1 02 5F 6C 63 72 65 61 74 00 .._lcreat
indice ordinale: 02D1 nome: _lcreat
etc.......
abbiamo i nomi, quindi torniamo nel secondo modulo e andiamo a vedere la quinta dword, 00006358, che ci
d� l'array (IAT):
F5 19 FA BF - AD 07 FA BF
BA 74 F7 BF - D7 07 FA BF - FE 09 FA BF - CC B5 F9 BF
D0 49 F7 BF - B4 48 F7 BF - BB B5 F9 BF - B4 48 F7 BF
D4 71 F7 BF - 07 7D F7 BF - 6F 7F F7 BF - BE 72 F7 BF
AD 77 F7 BF - 16 77 F7 BF - F8 D4 F8 BF - 32 73 F7 BF
2B 08 FA BF - 5F 6E F7 BF - D0 77 F7 BF - 40 7E F7 BF
A9 73 F7 BF - C0 64 F7 BF - 84 72 F7 BF - 57 7B F7 BF
DB 7A F7 BF - 6F 73 F7 BF - 6B 51 F8 BF - C6 20 F8 BF
5F 33 F9 BF - F8 72 F7 BF - 1B 6E F7 BF - 3D 6E F7 BF
DA C5 F8 BF - E4 74 F7 BF - D7 6D F7 BF - 00 00 00 00
e questi sono gli indirizzi veri e propri delle funzioni importate. Non sempre il numero dei nomi di
funzioni � effettivamente uguale al numero degli indirizzi della IAT.
Possiamo quindi riassumere la seguente corrispondenza:
OriginalFirstThunk | Nome Funzione | FirstThunk |
000066EE | DeleteFileA | BFFA19F5 |
000066FC | _lcreat | BFFA07AD |
etc...
ecco quindi spiegato tutto il meccanismo di import. Quando eseguiamo un file, il PE viene caricato in
memoria, poi il PE loader trova nella IT il nome della dll importata, quindi va a vedere gli Original-
FirstThunk, da l� ricava il nome della funzione, e poi va a trovare nella IAT l'indirizzo della funzione
in esame. Prendiamo il caso di DeleteFileA. Disassemblo il notepad.exe e vado a cercare una reference
a DeleteFileA, la trovo alla riga:
004032F7 FF1558634000 call [00406358]
bene, come vediamo il compilatore ha risolto la import con l'indirizzo 00403658. Leviamo l'image base e
ci resta 00006358. Cosa c'� a 6358 nel file? Il FirstThunk di DeleteFileA. Ora l'ultimo passo: andiamo
in sice e settiamo un bel bpx DeleteFileA. Provate ad aprire un file di testo con il notepad, e il sice
popper� nel modulo KERNEL32 all'indirizzo BFFA19E5. Ecco quindi che abbiamo raggiunto il codice della
funzione DeleteFileA.
Visto che anche l'IT � facile ora?
-- Recourse --
Pare che abbiano sbagliato la sintassi sul PEditor: questo � il campo Resource. Nelle risorse troviamo
le stringhe, le bitmap, le finestre, etc. Per il notepad abbiamo l'rva 00007000 ed il size di 51BC.
Non riporto tutti i bytes, ma ci andremo solo ad occupare della struttura directory di questo campo e
vedremo un p� come funzia. A 7000 troviamo la directory:
RESOURCE FLAGS | 00 00 00 00 |
TIME/DATE STAMP | 11 97 56 01 |
MAJOR VERSION | MINOR VERSION | 04 00 00 00 |
# NAME ENTRY | # ID ENTRY | 00 00 07 00 |
RESOURCE DIR ENTRIES | 03 00 00 00 |
()- Resource Flags
Non usato, sta sempre a zero.
()- Time/Date Stamp
Dword che contiene data e creazione delle risorse.
()- Major/Minor Version
Due dwords, nel nostro caso Major = 4 Minor = 0. Semplicemente il numero della versione.
()- #Name Entry/ #ID entry
Name entry contiene il numero di oggetti all'inizio dell'array di Directory che hanno i nomi associati.
ID entry contiene il numero di oggetti che hanno delle dwords come nome associato.
Nel nostro caso Name Entry � pari a 0, e ID entry � 7. Detto cos� non � molto chiaro, spiegheremo tutto
meglio fra poco.
()- Resource Dir Entries
In seguito alla directory c'� un array variabile di elementi che fanno parte della directory entries.
Ora torniamo a Name/ID entry: quelle due words ci dann� il numero di oggetti che hanno o una stringa o
un numero come nome identificatore. Nel nostro caso vediamo che abbiamo sette oggetti che sono
identificabili tramite interi a 32 bit. La struttura di un direcotry entry � fatta nel modo seguente:
NAME RVA/INTEGER ID DWORD
E | DATA ENTRY RVA/SUBDIR RVA DWORD
dove la prima dword � una union tra i campi NAME o INTEGER: pu� contenere o l'rva che punta al nome
della risorsa (rva a 31 bit per�) o un intero a 32 bit che indica l'identificativo della risorsa.
La seconda dword invece � determinata dal primo bit pi� a sinistra (E):
se E = 0 stiamo considerando i dati di una risorsa
e il seguente campo a 31 bit ci indica l'rva che punta ai dati
se E = 1 stiamo considerando una sottodirectory
e il seguente campo ci indica l'rva del Subdirectory Entry.
bene, ora per capire quello detto facciamo il classico esempio. Prendiamo le risorse del notepad. Queste
cominciano all'indirizzo 7000. Ci andiamo e vediamo i seguenti bytes:
00 00 00 00 - 11 97 56 01 - 04 00 00 00 - 00 00 07 00
questi fanno parte della directory e identificano i campi Resource Flags, TimeDate stamp, major/minor
version, Name/ID entry, e questo lo abbiamo visto. Dopo queste dwords inizia l'array di strutture
Resource Dir Entries (siccome ID entry � sette, prendiamo sette strutture da 2 dwords):
03 00 00 00 - 48 00 00 80 - 04 00 00 00 - 98 00 00 80
05 00 00 00 - B0 00 00 80 - 06 00 00 00 - D0 00 00 80
09 00 00 00 - 00 01 00 80 - 0E 00 00 00 - 20 01 00 80
10 00 00 00 - 40 01 00 80
queste strutture vanno considerate a gruppi di 2 dwords. Vediamone il significato:
00000003 numero identificativo
80000048 subdirectory all'offset 48
--------
00000004 numero identificativo
80000098 subdirectory all'offset 98
--------
00000005 numero identificativo
800000B0 subdirectory all'offset B0
etc
dopo questa struttura troviamo un altra directory:
00 00 00 00 | 11 97 56 01 | 04 00 00 00 | 00 00 08 00 |
FLAGS | TIME/DATE | MAJ/MIN | ENTRIES |
abbiamo 8 entries, quindi prendiamo le successive otto strutture:
01 00 00 00 - 58 01 00 80
02 00 00 00 - 70 01 00 80 - 03 00 00 00 - 88 01 00 80
04 00 00 00 - A0 01 00 80 - 05 00 00 00 - B8 01 00 80
06 00 00 00 - D0 01 00 80 - 07 00 00 00 - E8 01 00 80
08 00 00 00 - 00 02 00 80
anche queste le interpretiamo nel modo precedente:
00000001 numero identificativo
80000158 subdirectory all'offset 158
--------
00000002 numero identificativo
80000170 subdirectory all'offset 170
etc..
e dopo troviamo un altra directory, seguite da altre strutture Resource Dir Entries etc..
ora per� abbiamo trovato solo subdirectory, ancora non sappiamo dove sono effettivamente i dati relativi
alle risorse. Abbiamo trovato sempre strutture la cui seconda dword aveva il primo bit attivo (8xxxxxxx)
quindi adesso cerchiamo qualche risorsa vera. La prima la trovo all'offset 7158:
00 00 00 00 - 11 97 56 01 - 04 00 00 00 - 00 00 01 00
ha una sola entry, quindi prendo la seguente struttura:
10 04 00 00 - 38 03 00 00
NUM ID DATA OFFS
stavolta la seconda dword ha il primo bit a 0 (0xxxxxxxx), quindi vuol dire che i rimanenti 31 bit ci
indicano l'offset dei dati della risorsa: 00000338. Ora questo non � un indirizzo vero e proprio, ma �
un valore da sommare al'rva della Resource(7000): 7000+0338 = 7338. Andiamo a vedere a questo offset:
78 74 00 00 - 28 01 00 00
E4 04 00 00 - 00 00 00 00 - A0 75 00 00 - E8 02 00 00
E4 04 00 00 - 00 00 00 00 - 88 78 00 00 - E8 02 00 00
E4 04 00 00 - 00 00 00 00 - 70 7B 00 00 - 28 01 00 00
E4 04 00 00 - 00 00 00 00 - 98 7C 00 00 - 68 06 00 00
E4 04 00 00 - 00 00 00 00 - 00 83 00 00 - A8 08 00 00
E4 04 00 00 - 00 00 00 00 - A8 8B 00 00 - A8 0E 00 00
E4 04 00 00 - 00 00 00 00 - 50 9A 00 00 - 68 05 00 00
E4 04 00 00 - 00 00 00 00 - B8 9F 00 00 - 06 03 00 00
E4 04 00 00 - 00 00 00 00 - 24 A3 00 00 - CE 00 00 00
E4 04 00 00 - 00 00 00 00 - F4 A3 00 00 - A6 04 00 00
E4 04 00 00 - 00 00 00 00 - 9C A8 00 00 - 64 02 00 00
E4 04 00 00 - 00 00 00 00 - 00 AB 00 00 - 32 0A 00 00
E4 04 00 00 - 00 00 00 00 - 34 B5 00 00 - A8 02 00 00
E4 04 00 00 - 00 00 00 00 - DC B7 00 00 - 38 05 00 00
E4 04 00 00 - 00 00 00 00 - 14 BD 00 00 - 48 00 00 00
E4 04 00 00 - 00 00 00 00 - 5C BD 00 00 - 68 00 00 00
E4 04 00 00 - 00 00 00 00 - C4 BD 00 00 - 22 00 00 00
E4 04 00 00 - 00 00 00 00 - E8 BD 00 00 - 5A 00 00 00
E4 04 00 00 - 00 00 00 00 - 44 BE 00 00 - 78 03 00 00
E4 04 00 00 - 00 00 00 00
queste sono tutte le strutture puntate dalle resource dir entries. Ognuna di queste strutture � formata
da 4 dwords, che hanno il seguente significato:
DATA RVA
SIZE
CODEPAGE
RESERVED
quindi la prima struttura ha i seguenti dati:
00007478 DATA RVA
00000128 SIZE
000004E4 CODEPAGE
00000000 RESERVED
. Data rva
contiene l'rva che punta ai dati veri e propri (finalmente) della risorsa.
. Size
� la grandezza dei dati della risorsa
. CodePage
il valore che dovrebbe essere usato nella decodifica dei code point all'interno del blocco di dati.
. Reserved
riservato! Deve essere 0.
quindi sappiamo finalmente dove sono immagazzinati i dati della prima risorsa. Andiamo all'offset 7478
e troviamo il blocco:
28 00 00 00 10 00 00 00
20 00 00 00 01 00 04 00 00 00 00 00 80 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 80 00 00 80 00 00 00 80 80 00
80 00 00 00 80 00 80 00 80 80 00 00 C0 C0 C0 00
80 80 80 00 00 00 FF 00 00 FF 00 00 00 FF FF 00
FF 00 00 00 FF 00 FF 00 FF FF 00 00 FF FF FF 00
00 00 00 00 00 00 00 00 00 00 07 77 77 77 77 80
00 00 07 77 77 77 77 70 00 00 07 77 77 77 FF 70
00 00 07 77 77 77 77 70 00 00 00 00 00 77 77 70
6E EE EE EE E6 07 7F 70 0E EE EE EE EE 07 77 70
06 EE EE EE EE 60 77 70 00 EE EE EE EE E0 77 70
00 6E EE EE EE E6 07 70 00 0E E6 66 66 EE 07 70
00 06 E6 66 66 6E 60 70 00 00 EE EE EE EE E0 70
00 00 08 66 86 68 60 00 00 00 00 00 00 00 00 00
F8 01 00 00 F0 00 00 00 F0 00 00 00 F0 00 00 00
F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
80 00 00 00 80 00 00 00 C0 00 00 00 C0 00 00 00
E0 00 00 00 E0 00 00 00 F0 01 00 00 F8 03 00 00
28 00 00 00 20 00 00 00
Questo corrisponde al disegnino dell'icona del notepad. Basta cambiare questi bytes per cambiare l'icona
del notepad (vedremo fra un p� il visualizzatore di risorse). Si procede allo stesso modo per tutte le
altre risorse.
-- Exception -- & -- Security --
Questi due campi sono sconosciuti, non ho trovato nessuna documentazione.
-- Basereloce --
Sta per base relocation. Abbiamo gi� parlato delle rilocazioni, abbiamo detto che le variabili statiche,
le stringhe etc venivano caricate ad un diverso indirizzo da quello specificato in ImageBase. Il campo
basereloc ci d� un puntatore (alla sezione .reloc) alla struttura che contiene le informazioni per
effettuare la rilocaione. Nel caso del notepad abbiamo un D000 e un size di 93C. Al solito andiamo a
vedere con i nostri occhi la Relocation Table (anche chiamata Fixup Table):
00 10 00 00 - 68 02 00 00 - D5 30 1B 31 - 36 31 45 31
52 31 5A 31 - 61 31 67 31 - 6F 31 76 31 - 7D 31 81 31
8A 31 A2 31 - AF 31 BD 31 - C2 31 CC 31 - D1 31 D9 31
E7 31 EC 31 - F6 31 FB 31 - 06 32 0F 32 - 23 32 29 32
3D 32 74 32 - 7B 32 B9 32 - C3 32 C8 32 - DC 32 E6 32 continua....
ho riportato solo un pezzo, tanto basta e avanza. Al solito vediamo la struttura teorica di questa roba:
PAGE RVA
BLOCK SIZE
TYPE/OFFSET | TYPE/OFFSET
TYPE/OFFSET | continua...
la prima dword (page rva) � 00001000, ed ci d� l'rva che serve per individuare dove andare ad effettuare
la rilocazione, poi BlockSize indica il numero di bytes del blocco da rilocare. Tutte le word successive
sono TYPE/OFFSET formate nel modo:
bit 15---11---------0
Type Offset
in cui Type indica il tipo di rilocazione a seconda del suo valore:
. 0 = Assoluta
E' un nop. La rilocazione viene saltata.
. 1 = High
L'offset punta alla parte alta (16 bit a sinistra) di un indirizzo a 32 bit, in questa parte alta
vanno applicati i 16 bit di rilocazione.
. 2 = Low
Come prima, l'offset punta alla parte bassa (16 bit a destra) di un indirizzo a 32 bit, ed � l� che va
applicata la rilocazione.
. 3 = HighLow
L'intera relocazione a 32 bit va applicata all'intero indirizzo a 32 bit.
. 4 = HighAdjust
Si tratta sempre di una rilocazione a 32 bit, ma con dei calcoli in pi� (che sono incomprensibili).
. 5 = MipsJmpAddr
sconosciuto
. 6 = Section
sconosciuto
. 7 = Rel32
sconosciuto
tornando all'esempio pratico, nella nostra Relocation Table avevamo le seguenti info:
00 10 00 00 - 68 02 00 00 - D5 30 1B 31 - 36 31 45 31 etc...
cio� la rilocazione inizier� all'rva 00001000 e sar� lunga 0268h byte. La lunghezza per� comprende anche
le 2 dword iniziali, quindi per sapere quante rilocazioni occorreranno dobbiamo sottrarre 8 bytes a size
(gli 8 bytes delle prime 2 dwords), 268h - 8 = 260h, e ora dividiamo per 2 il numero di bytes (cos�
otteniamo le words delle reloc), 260h / 2 = 130h = 304 (dec) rilocazioni.
-- Debug --
Anche questo campo ci d� l'rva di una struttura. Questa contiene info per il debugging, la descriver�
fra poco.
-- Copyright -- & -- Globalptr --
Sconosciuti
-- Tls Table --
Ci d� l'indirizzo del Thread Local Storage. Ne parleremo fra poco.
-- Load Config --
sconosciuto
-- Bound Import --
Questo per rispondere a WebCide che mi ha fracassato di domande l'altro giorno in chat :-P. Abbiamo gi�
visto gli OriginalFirstThunk e i FirstThunk. Quando il PE loader carica un eseguibile, trova le imports,
carica in OriginalFirstThunk gli rva del file ai nomi di funzioni, al tempo stesso li carica anche nella
struttura dei FirstThunk. Poi carica le dll varie, ottiene gli entry point delle funzioni da importare
e le va a sostituire ai valori dei FirstThunk. Ora, nella maggior parte dei casi l'entry point di una
funzione in una dll sar� sempre lo stesso, quindi possiamo mettere nei FirstThunk direttamente gli entry
point, in modo che il loader non perda tempo a metterceli per noi. Questo processo si chiama "binding".
Ovviamente se abbiamo i FirstThunk gi� settati sugli Entry point delle funzioni, ma per un qualsiasi
motivo a quegli indirizzi non vengono trovate le funzioni, il loader se ne accorge (o dal numero di
versione della dll, o da una avvenuta relocazione) e quindi ecco che ritorna agli OriginalFirstThunk per
andarsi a ricalcolare l'entry point giusto delle funzioni considerate.
-- Import Address Table --
Abbiamo gi� trattato la IAT nella sezione della IT, cmq questo valore � superfluo, in quanto gi�
specificato nella directory entry dell'IT. Indica il puntatore rva all'inizio della IAT.
Okkey, adesso rimangono solo i pulsantini del gruppo view:
Exports, Imports, Resource, Debug, TLS.
Ognuno di essi apre una finestra a parte, e servono solo a visualizzare in una interfaccia utente i dati
delle export table, import table, risorse etc.
** EXPORTS **
Prendo la solita wz32.dll e guardo le imports. Sulla sinistra sono riportati tutti i valori della
struttura che gi� abbiamo commentato, nella destra avete le funzioni ordinate per indice, con i relativi
rva e nome.
** IMPORTS **
Nel riquadro superiore avete i nomi dei moduli importati (i file dll), cliccate su un modulo e nel
riquadro inferiore appariranno le funzioni importate da quel modulo, complete di rva, offset, hint e
nome (che gi� abbiamo descritto in dettaglio).
Il riquadro superiore merita un attimino di attenzione: nella descrizione dei moduli compaiono le voci
corrispondendi a FirstThunk, OriginalFirstThunk etc. che ho gi� descritto.
** RESOURCES **
Beh, avete il visualizzatore ad albero delle risorse, avete anche il visualizzatore delle icone! Mitico!
A destra avete le info sui valori che ho gi� discusso prima (name entries, etc..), e per ogni risorsa
avete in basso a destra la locazione (rva) e il suo size. La prima risorsa � un icona, ed � la classica
icona del notepad (come gi� visto prima; se osservate attentamente il blocco ascii relativo all'icona,
vi sembrer� di intravedere il disegnino del notepad! infatti � cos�).
** DEBUG **
Per vedere qualcosa in questo campo dovete creare e compilare un eseguibile con le info di debug (il
visual c++ lo fa in automatico). Debug quindi vi aprir� un dialog con le informazioni delle strutture di
debug. Una struttura di debug � formata nel seguente modo:
CHARACTERISTICS
TIME/DATE STAMP
MAJOR VERSION | MINOR VERSION
DEBUG TYPE
DATA SIZE
DATA RVA
DATA SEEK
dove: Characteristics � sempre a zero,
TimeDate Stamp � la data e ora della creazione delle info di debug
Maj/Min Version � la versione delle info di debug
Debug Type � il formato delle info di debug, che pu� essere in simboli COFF, FPO, o CodeView
Data size � il numero di bytes dei dati di debug (escluse le debug directory)
Data rva � appunto l'rva dei dati di debug
Data seek � il seek value dall'inizio del file fino ai dati di debug
Il campo Debug (nella Directory) ci indica l'rva dell'array di queste strutture di debug. Il debug
viewer semplicemente ci permette di scorrere una ad una tutte queste strutture. Francamente non sembra
funzionare molto bene.
** TLS **
Acronimo di Thread Local Storage. Questa struttura non la descriver�, anche perch� il suo funzionamento
non mi � ancora molto chiaro, e ci� � dovuto al fatto che non riesco a trovare un eseguibile che abbia
una TLS. La TLS contiene dei dati che verranno usati dai thread. In particolare la struttura contiene
anche un array di rva dei Callback, cio� un array di funzioni che devono essere richiamate prima e dopo
la creazione del thread.
-------------------------------------------------------------------------------------------------------+
-o NOTE
-------------------------------------------------------------------------------------------------------+
Nella descrizione della IT ho usato il modello classico, che � quello della struttura:
Image_Import_Descriptor Struct
union
characteristics dd? (Import Flags)
OriginalFirstThunk dd ?
ends
TimeDateStamp dd ?
ForwarderChain dd ?
Name1 dd ?
FirstThunk dd ?
Image_Import_Descriptor Ends
per� nella documentazione del pe.txt rilasciata da Microschif troviamo per l'IT la seguente struttura:
IMPORT FLAGS
TIME/DATE STAMP
MAJOR VERSION | MINOR VERSION
NAME RVA
IMPORT LOOKUP TABLE RVA
IMPORT ADDRESS TABLE RVA
la differenza � data dal fatto che alcuni compilatori usano la seconda struttura per costruire la IT.
Con questa struttura non avete gli OriginalFirstThunk, e dovete risalire al nome delle funzioni usando
i FirstThunk. Poi l'array di FirstThunk viene sovrascritto con gli entry point delle funzioni importate.
E' chiaro che in questo caso non potete avere le bound import. Di solito possiamo capire quale struttura
ci troviamo di fronte semplicemente dalla prima dword: se la struttura rientra nel primo caso, allora la
prima dword conterr� l'rva per i nomi delle funzioni, se la struttura rientra nel secondo caso allora la
prima dword sar� settata a zero. Praticamente � proprio questa union che determina il tipo di struttra
della IT.
Quando ho parlato del Rebuilder ho detto che potreste ritrovarvi ad aver ricostruito la IT di un prog,
per� scoprirete che tale prog funziona solo sul vostro pc. Il problema � legato ai Thunks. Infatti nei
FirstThunk troviamo o gli indirizzi degli entry point delle funzioni importate, oppure troviamo una
copia degli OriginalFirstThunk. In tal caso il loader pensa ad ottenere gli entry point delle funzioni e
a sovrascriverle alla copia degli OriginalFirstThunk per ottenere i veri FirstThunk. Se voi dumperete la
IT dopo questo processo, vi ritroverete una IT che non � l'originale, ma il file funziona in quanto ha
gi� prepatchati gli entry point delle funzioni. Pu� capitare che non tutte le dll carichino il loro
codice sempre allo stesso indirizzo (per vari motivi). Nello stesso pc al 99% delle volte gli indirizzi
sono sempre quelli, ecco perch� sul vostro pc funziona, per� su un altro pc gli indirizzi probabilmente
saranno diversi, e quindi il file non funzioner�. Voi direte: ma abbiamo sempre gli OriginalFirstThunks,
si ma questo se il file usa la prima struttura della IT. Se usa la seconda descritta poc'anzi, allora
non avremo gli OriginalFirstThunk e i FirstThunk sono sbagliati. Per risolvere il problema dovrete
quindi dumpare la vostra IT prima che venga sovrascritta con i FirstThunk.
-------------------------------------------------------------------------------------------------------+
-o LIBRARY, THANKS, LINKS AND E-MAIL
-------------------------------------------------------------------------------------------------------+
Per documentarvi meglio sul formato PE vi consiglio l'utilissimo documento PE.TXT che trovate un p�
dovunque (quello rilasciato da Microzozz Developers Support) e anche la documentazione di LuevelsMeyer.
Inoltre vi consiglio i tutorial sul PE che trovate sul RingZero, e vari tutorial sul Manual
Unpacking che trovate alla UIC. Vi consiglio anche i testi di Iczelion e tanti altri buoni testi che
adesso non mi vengono in mente, ma ce ne sono molti. Studiate, studiate!
Saluto tutta la UIC, il RingZero e gli Spippolatori, un salutone a Yado che col suo Krypton ci costringe
a studiare molto sul packing! Non dimentichiamo poi gli HackManiaci che mi hanno coinvolto nello scrivere
questo documento. And last but not least un salutone a Syscalo e Ritz che hanno supervisionato questo
testo (e che si sono incazzati perch� non li avevo salutati :-P)
E poi non poteva mancare il saluto al Quaqquaro!!! Ma io e te non dovevamo fare a botte? hihihihihi :-P
Some linkz:
http://ringzer0.goldrake.com - il ringzero! Grazie di esistere!
www.hackmaniaci.cjb.net - bravi guaglioni
www.uic-spippolatori.com - la UIC (universit� italiana crackers)
www.spippolatolri.com - gli spippolatori
...and MY stuff:
www.andreageddon.8m.com - il mio sito
[email protected] - la mia email
irc.azzurra.it -> #crack-it #hackmaniaci - canali bazzicati su irc.
- EOF - (pant pant!)
AndreaGeddon
Note Finali |
Disclaimer |