|
In questo articolo andiamo ad analizzare le strutture fondamentali in User Mode che
caratterizzano un processo. Verranno definite delle procedure per accedere ad
informazioni relative a processi remoti.
1. Cominciamo
L'obiettivo di questo articolo, non è quello di descrivere nel dettaglio i
passaggi (e conseguentemente le funzioni API utilizzate) che costituiscono la
creazione di un processo; andremo ad esaminare alcune tra le strutture più
importanti che si trovano nello spazio di memoria di ogni processo.

Come si può vedere dal grafico, lo spazio di memoria di un processo, contiene
il modulo .exe, kernel32.dll e ntdll.dll (moduli presenti in ogni processo) più
naturalmente tutte le altre dll necessarie nel caso specifico, più un insieme di
strutture che raccolgono le caratteristiche del processo medesimo. Naturalmente
la storia non finisce qui, si potrebbero scrivere biblioteche intere
sull'argomento, ma per quel che dobbiamo dire nel seguito, basta e avanza. La
cosa principale da dire è che in pratica il processo è un contenitore di threads.
Ogni processo (anche se non specifico direttamente una chiamata all'api
CreateThread) dispone di un thread: il thread è l'oggetto che esegue il codice.
Il processo è un oggetto che serve al sistema operativo per raccogliere tutto
ciò che è associato ad un determinato codice di esecuzione da fa girare sulla
CPU. Il processo dispone di uno spazio di memoria in cui viene caricato il
modulo .exe, ntdll.dll, kernel32.dll e tutte le altre dll presenti nella
Import Table del .exe, più naturalmente tutte le dll che verranno caricate tramite ad
esempio LoadLibrary. Nello stesso spazio di memoria vengono allocate delle
strutture che raccolgono le caratteristiche del processo (linea di comando,
elenco dei moduli dll caricati, con informazioni relative ad ognuno di essi, etc...).
Questo solo per fare un breve panoramica di quello che andremo ad approfondire.
La struttura di base che contiene poi i puntatori a tutto il resto, è il Process
Environment Block (PEB); tutte le volte che verrà creato un thread verrà allocata una
struttura analoga relativamente ad ogni thread chiamata Thread Environment
Block (TEB). La struttura RTL_User_Process_Parameters (detta anche
Process Parameter
Block o più brevemente PPB), raccoglie informazioni importanti
sul processo, tipo ad esempio il modulo .exe, la linea di comando con cui il
processo è stato creato, etc... La struttura PEB_LDR_DATA (il cosidetto
LOADER
DATA) raccoglie informazioni
relative ai moduli caricati: in pratica contiene soprattutto i puntatori
alle liste linkate composte da oggetti di tipo LDR_MODULE. La struttura
LDR_MODULE raccoglie le caratteristiche di un modulo caricato (es. indirizzo
base di caricamento, nome modulo, etc...)
2. Process Environment Block (PEB)
Il PEB è il punto di partenza per analizzare le varie informazioni relative
ad un processo: la sua composizione è decisamente complessa dal punto di vista
quantitativo in quanto è composto da numerosi campi; inoltre trattandosi di una
struttura non documentata ufficialmente, alla pari delle API Native può variare
da una versione all'altra di Windows o anche da un service pack all'altro:
intendiamoci, non è che all'uscita di un service pack ci troviamo un PEB che non
c'entra nulla col precedente, le modifiche se ci sono possono riguardare
l'aggiunta di alcuni campi in fondo, non si ha una modifica sostanziale; per
avere la struttura del record che mi rappresenta il PEB si può far riferimento
all'ottimo lavoro fatto da Marcel Van Brakel e Oliver Schneider di traduzione in Pascal degli header .h relativi
alle API Native in cui troviamo appunto anche la definizione delle varie
strutture dati quali appunto PEB, PPB, etc... Nella unit JwaNative.pas troviamo
le varie definizioni del PEB (Windows 2000, Windows XP, Windows 2003)
determinate con l'ausilio di WinDbg. Come si può notare, fino all'indirizzo $1d4
incluso, i 3 record sono identici, poi ci sono i campi aggiuntivi che variano da
versione a versione.
Process Environment Block
Behh, per maggiori informazioni relative ai singoli campi rimando all'ottimo
documento prelevabile all'indirizzo
Processes_Thread_Fibers_Jobs__By_Alex_Ionescu.pdf
che contiene poi informazioni dettagliate anche su tutte le altre strutture
precedentemente descritte e molto (ma mooolto) di più.
A noi interessano principalmente i valori in posizione $0C (12) e
$10 (16)
che contengono rispettivamente l'indirizzo del Process Parameter Block (PPB
ossia la struttura RTL_USER_PROCESS_PARAMETER contenente informazioni sul
processo quali linea di comando, etc...) e l'indirizzo del LOADER DATA (ossia
la struttura PEB_LDR_DATA che contiene i puntatori alle strutture LDR_MODULE
che raccolgono informazioni sui moduli mappati nello spazio di memoria del processo). A questo punto rimane però una cosa: a che indirizzo
si trova il PEB? Lo vediamo nel paragrafo successivo.
2.1 Indirizzo del PEB
Con la seguente funzione vado a determinare l'indirizzo del PEB nello spazio
di memoria di un processo
function NtQueryInformationProcess(
ProcessHandle : Cardinal;
ProcessInformationClass : Byte;
ProcessInformation : Pointer;
ProcessInformationLength : Cardinal;
ReturnLength : PCardinal
): Integer; stdcall; external 'ntdll.dll';
...
function GetPEBptr(
hProcess: Cardinal; //handle del processo
var addr: Cardinal //indirizzo del PEB
): Boolean;
type
TProcessBasicInformation = record
ExitStatus: Integer;
PebBaseAddress: Cardinal;
AffinityMask: Integer;
BasePriority: Integer;
UniqueProcessID: Integer;
InheritedFromUniqueProcessID: Integer;
end;
var
ProcInfo: TProcessBasicInformation;
status: Integer;
begin
result := False;
//prelevo le informazioni di base relative al processo
status := NtQueryInformationProcess(hProcess,
0,
@ProcInfo,
SizeOf(TProcessBasicInformation),
nil);
if status <> 0 then
begin
ErrStrNative('NtQueryInformationProcess', status);
Exit;
end;
addr := ProcInfo.PebBaseAddress;
Result := True;
end;
come si può vedere, basta usare l' api Native NtQuerySystemInformation (già trattata in precedenti articoli). A questo punto posso determinare l'indirizzo del Process Parameter Block e del Loader Data come vedremo nei paragrafi successivi. 3. Process Parameter Block (PPB) Il PPB può essere rappresentato dal seguente record _RTL_USER_PROCESS_PARAMETERS
PUNICODE_STRING = ^UNICODE_STRING;
UNICODE_STRING = record
Length: Word;
MaximumLength: Word;
Buffer: PWideChar;
end;
PANSI_STRING = ^ANSI_STRING;
ANSI_STRING = record
Length: Word;
MaximumLength: Word;
Buffer: PAnsiChar;
end;
CURDIR = record
(*000*)DosPath: UNICODE_STRING;
(*008*)Handle: Cardinal;
end;
_RTL_DRIVE_LETTER_CURDIR = record
(*000*)Flags: Word;
(*002*)Length: Word;
(*004*)TimeStamp: Cardinal;
(*008*)DosPath: ANSI_STRING;
end;
_RTL_USER_PROCESS_PARAMETERS = record
(*000*)MaximumLength: Cardinal;
(*004*)Length: Cardinal;
(*008*)Flags: Cardinal;
(*00c*)DebugFlags: Cardinal;
(*010*)ConsoleHandle: Cardinal;
(*014*)ConsoleFlags: Cardinal;
(*018*)StandardInput: Cardinal;
(*01c*)StandardOutput: Cardinal;
(*020*)StandardError: Cardinal;
(*024*)CurrentDirectory: CURDIR;
(*030*)DllPath: UNICODE_STRING;
(*038*)ImagePathName: UNICODE_STRING;
(*040*)CommandLine: UNICODE_STRING;
(*048*)Environment: Pointer;
(*04c*)StartingX: Cardinal;
(*050*)StartingY: Cardinal;
(*054*)CountX: Cardinal;
(*058*)CountY: Cardinal;
(*05c*)CountCharsX: Cardinal;
(*060*)CountCharsY: Cardinal;
(*064*)FillAttribute: Cardinal;
(*068*)WindowFlags: Cardinal;
(*06c*)ShowWindowFlags: Cardinal;
(*070*)WindowTitle: UNICODE_STRING;
(*078*)DesktopInfo: UNICODE_STRING;
(*080*)ShellInfo: UNICODE_STRING;
(*088*)RuntimeData: UNICODE_STRING;
(*090*)CurrentDirectories: array[0..31] of _RTL_DRIVE_LETTER_CURDIR;
end;
3.1 Indirizzo del PPB Come già detto in precedenza, l'indirizzo del PPB si trova nel PEB (4 byte a partire dal 16° byte)
function GetRTLProcessParametersPtr(
hProcess: Cardinal; //handle del processo
var addr: Cardinal //indirizzo del PPB
): Boolean;
var
PEBptr: Cardinal;
BytesRead: dword;
Usr: Pointer;
begin
result := False;
//determino l'indirizzo del PEB con GetPEBPtr
//definita in precedenza
if not GetPEBptr(hProcess, PEBptr) then
begin
Exit;
end;
if not ReadProcessMemory(
hProcess,
pointer(PEBptr + 16),
@addr,
4,
BytesRead
) then
begin
ErrStr('ReadProcessMemory');
Exit;
end;
result := True;
end;
4. Loader Data Il blocco Loader Data può essere rappresentato dal seguente record PEB_LDR_DATA
PEB_LDR_DATA = record
(*000*)Length: Cardinal;
(*004*)Initialized: BOOLEAN;
(*008*)SsHandle: Pointer;
(*00c*)InLoadOrderModuleList: LIST_ENTRY;
(*014*)InMemoryOrderModuleList: LIST_ENTRY;
(*01c*)InInitializationOrderModuleList: LIST_ENTRY;
(*024*)EntryInProgress: Pointer;
end;
4.1 Indirizzo del Loader Data Come già detto in precedenza, l'indirizzo del Loader Data si trova nel PEB (4 byte a partire dal 12° byte)
function GetPebLdrData(
hProcess: Cardinal; //handle del processo
var addr: Cardinal //indirizzo del Loader Data
): Boolean;
var
PEBptr: Cardinal;
BytesRead: dword;
begin
Result := False;
//determino l'indirizzo del PEB
//con GetPEBPtr definita in precedenza
if not GetPEBptr(hProcess, PEBptr) then
begin
Exit;
end;
if not ReadProcessMemory(
hProcess,
pointer(PEBptr + 12),
@addr,
4,
BytesRead
) then
begin
ErrStr('ReadProcessMemory');
Exit;
end;
result := True;
end;
5. LDR_MODULE LDR_MODULE definisce le caratteristiche di ogni modulo mappato nello spazio di memoria di un processo; esiste una istanza di LDR_MODULE per ogni modulo mappato; la struttura è la seguente
LDR_MODULE = record
InLoadOrderLinks: LIST_ENTRY;
InMemoryOrderLinks: LIST_ENTRY;
InInitializationOrderLinks: LIST_ENTRY;
DllBase: Cardinal;
EntryPoint: Cardinal;
SizeOfImage: Cardinal;
FullDllName: UNICODE_STRING;
BaseDllName: UNICODE_STRING;
Flags: Cardinal;
LoadCount: SmallInt;
TlsIndex: SmallInt;
HashLinks: LIST_ENTRY;
TimeDateStamp: Cardinal;
LoadedImports: Pointer;
EntryPointActivationContext: Pointer; // PACTIVATION_CONTEXT
PatchInformation: Pointer;
end;
Mi soffermerei sui primi 3 campi di tipo LIST_ENTRY; cosa esprime il tipo LIST_ENTRY? Questo tipo è un tipo usato da Windows nella gestione di tutte le più importanti informazioni di tipo "lista linkata" (come è il caso appunto delle informazioni relative all'elenco dei moduli mappati): dalla unit Windows di Delphi abbiamo:
type
PListEntry = ^TListEntry;
TListEntry = record
Flink: PListEntry; //puntatore al successivo
Blink: PListEntry; //puntatore al precedente
end;
In pratica quando voglio definire una lista linkata di oggetti di un determinato tipo basta che vado ad inserire nella definizione del tipo un campo di tipo TListEntry. Ma come funziona esattamente il tipo TListEntry? Come devo settare i suoi 2 campi Flink e Blink? La cosa migliore è passare direttamente ad un esempio, ma anzitutto definiamo meglio cosa significano i primi 3 campi di LDR_MODULE: InLoadOrderLinks: consente di collegare le varie istanze di LDR_MODULE nell' ordine di caricamento dei moduli associati; l'anello iniziale e finale della lista è il campo InLoadOrderModuleList di PEB_LDR_DATA (anch'esso chiaramente di tipo TListEntry) InMemoryOrderLinks: consente di collegare le varie istanze di LDR_MODULE nell' ordine di posizione in memoria dei moduli associati; l'anello iniziale e finale della lista è il campo InMemoryOrderModuleList di PEB_LDR_DATA (anch'esso chiaramente di tipo TListEntry) InInitializationOrderLinks: consente di collegare le varie istanze di LDR_MODULE nell' ordine di inizializzazione dei moduli associati; l'anello iniziale e finale della lista è il campo InInitOrderModuleList di PEB_LDR_DATA (anch'esso chiaramente di tipo TListEntry)
Bene. Ora passiamo al seguente grafico che ci rappresenta la situazione ipotetica in cui siano mappati in memoria 5 moduli: dll1, dll2, dll3, dll4, dll5 
Ok. Ora esaminiamo ognuno dei 3 ordinamenti: InLoadOrderLinks: esaminiamo l'ordine di caricamento; supponiamo che sia il seguente: dll4, dll2, dll5, dll1, dll3 avremo le seguenti impostazioni (in pseudocodice):
PEB_LDR_DATA.InLoadOrderModuleList.FLink = ptr4;
PEB_LDR_DATA.InLoadOrderModuleList.BLink = ptr3;
LDRM4.LoadOrder.FLink = ptr2;
LDRM4.LoadOrder.BLink = (@(PEB_LDR_DATA) + 12);
LDRM2.LoadOrder.FLink = ptr5;
LDRM2.LoadOrder.BLink = ptr4;
LDRM5.LoadOrder.FLink = ptr1;
LDRM5.LoadOrder.BLink = ptr2;
LDRM1.LoadOrder.FLink = ptr3;
LDRM1.LoadOrder.BLink = ptr5;
LDRM3.LoadOrder.FLink = (@(PEB_LDR_DATA) + 12);
LDRM3.LoadOrder.BLink = ptr1;
InMemoryOrderLinks: esaminiamo l'ordine di posizione in memoria; supponiamo che sia il seguente: dll3, dll2, dll4, dll1, dll5 avremo le seguenti impostazioni (in pseudocodice):
PEB_LDR_DATA.InMemoryOrderModuleList.FLink = ptr3 + 8;
PEB_LDR_DATA.InMemoryOrderModuleList.BLink = ptr5 + 8;
LDRM3.MemoryOrder.FLink = ptr2 + 8;
LDRM3.MemoryOrder.BLink = (@(PEB_LDR_DATA) + 20);
LDRM2.MemoryOrder.FLink = ptr4 + 8;
LDRM2.MemoryOrder.BLink = ptr3 + 8;
LDRM4.MemoryOrder.FLink = ptr1 + 8;
LDRM4.MemoryOrder.BLink = ptr2 + 8;
LDRM1.MemoryOrder.FLink = ptr5 + 8;
LDRM1.MemoryOrder.BLink = ptr4 + 8;
LDRM5.MemoryOrder.FLink = (@(PEB_LDR_DATA) + 20);
LDRM5.MemoryOrder.BLink = ptr1 + 8;
InInitOrderLinks: esaminiamo l'ordine di inizializzazione; supponiamo che sia il seguente: dll2, dll5, dll4, dll3, dll1 avremo le seguenti impostazioni (in pseudocodice):
PEB_LDR_DATA.InInitOrderModuleList.FLink = ptr2 + 16;
PEB_LDR_DATA.InInitOrderModuleList.BLink = ptr1 + 16;
LDRM2.InitOrder.FLink = ptr5 + 16;
LDRM2.InitOrder.BLink = (@(PEB_LDR_DATA) + 28);
LDRM5.InitOrder.FLink = ptr4 + 16;
LDRM5.InitOrder.BLink = ptr2 + 16;
LDRM4.InitOrder.FLink = ptr3 + 16;
LDRM4.InitOrder.BLink = ptr5 + 16;
LDRM3.InitOrder.FLink = ptr1 + 16;
LDRM3.InitOrder.BLink = ptr4 + 16;
LDRM1.InitOrder.FLink = (@(PEB_LDR_DATA) + 28);
LDRM1.InitOrder.BLink = ptr3 + 16;
Uhmm.. si, bisogna far un pò di verifiche, comunque è una str****ta una volta capita. 6. Un pò di codice E' arrivato il momento di scrivere un pò di codice. Partiremo dal Process Parameter Block e andremo a leggere un pò di informazioni relative al processo. 6.1 Informazioni sul processo Andremo a prelevare le informazioni da RTL_USER_PROCESS_PARAMETERS. Come prima cosa però è opportuno definire una procedura di supporto che ci consenta di ottenere una stringa corrispondente ad un valore UNICODE_STRING ad un determinato indirizzo; infatti alcuni dei parametri di RTL_USER_PROCESS_PARAMETERS sono di tipo UNICODE_STRING. 6.2 Procedura per leggere una UNICODE_STRING dallo spazio di memoria di un processo
function GetUnicodeStringRemoteProcess(
hProcess: Cardinal;
addr: Cardinal;
var remotestring: string
): Boolean;
var
Length: Cardinal;
BytesRead: Cardinal;
Buf: Cardinal;
Len: Word;
Buffer: PWideChar;
begin
result := False;
//leggo i primi 2 byte di _PROCESS_PARAMETER.ImageFile
//(che è Unicode_String e quindi i primi 2 byte sono
//il campo Length e quindi la lunghezza in byte della stringa)
if not ReadProcessMemory(
hProcess,
pointer(addr),
@Len,
2,
BytesRead
) then
begin
ErrStr('ReadProcessMemory');
Exit;
end;
GetMem(Buffer, Len);
try
//leggo gli ultimi 4 byte di _PROCESS_PARAMETER.ImageFile
//e quindi il puntatore alla stringa
if not ReadProcessMemory(
hProcess,
pointer(addr + 4),
@Buf,
4,
BytesRead
) then
begin
ErrStr('ReadProcessMemory');
Exit;
end;
//leggo la stringa
if not ReadProcessMemory(
hProcess,
pointer(Buf),
Buffer,
Len,
BytesRead
) then
begin
ErrStr('ReadProcessMemory');
Exit;
end;
//converto da WideChar a string
remotestring := WideCharToString(Buffer);
//Len è la lunghezza in byte della wideString
//--> la dimensione della stringa sarà la metà
SetLength(remotestring, Len div 2);
finally
FreeMem(Buffer);
end;
Result := True;
end;
a questo punto possiamo scrivere una bella funzioncina che preleva i valori di tutti i campi di RTL_USER_PROCESS_PARAMETERS di tipo UNICODE_STRING 6.3 Funzione per leggere i valori dei campi di RTL_USER_PROCESS_PARAMETERS di tipo UNICODE_STRING
//preleva informazioni di tipo UNICODE_STRING dal processo
//
(* Valori del parametro infoType
0: offset := 36; //CurrentDirectory
1: offset := 48; //DLLPath
2: offset := 56; //ImagePathName
3: offset := 64; //CommandLine
4: offset := 112; //WindowTitle
5: offset := 120; //DesktopInfo
6: offset := 128; //ShellInfo
7: offset := 136; //RuntimeData
*)
function GetRemoteProcessInfo(
Pid: Cardinal;
infoType: byte;
var Info: string
): Boolean;
var
Process: Cardinal;
BytesRead: Cardinal;
pRTLProcParams, Buf: Cardinal;
offset: Cardinal;
begin
Result := False;
Process := OpenProcessEx(
PROCESS_VM_READ or
PROCESS_QUERY_INFORMATION,
False,
Pid
);
if Process = 0 then
begin
ErrStr('OpenProcess');
Exit;
end;
//metto in pRTLProcParams l'indirizzo
//del RTL_PROCESS_PARAMETERS
if not GetRTLProcessParametersPtr(
Process,
pRTLProcParams
) then
begin
Exit;
end;
case infoType of
0: offset := 36; //CurrentDirectory
1: offset := 48; //DLLPath
2: offset := 56; //ImagePathName
3: offset := 64; //CommandLine
4: offset := 112; //WindowTitle
5: offset := 120; //DesktopInfo
6: offset := 128; //ShellInfo
7: offset := 136; //RuntimeData
end;
if not GetUnicodeStringRemoteProcess(
Process,
pRTLProcParams + offset,
Info
) then
begin
Exit;
end;
CloseHandle(Process);
Result := True;
end;
7. Elenco dei moduli mappati con elenco delle informazioni relative ad ognuno Siamo ora arrivati alla stesura di un pò di codice relativo alle strutture PEB_LDR_DATA e LDR_MODULE. Di seguito una funzione che fa appunto tutto questo:
//informazioni che vogliamo raccogliere
//relativamente ad ogni modulo mappato
type
ModuleInfo = record
BaseAddress: Cardinal;
EntryPoint: Cardinal;
SizeOfImage: Cardinal;
NomeDll: string;
NomeDllCompleto: string;
LoadCount: SmallInt;
TlsIndex: SmallInt;
end;
//funzione di callback
TElencoModuliCallBackFunc = procedure(var info: ModuleInfo);
...
...
(*
ListType: tipo di ordinamento richiesto
0: InLoadOrderModuleList
1: InMemoryOrderModuleList
2: InInitializationOrderModuleList
*)
function ElencoModuli(
Pid: Cardinal;
ListType: Byte;
cbFunc: TElencoModuliCallBackFunc //funzione di callback
): Boolean;
var
pPeb_ldr_data: Cardinal;
LdrData: PEB_LDR_DATA;
LdrModule: LDR_MODULE;
dwSize: Cardinal;
readAddr, readAddrHead: Pointer;
loadCount: SHORT;
DllName: PWideChar;
cnt: Cardinal;
Offset: Byte;
hProcess: Cardinal;
mInfo: ModuleInfo;
begin
result := False;
ModificaPrivilegio('SeDebugPrivilege', TRUE);
hProcess := OpenProcessEx(
PROCESS_VM_READ or
PROCESS_QUERY_INFORMATION,
False,
Pid
);
if hProcess = 0 then
begin
ErrStr('OpenProcess');
Exit;
end;
Offset := 8 * ListType;
GetPebLdrData(hProcess, pPeb_ldr_data);
// Now read the PEB_LDR_DATA structure
if not ReadProcessMemory(
hProcess,
Pointer(pPeb_ldr_data),
@LdrData,
sizeof(LdrData),
dwSize
) then
begin
ErrStr('ReadProcessMemory');
Exit;
end;
readAddrHead := Pointer(pPeb_ldr_data + 12 + Offset);
case ListType of
0: readAddr := Pointer(LdrData.InLoadOrderModuleList.Flink);
1: readAddr := Pointer(LdrData.InMemoryOrderModuleList.Flink);
2: readAddr := Pointer(LdrData.InInitializationOrderModuleList.Flink);
end;
cnt := 0;
repeat
Inc(cnt);
//
readAddr := Pointer(Cardinal(readAddr) - Offset);
//estraggo il modulo
if not ReadProcessMemory(
hProcess,
readAddr,
@LdrModule,
sizeof(LdrModule),
dwSize
) then
begin
ErrStr('ReadProcessMemory');
Exit;
end;
//inizializzo il record che conterrà
//le caratteristiche del Modulo esaminato
mInfo.BaseAddress := 0;
mInfo.EntryPoint := 0;
mInfo.SizeOfImage := 0;
mInfo.NomeDll := '';
mInfo.NomeDllCompleto := '';
mInfo.LoadCount := 0;
mInfo.TlsIndex := 0;
//
//leggo i dati
mInfo.BaseAddress := LdrModule.DllBase;
mInfo.EntryPoint := LdrModule.EntryPoint;
mInfo.SizeOfImage := LdrModule.SizeOfImage;
mInfo.LoadCount := LdrModule.LoadCount;
mInfo.TlsIndex := LdrModule.TlsIndex;
//leggo il nome della DLL
GetMem(DllName, LdrModule.BaseDllName.MaximumLength);
if ReadProcessMemory(
hProcess,
LdrModule.BaseDllName.Buffer,
DllName,
LdrModule.BaseDllName.MaximumLength,
dwSize
) then
begin
mInfo.NomeDll := DllName;
end;
FreeMem(DllName);
//leggo il nome completo della DLL
GetMem(DllName, LdrModule.FullDllName.MaximumLength);
if ReadProcessMemory(
hProcess,
LdrModule.FullDllName.Buffer,
DllName,
LdrModule.FullDllName.MaximumLength,
dwSize
) then
begin
mInfo.NomeDllCompleto := DllName
end;
FreeMem(DllName);
//chiamo la funzione di callback
cbFunc(mInfo);
//* Jump to next LDR_MODULE structure */
case ListType of
0: readAddr := Pointer(LdrModule.InLoadOrderLinks.Flink);
1: readAddr := Pointer(LdrModule.InMemoryOrderLinks.Flink);
2: readAddr := Pointer(LdrModule.InInitializationOrderLinks.Flink);
end;
//
until readAddr = readAddrHead;
CloseHandle(hProcess);
result := True;
end;
8. GetModuleHandle remoto Ebbene si: per finire andiamo ad eseguire GetModuleHandle su un processo remoto, ossia scriviamo una funzione che preleva l'indirizzo di base di caricamento di un modulo nello spazio di memoria di un processo remoto: il risultato sarà appunto l'indirizzo del modulo nello spazio di memoria del processo specificato
function RemoteGetModuleHandle(
hProcess: Cardinal; //handle del processo
nomeModulo: PAnsiChar; //nome del modulo
var BaseAddr: Cardinal //indirizzo di base
): Boolean;
var
pPeb_ldr_data: Cardinal;
LdrData: PEB_LDR_DATA;
LdrModule: LDR_MODULE;
dwSize: Cardinal;
readAddr, readAddrHead: Pointer;
loadCount: SHORT;
BaseDllName: PWideChar;
cnt: Cardinal;
Offset: Byte;
begin
result := False;
if not GetPebLdrData(
hProcess,
pPeb_ldr_data
) then
begin
Exit;
end;
//prelevo PEB_LDR_DATA
if not ReadProcessMemory(
hProcess,
Pointer(pPeb_ldr_data),
@LdrData,
sizeof(LdrData),
dwSize
) then
begin
ErrStr('ReadProcessMemory');
Exit;
end;
readAddrHead := Pointer(pPeb_ldr_data + 12);
readAddr := Pointer(LdrData.InLoadOrderModuleList.Flink);
cnt := 0;
repeat
Inc(cnt);
//
readAddr := Pointer(Cardinal(readAddr));
//estraggo il modulo
if not ReadProcessMemory(
hProcess,
readAddr,
@LdrModule,
sizeof(LdrModule),
dwSize
) then
begin
ErrStr('ReadProcessMemory');
Exit;
end;
//leggo il nome della DLL
GetMem(BaseDllName, LdrModule.BaseDllName.MaximumLength);
if ReadProcessMemory(
hProcess,
LdrModule.BaseDllName.Buffer,
BaseDllName,
LdrModule.BaseDllName.MaximumLength,
dwSize
) then
begin
if lstrcmpiA(
nomeModulo,
PAnsiChar(WideCharToString(BaseDllName))
) = 0 then
begin
BaseAddr := LdrModule.DllBase;
Result := True;
Break;
end;
end;
FreeMem(BaseDllName);
//* passo al successivo LDR_MODULE */
readAddr := Pointer(LdrModule.InLoadOrderLinks.Flink);
//
until readAddr = readAddrHead;
end;
chiaramente la funzione è il risultato della semplice modifica della funzione vista al paragrafo precedente: per ogni modulo incontrato si va a confrontarne il nome col nome desiderato ed in caso di match (abbiamo trovato il modulo desiderato) si va a salvare il BaseAddress e si esce dal loop; chiaramente non abbiamo bisogno di gestire tutte e 3 le liste linkate, così facciamo riferimento alla prima semplificando così le operazioni. Behh, un esempio in allegato è quello che ci vuole InfoProc.7z
|