tutto (o quasi) sul portable executable

Upload: debugger20

Post on 19-Jul-2015

33 views

Category:

Documents


0 download

TRANSCRIPT

Tutto (o quasi) sul Portable Executable

Tutto (o quasi) sul Portable ExecutableData 20/08/2003 by Ntoskrnl

UIC's Home PageGrazie NT... Pure tu... Che pazienza ;p

Published by Quequero

Bello riempire gli spazi vuoti!

Bello riempire gli spazi vuoti!

.... Difficolt

Home page se presente: http://pmode.cjb.net/ E-mail: [email protected]

....

(*)NewBies (*)Intermedio (*)Avanzato (*)Master

Portable Executable Forever! Tutto (o quasi) sul Portable ExecutableWritten by Ntoskrnl

Introduzione Ovvero tutto quello che avreste voluto sapere sul PE ma non avete mai osato chiedere (anche perch non potete mica stare a rompere i coglioni in chat per ogni cosa...). Ovviamente sto parlando del formato PE a 32bit (anche se nel tutorial non mancheranno accenni al 64bit). Per scaricare l'allegato al tutorial clickate qui. E' gi da tanto che il discorso del PE molto in voga e di materiale ce n' gi diverso, ma anche io ho deciso di dare il mio contributo, cercando magari di non stare a riproporre le stesse cose gi dette e ridette. Questo tutorial si propone di fare un discorso sul PE di respiro ampio. Perch mi sono deciso a fare questo tutorial? Semplicemente perch spesso si trovano in rete tutorials che spiegano il formato ma non l'utilizzo pratico di esso, oppure, dai sorgenti che offrono la persona alle prime armi non riesce a capire un granch. Il mio intento quello di spiegare il PE dal punto di vista del programmatore e non del reverser (per quello ci sono gi i bei tut di Kill3xx), comunque anche i reverser potranno usufruire del tutorial. Inoltre questo tutorial parte proprio da 0, molte persone hanno trovato difficolt a muovere i primi passi nel mondo del PE e io non ho mai saputo consigliargli un tut scritto proprio per newbies assoluti... Questo tutorial non premette proprio nessuna conoscenza del tipo PE/Reversing, le uniche cose che chiedo che abbiate una certa familiariet con la programmazione Win32 e il C. Fonte di informazioni di cui ho fatto uso? Ci sono tanti tutorial ma le fonti migliori sono quelle lasciateci da Matt Pietrek (fatevi un giro nel MSDN) e non dimentichiamoci dello stesso WinNt.h.

Essay

INTRODUZIONE Prima di partire con la pratica faccio una piccola introduzione casomai non sapeste proprio cosa il PE. Il PE (Portable Executable) un formato di file, pi precisamente un formato standard per gli eseguibili Win32, tale formato stato scritto dalla Microsoft in base al COFF (Common Object File Format) che appunto il formato standard per gli object-file su os unix-like e VMS e dal quale derivano tutti i formati quali PE, Elf ecc. Ma a cosa serve il PE? Serve a fornire al loader le informazioni necessarie per creare il processo/modulo. Una volta

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable

mappato in memoria il file comodissimo da gestire e il loader funziona con una semplicit alquanto straordinaria. Per chi non lo sapesse PE non sono solo gli exe Win32 ma pure le Dll, gli Ocx (e anche altri tipi di file). Inoltre su sistemi NT pure i Device Driver sono dei PE (al contrario di win9x/ME che usa VxD, i quali sono dei LE: Linear Executable). Basta con la storia, ci sarebbero anche altre cose da dire ma non voglio essere troppo prolisso su cose che pi che altro fanno da decorazione alla nostra conoscenza. Come forse saprete tutte le strutture e le dichiarazioni riguardanti il PE stanno nel WinNt.h. Anche dare un'occhiata a questo header sempre utile (anzi utilissimo). Il primo sorgente che vedremo non far altro che mappare in memoria un eseguibile. Innanzitutto vediamo la struttura di un PE, ovvero come un normale PE ci si presenta.DOS HEADER NT HEADERS (PE HEADER) SECTION TABLE SECTIONS

Cosa sono gli headers? Sono un insieme di informazioni riguardanti il corrente File, tutte le informazioni che ci interessano sul PE sono contenute negli Nt Headers che rappresentano l'header vero e proprio del PE e nella Section Table, che una tabella che contiene le informazioni sulle sezioni presenti nel PE. Le sezioni di un PE sono i luoghi che contengono codice, dati, imports, exports ecc. ecc. del nostro eseguibile. Il discorso delle sezioni lo affronteremo pi avanti. Spieghiamo per prima cosa come accedere alle informazioni di un file PE. Il modo pi semplice ed immediato e di caricarcelo tutto in memoria (con questo non voglio assolutamente dire che faremo da loader eh).#include #include int main() { HANDLE hFile, hMapObj, hBaseAddress; printf("\nOpening File...\n"); hFile = CreateFile("prova.exe", GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) { printf("Cannot Open the File\n"); return -1; } hMapObj = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, 0); if (!hMapObj) { CloseHandle(hFile); printf("Cannot Create File Mapping\n"); return -1; } if (!(hBaseAddress = MapViewOfFile(hMapObj, FILE_MAP_READ, 0, 0, 0))) { CloseHandle(hMapObj); CloseHandle(hFile); printf("Cannot create Map View of File\n"); return -1; } printf("File Mapped in Memory\n"); UnmapViewOfFile(hBaseAddress); CloseHandle(hMapObj); CloseHandle(hFile); printf("File Closed\n"); return 0; }

Questo codice non fa altro che caricare in memoria un file, sia esso o no un PE. Ho usato le funzioni di mapping per mappare (appunto) il file in memoria, questo metodo molto comune ma se dovete fare operazioni di riallocazione di memoria meglio se optate per un CreateFile, GetFileSize, Allocazione Memoria, ReadFile; in questo modo otterrete lo stesso risultato ma con libert di riallocazione. Siccome le Api per mappare un file in memoria sono sconosciute ai newbies ne spiego brevemente l'utilizzo. Come detto il codice non fa altro che mappare un eseguibile in memoria. Cominciamo ad analizzare il sorgente di questo programma, come prima cosa il prog apre il file con un CreateFile (spero che questa funzione non ve la debba spiegare), dopodich utilizzando l'handle riportato da CreateFile il programma chiama la funzione CreateFileMapping. Questa funzione serve a creare un oggetto di File-Mapping in memoria in modo da consentire di chiamare poi MapViewOfFile che mapper poi il file in memoria (quella del nostro processo). Infine viene poi unmappato (bel termine eh) il file dalla memoria e chiusi i restanti handle. Sintassi delle funzioni:

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable HANDLE CreateFileMapping( HANDLE hFile, LPSECURITY_ATTRIBUTES lpFileMappingAttributes, DWORD flProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCTSTR lpName );

hFile specifica l'handle del file dal quale creare un mapping. lpFileMappingAttributes di questo parametro non ci frega nulla. flProtect questo parametro specifica la protezione del map in memoria, i parametri possibili sono: PAGE_READONLY ( se il file stato aperto con GENERIC_READ), PAGE_READWRITE (se stato aperto con un GENERIC_READ | GENERIC_WRITE), PAGE_WRITECOPY (come per PAGE_READWRITE). dwMaximumSizeHigh specifica in high-order la grandezza massima per l'oggetto. dwMaximumSizeLow specifica in low-order la grandezza massima per l'oggetto, se entrambi questi parametri sono settati uguali a 0, la grandezza per l'oggetto sar della stessa dimensione del file. lpName specifica il nome per l'oggetto, se il parametro viene settato uguale a 0, l'oggetto sar senza nome. Se la funzione viene eseguita con successo, il valore di ritorno sar l'handle per l'oggetto creato, altrimenti sar 0. Ora vediamo la funzione MapViewOfFile che mapper il file in memoria.LPVOID MapViewOfFile( HANDLE hFileMappingObject, DWORD dwDesiredAccess, DWORD dwFileOffsetHigh, DWORD dwFileOffsetLow, DWORD dwNumberOfBytesToMap );

hFileMappingObject l'handle dell'oggetto creato con CreateFileMapping. dwDesiredAccess specifica l'accesso che pu essere: FILE_MAP_WRITE, FILE_MAP_READ, FILE_MAP_ALL_ACCESS (che la stessa cosa di FILE_MAP_WRITE) e FILE_MAP_COPY. Ovviamente i paramteri vanno messi corrispondenti a quelli per l'oggetto. dwFileOffsetHigh e dwFileOffsetLow non fanno altro che determinare l'offset di partenza dal quale mappare il file, anche questi li possiamo settare a 0. dwNumberOfBytesToMap specifica il numero di bytes da mappare, se questo parametro viene settato a 0, l'intero file viene mappato. Il valore di ritorno l'indirizzo da cui parte il file mappato se la funzione viene eseguita correttamente, altrimenti 0. La variante di MapViewOfFile MapViewOfFileEx (tanto per essere precisi). Come abbiamo visto nel sorgente il file viene poi unmappato dalla memoria, la funzione che svolge questo compito UnmapViewOfFile:BOOL UnmapViewOfFile( LPCVOID lpBaseAddress );

lpBaseAddress specifica l'indirizzo di partenza in memoria del file mappato. Spero di non dover aggiungere altro su UnmapViewOfFile. Breve riepilogo: MapViewOfFile ci restituisce l'indirizzo del file mappato, se non usate le funzioni di mapping l'indirizzo che vi serve quello che avete preso con l'allocazione di memoria (che sia fatta con funzioni ansi o api fa uguale). DOS HEADER Noi sappiamo che il primo insieme di elementi che troviamo si chiama Dos Header, vediamo il Dos header di un comune PE file (uso il mio whex che il mio hex editor preferito, okok non sono proprio imparziale):

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable Offset 00000000 00000010 00000020 00000030 0 4D B8 00 00 1 5A 00 00 00 2 90 00 00 00 3 00 00 00 00 4 03 00 00 00 5 00 00 00 00 6 00 00 00 00 7 00 00 00 00 8 04 40 00 00 9 00 00 00 00 A 00 00 00 00 B 00 00 00 00 C FF 00 00 E8 D FF 00 00 00 E 00 00 00 00 F 00 00 00 00 Ascii MZ....... .. .......@....... ................ ...............

Ok, sappiamo che queste sono le informazioni contenute nel Dos Header, ma come ricaviamo ogni singolo elemento? Semplice useremo un puntatore a struttura, noi abbiamo la dichiarazione della struttura (ovvero l'insieme di elementi) del Dos Header nel winnt.h:typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header WORD WORD WORD WORD WORD WORD WORD WORD WORD WORD WORD WORD WORD WORD WORD WORD WORD WORD LONG e_magic; e_cblp; e_cp; e_crlc; e_cparhdr; e_minalloc; e_maxalloc; e_ss; e_sp; e_csum; e_ip; e_cs; e_lfarlc; e_ovno; e_res[4]; e_oemid; e_oeminfo; e_res2[10]; e_lfanew; // // // // // // // // // // // // // // // // // // // Magic number Bytes on last page of file Pages in file Relocations Size of header in paragraphs Minimum extra paragraphs needed Maximum extra paragraphs needed Initial (relative) SS value Initial SP value Checksum Initial IP value Initial (relative) CS value File address of relocation table Overlay number Reserved words OEM identifier (for e_oeminfo) OEM information; e_oemid specific Reserved words File address of new exe header

} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

Se ci dichiaramo un puntatore a struttura cos:PIMAGE_DOS_HEADER pDosHeader;

Baster farlo puntare al nostro BaseAddress del PE file per avere una struttura piena di elementi, es:pDosHeader = (PIMAGE_DOS_HEADER) hBaseAddress;

