Compago

...free knowledge

 
  • Increase font size
  • Default font size
  • Decrease font size
Home Manuali Programmazione Struttura di un file PE

Struttura di un file PE

E-mail Stampa PDF
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:

  1. 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.
  2. Il PE loader controlla se il PE header è valido. Se è così, va alla fine dell'header.
  3. 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.
  4. 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.

PE in Memoria
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.



Ultimo aggiornamento ( Lunedì 09 Novembre 2009 16:49 )