| Indice Articolo |
|---|
| Struttura di un file PE |
| DOS Header |
| File Header |
| Optional Header |
| Section Header |
| Imports Address Table - IAT |
| Export Address Table - EAT |
| Tutte le pagine |
L'abbreviazione PE sta per Portable Executable. questo è il formato nativo di Win32. Il significato di "eseguibile portabile" consiste in un formato universale per la piattaforma win32, infatti il PE loader di ogni sistema win32 riconosce e usa questo formato di file indipendentemente dal tipo di hardware usato, tranne che per particolari ambienti in cui è stato compilato.
Questo formato è stato scritto dalla Microsoft basandosi sul formato COFF (Common Object File Format) che è appunto il formato standard per gli object-file sui sistemi operativi unix-like.
Al suo interno sono previste delle parti introduttive (gli header) che non contengono il codice eseguibile, ma i dati utili al loader di windows per creare il processo in memoria.
Vediamo un po la struttura di un file PE
|
DOS MZ header
|
64 bytes | |
|
DOS stub
|
||
|
PE header
|
PE00($00004550) | 4 bytes |
| File Header | 20 bytes | |
| Optional Header | ||
|
Section table
|
||
|
Section 1
|
||
|
Section 2
|
||
|
Section ...
|
||
|
Section n
|
||
L'immagine qui sopra mostra una generica struttura di un file PE. Tutti i file PE anche le DLL a 32-bit devono iniziare con un semplice DOS MZ header. Questa parte solitamente non ci interessa molto ed è stata inserita principalmente per la compatibilità con sistemi DOS , per cui se si tentasse l'avvio da DOS l'eseguibile verrebbe riconosciuto come eseguibile win32 e verrebbe bloccata la sua esecuzione, o meglio verrebbe eseguito il "DOS stub" successivo, che è un vero e proprio eseguibile ma che al suo interno contiene solo le istruzioni per un messaggio di errore tipo "This program requires Windows" o "This program cannot run in DOS mode" e poi provocherebbe la terminazione dell'eseguibile.
Nel caso invece ci trovassimo nella piattaforma giusta dopo aver letto la parte del DOS MZ header il sistema operativo passerebbe direttamente al PE header il cui indirizzo rispetto all'inizio del file è conservato nel primo header DOS. Questa parte del file PE è molto importante perché contiene molte importanti informazioni utilizzate dal PE loader. Al suo interno troviamo due importanti strutture il File Header e l'Optional Header che vedremo in dettaglio più avanti.
Subito dopo il PE header troveremo una specie di tabella che ci indica dove trovare le varie sezioni all'interno del file. La section table è un vettore di record e ogni record conterrà informazioni riguardo la propria sezione.
Il vero contenuto dell'eseguibile è diviso in blocchi chiamate sezioni. Una sezione è un insieme di dati con degli attributi in comune come code/data, lettura/scrittura etc. Queste sezioni possono avere diversi attributi come ad esempio "read only", file di sistema o nascosti. Questo è importante perché i dati all'interno dei file PE verranno raggruppati in base ai differenti attributi e non in maniera "logica", cioè non importa a come i dati verrano usati o che istruzioni contengono. quello che conta è che se dei dati hanno lo stesso attributo potranno essere inglobati in una stessa sezione. Quando il PE loader dovrà trasferire i dati delle sezioni nella memoria, leggera gli attributi della sezione e darà gli stessi attributi ai blocchi di memoria di destinazione.
Vediamo ora di fare il punto della situazione riguardo alla struttura e al funzionamente di un file PE:
- Quando il file PE viene avviato, il loader PE esamina l'header DOS MZ header per trovare la posizione dell'header PE. Se lo trova passa immediatamente li.
- Il PE loader controlla se il PE header è valido. Se è così, va alla fine dell'header.
- Immediatamente dopo il PE header c'è la tabella delle sezioni. Il PE loader legge le informazioni di ogni sezione e le "mappa" nella memoria, dando ai vari blocchi di memoria gli attributi specificati nella tabella delle sezioni.
- dopo che il file PE è stato mappato in memoria, il loader continuerà a lavorare sulla parte logica del file, come ad esempio l'importazione di moduli esterni etc...
Il processo di caricamento dell'eseguibile in memoria, per poi essere eseguito, non è un processo così scontato, dato che non avviene una semplice copia dei dati dal file alla memoria. Infatti il contenuto del file viene analizzato dal loader e le sue parti vengono copiate in porzioni di memoria ognuna coi sui attributi.
Solitamente questi blocchi di memoria sono di 4096 byte e le varie sezioni che stavano nel file PE saranno allineate a questi blocchi. Ne consegue che l'indirizzo che ha una funzione nel file PE sarà diversa da quella che avrà memoria (virtual address = VA).
Quindi vediamo come sfruttando le informazioni di un file PE possiamo ricavare l'indirizzo fisico di una istruzione partendo dal suo indirizzo virtuale.
| Indirizzo(hex) | Dimensione (hex) | Processo | Sezione | Contenuto | Accesso |
| 00400000 | 1000 | Prova | PE header | R | |
| 00401000 | 55000 | Prova | .text | Codice | R E |
| 00456000 | 1000 | Prova | .itext | Codice | R E |
| 00457000 | 2000 | Prova | .data | Dati | RW Copy on Wr |
| 00459000 | 5000 | Prova | .bss | RW Copy on Wr | |
| 0045E000 | 3000 | Prova | .idata | Imports | RW Copy on Wr |
| 00461000 | 1000 | Prova | .tls | RW | |
| 00462000 | 1000 | Prova | .rdata | R | |
| 00463000 | 6000 | Prova | .reloc | Relocations | R |
| 00469000 | 5000 | Prova | .rsrc | Risorse | R |
La tabella mostra come il file viene mappato in memoria. Le varie parti sono state inserite nei vari blocchi a partire da un indirizzo base che è $00400000 . Se con un debuger lancio l'eseguibile, il file verrà mappato in questo modo e inizia l'esecuzione del codice, per esempio:
CPU Disasm
Address(Hex) Codice Comando Comments
0045670C 55 PUSH EBP
0045670D 8BEC MOV EBP,ESP
0045670F 83C4 F0 ADD ESP,-10
00456712 B8 C4554500 MOV EAX,Prova.004555C4
00456717 E8 B0FEFAFF CALL 004065CC
0045671C A1 58894500 MOV EAX,DWORD PTR DS:[458958]
00456721 8B00 MOV EAX,DWORD PTR DS:[EAX]
00456723 E8 48CDFFFF CALL 00453470 ; [Prova.00453470
00456728 A1 58894500 MOV EAX,DWORD PTR DS:[458958]
0045672D 8B00 MOV EAX,DWORD PTR DS:[EAX]
0045672F B2 01 MOV DL,1
00456731 E8 5EEBFFFF CALL 00455294 ; [Prova.00455294
La prima istruzione evidenziata è un comando di PUSH e risiede in memoria all'indirizzo 0045670C , che, guardando la prima tabella, corrisponde alla sezione ".itext".
Ora la domanda è come facciamo a trovare questa stessa istruzione nel file fisico in formato PE?
Il dato di partenza è l'indirizzo virtuale (VA) 0045670C del codice nella memoria, da questo dobbiamo ricavarci l'indirizzo virtuale relativo (RVA) , che è un offset dall' indirizzo di base. Quest'ultimo lo ricaviamo dalle informazioni presenti nell'header del file PE, ed è in pratica il primo indirizzo di memoria che il loader usa nel mappare il file. L'RVA non è altro che la distanza tra il blocco di memoria dove risiede l'istruzione e l'indirizzo base del processo.

Come mostra l'immagine nell' Optional Header cerchiamo il campo ImageBase (IB) che contiene il valore 00400000, con questo possiamo ricavarci l'indirizzo virtuale relativo:
RVA = VA - IB = 0045670C - 00400000 = 0005670C
Ora non ci rimane che convertire l' RVA nell' raw address (RA) che è esattamente l'indirizzo fisico del codice sul file PE. A questo scopo andiamo a vedere nella tabella delle sezioni:

Riguardo alla sezione che ci interessa ( .itext) prendiamo i due valori corrispondenti al virtual address (00056000) e al raw address (00054C00). Ora il virtual address della immagine sarebbe il relative virtual address dell'inizio della sezione quindi per ottenere il raw address della mia istruzione devo sottrarre al mio RVA quello della sezione e usarlo come offset per il raw address:
RA = RVA - RVAsec + RAsec = 0005670C - 00056000 + 00054C00 = 5530C
Questo vuol dire che se con un qualsiasi hex editor andassimo ad analizzare il file PE all'indirizzo 5530C troveremo l'istruzione di push (codice 55) che volevamo.

Naturalmente questa è solo una super semplificata visione della struttura e delle funzioni di un PE file, ma spero comunque di aver dato una visione iniziale chiara e semplice di questo argomento.
Ecco un video espicativo che riassume qunto detto a proposito della struttura PE e degli haeder DOS.
Per capire meglio è bene ricordare come si usa il "debuger" di windows, il quale fondamentalmente lavora in ambiante DOS.
la sintassi è :
debug [[Drive:][Percorso] NomeFile [parametri]]
esempio
debug c:\windows\system32\notepad.exe
dove per parametri si intendono tutti i parametri necessari per l'esecuzione corretta del programma e non del debuger.
Per quanto riguarda i comandi interni al debuger, questi vengono elencati usando il comando "?", una descrizione completa la trovate a questo indirizzo.
Un altro programmino che disassembla un file PE creando un report html lo trovate qui.
Ritornando al discorso dei file PE abbiamo detto che sono un contenitore di codice eseguibile indipendentemente che si tratti di file exe o dll.
La differenza tra file exe e dll sta in un singolo bit che indica se il file eseguibile deve essere trattato come exe o come dll. Anche l'estensione .dll è superflua, infatti i controlli .ocx e le applet del control panel (.cpl) sono dll.