Di questa immensa strutturona ci servono solamente 2 elementi (gli altri elementi possono anche essere 0): e_magic e e_lfanew. e_magic come vedete una WORD e ci serve per un primo controllo, ovvero e_magic deve corrispondere alle lettere ascii MZ, se non corrisponde allora il nostro file non da considerarsi come eseguibile, nel winnt definito IMAGE_DOS_SIGNATURE (0x4D5A = 'MZ') che sar il valore con cui confrontare e_magic. Invece e_lfanew un file offset che ci dice dove stanno gli Nt Headers ovvero il PE Header. Solitamente il PE Header viene dopo il Dos Header e dopo il Dos Stub (che serve solo se un programma Win32 viene avviato in Dos: ovvero dice che non possibile, una cosa del genere: This program cannot be run in Dos mode), cmq un PE pu benissimo fare a meno del Dos Stub. e_lfanew fa in modo che il PE header possa essere praticamente ovunque nel file anche se in genere si trova dove abbiamo detto, questa cosa bene tenerla a mente. Quindi per ottenere l'inizio della struttura NtHeaders baster sommare all'indirizzo base del file in memoria il file offset contenuto in e_lfanew. Prima di andare avanti vediamo brevemente un piccolo sorgente che non fa altro che controllare la validit del MZ HEADER e mostrarci l'RVA del PE HEADER, tanto per andare passo passo.#include #include int main() { HANDLE hFile, hMapObj, hBaseAddress; IMAGE_DOS_HEADER *ImageDosHeader; printf("\nOpening File...\n"); hFile = CreateFile("prova.exe", GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) { printf("Cannot Open the File\n"); return -1; } hMapObj = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, 0); if (!hMapObj) { CloseHandle(hFile); printf("Cannot Create File Mapping\n"); return -1; } if (!(hBaseAddress = MapViewOfFile(hMapObj, FILE_MAP_READ, 0, 0, 0))) { CloseHandle(hMapObj); CloseHandle(hFile); printf("Cannot create Map View of File\n"); return -1;

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable } printf("File Mapped in Memory\n"); // si inizia dal Base Address ImageDosHeader = (IMAGE_DOS_HEADER *) hBaseAddress; // controlla il Dos Header if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { printf("Invalid Dos Header\n"); goto Close_Handles; } printf("Valid Dos Header\n"); printf("File Offset: %X\n", ImageDosHeader->e_lfanew); Close_Handles: UnmapViewOfFile(hBaseAddress); CloseHandle(hMapObj); CloseHandle(hFile); printf("File Closed\n"); return 0; }

Ok, andiamo avanti. PE HEADER Siamo finalmente giunti al PE Header ovvero agli NT Headers. Vediamoci tale struttura:typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

Ok, cosa abbiamo qua? Una dword e due strutture che conterranno cose di cui ancora non siamo a conoscenza. Iniziamo dal primo membro che sarebbe la dword-signature, questa dword come la word e_magic serve per controllare l'effettiva validit del PE file. Ovvero questo campo deve corrispondere ai caratteri 'PE00'. Come possiamo vedere in un comune eseguibile:Offset 000000E0 000000F0 00000100 0 1 2 3 4 5 6 7 8 9 A B C D E F Ascii PE..L. z?......... ........... 50 45 00 00 4C 01 06 00 7A AD 80 3F 00 00 00 00 00 00 00 00 E0 00 0E 01 0B 01 06 00 00 A0 02 00 00 D0 00 00 00 00 00 00

Dalle lettere PE inizia il nostro PE Header. La definizione di tale signature IMAGE_NT_SIGNATURE. Adesso vediamo entrambe le strutture andando in ordine, partiamo dal FileHeader. FILE HEADER Ecco la dichiarazione di tale struttura:typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

Machine specifica per quale CPU stato designato il file. Le varie CPU definite sono queste:#define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define #define IMAGE_FILE_MACHINE_UNKNOWN IMAGE_FILE_MACHINE_I386 IMAGE_FILE_MACHINE_R3000 IMAGE_FILE_MACHINE_R4000 IMAGE_FILE_MACHINE_R10000 IMAGE_FILE_MACHINE_WCEMIPSV2 IMAGE_FILE_MACHINE_ALPHA IMAGE_FILE_MACHINE_POWERPC IMAGE_FILE_MACHINE_SH3 IMAGE_FILE_MACHINE_SH3E IMAGE_FILE_MACHINE_SH4 IMAGE_FILE_MACHINE_ARM IMAGE_FILE_MACHINE_THUMB IMAGE_FILE_MACHINE_IA64 IMAGE_FILE_MACHINE_MIPS16 IMAGE_FILE_MACHINE_MIPSFPU IMAGE_FILE_MACHINE_MIPSFPU16 0 0x014c 0x0162 0x0166 0x0168 0x0169 0x0184 0x01F0 0x01a2 0x01a4 0x01a6 0x01c0 0x01c2 0x0200 0x0266 0x0366 0x0466 // // // // // // // // // // // // // // // // sconosciuta Intel 386. MIPS little-endian, 0x160 big-endian MIPS little-endian MIPS little-endian MIPS little-endian WCE v2 Alpha_AXP IBM PowerPC Little-Endian SH3 little-endian SH3E little-endian SH4 little-endian ARM Little-Endian Intel 64 MIPS MIPS MIPS

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable #define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64 #define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64

NumberOfSections beh il nome gi dice tutto...Il numero delle sezioni che il PE dovr contenere. TimeDateStamp da qui si ricava data e ora di creazione del file (inutile direi). PointerToSymbolTable offset per la symbol table (utile solo per debug). NumberOfSymbols numero di simboli nella symbol table. SizeOfOptionalHeader specifica la grandezza dell'OPTIONAL HEADER (parametro mooolto importante). Characteristics specifica alcune informazioni sul file, eccovi quelle definite nel winnt:#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // Relocation info stripped from file. #define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // File is executable (i.e. no unresolved externel references). #define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // Line nunbers stripped from file. #define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // Local symbols stripped from file. #define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 // Agressively trim working set #define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // App can handle >2gb addresses #define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // Bytes of machine word are reversed. #define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32 bit word machine. #define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // Debugging info stripped from file in .DBG file #define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // If Image is on removable media, copy and run from the swap file. #define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 // If Image is on Net, copy and run from the swap file. #define IMAGE_FILE_SYSTEM 0x1000 // System File. #define IMAGE_FILE_DLL 0x2000 // File is a DLL. #define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // File should only be run on a UP machine #define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // Bytes of machine word are reversed

Non ho tradotto visto che i commenti in inglese trovati nel winnt mi parevano gi abbastanza chiari (se non lo sono, allora dovete gettare via questo tutorial e mettervi a studiare le basi dell'inglese, non del PE). Non vi fate spaventare da tutti questi campi che sembrano cos oscuri, vi assicuro che di essenziale in questa struttura vi solamente il campo NumberOfSections e il SizeOfOptionalHeader: questo campo ci dice le dimensioni dell'Optional Header e tramite queste potremo ricavare l'indirizzo della Section Table. OPTIONAL HEADER Vediamo adesso la struttura dell'OPTIONAL HEADER che una struttura interessantissima. Vi avverto che questa struttura grandicella. Non fatevi impressionare: cercher di farvi capire tutto.typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; DWORD ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; DWORD SizeOfStackCommit; DWORD SizeOfHeapReserve; DWORD SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

Magic questa word identifica lo stato dell'image file, i valori definiti sono:#define IMAGE_NT_OPTIONAL_HDR32_MAGIC #define IMAGE_NT_OPTIONAL_HDR64_MAGIC #define IMAGE_ROM_OPTIONAL_HDR_MAGIC 0x10b // normale eseguibile 32bit 0x20b // 64bit 0x107 // ROM

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable

MajorLinkerVersion e MinorLinkerVersion rappresentano la versione del linker che ha creato il file (del tipo il mio VC++ 6 crea campi 06-00). SizeOfCode numero di bytes di codice, sono quindi sommate tutte le varie sezioni con propriet di EXECUTABLE, visto che in genere la sezione solamente una di codice, questo parametro spesso corrisponde al numero di bytes in quella sezione ( un campo superfluo, ovvero il loader non ci fa veramente caso, potrebbe anche essere sballato). SizeOfInitializedData somma di tutte le varibili inizializzate (sempre superfluo). SizeOfUninitializedData indovinate un po'? AddressOfEntryPoint un RVA che corrisponde all'Entry Point del PE, specifica da dove deve partire il codice all'interno del processo. Servono adesso dei chiarimenti, ho usato il termine RVA cosa vuol dire? Perch ho detto processo e non file? Procediamo con ordine, dato che la prima risposta pu rendere intuitiva quella alla seconda domanda. Innanzitutto dovete sapere che quando il loader carica un PE in memoria non lo carica come sul disco ma per quanto riguarda le sezioni le carica dove la Section Table gli dice di caricarle. Inoltre vi un altro campo nel PE che specifica l'indirizzo al quale deve essere mappato il PE (il campo si chiama Image Base), in genere per gli exe l'ImageBase 00400000h e questo un Virtual Address. Se la Section Table ci dice che la prima sezione (mettiamo sia quella che contiene il codice, in genere cos) che sta al File Offset (ovvero l'offset della sezione su disco) 200h deve in memoria essere collocata all'RVA (Relative Virtual Address) 1000h, avremo la sezione al VA 00401000h. Da questo possiamo enunciare che: RVA = VA - ImageBase e VA = RVA + ImageBase. Come arrivare da un RVA al File Offset lo vedremo dopo. In ogni caso passando alla seconda domanda, una volta mappato il file PE come la Section Table dice al loader, il campo AddressOfEntryPoint punta direttamente all'indirizzo di memoria. Siccome noi lavoriamo sempre e solo caricando il File direttamente dal disco senza tenere conto della Section Table quando lavoriamo con RVA dovremo sempre riconvertirli in File Offsets. Non vi preoccupate se non avete ancora capito tanto procederemo sempre gradualmente. BaseOfCode BaseAddress per il codice, ovvero da dove inizia (superfluo). BaseOfData BaseAdress per la sezione data (superfluo). ImageBase specifica l'indirizzo dal quale deve essere mappato in memoria il file (quello che abbiamo citato parlando dell'AddressOfEntryPoint). SectionAlignment specifica l'allineamento del file in memoria, ovvero delle varie sezioni (zona headers compresa). Dunque ci sono due tipi di allineamento: quello in memoria e quello su disco. Diciamo che su disco il PE allineato a 200h, questo vuol dire che a partire dalla zona header tutto allineato a 200h:ZONA HEADER SECTION 1 SECTION 2 - SIZE: Multiplo di 200h - IDEM - IDEM

Se una sezione misura 522h su disco la sua grandezza sar 600h. In memoria invece sar grande 1000h. Gli allineamenti possibili su disco devono essere 200h o suoi multipli (tranne per i loaders su NT/2k/XP che consentono anche allineamenti di 1 byte). FileAlignment specifica l'allineamento del file (come detto appunto). MajorOperatingSystemVersion e MinorOperatingSystemVersion specificano la versione dell'OS (superfluo). MajorImageVersion e MinorImageVersion versione immagine (superfluo). MajorSubsystemVersion e MinorSubsystemVersion versione sottosistema (superfluo). Win32VersionValue boh non lo so (ma tanto fa parte di quei parametri totalmente inutili). SizeOfImage specifica le dimensioni dell'immagine del PE una volta mappato in memoria, su 9x questo parametro non viene considerato ma se sballato su NT l'exe non si esegue. Ho dato una spiegazione un po' vaga, vediamo di capire meglio di che cosa si tratta, abbiamo detto che esiste un Section Alignment... Ecco se noi sommiamo la grandezza della Header Zone (allineata) e la grandezza di tutte le sezioni (ognuna di queste allineata) allora abbiamo calcolato il SizeOfImage. La grandezza della Header Zone facile ricavarla basta prendere l'RVA dal quale inizia la prima sezione e sommare poi le grandezze di tutte le sezioni.

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable

