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;
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 <windows.h>
#include <stdio.h>
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;
}
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)
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 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 TimeDateStamp;
DWORD ForwarderChain;
DWORD Name;
DWORD 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. 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 <windows.h>
#include <stdio.h>
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)
{
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);
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 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)
oppure
if (Name & IMAGE_RESOURCE_NAME_IS_STRING)
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 <windows.h>
#include <stdio.h>
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;
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++)
{
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 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 <windows.h>
#include <stdio.h>
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)
{
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++)
{
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 discesa... Le cose peggiori sono passate.
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 stupidaggini... 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 {
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 IMAGE_REL_BASED_ABSOLUTE
0
#define IMAGE_REL_BASED_HIGH
1
#define IMAGE_REL_BASED_LOW
2
#define IMAGE_REL_BASED_HIGHLOW
3
#define IMAGE_REL_BASED_HIGHADJ
4
#define IMAGE_REL_BASED_MIPS_JMPADDR 5
#define
IMAGE_REL_BASED_SECTION 6
#define IMAGE_REL_BASED_REL32
7
#define IMAGE_REL_BASED_MIPS_JMPADDR16 9
#define
IMAGE_REL_BASED_IA64_IMM64 9
#define
IMAGE_REL_BASED_DIR64
10
#define IMAGE_REL_BASED_HIGH3ADJ
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 <windows.h>
#include <stdio.h>
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);
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_BASERELOC].VirtualAddress)
{
printf("This PE doesn't contain Relocations\n");
free(BaseAddress);
CloseHandle(hFile);
return -1;
}
// converte in offset fisico l'RVA
Reloc_Offset =
RvaToOffset(ImageNtHeaders,
ImageNtHeaders->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
Reloc_Size =
ImageNtHeaders->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
// è valido?
if
(Reloc_Offset == NULL)
{
printf("This PE doesn't contain
Relocations\n");
free(BaseAddress);
CloseHandle(hFile);
return -1;
}
printf("\nRelocations:");
//
continua il ciclo finché
// la size attuale è minore
// della
totale dimensione
// della Reloc Table
while (Size < Reloc_Size)
{
// ricavo l'attuale reloc base
ImageRelocation =
(IMAGE_BASE_RELOCATION *)(DWORD)
(Reloc_Offset + Size + (DWORD)
BaseAddress);
// stampo le info relative alla reloc base
printf("\n\nVirtual Addr: %X\n"
"Size Of Block: %X\n"
"Type Offset Members %d",
ImageRelocation->VirtualAddress,
ImageRelocation->SizeOfBlock,
((ImageRelocation->
SizeOfBlock -
IMAGE_SIZEOF_BASE_RELOCATION) / sizeof (WORD)));
// somma al size attuale la grandezza
// della reloc attuale
Size += ImageRelocation->SizeOfBlock;
}
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;
}
Be' già il paragrafo è semplice e altri commenti al
codice non mi sembrano necessari, quindi andiamo avanti.
DEBUG
DIRECTORY
Questa directory consiste solo in un array di strutture
IMAGE_DEBUG_DIRECTORY che specificano ogni tipo di informazione che potrebbe
servire (ovviamente stiamo parlando di una sezione che non è assolutamente
vitale, la trovate negli exe di debug che crea il compilatore per esempio). Il
numero di IMAGE_DEBUG_DIRECTORY si ricava dividendo la dimensione della
directory per la dimensione della struttura, ma prima di tutto vediamoci la
struttura:
typedef struct _IMAGE_DEBUG_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Type;
DWORD SizeOfData;
DWORD AddressOfRawData;
DWORD PointerToRawData;
}
IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;
Characteristics
...Boh.
TimeDateStamp indovinate!
MajorVersion e
MinorVersion sempre info moooolto utili, bah.
Type specifica
il tipo di informazione di debug a cui la struttura fa riferimento, i tipi
dichiarati nel winnt.h sono:
#define IMAGE_DEBUG_TYPE_UNKNOWN
0
#define IMAGE_DEBUG_TYPE_COFF
1
#define IMAGE_DEBUG_TYPE_CODEVIEW
2
#define IMAGE_DEBUG_TYPE_FPO
3
#define IMAGE_DEBUG_TYPE_MISC
4
#define IMAGE_DEBUG_TYPE_EXCEPTION 5
#define IMAGE_DEBUG_TYPE_FIXUP
6
#define IMAGE_DEBUG_TYPE_OMAP_TO_SRC 7
#define
IMAGE_DEBUG_TYPE_OMAP_FROM_SRC 8
#define IMAGE_DEBUG_TYPE_BORLAND
9
#define IMAGE_DEBUG_TYPE_RESERVED10 10
#define
IMAGE_DEBUG_TYPE_CLSID 11
Il tipo di info di debug più comune è sicuramente il Code View. Gli unici altri
tipi di debug info che ho visto sono: Misc (in qualche dll MS) e gli omap
(sempre in qualche prog MS). Il Coff si può far usare anche dal VC++ però
francamente lo si vede poco (se vi interessa, una descrizione molto accurata del
formato di debug info Coff la trovate nell'ormai (ahimé) vecchio libro di
Pietrek: Windows Programming Secrets) e per quanto riguarda il borland... Non
uso compilatori borland, boh si ho il delphi installato ma mi fa fatica di
andare a vedere il tipo di debug info prodotto. L'FPO invece viene prodotto
dagli exe compilati dal .NET.
AddressOfRawData sempre 0 lo
trovo...
PointerToRawData RVA che punta alle info di debug.
Adesso avendo le info necessario, basta solo considerare il tipo di info e
comportarsi di proposito. Nel WinNt.h sono dichiarate alcune strutture relative
ad alcuni tipi di debug info:
// Coff
typedef struct
_IMAGE_COFF_SYMBOLS_HEADER {
DWORD NumberOfSymbols;
DWORD LvaToFirstSymbol;
DWORD NumberOfLinenumbers;
DWORD LvaToFirstLinenumber;
DWORD
RvaToFirstByteOfCode;
DWORD RvaToLastByteOfCode;
DWORD RvaToFirstByteOfData;
DWORD RvaToLastByteOfData;
} IMAGE_COFF_SYMBOLS_HEADER, *PIMAGE_COFF_SYMBOLS_HEADER;
// FPO
typedef struct _FPO_DATA {
DWORD ulOffStart;
// offset 1st byte of function code
DWORD cbProcSize;
// # bytes in function
DWORD cdwLocals;
// # bytes in locals/4
WORD cdwParams;
// # bytes in params/4
WORD cbProlog : 8;
// # bytes in prolog
WORD cbRegs : 3;
// # regs saved
WORD fHasSEH : 1;
// TRUE if SEH in func
WORD fUseBP : 1;
// TRUE if EBP has been allocated
WORD reserved : 1;
// reserved for future use
WORD cbFrame : 2;
// frame type
} FPO_DATA, *PFPO_DATA;
// MISC
typedef struct
_IMAGE_DEBUG_MISC {
DWORD DataType;
// type of misc data, see defines
DWORD Length;
// total length of record, rounded to four
// byte multiple.
BOOLEAN Unicode;
// TRUE if data is unicode string
BYTE Reserved[ 3 ];
BYTE Data[ 1 ]; // Actual data
}
IMAGE_DEBUG_MISC, *PIMAGE_DEBUG_MISC;
In ogni caso i formati di debug
info non trovano posto in questo tutorial, infatti vanno oltre il discorso PE.
Quindi andiamo pure avanti (codice esplicativo mi pare supefluo).
ARCHITECTURE/COPYRIGHT DIRECTORY
Della Architecture Directory potrei
anche descrivervi la struttura ma francamente non ho la minima idea a che cosa
possa servire, anche perché non l'ho mai vista all'interno di un PE... Per
quanto riguarda invece la Copyright: fate conto che questa entry non esista.
GLOBAL PTR DIRECTORY
Ecco fate conto che ANCHE questa entry non
esista.
THREAD LOCAL STORAGE
O più brevemente TLS:
finalmente una sezione che serve a qualcosa!
Ogni qualvolta che usate la
direttiva __declspec(thread) per dichiare una variabile in un vostro programma,
questa variabile verrà messa nella sezione .tls. Ma a cosa serve una
dichiarazione __declspec(thread) per una variabile? Fa in modo che tutti i
thread del processo abbiano una propria copia della variabile dichiarata.
Partendo dalla Data Dir troverete la struttura IMAGE_TLS_DIRECTORY, in questa
struttura stanno sei membri di cui quattro sono degli indirizzi
(StartAddressOfRawData, EndAddressOfRawData, AddressOfIndex,
AddressOfCallBacks). E' però molto importante tenere a mente che tutti questi
indirizzi non sono degli RVAs bensì sono dei VAs (occhio).
LOAD
CONFIGURATION
Vi è una struttura adibita a questa directory, ma
francamente non l'ho mai vista in un PE. Inoltre non è documentata nel WinNt.h
né da Pietrek... Mi invento il significato dei membri della struttura?
BOUND IMPORT DIRECTORY
A questo paragrafo avevo già fatto
riferimento in quello riguardante la IT ed è anche interessante. Come ben
sapete, quando un PE viene loadato, il loader riempie la IAT con gli indirizzi
effettivi in memoria delle funzioni importate. Il binding dell'IT non consiste
in altro che nel fare in modo di saltare questo processo, ovvero la IAT conterrà
già nel PE sul disco gli indirizzi di memoria delle funzioni importate. Questo
come ben potete immaginare aumenta la velocità di caricamento (bah) ed infatti
la gran parte degli eseguibili di sistema hanno una IT bind-ata. Innanzitutto
bisogna dire che per bind-are una IT è necessario che vi siano gli
OriginalFirstThunk, altrimenti il loader non vi caricherà nemmeno l'exe perché
trovando solo una IAT già riempita con indirizzi di memoria penserà che la IT
sia distrutta. Ovviamente però non vi è la sicurezza che gli indirizzi della IAT
corrispondano poi a quelli effettivi delle funzioni, se non corrispondessero, ci
penserebbe il loader a sistemare la IAT. Come fa il loader a verificare però la
validità della IAT? Be', se il modulo è stato rilocato allora lo saprà lo stesso
loader, altrimenti proprio grazie a questa directory! Per esempio se cambiasse
la versione del modulo, quasi sicuramente anche gli entry point delle funzioni
non sarebbero più gli stessi e proprio per questo servono gli OFT perché nel
caso di invalidità della IAT il loader è costretto a fare quello che fa sempre,
ignorando il fatto che la IAT sia bind-ata.
Ci sono due modi di bind-are
un exe, il primo di questi è però obsoleto e davvero non merita di essere
descritto, anche perché non lo si trova più.
Partendo dalla Data Dir
troviamo un insieme di strutture IMAGE_BOUND_IMPORT_DESCRIPTOR (una per ogni
modulo a cui l'exe è stato bind-ato), vediamo tale struttura:
typedef
struct _IMAGE_BOUND_IMPORT_DESCRIPTOR {
DWORD
TimeDateStamp;
WORD OffsetModuleName;
WORD NumberOfModuleForwarderRefs;
// Array of zero or more
IMAGE_BOUND_FORWARDER_REF follows
} IMAGE_BOUND_IMPORT_DESCRIPTOR,
*PIMAGE_BOUND_IMPORT_DESCRIPTOR;
TimeDateStamp contiene appunto il
Time/Date Stamp del modulo a cui questa struttura si riferisce.
OffsetModuleName punta (partendo dall'inizio di questa directory) al nome
del modulo a cui la struttura si riferisce.
NumberOfModuleForwarderRefs questo parametro serve nel caso il modulo a cui
la struttura fa riferimento faccia uso (per una delle funzioni importate dal
nostro programma) di Export Forwarding, se così fosse, anche il modulo che
contiene effettivamente deve essere segnalato. Quindi questo membro non
rappresenta altro che il numero di strutture IMAGE_BOUND_FORWARDER_REF che
seguono la corrente struttura IMAGE_BOUND_IMPORT_DESCRIPTOR. Ogni struttura
IMAGE_BOUND_FORWARDER_REF fa riferimento ad un modulo che contiene una o più
funzioni forwardate dal modulo corrente. Vediamo la struttura
IMAGE_BOUND_FORWARDER_REF:
typedef struct _IMAGE_BOUND_FORWARDER_REF {
DWORD TimeDateStamp;
WORD OffsetModuleName;
WORD Reserved;
} IMAGE_BOUND_FORWARDER_REF, *PIMAGE_BOUND_FORWARDER_REF;
TimeDateStamp lo sapete già.
OffsetModuleName anche questo
lo sapete già ed è sempre a partire dall'inizio della directory di cui stiamo
parlando.
Reserved nulla.
In genere gli eseguibili vengono
bind-ati durante le installazioni di programmi e comunque è frequente solo nei
PE di sistema in genere. Per bind-are un eseguibile non è necessario fare alcuna
fatica, basta veramente solo fare uso di una delle due api BindImage/Ex (che
avevo tra l'altro già accennato parlando della imagehlp.dll). In ogni caso mi
sembra opportuno un piccolo esempio di codice che non bind-a nulla, ma
semplicemente mostra questa directory.
// sintassi: nome_file
#include <windows.h>
#include <stdio.h>
DWORD
RvaToOffset(IMAGE_NT_HEADERS *NT, DWORD Rva);
int main(int argc, char
*argv[])
{
HANDLE hFile;
BYTE *BaseAddress;
DWORD
FileSize, BR, Bound_Offset;
IMAGE_DOS_HEADER *ImageDosHeader;
IMAGE_NT_HEADERS *ImageNtHeaders;
IMAGE_BOUND_IMPORT_DESCRIPTOR
*ImgBoundDescr;
IMAGE_BOUND_FORWARDER_REF *ImgForwRef;
DWORD
Size = 0, x;
char *ModName;
// 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_BOUND_IMPORT].VirtualAddress)
{
printf("The IT of this PE isn't bound\n");
free(BaseAddress);
CloseHandle(hFile);
return -1;
}
// converte in
offset fisico l'RVA
Bound_Offset = RvaToOffset(ImageNtHeaders,
ImageNtHeaders->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT].VirtualAddress);
// è
valido?
if (Bound_Offset == NULL)
{
printf("The IT of this
PE isn't bound\n");
free(BaseAddress);
CloseHandle(hFile);
return -1;
}
printf("\nBinding Info:");
while
(TRUE)
{
// prendo il Bound Descr
ImgBoundDescr =
(IMAGE_BOUND_IMPORT_DESCRIPTOR *)
(DWORD) (Bound_Offset + Size +
(DWORD) BaseAddress);
// finiti i descriptors?
if
(!ImgBoundDescr->OffsetModuleName)
break;
// ricavo il
nome basandomi
// sull'inizio della directory
ModName = (char
*) (DWORD)
(ImgBoundDescr->OffsetModuleName +
Bound_Offset + (DWORD) BaseAddress);
// stampo le info
printf("\n\nModule Name: %s\n"
"Time/Date Stamp: %08X\n"
"Forwarder Mods: %d", ModName,
ImgBoundDescr->TimeDateStamp,
ImgBoundDescr->NumberOfModuleForwarderRefs);
Size +=
sizeof (IMAGE_BOUND_IMPORT_DESCRIPTOR);
// ci sono forwarder
modules?
if (ImgBoundDescr->NumberOfModuleForwarderRefs)
printf("\n\nForwarder Modules:");
for (x = 0; x <
ImgBoundDescr->NumberOfModuleForwarderRefs; x++)
{
// vabbe
stessa cosa di prima solo fatto
// per i forwarder mod
ImgForwRef = (IMAGE_BOUND_FORWARDER_REF *) (DWORD)
(Bound_Offset + Size + (DWORD) BaseAddress);
ModName
= (char *) (DWORD)
(ImgForwRef->OffsetModuleName +
Bound_Offset + (DWORD) BaseAddress);
printf("\n\nModule Name: %s\n"
"Time/Date Stamp: %08X",
ModName,
ImgForwRef->TimeDateStamp);
Size +=
sizeof (IMAGE_BOUND_FORWARDER_REF);
}
}
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;
}
Mi sembra semplice il codice, andiamo avanti...
Coraggio: manca poco ormai.
IMPORT ADDRESS TABLE
Siete
giunti a questa directory e non sapete già cosa contiene? A-ha, volevate fare i
furbi, sì, voi newbies del reversing, andate alla ricerca di definizioni come
IAT ecc per imparare a unpackare??? Vi ho beccati!! Tornate subito all'inizio
del tutorial, ahahah.
DELAY LOAD IMPORTS
Capire questa
directory non vi costerà molto tempo. In ogni caso è abbastanza frequente
soprattutto per quanto riguarda gli eseguibili di sistema. Dunque a cosa serve
questa directory? Mettiamo che voi (programmatori) abbiate scritto un programma
che importa un tot di dll, ora, voi non sapete se le funzioni in una o più dll
verranno chiamate durante l'esecuzione del programma (potrebbe darsi di sì come
potrebbe darsi di no). Oppure, altro caso, c'è una o più dll di cui la/e
funzione/i non vi serve/ono subito ma solo più avanti nell'esecuzione del
programma. In questi due casi si potrebbe aumentare la velocità di caricamento
del programma evitando di far caricare al loader subito tutte le dll (e relative
funzioni. Il VC++ 6 introduce la possibilità del Delay Load delle Imports, è
sufficiente aggiungere tra le lib importate dal programma la Delayimp.lib,
dopodiché aggiungere tra le impostazioni del linker /delayload:nome_dll per ogni
dll che volete caricare quando c'è la necessità. Non si può però fare il delay
loading del kernel32 poiché questa dll serve proprio alla routine (vedremo cosa
è) di delay loading all'interno dell'eseguibile. Cioè vediamo di capire, il
delay loading non è un processo fornito dal WinNt.h, bensì dal compilatore che
aggiunge una routine a cui ogni call a funzione di una delle dll che devono
essere caricate se è necessario (specificate dal /delayload insomma) fa
riferimento. Per farvi capire meglio mi sono creato un eseguibile col VC++ 6 (un
semplice Hello World) e ho specificato come /delayload la user32.dll.
Innazitutto nessuna user32.dll risulta da caricare nella Import Table
dell'esebuibile, però una qualsiasi chiamata a una delle funzioni della user32,
assume finalmente questo aspetto:
:0040141A push ecx
:0040141B
push edx
:0040141C push offset UpdateWindow ; nome della funzione che
; è stata chiamata
:00401421 jmp loc_401376
; salta alla routine di delay loading
Il programma cerca di caricare la
dll in questione:
:00401595 push [ebp+lpLibFileName]
:00401598
call ds:LoadLibraryA
Se non ci riesce, genera un'eccezione:
:004015CD push eax
; lpArguments
:004015CE push 1
;
nNumberOfArguments
:004015D0 push 0
; dwExceptionFlags
:004015D2 push 0C06D007Eh
; dwExceptionCode
:004015D7 call ds:RaiseException
Poi prova a prendere l'indirizzo
in memoria della funzione che deve essere eseguita:
:00401677 push
[ebp+lpProcName]
:0040167A push edi
:0040167B
call ds:GetProcAddress
E anche in questo caso, se non gli riesce chiama
RaiseException. Se invece è riuscito a fare tutto, sostituirà nella IAT a cui le
funzioni puntano, l'indirizzo vero della funzione di modo che la prossima volta
che la funzione viene chiamata non debba venir rieseguita la delay loading
function.
A titolo di informazione vi incollo la routine di delay loading
del VC++ (contenuta nel DelayHlp.cpp nella dir include):
extern "C"
FARPROC WINAPI __delayLoadHelper(PCImgDelayDescr pidd,
FARPROC * ppfnIATEntry)
{
// Set up some data we use for the hook procs but also useful for
// our own use
//
DelayLoadInfo dli = {
sizeof DelayLoadInfo,
pidd,
ppfnIATEntry,
pidd->szName,
{ 0 },
0,
0,
0
};
HMODULE hmod =
*(pidd->phmod);
// Calculate the index for the
name in the import name table.
// N.B. it is ordered
the same as the IAT entries so the calculation
//
comes from the IAT side.
//
unsigned iINT;
iINT =
IndexFromPImgThunkData(PCImgThunkData(ppfnIATEntry), pidd->pIAT);
PCImgThunkData pitd = &((pidd->pINT)[iINT]);
if
(dli.dlp.fImportByName = ((pitd->u1.Ordinal & IMAGE_ORDINAL_FLAG) == 0)) {
dli.dlp.szProcName = LPCSTR(pitd->u1.AddressOfData->Name);
}
else {
dli.dlp.dwOrdinal = IMAGE_ORDINAL(pitd->u1.Ordinal);
}
// Call the initial hook. If it exists and
returns a function pointer,
// abort the rest of the
processing and just return it for the call.
//
FARPROC pfnRet = NULL;
if (__pfnDliNotifyHook) {
if (pfnRet = ((*__pfnDliNotifyHook)(dliStartProcessing, &dli))) {
goto HookBypass;
}
}
if (hmod == 0) {
if (__pfnDliNotifyHook) {
hmod = HMODULE(((*__pfnDliNotifyHook)(dliNotePreLoadLibrary, &dli)));
}
if (hmod == 0) {
hmod = ::LoadLibrary(dli.szDll);
}
if (hmod == 0) {
dli.dwLastError = ::GetLastError();
if (__pfnDliFailureHook) {
// when the hook is called on LoadLibrary failure, it will
// return 0 for failure and an hmod for the lib if it fixed
// the problem.
//
hmod = HMODULE((*__pfnDliFailureHook)(dliFailLoadLib, &dli));
}
if (hmod == 0) {
PDelayLoadInfo pdli = &dli;
RaiseException(
VcppException(ERROR_SEVERITY_ERROR, ERROR_MOD_NOT_FOUND),
0,
1,
PDWORD(&pdli)
);
// If we get to here, we blindly assume that the handler of the exception
// has magically fixed everything up and left the function pointer in
// dli.pfnCur.
//
return dli.pfnCur;
}
}
// Store the library handle. If it is already there, we infer
// that another thread got there first, and we need to do a
// FreeLibrary() to reduce the refcount
//
HMODULE hmodT =
HMODULE(::InterlockedExchange(LPLONG(pidd->phmod), LONG(hmod)));
if (hmodT != hmod) {
// add lib to unload list if we have unload data
if (pidd->pUnloadIAT) {
ULI * puli = new ULI(pidd);
(void *)puli;
}
}
else {
::FreeLibrary(hmod);
}
}
// Go for the procedure now.
dli.hmodCur = hmod;
if (__pfnDliNotifyHook) {
pfnRet = (*__pfnDliNotifyHook)(dliNotePreGetProcAddress, &dli);
}
if (pfnRet == 0) {
if (pidd->pBoundIAT && pidd->dwTimeStamp) {
// bound imports exist...check the timestamp from the target image
PIMAGE_NT_HEADERS pinh(PinhFromImageBase(hmod));
if (pinh->Signature == IMAGE_NT_SIGNATURE &&
TimeStampOfImage(pinh) == pidd->dwTimeStamp &&
FLoadedAtPreferredAddress(pinh, hmod)) {
OverlayIAT(pidd->pIAT, pidd->pBoundIAT);
pfnRet = FARPROC(pidd->pIAT[iINT].u1.Function);
goto HookBypass;
}
}
pfnRet = ::GetProcAddress(hmod, dli.dlp.szProcName);
}
if (pfnRet == 0) {
dli.dwLastError = ::GetLastError();
if (__pfnDliFailureHook) {
// when the hook is called on GetProcAddress failure, it will
// return 0 on failure and a valid proc address on success
//
pfnRet = (*__pfnDliFailureHook)(dliFailGetProc, &dli);
}
if (pfnRet == 0) {
PDelayLoadInfo pdli = &dli;
RaiseException(
VcppException(ERROR_SEVERITY_ERROR, ERROR_PROC_NOT_FOUND),
0,
1,
PDWORD(&pdli)
);
// If we get to here, we blindly assume that the handler of the exception
// has magically fixed everything up and left the function pointer in
// dli.pfnCur.
//
pfnRet = dli.pfnCur;
}
}
*ppfnIATEntry = pfnRet;
HookBypass:
if (__pfnDliNotifyHook) {
dli.dwLastError = 0;
dli.hmodCur = hmod;
dli.pfnCur = pfnRet;
(*__pfnDliNotifyHook)(dliNoteEndProcessing, &dli);
}
return pfnRet;
}
Ma vediamo di capire
come la corrente directory è composta, per farlo non dovremo andare a cercare
nel WinNt.h, ma nel DelayImp.H (che non trovate nell'sdk ma sempre tra gli
include del VC++). A partire dalla Data Dir questa directory è composta da un
array di ImgDelayDescr (uno per ogni modulo segnato come delay import), vediamo
la struttura:
typedef struct ImgDelayDescr {
DWORD grAttrs;
LPCSTR szName;
HMODULE * phmod;
PImgThunkData pIAT;
PCImgThunkData pINT;
PCImgThunkData
pBoundIAT;
PCImgThunkData pUnloadIAT;
DWORD dwTimeStamp;
}
ImgDelayDescr, * PImgDelayDescr;
grAttrs attributi.
szName puntatore al nome del modulo a cui la struttura fa riferimento.
phmod indirizzo in memoria del modulo (nel caso fosse già stato caricato
tramite una qualche precedente chiamata a funzione di delay loading).
pIAT indirizzo della IAT, per il VC++ 6 questi sono dei VA.
pINT
indirizzo della Import Name Table, per i nomi delle funzioni importate da questo
modulo.
pBoundIAT nel caso ci fosse una bound IAT, altrimenti 0.
pUnloadIAT indirizzo di una copia opzionale della IAT.
dwTimeStamp questo è generalmente 0, ma se la IAT è bound allora contiene il
Time/Date Stamp del modulo corrente, in ogni caso serve solo per IAT che
usufruiscono del vecchio metodo per bind-are.
Per chiarire le idee eccovi
un piccolo esempio che elenca le info di delay import. Però prima di mostrarvelo
un piccolo appunto, vi ho detto che il VC++ 6 usa VA nei suoi indirizzi, ma ciò
non vale per il .NET o altri compilatori a 64 bit che fanno uso di RVA. Per
capire se l'exe fa uso di RVA o VA bisogna controllare il campo grAttrs. Se il
primo bit è settato allora gli indirizzi sono dei RVA altrimenti sono dei VA.
// sintassi: nome_file
#include <windows.h>
#include <stdio.h>
#include <delayimp.h>
DWORD VaToOffset(IMAGE_NT_HEADERS *NT, DWORD Va);
DWORD RvaToOffset(IMAGE_NT_HEADERS *NT, DWORD Rva);
int main(int argc,
char *argv[])
{
HANDLE hFile;
BYTE *BaseAddress;
DWORD
FileSize, BR, Delay_Offset;
IMAGE_DOS_HEADER *ImageDosHeader;
IMAGE_NT_HEADERS *ImageNtHeaders;
ImgDelayDescr *ImgDelay;
DWORD Size = 0;
char *ModName;
// 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_DELAY_IMPORT].VirtualAddress)
{
printf("There is Delay Import Info\n");
free(BaseAddress);
CloseHandle(hFile);
return -1;
}
// converte in
offset fisico l'RVA
Delay_Offset = RvaToOffset(ImageNtHeaders,
ImageNtHeaders->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT].VirtualAddress);
// è
valido?
if (Delay_Offset == NULL)
{
printf("There is Delay
Import Info\n");
free(BaseAddress);
CloseHandle(hFile);
return -1;
}
printf("\nDelay Import Info:");
while (TRUE)
{
// prendo l'attuale Delay Descriptor
ImgDelay = (ImgDelayDescr *) (DWORD)
(Delay_Offset + Size +
(DWORD) BaseAddress);
// siamo giunti alla fine dell'array?
if (ImgDelay->pIAT == NULL)
break;
// prendo il
nome del modulo
// controllando se ho VA o RVA
if
(ImgDelay->grAttrs & 1)
{
ModName = (char *)(DWORD)
(RvaToOffset(ImageNtHeaders, (DWORD) ImgDelay->szName) +
(DWORD) BaseAddress);
}
else
{
ModName = (char *)(DWORD)
(VaToOffset(ImageNtHeaders,
(DWORD) ImgDelay->szName) +
(DWORD) BaseAddress);
}
// stampo solo le info più importanti
printf("\n\nName: %s\n"
"IAT: %X\nINT: %X", ModName,
ImgDelay->pIAT,
ImgDelay->pINT);
Size += sizeof (ImgDelayDescr);
}
free(BaseAddress);
CloseHandle(hFile);
return 0;
}
// per passare da VA a Offset
DWORD VaToOffset(IMAGE_NT_HEADERS
*NT, DWORD Va)
{
return RvaToOffset(NT,
(Va -
NT->OptionalHeader.ImageBase));
}
// 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;
}
Non mi sembra ci sia altro da dire, se volete vedere
cose come la routine per unloadare le dll caricate tramite delay loading, date
un'occhiata ai file che vi ho segnalato.
COM DESCRIPTOR
La directory è in sé obsoleta, però viene usata per un header dal .NET però non
mi interessa come discorso (almeno per adesso) e non l'ho mai approfondito. In
ogni caso potete sempre andare sul msdn...
CONCLUSIONI
Abbiamo finalmente FINITO! Questo è il tipico tutorial che è più faticoso da
leggere/comprendere che da scrivere (lavoro di due serate).
Alla prossima.
Daniel Pistelli