Home | Chi sono | Contattami
 

Progr. lineare

Delphi
 
Componenti
  Database
 
Miei articoli

Windows

Miei articoli 

 

Analisi della Export Directory in un PE Binary.
Parte1: GetProcAddress su un processo remoto.

 

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

 

 

 

 
 
Your Ad Here