SizeOfHeaders specifica le dimensioni degli headers (grandezza allineata al File Alignment), per sapere la grandezza basta prendere l'indirizzo fisico della prima sezione del PE (questo se non volete ricalcolare la grandezza per ottimizzazione, in quel caso dovreste sommare i vari header fino alla Section Table e allineare). CheckSum corrisponde al checksum dell'immagine del PE (lo calcola il compilatore, ma c' anche un Api per calcolare il valore... Ma la vedremo dopo). Subsystem specifica il sottosistema nel quale l'eseguibile lavora, i valori definiti sono:#define IMAGE_SUBSYSTEM_UNKNOWN #define IMAGE_SUBSYSTEM_NATIVE device drivers #define IMAGE_SUBSYSTEM_WINDOWS_GUI #define IMAGE_SUBSYSTEM_WINDOWS_CUI #define IMAGE_SUBSYSTEM_OS2_CUI #define IMAGE_SUBSYSTEM_POSIX_CUI #define IMAGE_SUBSYSTEM_NATIVE_WINDOWS #define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI 0 // sottosistema sconosciuto 1 // non richiede sottosistema, per esempio questo vale per i 2 3 5 7 8 9 // // // // // // sottosistema Windows GUI sotto sistema Windows CONSOLE OS/2 CONSOLE Posix CONSOLE driver 9x nativo sottosistema Windows CE

DllCharacteristics parametro obsoleto che specifica in quali circostanze inizializzare la DLL (se il PE una Dll). SizeOfStackReserve specifica la quantit di memoria da riservare per lo stack iniziale del thread. SizeOfStackCommit specifica la quantit di memoria inizialmente presa per il thread iniziale. SizeOfHeapReserve specifica la quanit di memoria da riservare per l'heap iniziale del processo. SizeOfHeapCommit specifica la quantit di memoria inizialemente presa nel heap del processo. LoaderFlags parametro obsoleto che serve per il debug. NumberOfRvaAndSizes numero di entry nell'array di DataDirectory. DataDirectory questo array serve al loader per trovare in modo rapido diverse sezioni (da non confondere con le sezioni della Section Table) chiamate directory.typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; // non veramente un VA ma un RVA DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

le directory sono: Export Table, Import Table, Resource Direcotry, Exception, Security, Base Relocation, Debug, Copyright, Global Ptr, Thread Local Storage, Load Configuration, Bound Import Table, Import Address Table, Delay Import, COM Descriptor. Queste in fondo non sono altro che (semplificando) sottosezioni delle sezioni che specifica la Section Table. In pratica la DataDirectory non altro che un'ulteriore suddivisione. In ogni caso per adesso non ci interessa la DataDirecotry, dobbiamo ancora trattare prima la Section Table. SECTION TABLE Inanzitutto cos' la Section Table? E' un array di strutture IMAGE_SECTION_HEADER (una per ogni sezione, vedremo dopo la struttura) e il numero di elementi nella struttura determinato dal NumberOfSections nella struttura File Header. Vediamo come giungiamo alla Section Table: bisogna sommare l'indirizzo dal quale inizia l'NT headers alla grandezza dell'Optional Header (campo del File Header, ricordate?) a 18h (che sarebbe la grandezza del File Header + la dword-signature). Una comoda macro fornitaci dal winnt.h IMAGE_FIRST_SECTION, la sua sintassi :ImageSectionHeader = IMAGE_FIRST_SECTION(ImageNtHeaders);

Passando semplicemente l'indirizzo degli NT Headers mi ritorna l'indirizzo della prima sezione in un PE. Ma cosa ImageSectionHeader? E' un puntatore a struttura IMAGE_SECTION_HEADER, vediamo tale struttura della quale come detto ve ne una per ogni sezione.typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // nome sezione max 8 caratteri union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD DWORD DWORD DWORD VirtualAddress; SizeOfRawData; PointerToRawData; PointerToRelocations; // // // // // serve solo se il file .obj // la grandezza della sezione indica da quale RVA il loader deve mappare la sezione grandezza della sezione arrotondata in base al file alignement file offset della sezione negli exe questo campo sempre 0 dato che non serve (solo per

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable obj) DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; // // // // come PointerToRelocations solo obj mai visto utilizzato manco questo (nei comuni PE dico) caratteristiche della sezione

} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; #define IMAGE_SIZEOF_SECTION_HEADER 40

Riassumendo i campi fondamentali sono: Nome della sezione, Virtual Address ( una RVA quindi per sapere a che VA sta una sezione di un processo/modulo dovrete sommare l'image base a questo valore), VirtualSize (in genere il virtual size corrisponde alla grandezza della sezione vera e propria, senza Section Alignment, dato che ci pensa il loader ad allineare e quindi in genere l'unica sezione con Virtual Size maggiore di Raw Size la .data), Raw Address e Raw Size (questi ultimi vengono anche chiamati Physical Address e Physical Size). L'ultimo campo importante Characteristic, esso indica le caratteristiche della sezione. I flag pi importanti sono (ricordatevi che sono tutti combinabili):00000020h 00000040h 00000080h 10000000h 20000000h 40000000h 80000000h la sezione contiene codice contiene dati inizializzati contiene dati non inizializzati sezione condivisibile sezione eseguibile sezione leggibile la sezione scrivibile

Ma per essere esaurienti eccovi tutti i flags disponibili:#define IMAGE_SCN_CNT_CODE #define IMAGE_SCN_CNT_INITIALIZED_DATA #define IMAGE_SCN_CNT_UNINITIALIZED_DATA #define IMAGE_SCN_LNK_OTHER 0x00000020 // Section contains code. 0x00000040 // Section contains initialized data. 0x00000080 // Section contains uninitialized data. 0x00000100 // Reserved. // Section contains comments or some other type of // Section contents will not become part of // Section contents comdat. // Reset speculative exceptions handling bits in // Section content can be accessed relative to GP

#define IMAGE_SCN_LNK_INFO 0x00000200 information. // IMAGE_SCN_TYPE_OVER 0x00000400 // Reserved. #define IMAGE_SCN_LNK_REMOVE 0x00000800 image. #define IMAGE_SCN_LNK_COMDAT 0x00001000 // 0x00002000 // Reserved. // IMAGE_SCN_MEM_PROTECTED - Obsolete 0x00004000 #define IMAGE_SCN_NO_DEFER_SPEC_EXC 0x00004000 the TLB entries for this section. #define IMAGE_SCN_GPREL 0x00008000 #define IMAGE_SCN_MEM_FARDATA 0x00008000 // IMAGE_SCN_MEM_SYSHEAP - Obsolete 0x00010000 #define IMAGE_SCN_MEM_PURGEABLE 0x00020000 #define IMAGE_SCN_MEM_16BIT 0x00020000 #define IMAGE_SCN_MEM_LOCKED 0x00040000 #define IMAGE_SCN_MEM_PRELOAD 0x00080000 #define IMAGE_SCN_ALIGN_1BYTES #define IMAGE_SCN_ALIGN_2BYTES #define IMAGE_SCN_ALIGN_4BYTES #define IMAGE_SCN_ALIGN_8BYTES #define IMAGE_SCN_ALIGN_16BYTES #define IMAGE_SCN_ALIGN_32BYTES #define IMAGE_SCN_ALIGN_64BYTES #define IMAGE_SCN_ALIGN_128BYTES #define IMAGE_SCN_ALIGN_256BYTES #define IMAGE_SCN_ALIGN_512BYTES #define IMAGE_SCN_ALIGN_1024BYTES #define IMAGE_SCN_ALIGN_2048BYTES #define IMAGE_SCN_ALIGN_4096BYTES #define IMAGE_SCN_ALIGN_8192BYTES // Unused 0x00F00000 #define IMAGE_SCN_LNK_NRELOC_OVFL #define IMAGE_SCN_MEM_DISCARDABLE #define IMAGE_SCN_MEM_NOT_CACHED #define IMAGE_SCN_MEM_NOT_PAGED #define IMAGE_SCN_MEM_SHARED #define IMAGE_SCN_MEM_EXECUTE #define IMAGE_SCN_MEM_READ #define IMAGE_SCN_MEM_WRITE 0x00100000 0x00200000 0x00300000 0x00400000 0x00500000 0x00600000 0x00700000 0x00800000 0x00900000 0x00A00000 0x00B00000 0x00C00000 0x00D00000 0x00E00000 0x01000000 0x02000000 0x04000000 0x08000000 0x10000000 0x20000000 0x40000000 0x80000000

// // // // // Default alignment if no others are specified. // // // // // // // // // // // // // // // // // Section Section Section Section Section Section Section Section contains extended relocations. can be discarded. is not cachable. is not pageable. is shareable. is executable. is readable. is writeable.

Come detto le sezioni contengono tutto ci che c' in un File PE (headers esclusi), vi sono diverse sezioni in genere in un PE (nulla ci vieta di mettere tutto in una sola eh), ma generalmente quelle che vengono prodotte dai compilatori sono .text e .code (codice), .data (variabili inizializzate), .bss (variabili non iniziallizzate), .rdata (pu contenere diverse cose: info di debug, description string, GUIDs e TLS Directory), .idata (imports), .edata (exports), .tls (TLS Direcory), .rsrc (resource), .reloc (relocations) ecc. Chiaramente le descrizioni fra parentesi vi sembrano vage, ma non preoccupatevi, parleremo pi avanti delle sottosezioni, quando parleremo della DataDirectory. Comunque non mi va di legare i nomi delle sezioni alle sottosezioni dato che i nomi delle sezioni sono convenzionali ma non obbligatori. Fra un po' vedremo un sorgente che aggiunger una sezione ad un PE, ma prima di fare ci vi mostro un piccolo sorgente che non fa altro che elencare le sezioni di un PE (tanto per non andare troppo in fretta). Ah lo dico subito da questo sorgente in poi rinuncio alle funzioni MapViewOfFile ecc., di quelle volevo solo dare una dimostrazione di funzionamento dato che sono molto usate... Adesso si fa tutto con ReadFile.#include

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable #include int main() { HANDLE hFile; BYTE *BaseAddress; WORD x; DWORD FileSize, BR; IMAGE_DOS_HEADER *ImageDosHeader; IMAGE_NT_HEADERS *ImageNtHeaders; IMAGE_SECTION_HEADER *ImageSectionHeader; char FileName[MAX_PATH]; char SectionName[9] = { 0 }; printf("\nFile Name: "); scanf("%s", FileName); printf("\nOpening File...\n"); hFile = CreateFile(FileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) { printf("Cannot Open the File\n"); return -1; } FileSize = GetFileSize(hFile, NULL); BaseAddress = (BYTE *) malloc(FileSize); if (!ReadFile(hFile, BaseAddress, FileSize, &BR, NULL)) { free(BaseAddress); CloseHandle(hFile); return -1; } ImageDosHeader = (IMAGE_DOS_HEADER *) BaseAddress; // controlliamo il Dos Header if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { printf("Invalid Dos Header\n"); free(BaseAddress); CloseHandle(hFile); return -1; } ImageNtHeaders = (IMAGE_NT_HEADERS *) (ImageDosHeader->e_lfanew + (DWORD) ImageDosHeader); // controlliamo il PE Header if (ImageNtHeaders->Signature != IMAGE_NT_SIGNATURE) { printf("Invalid PE Header\n"); free(BaseAddress); CloseHandle(hFile); return -1; } // prende l'indirizzo della prima sezione ImageSectionHeader = IMAGE_FIRST_SECTION(ImageNtHeaders); // mostra la section table for (x = 0; x < ImageNtHeaders->FileHeader.NumberOfSections; x++) { memcpy(SectionName, ImageSectionHeader[x].Name, IMAGE_SIZEOF_SHORT_NAME); printf("\n\nSection Name: %s\n" "Virtual Address: %08X\n" "Virtual Size: %08X\n" "Raw Address: %08X\n" "Raw Size: %08X\n" "Characteristics: %08X", SectionName, ImageSectionHeader[x].VirtualAddress, ImageSectionHeader[x].Misc.VirtualSize, ImageSectionHeader[x].PointerToRawData, ImageSectionHeader[x].SizeOfRawData, ImageSectionHeader[x].Characteristics); } printf("\n\nThis was the Section Table\n"); free(BaseAddress); CloseHandle(hFile); return 0; }

