|
Nell'articolo articolo81.htm abbiamo fatto una
piccola analisi di alcune strutture che riguardano ogni processo ed abbiamo
concluso con la definizione della funzione RemoteGetModuleHandle che va
ad eseguire GetModuleHandle (e quindi restituisce l'indirizzo di un
modulo mappato) nell'ambito di un processo remoto. Ora andremo di pari passo e
progrediremo verso la definizione di una GetProcAddress (quindi
restituisce il Virtual Address di una funzione esportata da un modulo
mappato) da eseguirsi nell'ambito di un processo remoto. Come prima cosa è
doveroso analizzare il formato PE (Portable Executable) focalizzando la nostra
attenzione sulla Export Directory.
1. PE format e Export Directory
Anche in questo caso è importante partire da un grafico che definisce le
parti del PE che ci serviranno:

Soffermiamoci un attimo sulla dichiarazione dell'api win32 GetProcAddress
function GetProcAddress(
hModule: Cardinal;
lpProcName: PAnsiChar
): Pointer; stdcall;
- hModule: indirizzo di base, nello spazio di memoria del processo chiamante, della dll in cui si trova la funzione
- lpProcName: valore a 4 bit che può avere 2 significati distinti: 1) se il valore è compreso tra 0 e $FFFF (ossia la High-Order Word è pari a 0) allora il valore rappresenta l'ordinal della funzione; 2) altrimenti rappresenta un puntatore ad una stringa che mi definisce il nome della funzione
L'output della GetProcAddress è appunto l'indirizzo della funzione (nullo in caso di errore).
Riallacciandosi al duplice significato del parametro lpProcName, una cosa importante da dire è che ogni funzione esportata ha associato un Ordinal che altro non è che un valore di 2 byte (Word) e quindi definito nel range [0,$FFFF]; invece non è obbligatorio che la funzione esportata abbia associato un nome (anche se nella stragrande maggioranza dei casi è così). Facendo riferimento alla figura sopra, il campo NumberOfFunctions della IMAGE_EXPORT_DIRECTORY contiene il numero di funzioni esportate dal modulo, mentre il successivo campo NumberOfNames contiene in numero di funzioni esportate dal modulo che hanno anche associato un nome. Il campo Base della IMAGE_EXPORT_DIRECTORY contiene il valore di base della sequenza di Ordinal con cui sono state esportate le funzioni. Analizziamo ora i 3 array puntati dagli ultimi 3 campi della IMAGE_EXPORT_DIRECTORY: AddressOfFunctions: contiene l'RVA a partire dal quale è memorizzato l'array contenente l'elenco degli RVA di tutte le funzioni esportate; la dimensione di tale array è appunto il valore del campo IMAGE_EXPORT_DIRECTORY.NumberOfFunctions. AddressOfNames: contiene l'RVA a partire dal quale è memorizzato l'array contenente l'elenco degli RVA in cui si trovano i nomi delle funzioni esportate con nome: in pratica ogni elemento dell'array contiene l'RVA a cui è possibile trovare la sequenza di caratteri che mi definisce il nome di una funzione esportata con nome; la dimensione di tale array è il valore IMAGE_EXPORT_DIRECTORY.NumberOfNames. AddressOfNameOrdinals: contiene l'RVA a partire dal quale è memorizzato l'array contenente l'elenco degli Ordinal associati ai corrispondenti valori dell'array puntato dal campo IMAGE_EXPORT_DIRECTORY.AddressOfNames; la dimensione di tale array chiaramente è il valore IMAGE_EXPORT_DIRECTORY.NumberOfNames. Vediamo ora come ottenere l'RVA di una funzione specifica: l'obiettivo è determinare l'indice corrispondente nell'array puntato dal campo IMAGE_EXPORT_DIRECTORY.AddressOfFunctions (array che considereremo in base 0); avremo 2 casi 1) Viene specificato l'Ordinal della funzione (valore nel range [0;$FFFF]): in questo caso basta sottrarre all'Ordinal in questione il valore IMAGE_EXPORT_DIRECTORY.Base; il risultato è appunto l'indice, corrispondente alla funzione che cerchiamo, nell'array puntato dal campo IMAGE_EXPORT_DIRECTORY.AddressOfFunctions. Il contenuto di quell'elemento dell'array (l'elemento che si trova nella posizione specificata dall'indice appena trovato) è appunto l'RVA della funzione esportata. 2) Viene specificato il nome della funzione: in questo caso entrano in gioco i 2 array puntati rispettivamente dai campi IMAGE_EXPORT_DIRECTORY.AddressOfNames e IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals. In pratica, viene scansionato l'array puntato da AddressOfNames: per ogni elemento dell'array ci si sposta all'RVA specificato dall'elemento stesso; a quell'RVA si trova il nome della funzione corrispondente: se è questa la funzione che cerchiamo allora STOP: supponiamo di essere all'elemento j-simo dell'array puntato da AddressOfNames, l'elemento j-simo dell'array puntato da AddressOfNameOrdinals conterrà l'indice della funzione richiesta nell'array puntato da AddressOfFunctions. Il contenuto di quell'elemento dell'array (l'elemento che si trova nella posizione specificata dall'indice appena trovato) è appunto l'RVA della funzione esportata. Bene, a questo punto manca ancora un piccolo dettaglio al completamento dell'opera: si tratta del forwarding di funzione; in pratica, una volta trovato l'RVA della funzione, bisogna verificare che tale valore si trovi al di fuori della Export Directory: in soldoni si deve avere (Function RVA) not in [DataDirectory.ExportRVA, DataDirectory.ExportRVA+ DataDirectory.Size] Se invece l'RVA è all'interno della Export Directory, allora a quell'indirizzo si trova una stringa del tipo <nome_dll>.<nome_funzione> oppure <nome_dll>.#Ordinal (a seconda che la funzione a cui si punta venga definita tramite nome o tramite Ordinal). Ad esempio nella dll kernle32.dll l'RVA della funzione EnterCriticalSection è un indirizzo interno alla Export Directory e a quell'indirizzo si trova la stringa NTDLL.RtlEnterCriticalSection: ciò significa che, quando vado a chiamare kernel32.EnterCriticalSection, automaticamente viene chiamata la funzione RtlEnterCriticalSection esportata dalla dll ntdll.dll. Quindi dovremo calcolare l'indirizxo di ntdll.RtlenterCriticalSection. Behh ... non è difficile, l'obiettivo sarà quello di eseguire sta pappardella su un processo remoto. 2. Implementazione Anche in questo caso, come per altri articoli, è opportune buttarsi direttamente sul codice e seguire i commenti nel codice stesso:
function RemoteGetProcAddress(
//handle al processo
hProcess: Cardinal;
//Indirizzo di base del modulo (ottenuto con RemoteGetModuleHandle)
hModule: Cardinal;
//NomeModulo: string;
//NomeFunzione: string;
//puntatore ad una stringa che definisce il nome della funzione
ProcName: PAnsiChar;
var FuncAddress: Cardinal): Boolean;
var
DosHeader : TImageDosHeader; //Dos Header
NtHeaders : TImageNtHeaders; //PE Header
DataDirectory : TImageDataDirectory; //Data Directory
ExportDirectory : TImageExportDirectory; //Export Directory
BytesRead: Cardinal; //var per ReadProcessMemory
//estremo inferiore e superiore della Export Directory
ExportDataDirectoryLow, ExportDataDirectoryHigh: cardinal;
//valore Base di IMAGE_EXPORT_DIRECTORY: valore base degli Ordinal
BaseOrdinal: Cardinal;
//
NumberOfFunctions: Cardinal;
NumberOfNames: Cardinal;
//puntatori ai 3 array
First_AddressOfFunctions: Cardinal;
First_AddressOfNames: Cardinal;
First_AddressOfNameOrdinals: Cardinal;
Actual_AddressOfFunctions: Cardinal;
Actual_AddressOfNames: Cardinal;
Actual_AddressOfNameOrdinals: Cardinal;
//
//indice della funzione nell'array puntato da
//IMAGE_EXPORT_DIRECTORY.AddressOfFunctions:
//l'elemento dell'array che si trova in questa
//posizione contiene l'RVA della funzione
FunctionIndex: Cardinal;
//RVA presente in un elemento dell'array puntato
//da IMAGE_EXPORT_DIRECTORY.AddressOfNames
FunctionNameRVA: Cardinal;
//nome puntato dall'RVA presente in un
//elemento dell'array puntato da
//IMAGE_EXPORT_DIRECTORY.AddressOfNames
FunctionName: PAnsiChar;
FunctionNameFound: Boolean;
//RVA della funzione che cerchiamo: è presente
//in un elemento dell'array puntato da
//IMAGE_EXPORT_DIRECTORY.AddressOfFunctions
FunctionRVA: Cardinal;
//Forwarding
FunctionForwardName: PAnsiChar;
ForwardPuntoPos: Cardinal;
ExportForward: Boolean;
FunctionForward_ModuleName: string;
FunctionForward_FunctionName: string;
FunctionForward_FunctionOrdinal: Word;
FunctionForwardByOrdinal: Boolean;
FunctionForward: Pointer;
FunctionForward_ModuleBaseAddr: Cardinal;
FunctionForward_FunctionAddr: Cardinal;
//
i: Cardinal;
begin
Result := False;
//verifica sui valori dei parametri
if (hProcess = 0) or
(ProcName = nil) then
begin
Exit;
end;
try
//prelevo il Dos Header
if not ReadProcessMemory(
hProcess,
Pointer(hModule),
@DosHeader,
sizeof(DosHeader),
BytesRead
) then
begin
ErrStr('ReadProcessMemory');
Exit;
end;
//verifica della validità
if (DosHeader.e_magic <> IMAGE_DOS_SIGNATURE) then
Exit;
//prelevo il PE Header
if not ReadProcessMemory(
hProcess,
Pointer(hModule + DosHeader._lfanew),
@NtHeaders,
sizeof(NtHeaders),
BytesRead
) then
begin
ErrStr('ReadProcessMemory');
Exit;
end;
//verifica della validità
if (NtHeaders.Signature <> IMAGE_NT_SIGNATURE) then
Exit;
//se il modulo non ha la directory di Export allora esco
//valuto l' RVA della directory di export
if NTHeaders.OptionalHeader.DataDirectory[0].VirtualAddress = 0 then
Exit;
//calcolo il range di definizione della Export Directory
//mi servirà per valutare se l'RVA di una funzione punta alla definizione
//della funzione (valore esterno all'intervallo) oppure punta ad una stringa
//del tipo nome_dll.nome_funzione (valore interno all'intervallo)
with NTHeaders.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT] do
begin
ExportDataDirectoryLow := VirtualAddress;
ExportDataDirectoryHigh := VirtualAddress + Size;
end;
//prelevo la Export Directory
if not ReadProcessMemory(
hProcess,
Pointer(hModule + ExportDataDirectoryLow),
@ExportDirectory,
sizeof(ExportDirectory),
BytesRead
) then
begin
ErrStr('ReadProcessMemory');
Exit;
end;
//Determino il valore base degli Ordinal
BaseOrdinal := ExportDirectory.Base;
//
NumberOfFunctions := ExportDirectory.NumberOfFunctions;
NumberOfNames := ExportDirectory.NumberOfNames;
First_AddressOfFunctions := hModule +
Cardinal(ExportDirectory.AddressOfFunctions);
First_AddressOfNames := hModule +
Cardinal(ExportDirectory.AddressOfNames);
First_AddressOfNameOrdinals := hModule +
Cardinal(ExportDirectory.AddressOfNameOrdinals);
//allocazione di memoria per puntatori
//che riceveranno stringhe di caratteri
GetMem(FunctionName, 100);
GetMem(FunctionForwardName, 100);
FunctionIndex := 0;
if Cardinal(ProcName) <= $FFFF then
//ho passato l'Ordinal della funzione
begin
FunctionIndex := Cardinal(ProcName) - BaseOrdinal;
//verifico di aver passato un Ordinal valido
if (FunctionIndex < 0) or
(FunctionIndex > NumberOfFunctions) then
Exit;
end
else
//ho passato il puntatore ad una stringa
//che rappresenta il nome della funzione
begin
//scanno l'array puntato da IMAGE_EXPORT_DIRECTORY.AddressOfNames
//che contiene i nomi delle funzioni esportate con associato un nome;
//ogni elemento dell'array è 4 byte (è infatti l'RVA del nome della funzione)
FunctionNameFound := False;
for i := 0 to NumberOfNames - 1 do {for each export do}
begin
Actual_AddressOfNames := First_AddressOfNames + SizeOf(Cardinal) * i;
//prelevo l'RVA del nome: FunctionNameRVA
ReadProcessMemory(
hProcess,
Pointer(Actual_AddressOfNames) ,
@FunctionNameRVA,
4,
BytesRead);
//prelevo il nome: FunctionName
ReadProcessMemory(
hProcess,
Pointer(hModule + FunctionNameRVA) ,
FunctionName,
100,
BytesRead
);
//vado a vedere se il nome che ho
//trovato equivale a quello che cerco
if lstrcmpiA(FunctionName, ProcName) = 0 then
begin
//vado nell'array puntato da IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals:
//l'elemento di posizione i contiene l'indice della funzione nell'array
//puntato da IMAGE_EXPORT_DIRECTORY.AddressOfFunctions
Actual_AddressOfNameOrdinals := First_AddressOfNameOrdinals +
SizeOf(Word) * i;
//prelevo l'indice: FunctionIndex
ReadProcessMemory(
hProcess,
Pointer(Actual_AddressOfNameOrdinals) ,
@FunctionIndex,
2,
BytesRead
);
FunctionNameFound := True;
Break;
end;
end;
//verifico di aver trovato il nome specificato tra i nomi di funzione
//in caso contrario esco
if not FunctionNameFound then
Exit;
end;
//Perfetto ho ottenuto l'indice nell'array puntato
//da IMAGE_EXPORT_DIRECTORY.AddressOfFunctions; a questo
//punto posso prelevare l'RVA della funzione
Actual_AddressOfFunctions := First_AddressOfFunctions +
SizeOf(Cardinal) * FunctionIndex;
//prelevo l'RVA
ReadProcessMemory(
hProcess,
Pointer(Actual_AddressOfFunctions) ,
@FunctionRVA,
4,
BytesRead
);
//Bene: ho l'RVA della funzione; devo ora vedere se
//rientra nella Export Directory: in tal caso punta
//ad una stringa del tipo nome_dll.nome_funzione o
//nome_dll.#Ordinal ossia si è di fronte ad un
//Forwarding e dovremo chiamare RemoteGetProcAddress
//su questi nuovi valori
if (FunctionRVA > ExportDataDirectoryLow) and
(FunctionRVA <= ExportDataDirectoryHigh) then
//questo è un forwarding
begin
//prelevo la stringa modello nome_dll.nome_funzione o
//nome_dll.#Ordinal
ReadProcessMemory(
hProcess,
Pointer(hModule + FunctionRVA) ,
FunctionForwardName,
100,
BytesRead
);
//estraggo nome del modulo e della funzione
ForwardPuntoPos := Pos('.', FunctionForwardName);
if (ForwardPuntoPos > 0) then
begin
FunctionForward_ModuleName := Copy(
FunctionForwardName,
1,
ForwardPuntoPos - 1
) + '.dll';
FunctionForward_FunctionName := Copy(
FunctionForwardName,
ForwardPuntoPos + 1,
Length(FunctionForwardName)
);
//Vado a vedere se FunctionForward_FunctionName è del tipo #Ordinal
//in tal caso significa che la funzione a cui si punta è definita
//tramite il suo Ordinal
FunctionForwardByOrdinal := False;
if string(FunctionForward_FunctionName)[1] = '#' then
begin
FunctionForwardByOrdinal := True;
FunctionForward_FunctionOrdinal := StrToInt(Copy(
FunctionForward_FunctionName,
2,
Length(FunctionForward_FunctionName)
));
end;
//vado a rieseguire GetRemoteProcAddress sui valori di Forwarding
//prima di tutto calcolo il base address del nuovo modulo:
//deve esistere per forza, però non si sa mai, sempre meglio
//gestire il caso in cui la funzione RemoteGetModuleHandle
//per qualche motivo fallisce
if not RemoteGetModuleHandle(
hProcess,
PAnsiChar(FunctionForward_ModuleName),
FunctionForward_ModuleBaseAddr) then
Exit;
//una volta trovato il base address del modulo, chiamo
//RemoteGetProcAddress
if FunctionForwardByOrdinal then
//"nome_dll".#Ordinal
FunctionForward := Pointer(FunctionForward_FunctionOrdinal)
else
//"nome_dll"."nome_funzione"
FunctionForward := PAnsiChar(FunctionForward_FunctionName);
if not RemoteGetProcAddress(
hProcess,
FunctionForward_ModuleBaseAddr,
FunctionForward,
FunctionForward_FunctionAddr
) then
Exit;
//se tutto è andato OK
FuncAddress := FunctionForward_FunctionAddr;
end;
end
else //non si tratta di un Forwarding
begin
//sommo all'RVA della funzione il Base Address del modulo:
//in questo modo ottengo il Virtual Address della funzione
//ossia il risultato finale
FuncAddress := hModule + FunctionRVA;
end;
Result := True;
finally
if FunctionName <> nil then
FreeMem(Pointer(FunctionName));
if FunctionForwardName <> nil then
FreeMem(Pointer(FunctionForwardName));
end;
end;
Sembra difficile, ma studiando un pò il codice ed i commenti si vede la luce rapidamente. Di seguito ovviamente un esempino da scaricare RemoteGetProcAddress.7z
|