Il sorcio semplice e non credo necessiti di ulteriori chiarimenti. Voglio farvi presente una cosa: quando si lavora con file PE e quindi con indirizzi di memoria sarebbe opportuno lavorare con una gestione delle eccezioni altrimenti il primo PE sballato vi far crashare il programma. Io nei miei codici di esempio non metto la gestione delle eccezioni (n controllo i permessi di accesso alla memoria) per non appesantire troppo il codice, questi sono esempi e servono per farvi imparare, non voglio che troppe cose distolgano la vosta attenzione dalle righe di codice importanti (ecco perch programmo in console).

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable

Ed adesso complico un poco le cose, vi propongo un source che serve ad aggiungere una nuova sezione alla Section Table di un PE.// sintassi: nome_file nome_nuova_sezione dimensioni_sezione #include #include DWORD CalcAlignment(DWORD Alignment, DWORD TrueSize); int main(int argc, char *argv[]) { HANDLE hFile; BYTE *BaseAddress; WORD nSection; DWORD FileSize, BRW, NameSize, Size; IMAGE_DOS_HEADER *ImageDosHeader; IMAGE_NT_HEADERS *ImageNtHeaders; IMAGE_SECTION_HEADER *ImageSectionHeader; // controlla numero argomenti if (argc < 4) { printf("\nNeed More Arguments\n"); return -1; } printf("\nOpening File...\n"); hFile = CreateFile(argv[1], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) { printf("Cannot Open the File\n"); return -1; } FileSize = GetFileSize(hFile, NULL); BaseAddress = (BYTE *) malloc(FileSize); if (!ReadFile(hFile, BaseAddress, FileSize, &BRW, NULL)) { free(BaseAddress); CloseHandle(hFile); return -1; } ImageDosHeader = (IMAGE_DOS_HEADER *) BaseAddress; // controlliamo il Dos Header if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { printf("Invalid Dos Header\n"); free(BaseAddress); CloseHandle(hFile); return -1; } ImageNtHeaders = (IMAGE_NT_HEADERS *) (ImageDosHeader->e_lfanew + (DWORD) ImageDosHeader); // controlliamo il PE Header if (ImageNtHeaders->Signature != IMAGE_NT_SIGNATURE) { printf("Invalid PE Header\n"); free(BaseAddress); CloseHandle(hFile); return -1; } // prende le dimensioni Size = atoi(argv[3]); printf("Creating New Section...\n"); nSection = ImageNtHeaders->FileHeader.NumberOfSections; // aggiorna il numero di sezioni nel FILE HEADER ImageNtHeaders->FileHeader.NumberOfSections++; ImageSectionHeader = IMAGE_FIRST_SECTION(ImageNtHeaders); // aggiorna Size Of Image ImageNtHeaders->OptionalHeader.SizeOfImage += CalcAlignment(ImageNtHeaders->OptionalHeader.SectionAlignment, Size); // // // // // // // // teoricamente dovremmo controllare se c' abbastanza spazio tra la fine della section table e la prima sezione e se non c' dobbiamo ingrandire lo spazio che intercorre tra la header zone e la prima sezione per inserire la nostra sezione, ma dato che questo un esempio lasciamo perdere e prendiamo per buono che lo spazio c'

ZeroMemory(&ImageSectionHeader[nSection], IMAGE_SIZEOF_SECTION_HEADER);

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable

if (strlen(argv[2]) OptionalHeader.SectionAlignment, (ImageSectionHeader[nSection - 1].VirtualAddress + ImageSectionHeader[nSection - 1].Misc.VirtualSize)); // Virtual Size = Size vero della sezione // potremmo anche allinearlo per ImageSectionHeader[nSection].Misc.VirtualSize = Size; // controllo se la sezione precedente // allineata correttamente if (ImageSectionHeader[nSection - 1].SizeOfRawData % ImageNtHeaders->OptionalHeader.FileAlignment) { // se no ci penso io ImageSectionHeader[nSection - 1].SizeOfRawData = CalcAlignment(ImageNtHeaders->OptionalHeader.FileAlignment, ImageSectionHeader[nSection - 1].SizeOfRawData); SetFilePointer(hFile, (ImageSectionHeader[nSection - 1].PointerToRawData + ImageSectionHeader[nSection - 1].SizeOfRawData), NULL, FILE_BEGIN); SetEndOfFile(hFile); } // setto l'address fisico ImageSectionHeader[nSection].PointerToRawData = GetFileSize(hFile, NULL); // e la grandezza fisica ImageSectionHeader[nSection].SizeOfRawData = CalcAlignment(ImageNtHeaders->OptionalHeader.FileAlignment, Size); // caratteristiche (readable) ImageSectionHeader[nSection].Characteristics = IMAGE_SCN_MEM_READ; // aggiungo i byte in fondo al file SetFilePointer(hFile, ImageSectionHeader[nSection].SizeOfRawData, NULL, FILE_END); SetEndOfFile(hFile); // salvo le modifiche SetFilePointer(hFile, 0, NULL, FILE_BEGIN); WriteFile(hFile, BaseAddress, FileSize, &BRW, NULL); free(BaseAddress); CloseHandle(hFile); printf("\nNew Section Added\n"); return 0; } // calcola l'allineamento in base alla grandezza originale DWORD CalcAlignment(DWORD Alignment, DWORD TrueSize) { DWORD CalculatedAlignment; for(CalculatedAlignment = Alignment; ; CalculatedAlignment += Alignment) { if (TrueSize PointerToRawData) return Rva; for (int i = 0; i < NT->FileHeader.NumberOfSections; i++) { if (Img[i].SizeOfRawData) Limit = Img[i].SizeOfRawData; else Limit = Img[i].Misc.VirtualSize; if (Rva >= Img[i].VirtualAddress && Rva < (Img[i].VirtualAddress + Limit)) { if (Img[i].PointerToRawData != 0) { Offset -= Img[i].VirtualAddress; Offset += Img[i].PointerToRawData; } return Offset; } } return NULL; }

Questa funzione (che ho copiato da un foglio del mio wark) non fa altro che controllare gli spazi fisici tra le sezioni e calcolare l'RVA corrispondente a un certo offset fisico. Come vedete i paremetri della funzione sono solamente due: un Pointer a ImageNtHeader e l'RVA da convertire. Se la funzione non trova un offset fisico corrispondente ritorna NULL. Ah piccola nota, tutte le entry della Data Dir si chiamano directory (come gi detto) ma io mi ostino a chiamarle sezioni per abitudine (directory fa schifo, scusate). Quindi, se mi scappasse ogni tanto 'sezione' al posto di 'directory', non fateci caso, thx. EXPORT TABLE Bene, adesso che sappiamo anche quest'ultimo cosa possiamo finalmente parlare di Export Table.typedef struct _IMAGE_EXPORT_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; DWORD Name; DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames;

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable DWORD AddressOfFunctions; DWORD AddressOfNames; DWORD AddressOfNameOrdinals; } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

Vediamo ad uno ad uno i campi: Characteristics non so a cosa possa servire, in tutti i PE sta sempre a 0. TimeDateStamp specifica la data di creazione. MajorVersion e MinorVersion altri campi superflui che troverete sempre a 0. Name un RVA che punta al nome interno del modulo. Del tipo la kernel32.dll ha nome interno: KERNEL32.dll. Base un numero che va sommato all'indice della funzione per ottenere l'ordinal di tale funzione, la prima funzione esportata da un PE pari a 0, se Base pari a 1 dovremo fare 0 + 1 = 1 e avremo ottenuto l'ordinal di tale funzione. NumberOfFunctions indica il numero di funzioni esportate dal modulo. NumberOfNames indica il numero di funzioni esportate con nome. Come spero sappiate possibile importare (e quindi anche esportare) una funzione sia per ordinal che per nome... Avete presente? No? Avete mai provato a usate GetProcAddress? Anche quest'API consente di prenderci l'indirizzo di una funzione di un modulo caricato in memoria sia per ordinal che per nome. Be' se non avete presente aprite un PE editor e l'API Reference. AddressOfFunctions un RVA che punta ad un array che contiene gli entry points di ogni funzione esportata. La grandezza di questo array specificata da NumberOfFunctions. AddressOfNames un RVA che punta a un array di RVAs che puntano ai vari nomi delle funzioni. Come detto non obbligatorio che a una funzione corrisponda anche un nome. AddressOfNameOrdinals un array di WORDs che contengono gli ordinals delle funzioni con nome. La grandezza di questo array data da NumberOfNames, questo array ci serve per scovare quali sono le funzioni che hanno anche un nome. Se siete dei Newbies sarete sicuramente ancora molto confusi... Io dovrei parlare ancora dell'Export Forwarding, ma lo faccio dopo; per adesso meglio proporre un sorcio riassuntivo il cui scopo quello di elencare le funzioni esportate da un modulo. Ah, del Size che ci fornisce la DataDir dell'ET me ne frego, non viene neanche considerato per enumerare le exports (tanto ancora non siamo arrivati al Forwarding).// sintassi: nome_file #include #include DWORD RvaToOffset(IMAGE_NT_HEADERS *NT, DWORD Rva); int main(int argc, char *argv[]) { HANDLE hFile; BYTE *BaseAddress; DWORD FileSize, BR, ET_Offset; IMAGE_DOS_HEADER *ImageDosHeader; IMAGE_NT_HEADERS *ImageNtHeaders; IMAGE_EXPORT_DIRECTORY *ImageExportDir; DWORD *Functions, *Names; WORD *NameOrds, x, y; char *Name, *FName; // controlla numero argomenti if (argc < 2) { printf("\nNeed More Arguments\n"); return -1; } printf("\nOpening File...\n"); hFile = CreateFile(argv[1], GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) { printf("Cannot Open the File\n"); return -1;

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable } FileSize = GetFileSize(hFile, NULL); BaseAddress = (BYTE *) malloc(FileSize); if (!ReadFile(hFile, BaseAddress, FileSize, &BR, NULL)) { free(BaseAddress); CloseHandle(hFile); return -1; } ImageDosHeader = (IMAGE_DOS_HEADER *) BaseAddress; // controlliamo il Dos Header if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { printf("Invalid Dos Header\n"); free(BaseAddress); CloseHandle(hFile); return -1; } ImageNtHeaders = (IMAGE_NT_HEADERS *) (ImageDosHeader->e_lfanew + (DWORD) ImageDosHeader); // controlliamo il PE Header if (ImageNtHeaders->Signature != IMAGE_NT_SIGNATURE) { printf("Invalid PE Header\n"); free(BaseAddress); CloseHandle(hFile); return -1; } if (!ImageNtHeaders->OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress) { printf("This PE Doesn't contain an ET\n"); free(BaseAddress); CloseHandle(hFile); return -1; } // converte in offset fisico l'RVA ET_Offset = RvaToOffset(ImageNtHeaders, ImageNtHeaders->OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); // valido? if (ET_Offset == NULL) { printf("This PE Doesn't contain an ET\n"); free(BaseAddress); CloseHandle(hFile); return -1; } printf("Export Table:\n"); // ricava la Export Dir ImageExportDir = (IMAGE_EXPORT_DIRECTORY *) (ET_Offset + (DWORD) BaseAddress); Name = (char *) (RvaToOffset(ImageNtHeaders, ImageExportDir->Name) + (DWORD) BaseAddress); // stampa le info importanti dell'ET printf("\n\nName: %s\n" "Base: %08X\n" "Number Of Functions: %08X\n" "Number Of Names: %08X\n" "Addr Of Functions: %08X\n" "Addr Of Names: %08X\n" "Addr Of Name Ords: %08X\n\n" "Exports:\n", Name, ImageExportDir->Base, ImageExportDir->NumberOfFunctions, ImageExportDir->NumberOfNames, ImageExportDir->AddressOfFunctions, ImageExportDir->AddressOfNames, ImageExportDir->AddressOfNameOrdinals); // prende i vari arrays Functions = (DWORD *) (RvaToOffset(ImageNtHeaders, ImageExportDir->AddressOfFunctions) + (DWORD) BaseAddress); Names = (DWORD *) (RvaToOffset(ImageNtHeaders, ImageExportDir->AddressOfNames) + (DWORD) BaseAddress); NameOrds = (WORD *) (RvaToOffset(ImageNtHeaders, ImageExportDir->AddressOfNameOrdinals) + (DWORD) BaseAddress); // enumera le funzioni for (x = 0; x < ImageExportDir->NumberOfFunctions; x++) { // controllo se l'EP 0 // se s allora passa alla prossima funzione if (Functions[x] == 0)

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable continue; printf("\nOrd: %04X\nEP: %08X\n", (x + ImageExportDir->Base), Functions[x]); // vedo se la funzione ha anche un nome for (y = 0; y < ImageExportDir->NumberOfNames; y++) { // trovata una funzione con nome? if (NameOrds[y] == x) { FName = (char *) (RvaToOffset(ImageNtHeaders, Names[y]) + (DWORD) BaseAddress); printf("Name: %s\n", FName); break; } } } free(BaseAddress); CloseHandle(hFile); return 0; } // sappiamo a cosa serve DWORD RvaToOffset(IMAGE_NT_HEADERS *NT, DWORD Rva) { DWORD Offset = Rva, Limit; IMAGE_SECTION_HEADER *Img; WORD i; Img = IMAGE_FIRST_SECTION(NT); if (Rva < Img->PointerToRawData) return Rva; for (i = 0; i < NT->FileHeader.NumberOfSections; i++) { if (Img[i].SizeOfRawData) Limit = Img[i].SizeOfRawData; else Limit = Img[i].Misc.VirtualSize; if (Rva >= Img[i].VirtualAddress && Rva < (Img[i].VirtualAddress + Limit)) { if (Img[i].PointerToRawData != 0) { Offset -= Img[i].VirtualAddress; Offset += Img[i].PointerToRawData; } return Offset; } } return NULL; }

Uhm, non credo ci sia altro da aggiungere ai commenti, il source relativamente semplice. Adesso voglio perdere due paroline (anche perch in fondo non c' molto da dire) sull'Export Forwarding. Ho visto che anche Pietrek lo ha trattato in alcuni articoli sul PE, ma a quanto pare anche Matt non ne sa pi di quanto ne so io. La prima volta che mi trovai di fronte all'Export Forwarding (2 anni fa circa) era mentre stavo spulciando la mia wsock32.dll, volevo recarmi con IDA all'indirizzo di una funzione esportata per analizzarmela e mi sono trovato di fronte ad una stringa di questo tipo:WSAAsyncGetHostByAddr db 'ws2_32.WSAAsyncGetHostByAddr', 0

In effetti recandomi all'RVA-File Offset della funzione ho trovato questa forma:ws2_32.WSAAsyncGetHostByAddr, 0

cio il nome di una dll seguito da un punto, quello di una funzione e il terminatore 0. A cosa serve tutto ci si capisce intuitivamente, il primo nome indica il nome della dll da caricare e il secondo quello della funzione. Spiegandomi meglio, la funzione WSAAsyncGetHostByAddr nella Dll wsock32.dll corrisponde in verit alla funzione WSAAsyncGetHostByAddr nella Dll ws2_32.dll. Come si fa a capire se una funzione presente nel modulo stesso o deve essere fowardata? Beh bisogna vedere se l'RVA della funzione contenuto nella Export Table (basatevi su indirizzo e grandezza che vi fornisce la Data Directory). Con ci dichiaro concluso il paragrafo sulla Export Table. IMPORT TABLE Ridendo e scherzando (pluralis majestatis, voi avete poco di cui stare allegri) siamo giunti alla seconda entry della Data Directory: la Import Table. Questa veramente una directory importantissima, senza averla capita non potete procedere in nulla... Anzi verranno proprio a bastonarvi a casa. In ogni caso state tranquilli, sebbene non sia pi semplice della ET almeno pi divertente. Inoltre per la maggior parte di voi sar sicuramente pi proficuo conoscere la IT che la ET (senza voler sminuire la ET di importanza), specialmente chi

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable

vuole dedicarsi al reversing di crypters/packers deve conoscere perfettamente la IT, altrimenti il massimo che riuscir a fare di farsi ricostruire la IT da un tool ad hoc (wark compreso). Per quanto riguarda l'IT ci serve solo sapere l'RVA di dove sta, il Size totalmente inutile. Quindi se partiamo dall'RVA e ce lo convertiamo in File Offset, partendo dall'indirizzo calcolato troveremo un array di strutture IMAGE_IMPORT_DESCRIPTOR (ognuna di queste corrisponde ad un modulo da cui vengono importate funzioni). L'array si conclude con una struttura IMAGE_IMPORT_DESCRIPTOR nulla. Vediamoci tale struttura:typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; }; DWORD DWORD DWORD DWORD TimeDateStamp; ForwarderChain; Name; FirstThunk;

} IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

Vediamo i vari elementi della struttura: OriginalFirstThunk un RVA che punta ad un array di dwords ricordandoci che stiamo parlando a 32bit, sarebbe quindi pi corretto dire un array dire strutture IMAGE_THUNK_DATA (che altro non sono che dwords o qword se siamo a 64bit) che possono avere significa diversi (che vedremo dopo). L'array si conclude con un elemento IMAGE_THUNK_DATA nullo. TimeDateStamp generalmente 0 ma se la IT stata bind-ata (da bind: che neologismo. Probabilmente non sapete cosa vuol dire ma una cosa che vedremo dopo quando parleremo della IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT, quindi se adesso non capite non preoccupatevi) teoricamente dovrebbe contenere la data del modulo che viene importato da questo descriptor, in ogni caso dovrebbe essere diverso da 0. ForwarderChain serve ancora se la IT stata bindata, comunque anche in quel caso semplicemente baster settarlo diverso da 0 poich fa parte di un metodo obsoleto per bindare. Name questo campo un RVA che punta a una stringa terminata da uno 0 che altro non che il nome del modulo da importare. A questo proposito una piccola curiosit: se il nome del modulo da importare manca di estensione il loader di Win2k cerca un modulo senza estensione come suggerito, mentre quello di XP se non trova estensione fa automaticamente un strcat(NomeModulo, ".DLL"). FirstThunk esattamente come OriginalFirstThunk un RVA che punta ad un array di IMAGE_THUNK_DATA ma non assolutamente lo stesso array di OriginalFirstThunk, cio pu benissimo esserlo ma a quel punto, come in seguito vedremo, si pu fare direttamente a meno dell'OriginalFirstThunk. Bene, ma adesso cerchiamo di capire cosa pu contenere un IMAGE_THUNK_DATA. Siccome il discorso non proprio semplicissimo da capire alla prima lettura cerco di spiegarmi il meglio possibile. Dunque abbiamo detto che OriginalFirstThunk (dimentichiamoci un secondo di FirstThunk) un array di DWORDs (sempre 32bit speaking: adesso smetto di dirlo eh) che servono per importare funzioni, ogni dword corrisponde ad una funzione importata dal modulo importato dal descriptor, l'array terminato da una dword nulla. Ok, ma cosa vuol dire: serve ad importare una funzione? Ci sono due modi di importare una funzione: per nome e per ordinal (ordinal non credo che debba spiegarvi, semmai rileggetevi il paragrafo sull'ET). Se la funzione importata per nome allora la dword un RVA che punta ad una struttura IMAGE_IMPORT_BY_NAME, vediamone la dichiarazione:typedef struct _IMAGE_IMPORT_BY_NAME { WORD Hint; BYTE Name[1]; } IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

La struttura consiste di due membri: una word che sarebbe l'ordinal della funzione da importare (che generalmente sempre settato a 0 dato che non ce ne frega nulla di sapere l'ordinal se abbiamo il nome della funzione) e il nome della funzione che segue la word, il nome terminato da uno 0. Quindi nel PE troviamo una cosa tipo: WORD_ORDINAL - NOME_FUNZIONE - 0. L'altro metodo di importare una funzione per ordinal, non abbiamo il nome della funzione n alcuna struttura IMAGE_IMPORT_BY_NAME e quindi risparmiamo diverso spazio... Per in genere sconsigliato importare per ordinal poich spesso le Dll (soprattutto quelle di sistema) non hanno sempre lo stesso ordinal per una funzione: su una versione di windows potrei avere una funzione con un certo ordinal e in un'altra versione quell'ordinal corrisponde ad una funzione diversa e questo ovviamente porterebbe a cose alquanto spiacevoli.

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable

Se una funzione importata per ordinal il bit pi alto della dword settato, quindi non resta che prendersi solo gli altri 31 e considerarli come ordinal. Spiego meglio, sappiamo che se il bit pi alto di una dword settato (e solo quello) allora la dword 80000000h, se l'ordinal della funzione che voglio importare 45 allora l'IMAGE_THUNK_DATA sar 80000045h (se siamo a 64 bit sar 8000000000000045h). Per sapere se una funzione viene importata per ordinal basta fare un controllo come questo:if (ImgTDataValue & IMAGE_ORDINAL_FLAG) { // la funzione importata per ordinal }

Spero sia tutto chiaro fin qui. FirstThunk svolge inizialmente (se il PE non bindato) lo stesso compito di OriginalFirstThunk, anch'esso un RVA che punta a un array di dword che a loro volta contengono gli stessi valori degli elementi dell'array a cui punta OriginalFirstThunk. Per esempio se l'array di OriginalFirstThunk contiene IMAGE_THUNK_DATA che puntano a strutture IMAGE_IMPORT_BY_NAME, FirstThunk punter alle stesse strutture (cio entrambi gli array contengono dword che devono importare la stessa funzione). Quando il loader carica un PE legge l'array di OriginalFirstThunk e sovrascrive quello di FirstThunk con i veri indirizzi delle funzioni in memoria ecco perch l'array puntato da FirstThunk costituisce la Import Address Table (o pi semplicemente IAT). Facciamo qualche specificazione, non strettamente obbligatoria la presenza dell'array OriginalFirstThunk (e chi ha avuto a che fare coll'un/packing sa cosa intendo dire), si pu fare a meno di questa prensenza settando OriginalFirstThunk a 0, in questo caso il loader considerer l'array puntato da FirstThunk per ricavare le funzioni da importare (quindi una volta loadato il PE in memoria non troveremo solo un array di FirstThunk sovrascritto). Nel caso per che un eseguibile lo si voglia bindare strettamente necessaria la presenza di un OriginalFirstThunk: questo mi sembra importante ricordarlo per evitare di prendere il discorso troppo alla leggera: bene che i PE abbiano entrambi gli array, poi se qualche packer elimina l'OFT (abbrevio) allora pazienza. Spero che abbiate capito tutto, semmai rileggete... In ogni caso eccovi un esempio per fissare le idee (l'ho scritto per 32bit per semplificare le cose come tutti gli esempi in questo tutorial).// sintassi: nome_file #include #include DWORD RvaToOffset(IMAGE_NT_HEADERS *NT, DWORD Rva); int main(int argc, char *argv[]) { HANDLE hFile; BYTE *BaseAddress; DWORD FileSize, BR, IT_Offset; UINT x = 0, y; IMAGE_DOS_HEADER *ImageDosHeader; IMAGE_NT_HEADERS *ImageNtHeaders; IMAGE_IMPORT_DESCRIPTOR *ImageImportDescr; DWORD *Thunks; char *Name; IMAGE_IMPORT_BY_NAME *ImgName; // controlla numero argomenti if (argc < 2) { printf("\nNeed More Arguments\n"); return -1; } printf("\nOpening File...\n"); hFile = CreateFile(argv[1], GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) { printf("Cannot Open the File\n"); return -1; } FileSize = GetFileSize(hFile, NULL); BaseAddress = (BYTE *) malloc(FileSize); if (!ReadFile(hFile, BaseAddress, FileSize, &BR, NULL)) { free(BaseAddress); CloseHandle(hFile); return -1; } ImageDosHeader = (IMAGE_DOS_HEADER *) BaseAddress; // controlliamo il Dos Header if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE)

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable { printf("Invalid Dos Header\n"); free(BaseAddress); CloseHandle(hFile); return -1; } ImageNtHeaders = (IMAGE_NT_HEADERS *) (ImageDosHeader->e_lfanew + (DWORD) ImageDosHeader); // controlliamo il PE Header if (ImageNtHeaders->Signature != IMAGE_NT_SIGNATURE) { printf("Invalid PE Header\n"); free(BaseAddress); CloseHandle(hFile); return -1; } if (!ImageNtHeaders->OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress) { printf("This PE Doesn't contain an IT\n"); free(BaseAddress); CloseHandle(hFile); return -1; } // converte in offset fisico l'RVA IT_Offset = RvaToOffset(ImageNtHeaders, ImageNtHeaders->OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); // valido? if (IT_Offset == NULL) { printf("This PE Doesn't contain an IT\n"); free(BaseAddress); CloseHandle(hFile); return -1; } printf("Import Table:\n"); // ricava la Import Dir // e quindi i descriptors ImageImportDescr = (IMAGE_IMPORT_DESCRIPTOR *) (IT_Offset + (DWORD) BaseAddress); // passa in rassegna tutti i descriptors while (ImageImportDescr[x].FirstThunk != 0) { Name = (char *) (RvaToOffset(ImageNtHeaders, ImageImportDescr[x].Name) + (DWORD) BaseAddress); printf("\nModule Name: %s\n\nFunctions:\n\n", Name); // guarda quale array considerare Thunks = (DWORD *) (RvaToOffset(ImageNtHeaders, ImageImportDescr[x].OriginalFirstThunk != 0 ? ImageImportDescr[x].OriginalFirstThunk : ImageImportDescr[x].FirstThunk) + (DWORD) BaseAddress); y = 0; // passa in rassegna le funzioni while (Thunks[y] != 0) { // importata per ordinal? if (Thunks[y] & IMAGE_ORDINAL_FLAG) { printf("Ordinal: %08X\n", (Thunks[y] IMAGE_ORDINAL_FLAG)); y++; continue; } ImgName = (IMAGE_IMPORT_BY_NAME *) (RvaToOffset( ImageNtHeaders, Thunks[y]) + (DWORD) BaseAddress); printf("Name: %s\n", &ImgName->Name); y++; } x++; } free(BaseAddress); CloseHandle(hFile); return 0; } // sappiamo a cosa serve DWORD RvaToOffset(IMAGE_NT_HEADERS *NT, DWORD Rva) { DWORD Offset = Rva, Limit; IMAGE_SECTION_HEADER *Img; WORD i; Img = IMAGE_FIRST_SECTION(NT);

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable if (Rva < Img->PointerToRawData) return Rva; for (i = 0; i < NT->FileHeader.NumberOfSections; i++) { if (Img[i].SizeOfRawData) Limit = Img[i].SizeOfRawData; else Limit = Img[i].Misc.VirtualSize; if (Rva >= Img[i].VirtualAddress && Rva < (Img[i].VirtualAddress + Limit)) { if (Img[i].PointerToRawData != 0) { Offset -= Img[i].VirtualAddress; Offset += Img[i].PointerToRawData; } return Offset; } } return NULL; }

Spero sia tutto chiaro, come detto i concetti di IT, IAT ecc sono fondamentali per continuare, assicuratevi di averli assimilati. NOTA Prima di andare avanti giunto il momento per introdurvi la imagehlp.dll che una dll molto utile. Io ve la introduco ma non capirete ancora a cosa servono tutte le funzioni esportate... Per prima o poi devo comunque parlarne e farlo alla fine del tutorial mi pare un po' tardi. Vediamo alcune delle funzioni pi interessanti (riguardanti il PE) che esporta questa dll: RemoveRelocations serve per rimuovere le rilocazioni da un PE (roba che comunque tratteremo pi avanti. BindImage/Ex di queste due funzioni discuter quando tratter di come bindare un exe. CheckSumMappedFile calcola il checksum di un file mappato in memoria, vi ricordate del campo CheckSum nell'Optional Header vero? MapFileAndCheckSumA/W mappa un file e calcola il suo checksum. ImageDirectoryEntryToData/Ex servono per ricavare un puntatore per una specificata entry nella DataDir. ImageNtHeader rivaca per voi l'ImageNtHeader partendo dalla base del file caricato in memoria. ImageRvaToSection vi restituisce il section header della sezione a cui appartiene l'RVA passato. ImageRvaToVa converte un RVA in un VA. Queste tre ultime funzioni in verit sono alquanto inutili dato che basta un secondo di coding per non doverle importare. MapDebugInformation prende le informazioni di debug per un immagine e le mette in una struttura IMAGE_DEBUG_INFORMATION appositamente allocata. Dopo l'utilizzo necessario usare UnmapDebugInformation per deallocare la struttura. ReBaseImage questa funzione serve per cambiare il load address di un modulo per ridurre il tempo necessario per la sua esecuzione (tutti i cambiamenti che devono essere in seguito fatti vengono calcolati da questa funzione: dbg info, checksum ecc.). Capirete meglio a cosa serve la funzione quando arriveremo alle relocations. In ogni caso ci sono anche altre funzioni esportate da questa dll interessanti che magari non sono essenziali per conoscere il PE ma che potrebbero fare comodo prima o poi quindi datevi uno sguardo a questa dll. Bene passiamo al prossimo paragrafo. RESOURCE DIRECTORY Siamo giunti alla entry pi noiosa di tutto il formato PE... Che bello! Suppongo che tutti abbiate almeno

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable

presente cosa contiene questa directory, be' nel caso cos non fosse, essa contiene tutte le risorse tipo icone, bitmaps, dialogs, menus, string tables, version info di un PE. Se non avete presente provate ad aprire una volta Resource Hacker (che tra l'altro non amo neanche troppo come prog... D'altronde non c' di meglio). C' da dire che, nonostante l'interessamento che questa directory pu suscitare, essa sempre stata trattata poco rispetto alle altre directory. Ovviamente doveroso ringraziare Pietrek per aver portato (gi agli albori del PE) la luce sull'argomento risorse, ma in ogni caso io per questa directory ho fatto affidamento solo al WinNt.h (che contiene un casino di informazioni), ad un hex editor e a un resource walker. In ogni caso non tutto sto gran casino, solo un po' ostica. Vedrete che alla fin fine tutto si riduce solo ad una gerarchia di strutture. Partiamo dalla Data Directory, la prima struttura che ci viene incontro IMAGE_RESOURCE_DIRECTORY. Vediamoci la struttura.typedef struct _IMAGE_RESOURCE_DIRECTORY { DWORD Characteristics; DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; WORD NumberOfNamedEntries; WORD NumberOfIdEntries; // IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[]; } IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;

Characteristics primo membro inutile (sempre a 0). TimeDateStamp dovrebbe segnare il tempo di creazione delle risorse: secondo membro inutile. MajorVersion e MinorVersion terzo e quarto membro inutili. NumberOfNamedEntries numero di Directory Entries ad avere un nome. NumberOfIdEntries numero di Directory Entries ad avere un ID. DirectoryEntries array di strutture IMAGE_RESOURCE_DIRECTORY_ENTRY che seguono la struttura IMAGE_RESOURCE_DIRECTORY. Per ottenere la grandezza necessario sommare NumberOfNamedEntries a NumberOfIdEntries. Faccio notare che non veramente un membro della struttura. Ok, ma cosa vuol dire tutto ci? Dunque in un PE tutte le risorse sono ordinate per tipo, le dialogs stanno nella directory delle dialogs, le bitmaps in quella delle bitmaps ecc. Se voi aprite un programma come ad esempio Resource Hacker, ma in ogni caso qualsiasi resource walker andr bene, esso vi mostrer le diverse directory: per esempio aprendo la Resource Section del wark trovo le seguenti directory:CURSORS - BITMAPS - ICONS - MENUS - DIALOGS - STRING TABLES - ACCELERATORS - CURSOR GROUPS - ICON GROUPS - VERSION INFO

Potremmo definire queste directory il secondo livello della Resource Section (il primo livello dato dalla Resource Dir da cui parte tutto). Vediamo la struttura IMAGE_RESOURCE_DIRECTORY_ENTRY:typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY { union { struct { DWORD NameOffset:31; DWORD :1; }; DWORD Name; WORD Id; }; union { DWORD OffsetToData; struct { DWORD OffsetToDirectory:31; DWORD DataIsDirectory:1; }; }; } IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;

La struttura composta da due dword sole: Name se in questa prima dword (ho scelto name perch era appunto l'unica dword della union) il bit pi alto settato allora i rimanenti 31 bit indicano l'offset del nome dell'entry a partire dalla Reource Dir, ovvero: Name = NameOffset + ResourceDirOffset. Altrimenti se il bit pi alto nullo, allora necessario considerare solo l'ID. Capire se il bit pi alto settato o no facile, si pu fare in due modi:if (NameIsString == TRUE)

oppureif (Name & IMAGE_RESOURCE_NAME_IS_STRING)

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable

Nel caso avesse effettivamente un nome e noi trovassimo l'offset allora dovremmo ricondurre questo offset ad una struttura IMAGE_RESOURCE_DIR_STRING_U:typedef struct _IMAGE_RESOURCE_DIR_STRING_U { WORD Length; WCHAR NameString[ 1 ]; } IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;

La word indica la lungezza della stringa seguita da un array di words che rappresenterebbe la la stringa (unicode). C' da fare per ancora un discorso sugli ID, nel caso di directories predefinite come DIALOGS et simil, esse non usufruiscono di nome ma di ID, quindi ci sono alcuni ID che vanno necessariamente associati ad un nome (ricordatevi che stiamo parlando del secondo livello, mi raccomando!), eccovi un piccolo schema:ID DIRECTORY 1 CURSORS 2 BITMAPS 3 ICONS 4 MENUS 5 DIALOGS 6 STRING TABLES 7 FONT DIRECORY 8 FONTS 9 ACCELERATORS 10 RCDATA 11 MESSAGE TABLES 12 CURSOR GROUPS 14 ICON GROUPS 16 VERSION INFO 23 HTML PAGES 24 CONFIGURATION FILES

Dal .NET in poi esiste anche questa risorsa, penso anzi di essere il primo che l'ha menzionata in un tutorial sul PE, all'interno vi sono info generali/direttive sullo startup, l'esecuzione ecc. Se volete approfondire il discorso, sul msdn trovate tutte le info che vi servono (che non sono poche). Inoltre eccovi una piccola descrizione approssimativa sempre presa dal msdn: Configuration Files are standard XML files. The .NET Framework defines a set of elements that implement configuration settings. This section describes the configuration schema for the machine configuration file, application configuration files, and the security configuration file. Accorgermi dello schema sovrastante (al tempo) non stato difficile bastato stampare, con un prog fatto da me, gli IDs e confrontarli con il risultato di un resource walker: associare quindi un ID ad un nome stato semplice (alcuni prog dovrebbero anche dirvi l'ID oltre al nome, quindi: figuriamoci). In ogni caso la mia tabella forse pi completa di molte altre proprio perch deriva dall'osservazione di tanti PE. Between, ho visto che Pietrek nel suo celebre PEDump (ultima versione) omette gli ultimi due tipi (23, 24) e ne mette di altri prima che per a essere sincero non mi mai capitato di vedere... In ogni caso siete liberi di fare altre ricerche per approfondire il discorso. OffsetToData questa dword invece ci interesser quando parleremo del terzo e successivi livello/i, il bit pi alto indica se settato che i 31 bit rappresentano l'offset (a partire dalla Res Dir) di una struttura IMAGE_RESOURCE_DIRECTORY, se invece il bit non settato allora l'offset punta (sempre a partire dalla Res Dir) ad una struttura IMAGE_RESOURCE_DATA_ENTRY. Per capire se il bit alto settato potete fare:if (DataIsDirectory == TRUE)

o:if (OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY)

Ma adesso prima di andarci ad occupare degli altri livelli costituiti da altre Res Dir (oltre a quella principale da cui parte tutto: primo livello) e prima di andare a vedere cosa la struttura IMAGE_RESOURCE_DATA_ENTRY, vediamo un piccolo prog che non fa altro fuorch elencarci le directories di secondo livello.// sintassi: nome_file #include #include DWORD RvaToOffset(IMAGE_NT_HEADERS *NT, DWORD Rva); char *ResNames[] = { "CURSORS", "BITMAPS", "ICONS", "MENUS", "DIALOGS", "STRING TABLES", "FONT DIRECORY", "FONTS", "ACCELERATORS", "RCDATA", "MESSAGE TABLES", "CURSOR GROUPS" }; int main(int argc, char *argv[]) { HANDLE hFile; BYTE *BaseAddress; DWORD FileSize, BR, Res_Offset; IMAGE_DOS_HEADER *ImageDosHeader; IMAGE_NT_HEADERS *ImageNtHeaders;

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable IMAGE_RESOURCE_DIRECTORY *ImgResDir; IMAGE_RESOURCE_DIRECTORY_ENTRY *ImgResDirEntry; DWORD ResDirs, x; IMAGE_RESOURCE_DIR_STRING_U *uString; char DirName[100]; // controlla numero argomenti if (argc < 2) { printf("\nNeed More Arguments\n"); return -1; } printf("\nOpening File...\n"); hFile = CreateFile(argv[1], GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) { printf("Cannot Open the File\n"); return -1; } FileSize = GetFileSize(hFile, NULL); BaseAddress = (BYTE *) malloc(FileSize); if (!ReadFile(hFile, BaseAddress, FileSize, &BR, NULL)) { free(BaseAddress); CloseHandle(hFile); return -1; } ImageDosHeader = (IMAGE_DOS_HEADER *) BaseAddress; // controlliamo il Dos Header if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { printf("Invalid Dos Header\n"); free(BaseAddress); CloseHandle(hFile); return -1; } ImageNtHeaders = (IMAGE_NT_HEADERS *) (ImageDosHeader->e_lfanew + (DWORD) ImageDosHeader); // controlliamo il PE Header if (ImageNtHeaders->Signature != IMAGE_NT_SIGNATURE) { printf("Invalid PE Header\n"); free(BaseAddress); CloseHandle(hFile); return -1; } if (!ImageNtHeaders->OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress) { printf("This PE Doesn't Contain Resources\n"); free(BaseAddress); CloseHandle(hFile); return -1; } // converte in offset fisico l'RVA Res_Offset = RvaToOffset(ImageNtHeaders, ImageNtHeaders->OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress); // valido? if (Res_Offset == NULL) { printf("This PE Doesn't Contain Resources\n"); free(BaseAddress); CloseHandle(hFile); return -1; } // ricavo la Resource Dir ImgResDir = (IMAGE_RESOURCE_DIRECTORY *)(DWORD) (Res_Offset + (DWORD) BaseAddress); // ricavo il numero delle Resource Dirs ResDirs = ImgResDir->NumberOfIdEntries + ImgResDir->NumberOfNamedEntries; printf("\nNumber of Resource Directories: %d\n", ResDirs); // ricavo il puntatore alle Res Dirs ImgResDirEntry = (IMAGE_RESOURCE_DIRECTORY_ENTRY *) (DWORD) (sizeof (IMAGE_RESOURCE_DIRECTORY) + (DWORD) ImgResDir); printf("\nDirectories:\n\n"); // mostro il 'secondo livello for (x = 0; x < ResDirs; x++)

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable { printf("\nDirectory %d", (x + 1)); // la dir ha nome? if (ImgResDirEntry[x].NameIsString == TRUE) { uString = (IMAGE_RESOURCE_DIR_STRING_U *) (DWORD) (ImgResDirEntry[x].NameOffset + (DWORD) ImgResDir); ZeroMemory(DirName, sizeof(DirName)); // converto la stringa unicode WideCharToMultiByte(CP_ACP, NULL, (LPCWSTR) &uString->NameString, uString->Length, DirName, sizeof (DirName), NULL, NULL); printf(" Name: %s\n", DirName); } else { // stampa l'ID printf(" ID: %d", ImgResDirEntry[x].Id); // controllo se l'ID risulta tra quelli identificati if (ImgResDirEntry[x].Id > 0 && ImgResDirEntry[x].Id < 13) { printf(" - %s", ResNames[ImgResDirEntry[x].Id - 1]); } else if (ImgResDirEntry[x].Id == 14) { printf(" - ICON GROUPS"); } else if (ImgResDirEntry[x].Id == 16) { printf(" - VERSION INFO"); } else if (ImgResDirEntry[x].Id == 23) { printf(" - HTML PAGES"); } else if (ImgResDirEntry[x].Id == 24) { printf(" - CONFIGURATION FILES"); } printf("\n"); } } free(BaseAddress); CloseHandle(hFile); return 0; } // sappiamo a cosa serve DWORD RvaToOffset(IMAGE_NT_HEADERS *NT, DWORD Rva) { DWORD Offset = Rva, Limit; IMAGE_SECTION_HEADER *Img; WORD i; Img = IMAGE_FIRST_SECTION(NT); if (Rva < Img->PointerToRawData) return Rva; for (i = 0; i < NT->FileHeader.NumberOfSections; i++) { if (Img[i].SizeOfRawData) Limit = Img[i].SizeOfRawData; else Limit = Img[i].Misc.VirtualSize; if (Rva >= Img[i].VirtualAddress && Rva < (Img[i].VirtualAddress + Limit)) { if (Img[i].PointerToRawData != 0) { Offset -= Img[i].VirtualAddress; Offset += Img[i].PointerToRawData; } return Offset; } } return NULL; }

Questo esempio elenca, nel caso siano presenti, prima le dirs con nomi, perch? Semplicemente perch fisicamente le dirs con nomi vengono prima di quelle identificate per ID. Ok, adesso possiamo parlare dei livelli successivi. Cominciamo col parlare del terzo livello che comune a tutte le normali dir (parleremo dopo di altri livelli). Noi sappiamo cosa rappresenta il secondo livello ma prendiamo una dir di questo secondo livello, per esempio quella delle dialogs (ho scelto a caso eh) a cosa punter la entry

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable

della dialogs (ID 5) ? Be' semplice, ad un'altra IMAGE_RESOURCE_DIRECTORY a cui seguir un array di IMAGE_RESOURCE_DIRECTORY_ENTRY (una per ogni dlg). Come per le dir ci saranno Dlgs con nome o con ID. Una volta arrivati alle entry delle Dlgs queste a loro volta punteranno nuovamente a una IMAGE_RESOURCE_DIRECTORY che avr solamente una IMAGE_RESOURCE_DIRECTORY_ENTRY che questa volta punter a una struttura IMAGE_RESOURCE_DATA_ENTRY, vediamo tale struttura:typedef struct _IMAGE_RESOURCE_DATA_ENTRY { DWORD OffsetToData; DWORD Size; DWORD CodePage; DWORD Reserved; } IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;

OffsetToData offset della raw data a partire dalla Res Dir ( sempre cos). Size dimensioni della raw data. CodePage oramai sempre unicode page. Reserved .... Riservato? gh So che il discorso non cos semplice ma eccovi un esempio di codice, che sebbene sia stato scritto in modo veloce, potr chiarirvi forse le idee.// sintassi: nome_file #include #include DWORD RvaToOffset(IMAGE_NT_HEADERS *NT, DWORD Rva); int main(int argc, char *argv[]) { HANDLE hFile; BYTE *BaseAddress; DWORD FileSize, BR, Res_Offset; IMAGE_DOS_HEADER *ImageDosHeader; IMAGE_NT_HEADERS *ImageNtHeaders; IMAGE_RESOURCE_DIRECTORY *ImgResDir; IMAGE_RESOURCE_DIRECTORY_ENTRY *ImgResDirEntry; DWORD ResDirs, x; IMAGE_RESOURCE_DIR_STRING_U *uString; char DlgName[100]; BOOL bFound = FALSE; IMAGE_RESOURCE_DIRECTORY *ImgDlgsResDir; IMAGE_RESOURCE_DIRECTORY_ENTRY *ImgDlgsResDirEntry; DWORD Dlgs; IMAGE_RESOURCE_DIRECTORY *ImgDlgDir; IMAGE_RESOURCE_DIRECTORY_ENTRY *ImgDlgEntry; IMAGE_RESOURCE_DATA_ENTRY *ImgDlgDataEntry; // controlla numero argomenti if (argc < 2) { printf("\nNeed More Arguments\n"); return -1; } printf("\nOpening File...\n"); hFile = CreateFile(argv[1], GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if (hFile == INVALID_HANDLE_VALUE) { printf("Cannot Open the File\n"); return -1; } FileSize = GetFileSize(hFile, NULL); BaseAddress = (BYTE *) malloc(FileSize); if (!ReadFile(hFile, BaseAddress, FileSize, &BR, NULL)) { free(BaseAddress); CloseHandle(hFile); return -1; } ImageDosHeader = (IMAGE_DOS_HEADER *) BaseAddress; // controlliamo il Dos Header if (ImageDosHeader->e_magic != IMAGE_DOS_SIGNATURE) {

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable printf("Invalid Dos Header\n"); free(BaseAddress); CloseHandle(hFile); return -1; } ImageNtHeaders = (IMAGE_NT_HEADERS *) (ImageDosHeader->e_lfanew + (DWORD) ImageDosHeader); // controlliamo il PE Header if (ImageNtHeaders->Signature != IMAGE_NT_SIGNATURE) { printf("Invalid PE Header\n"); free(BaseAddress); CloseHandle(hFile); return -1; } if (!ImageNtHeaders->OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress) { printf("This PE Doesn't Contain Resources\n"); free(BaseAddress); CloseHandle(hFile); return -1; } // converte in offset fisico l'RVA Res_Offset = RvaToOffset(ImageNtHeaders, ImageNtHeaders->OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_RESOURCE].VirtualAddress); // valido? if (Res_Offset == NULL) { printf("This PE Doesn't Contain Resources\n"); free(BaseAddress); CloseHandle(hFile); return -1; } // ricavo la Resource Dir ImgResDir = (IMAGE_RESOURCE_DIRECTORY *)(DWORD) (Res_Offset + (DWORD) BaseAddress); // ricavo il numero delle Resource Dirs ResDirs = ImgResDir->NumberOfIdEntries + ImgResDir->NumberOfNamedEntries; printf("\nNumber of Resource Directories: %d\n", ResDirs); // ricavo il puntatore alle Res Dirs ImgResDirEntry = (IMAGE_RESOURCE_DIRECTORY_ENTRY *) (DWORD) (sizeof (IMAGE_RESOURCE_DIRECTORY) + (DWORD) ImgResDir); printf("\nDialogs:\n\n"); // trovo la Res Dir per le dialogs for (x = 0; x < ResDirs; x++) { if (ImgResDirEntry[x].NameIsString == FALSE && ImgResDirEntry[x].Id == 5) { bFound = TRUE; break; } } // non esiste? if (bFound == FALSE) { printf("This PE Doesn't contain a Dialogs Dir\n"); free(BaseAddress); CloseHandle(hFile); return -1; } // tanto mi serve solo questa Entry per le Dlgs ImgResDirEntry = (IMAGE_RESOURCE_DIRECTORY_ENTRY *) (DWORD) &ImgResDirEntry[x]; // ci dovr pur essere una Res Dir per le dlgs if (ImgResDirEntry->DataIsDirectory == TRUE) { ImgDlgsResDir = (IMAGE_RESOURCE_DIRECTORY *)(DWORD) (ImgResDirEntry->OffsetToDirectory + (DWORD) ImgResDir); // sommo le dlgs con nome a quelle senza // e ottengo il totale Dlgs = ImgDlgsResDir->NumberOfNamedEntries + ImgDlgsResDir->NumberOfIdEntries; printf("\nNumber of Dialogs: %d\n", Dlgs); // prendo l'array di dlgs ImgDlgsResDirEntry = (IMAGE_RESOURCE_DIRECTORY_ENTRY *) (DWORD) (sizeof (IMAGE_RESOURCE_DIRECTORY) + (DWORD) ImgDlgsResDir); // elenco tutte le dlgs for (x = 0; x < Dlgs; x++) {

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable printf("\nDialog %d", (x + 1)); // la dlg ha nome? if (ImgDlgsResDirEntry[x].NameIsString == TRUE) { uString = (IMAGE_RESOURCE_DIR_STRING_U *) (DWORD) (ImgDlgsResDirEntry[x].NameOffset + (DWORD) ImgResDir); ZeroMemory(DlgName, sizeof(DlgName)); // converto la stringa unicode WideCharToMultiByte(CP_ACP, NULL, (LPCWSTR) &uString->NameString, uString->Length, DlgName, sizeof (DlgName), NULL, NULL); // stampa il nome printf(" Name: %s\n", DlgName); } else { // stampa l'ID printf(" ID: %d\n", ImgDlgsResDirEntry[x].Id); } // be' sempre uguale // devo ricavare la res dir per la dlg if (ImgDlgsResDirEntry[x].DataIsDirectory == TRUE) { ImgDlgDir = (IMAGE_RESOURCE_DIRECTORY *) (DWORD) (ImgDlgsResDirEntry[x].OffsetToDirectory + (DWORD) ImgResDir); ImgDlgEntry = (IMAGE_RESOURCE_DIRECTORY_ENTRY *) (DWORD) (sizeof (IMAGE_RESOURCE_DIRECTORY) + (DWORD) ImgDlgDir); // finalmente arrivo alla Data Entry ImgDlgDataEntry = (IMAGE_RESOURCE_DATA_ENTRY *) (DWORD) (ImgDlgEntry->OffsetToData + (DWORD) ImgResDir); // stampe le info riguardanti la dlg // ometto offset e reserved che sono // inutili a titolo di Info printf("Size: %d bytes - Code Page: %d\n", ImgDlgDataEntry->Size, ImgDlgDataEntry->CodePage); } } } free(BaseAddress); CloseHandle(hFile); return 0; } // sappiamo a cosa serve DWORD RvaToOffset(IMAGE_NT_HEADERS *NT, DWORD Rva) { DWORD Offset = Rva, Limit; IMAGE_SECTION_HEADER *Img; WORD i; Img = IMAGE_FIRST_SECTION(NT); if (Rva < Img->PointerToRawData) return Rva; for (i = 0; i < NT->FileHeader.NumberOfSections; i++) { if (Img[i].SizeOfRawData) Limit = Img[i].SizeOfRawData; else Limit = Img[i].Misc.VirtualSize; if (Rva >= Img[i].VirtualAddress && Rva < (Img[i].VirtualAddress + Limit)) { if (Img[i].PointerToRawData != 0) { Offset -= Img[i].VirtualAddress; Offset += Img[i].PointerToRawData; } return Offset; } } return NULL; }

L'esempio stato effettuato su delle dlg, ma qualsiasi tipo di risorsa standard va bene. Per quanto riguarda i livelli dicevo, possono anche aumentare come ben capite, ma per le normali risorse i livelli sono tre (quindi non preoccupatevi). Ora non vi resta che approfondire il discorso sui 'tipi' di risorse che si possono trovare, ma questo va al di l dell'argomento trattato da questo tutorial che appunto la struttura del PE. Beh se siete arrivati fino a questo punto del tutorial e avete compreso tutto allora adesso la strada tutta in

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable

discesa... Le cose peggiori sono passate. Stringete i denti che alla fine del tutorial ci sar per premio una bella sopresa (non informatica). EXCEPTIONS DIRECTORY Questo paragrafo sar molto breve anche perch non c' molto da dire o al momento non vedo la necessit di tirare il discorso per le lunghe. Alcune architetture fanno uso di tabelle per segnalare le funzioni nelle quali potrebbe verificarsi un'eccezione, nelle tabelle sono contenute diverse informazioni a seconda dell'architettura. La sezione Exceptions solo un array di strutture IMAGE_RUNTIME_FUNCTION_ENTRY, il numero di elementi dell'array lo ricaviamo dividendo la grandezza della sezione per le dimensioni della struttura IMAGE_RUNTIME_FUNCTION_ENTRY. Il nome IMAGE_RUNTIME_FUNCTION_ENTRY solo il ricavato di un typedef che cambia a seconda dell'architettura sulla quale stiamo compilando, ecco le diverse strutture dichiarate nel Winnt.h:// per architettura IA-64 typedef struct _IMAGE_IA64_RUNTIME_FUNCTION_ENTRY { DWORD BeginAddress; DWORD EndAddress; DWORD UnwindInfoAddress; } IMAGE_IA64_RUNTIME_FUNCTION_ENTRY, *PIMAGE_IA64_RUNTIME_FUNCTION_ENTRY; // per alpha/alpha64 typedef struct _IMAGE_ALPHA_RUNTIME_FUNCTION_ENTRY { DWORD BeginAddress; DWORD EndAddress; DWORD ExceptionHandler; DWORD HandlerData; DWORD PrologEndAddress; } IMAGE_ALPHA_RUNTIME_FUNCTION_ENTRY, *PIMAGE_ALPHA_RUNTIME_FUNCTION_ENTRY; typedef struct _IMAGE_ALPHA64_RUNTIME_FUNCTION_ENTRY { ULONGLONG BeginAddress; ULONGLONG EndAddress; ULONGLONG ExceptionHandler; ULONGLONG HandlerData; ULONGLONG PrologEndAddress; } IMAGE_ALPHA64_RUNTIME_FUNCTION_ENTRY, *PIMAGE_ALPHA64_RUNTIME_FUNCTION_ENTRY; // per Win CE typedef struct _IMAGE_CE_RUNTIME_FUNCTION_ENTRY { DWORD FuncStart; DWORD PrologLen : 8; DWORD FuncLen : 22; DWORD ThirtyTwoBit : 1; DWORD ExceptionFlag : 1; } IMAGE_CE_RUNTIME_FUNCTION_ENTRY, * PIMAGE_CE_RUNTIME_FUNCTION_ENTRY;

A tutte le strutture comune il BeginAddres e EndAddress (in Win CE ricavabile) della funzione, le informazioni aggiuntive riguardano la gestione dell'eccezione per la funzione relativa alla struttura. Per approfondire i parametri di gestione fatevi un giro sul msdn e andate a cercare per l'architettura che vi interessa, sono sicuro che troverete tutto. Non credo che per questo paragrafo sia necessario un esempio di codice.... Sono 4 cazzate... Quindi passiamo pure al prossimo paragrafo. SECURITY DIRECTORY Non c' nulla da dire su questa directory, evviva l'inutilit! BASE RELOCATION TABLE Ecco questo paragrafo mi sembra molto interessante, tranquilli che non difficile. Questa directory che generalmente rappresenta la sezione .reloc di un PE molto utile, anche se spesso viene messa inutilmente da dei compilatori (TASM sucks). Immaginate di avere un processo con diverse dlls, mettiamo che due dll abbiano lo stesso Image Base, in questo caso la seconda che viene caricata non pu venir caricata allo stesso indirizzo della prima... Il loader quindi costretto a caricare la dll (insomma un qualsiasi modulo) ad una diversa locazione. Ok per fatto questo tutti gli indirizzi basati su VAs all'interno della dll saranno sbagliati dato che l'Image Base indicato dal PE non quello utilizzato dal loader. Per ovviare questo problema il loader dovr aggiornare tutti gli indirizzi all'interno della dll, ma chi glieli dice questi indirizzi? Proprio questo il compito della Relocation Table. Tutta la sezione non altro che un array (eh lo so queste ultime sezioni vanno parecchio per array) di strutture IMAGE_BASE_RELOCATION, vediamo tale struttura:typedef struct _IMAGE_BASE_RELOCATION {

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable DWORD VirtualAddress; DWORD SizeOfBlock; // WORD TypeOffset[1]; } IMAGE_BASE_RELOCATION; typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

VirtualAddress si tratta di un RVA che specifica l'indirizzo della zona da aggiornare. SizeOfBlock specifica i byte di questa base relocation. TypeOffset (che non veramente un membro della struttura) un array di word, le dimensioni di questo array si possono ricavare da SizeOfBlock: bisogna solo sottrarre a SizeOfBlock il valore 8 (che sarebbe la dimensione della struttura senza contare l'array) e dividere per la grandezza di una WORD. Ma a cosa servono queste word? Queste word sono composte da due parti. I 4 bit pi alti di ogni dword indicano il tipo di allocazione, i tipi disponibili sono:#define #define #define #define #define #define #define #define #define #define #define #define IMAGE_REL_BASED_ABSOLUTE IMAGE_REL_BASED_HIGH IMAGE_REL_BASED_LOW IMAGE_REL_BASED_HIGHLOW IMAGE_REL_BASED_HIGHADJ IMAGE_REL_BASED_MIPS_JMPADDR IMAGE_REL_BASED_SECTION IMAGE_REL_BASED_REL32 IMAGE_REL_BASED_MIPS_JMPADDR16 IMAGE_REL_BASED_IA64_IMM64 IMAGE_REL_BASED_DIR64 IMAGE_REL_BASED_HIGH3ADJ 0 1 2 3 4 5 6 7 9 9 10 11

I pi comuni sono: IMAGE_REL_BASED_ABSOLUTE se la rilocazione di questo tipo non viene effettuato nulla, una rilocazione che sta l per un semplice discorso di allineamento. IMAGE_REL_BASED_HIGHLOW l'x86 usa sempre questo tipo di rilocazione, il significato che bisogna aggiornare la zona interessata sia con la parte alta che quella bassa del delta (che una dword, vedremo dopo cosa e come si calcola il delta). IMAGE_REL_BASED_DIR64 per quanto riguarda IA-64 (fidandoci stavolta di Pietrek) sempre questo il tipo di rilocazione. I restanti 12 bit della word sono un offset che sommato all'RVA di VirtualOffset portano ad un puntatore dword a cui sommare il, gi menzionato, delta. Il delta per le rilocazione molto semplice da calcolare, sufficiente sottrarre il vecchio Image Base al nuovo Image Base, es:Delta = NewImgBase - OldImageBase;

Come gi detto arrivando a un offset, sommando VirtualOffset + 12 bit della word, giungiamo ad una dword che punter ad una zona che deve essere aggiornata, baster quindi fare Block += Delta per ottenere l'aggiornamento (di indirizzi nella IAT, indirizzi di stringhe... Quello che vi pare). Come capirete questo sistema di rilocazione molto veloce e facile da usare. Questa volta sebbene sia facile ritengo comunque che sia opportuno mettere un piccolo codice che elenca le rilocazioni in un programma.// sintassi: nome_file #include #include DWORD RvaToOffset(IMAGE_NT_HEADERS *NT, DWORD Rva); int main(int argc, char *argv[]) { HANDLE hFile; BYTE *BaseAddress; DWORD FileSize, BR, Reloc_Offset, Reloc_Size; IMAGE_DOS_HEADER *ImageDosHeader; IMAGE_NT_HEADERS *ImageNtHeaders; IMAGE_BASE_RELOCATION *ImageRelocation; DWORD Size = 0; // controlla numero argomenti if (argc < 2) { printf("\nNeed More Arguments\n"); return -1; } printf("\nOpening File...\n"); hFile = CreateFile(argv[1], GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);

http://quequero.org/store/program/ntos_pe.htm[01/04/2012 18:49:33]

Tutto (o quasi) sul Portable Executable if (hFile == INVALID_HANDLE_VALUE) { printf("Cannot Open the