Home | Chi sono | Contattami
 

Progr. lineare

Delphi
 
Componenti
  Database
 
Miei articoli

Windows

Miei articoli 

 

Dll Injection
 

L' obiettivo di questo articolo presentare la tecnica di dll injection che consente l 'esecuzione del codice del main di una dll nel contesto di un determinato processo. Vediamo per gradi quali sono i passaggi da seguire.

Inanzitutto apriamo il processo destinazione (quello all' interno del quale vogliamo iniettare il nostro codice) tramite l' api OpenProcess definita nel seguente modo 

function OpenProcess(dwDesiredAccess: DWORD; bInheritHandle: BOOL; dwProcessId: DWORD ): THandle; stdcall;

dwDesiredAccess definisce il tipo di accesso al processo: per stare sul sicuro (per il momento, poi vedremo in seguito nei dettagli qual effettivamente il tipo di accesso richiesto dalle operazioni che dovremo andare ad eseguire) assegnamo il massimo ossia PROCESS_ALL_ACCESS (definito nella unit Windows). bInheritHandle sta ad indicare se si vuole o meno (TRUE o FALSE) che l' Handle al processo in questione (ossai il valore restituito da OpenProcess) sia disponibile per il processi figli del processo in questione: in questo contesto non ci interessa per cui lo impostiamo a FALSE.
dwProcessId il PID (identificativo di processo) del processo in questione: per ottenere il PID di un processo dato il nome dell'eseguibile si pu utilizzare la seguente procedura: 

function PidProcesso(NomeProcesso: string): LongWord; var pe: TProcessEntry32; hSnap: THandle; begin hSnap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); pe.dwSize := sizeof(TProcessEntry32); //Prelevo informazioni sul primo processo nello snapshot di sistema Process32First(hSnap, pe); repeat //loop sui processi Result := pe.th32ProcessID; if (pe.szExeFile = NomeProcesso) then begin break; end; until (not (Process32Next(hSnap, pe)) ) ; CloseHandle(hSnap); end;

(N. B. Bisogna includere la unit TlHelp32). Per poter avere un accesso completo al processo destinazione (poterlo aprire con PROCESS_ALL_ACCESS) bene procedere all' abilitazione del privilegio di Debug: per l' abilitazione (ed anche disabilitazione di privilegi) si pu utilizzare la seguente procedura:

procedure ModificaPrivilegio(szPrivilege: pChar; fEnable: Boolean); var NewState: TTokenPrivileges; luid: TLargeInteger; hToken: THandle; ReturnLength: DWord; begin OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, hToken); LookupPrivilegeValue(nil, szPrivilege, luid); NewState.PrivilegeCount := 1; NewState.Privileges[0].Luid := luid; if fEnable then NewState.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED else NewState.Privileges[0].Attributes := 0; AdjustTokenPrivileges( hToken, FALSE, NewState, sizeof(NewState), nil, ReturnLength); CloseHandle(hToken); end;

Per abilitare il privilegio di Debug al processo corrente (ossia il priogramma che stiamo sviluppando) sufficiente chiamare la suddetta procedura in questa maniera

ModificaPrivilegio('SeDebugPrivilege', TRUE);

Bene: a questo punto dobbiamo fare in modo che il processo destinazione (che abbiamo appena aperto con accesso completo) vada a caricare la dll il cui main contiene il codice che vogliamo che venga eseguito nel contesto del processo destinazione; come fare??? Il caricamento della dll avviene tramite l' api LoadLibrary che prende in input appunto il nome della dll in questione: con una chiamata a LoadLibrary('nome.dll') un processo carica nel proprio spazio di memoria la dll 'nome.dll'. Quindi in sostanza dobbiamo fare in modo che il processo destinazione esegua LoadLibrary('nome.dll'). Tutto questo pu essere realizzato tramite l' api CreateRemoteThread che appunto attiva un thread nel contesto di un qualsiasi processo remoto (diverso da quello corrente). CreateRemoteThread definita nel seguente modo 

function CreateRemoteThread(hProcess: THandle; lpThreadAttributes: Pointer; dwStackSize: DWORD; lpStartAddress: TFNThreadStartRoutine; lpParameter: Pointer; dwCreationFlags: DWORD; var lpThreadId: DWORD ): THandle; stdcall;

hProcess l' handle del processo destinazione (ottenuto tramite OpenProcess);
lpThreadAttributes
non ci interessa e lo settiamo a nil;
dwStackSize non ci interessa e viene quindi settato a 0.
lpStartAddress
un puntatore alla procedura che definisce l' operato del thread;
lpParameter
un puntatore all' area di memoria (nel contesto del processo destinazione) che contiene i parametri di input della procedura riferita dal puntatore lpStartAdress.
dwCreationFlags
non ci interessa e lo impostiamo quindi a 0.
lpThreadId
ricever l' identificativo del thread creato.

Concentriamoci sui paramatri lpStartaddress e lpParameter (rispettivamente procedura ed elenco dei paramatri di input della medesima): la procedura a cui punta lpStartAddress potrebbe essere la LoadLibrary (definita nella dll Kernel32.dll) mentre nell' area di memoria puntata da lpParameter potrei inserire la stringa che mi definisce il nome della dll che voglio appunto iniettare nel processo destinazione (ricordiamo infatti LoadLibrary('nome.dll')). Partiamo dall' assegnazione del paramatro lpStartaddress (e poi passeremo a lpParamater): dobbiamo in sostanza passargli l' indirizzo a partire dal quale implementata la funzione LoadLibrary (definita nella dll Kernel32.dll). Se il mio processo ha una dll di nome ad esempio pippo.dll caricata nel proprio spazio di memoria, allora posso ottenere un puntatore alla funzione pluto implementata nella dll in questione tramite la chiamata seguente:

GetProcAddress(GetModuleHandle('pippo.dll'), 'pluto');

Il risultato mi dice in pratica a quale indirizzo di memoria (relativamante allo spazio di memoria utilizzato dal mio processo) inizia l' implementazione della funzione pippo. Bene: se la stessa dll viene caricata da un altro processo nel proprio spazio di memoria non st scritto da nessuna parte che l' indirizzo di memoria a partire dal quale stata caricata sia identico all' indirizzo di memoria a partire dal quale stata caricata nel contesto del mio processo; in sostanza non garantito che una determinata dll venga caricata sempre nello stesso punto nell' ambito dello spazio di memoria di un processo. E di conseguenza l' indirizzo di una funzione implementata nella dll medesima (ottenuto appunto tramite GetProcAddress) pu variare a seconda del processo. Stando a quanto detto quindi, non potrei ottenere l' indirizzo dell' api LoadLibrary tramite una chiamata del tipo

GetProcAddress(GetModuleHandle('kernel32.dll'), 'LoadLibrary');

ed utilizzarlo nel contesto di un altro processo (e quindi in sostanza passarlo come valore del parametro lpStartaddress. Fortunatamente per la dll Kernel32.dll (l' eccezione che conferma la regola) viene caricata sempre allo stesso indirizzo e quindi l' operazione effettivamante possibile: quindi posso passare come valore del parametro lpStartAddress il valore

GetProcAddress(GetModuleHandle('kernel32.dll'), 'LoadLibrary');

Bene, a questo punto il momento del parametro lpParameter: deve essere il valore dell' indirizzo di memoria (nel contesto del processo destinazione) a partire dal quale presente la stringa che mi definisce il nome della dll da caricare. In questo ambito mi vengono in aiuto le api VirtualAllocEx e VirutalAllocFree (per allocare e liberare memoria in un processo diverso da quello corrente) e WriteProcessMemory (per scrivere nello spazio di memoria di un processo diverso da quello corrente); entriamo un p pi nei dettagli

function VirtualAllocEx(hProcess: THandle; lpAddress: Pointer; dwSize, flAllocationType: DWORD; flProtect: DWORD ): Pointer; stdcall;

L' api VirtualAllocEx va ad allocare (o alternativamente a riservare) una regione di memoria nello spazio di memoria di un determinato processo.
hProcess
rappresenta l' handle del processo destinazione (ottenuto tramite OpenProcess);
lpAddress
rappresenta l' indirizzo, nel contesto del processo remoto, a partire dal quale verr allocata la memoria: non ci interessa un indirizzo particolare e quindi lasciamo che sia il sistema ad allocare la memoria dove meglio crede: settiamo quindi a nil il valore del parametro lpAddress.
dwSize
esprime la dimensione della regione di memoria che si vuole allocare: nel nostro caso sar la lunghezza della stringa che mi indica il nome della dll (con eventuale percorso completo) e quindi
Lenght(nome_dll).
flAllocationType
indica il tipo di operazione da effettuare (in quanto la funzione VirtualAllocEx oltre ad effettuare allocazioni di memoria si occupa anche di riservare l' area specificata dai paramateri lpAddress e dwSize): nel nostro contesto, ossia allocazione di memoria, dobbiamo settare il valore a MEM_COMMIT (allocazione di memoria di dimensione pari a dwSize con inizializzazione a 0).
flProtect
indica il tipo di accesso alla memoria allocata: nel nostro caso abbiamo bisogno di accesso in lettura e scrittura e quindi gli diamo il valore PAGE_READWRITE (in altri contesti come ad esempio la copia di un intero eseguibile nell' area allocata ci sarebbe stato bisogno ad esempio acnhe del valore PAGE_EXECUTE). La funzione restituisce un puntatore all 'indirizzo (nel contesto del processo destinazione) a partire dal quale vi la regione di memoria appena allocata. Un valore 0 indica invece un errore. 

function WriteProcessMemory(hProcess: THandle; const lpBaseAddress: Pointer; lpBuffer: Pointer; nSize: DWORD; var lpNumberOfBytesWritten: DWORD ): BOOL; stdcall;

L' api WriteProcessMemory scrive nello spazio di memoria di un dato processo.
hProcess
l' Handle del processo destinazione (ricavabile tramite OpenProcess);
lpAddress
indica l' indirizzo (nel contesto del processo destinazione) a partire dal quale inizia la scrittura: pu essere ad esempio il puntatore restituito da VirtualAllocEx.
lpBuffer
un puntatore (nel contesto del processo corrente) all' informazione che deve essere scritta nel processo destinazione (analogo al parametro dwSize dell' api VirtualAllocEx).
nSize
la dimensione dell' informazione che deve essere scritta.
lpNumberOfBytesWritten
restituisce il numero di byte scritti: lo si pu settare a nil per trascurarlo.

function VirtualFreeEx(hProcess: THandle; lpAddress: Pointer; dwSize, dwFreeType: DWORD ): Pointer; stdcall;

L' api VirtualFreeEx va a deallocare una regione di memoria nello spazio di memoria di un determianto processo (nel nostro caso serve per deallocare la memoria allocata tramite VirtualAllocEx).
hProcess
l' handle del processo destinazione;
lpAddress
rappresenta l' indirizzo di partenza (nel contesto del processo destinazione) della regione di memoria da deallocare (nel nostro caso il valore restituito da VirtualAllocEx);
dwSize
rappresenta la dimensione della regione di memoria da deallocare (quanti byte devono essere deallocati a partire dall' indirizzo specificato da lpAddress).
dwFreeType
indica il tipo di operazione da effettuare sulla regione di memoria identificata da lpAddress e dwSize (stesse considreazioni che abbiamo fatto sul parametro flAllocationType della funzione VirtualAllocEx): il valore da asseganre MEM_RELEASE. Se la funzione ha successo restituisce un valore diverso da 0; 0 altrimenti (in caso di errore).

Facciamo quindi un riassuntino:

1) abilito il privilegio di debug per poter avere accesso completo al processo destinazione

2) Ottengo un handle al processo destinazione trmite OpenProcess

3) Vado ad allocare una regione di memoria nello spazio di memoria del processo destinazione tramite VirtualAllocEx: tale regione deve contenere il nome della dll (con eventuale percorso completo); deve avere quindi una dimensione pari alla lunghezza del nome della dll.

4) Vado a scrivere il nome della dll nello spazio di memoria del processo destinazione a partire dall' indirizzo (relativo allo spazio di memoria del processo destinazione) restituito dalla funzione VirtualAllocEx: per fare questo mi servo della funzione WriteProcessMemory.

5) Creo un thread nel processo destinazione utilizzando la funzione CreateRemoteThread: il Thread consiste nell' esecuzione della funzione LoadLibrary implementata nella dll Kernel32.dll e che si trova sempre nella stessa posizione nell' ambito di tutti i processi grazie al fatto che la kernel32.dll, a differenza del comportamento di tutte le altre dll, viene caricata sempre nello stesso punti in tutti i processi. In pratica come indirizzo del ciodice da eseguire (parametro lpStartAddress) gli passo l' indirizzo della LoadLibrary che vado ad ottenere tramite la seguente chiamata

GetProcAddress(GetModuleHandle('kernel32.dll'), 'LoadLibrary');

Come indirizzo a partire dal quale sono memorizzati i parametri (parametro lpParameter), gli passo il valore restituito dalla funzione VirtualAllocEx.

La CreateRemoteThread restituisce l' identificativo del thread creato nel processo destinazione

A questo punto devo attendere la terminazione del thread appena creato nel processo destinazione: lo posso fare con la seguente chiamata

WaitForSingleObject(Thread, INFINITE);

dove Thread rappresenta appunto l' handle del thread in questione (valore restituito da CreateRemoteThread) ed INFINITE st ad indicare che non vi alcun timeout e quindi si pu passare all' istruzione successiva solo dopo che il thread terminato

Per compeltatre l' opera (una volta concluso il thread creato nel processo remoto) si chiudono gli handle rispettivamente al processo destinazione (ottenuto con OpenProcess) e al thread creato nel processo destinazione (restituito da CreateRemoteThread) passando i medesimi alla funzione CloseHandle

Per finire un approfondimento sul tipo di accesso richiesto sul processo destinazione per effettuare il tutto: si tratta in pratica del valore da assegnare al parametro dwDesiredAccess della funzione OpenProcess (che all' inizio abbiamo impostato pari a PROCESS_ALL_ACCESS)

Bene: (elenchiamo le singole api ed il tipo di accesso minimo richiesto)

VirtualAllocEx: PROCESS_VM_OPERATION

WriteProcessMemory: PROCESS_VM_OPERATION, PROCESS_VM_WRITE

CreateRemoteThread: PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, PROCESS_VM_READ

VirtualFreeEx: PROCESS_VM_OPERATION

Quindi: PROCESS_CREATE_THREAD, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_WRITE, PROCESS_VM_REA

Di seguito il sorgente della procedura di dll injection:

procedure InjectDll(PID: dword; DLL: pChar); var BytesWritten, Process, Thread, ThreadId: dword; Paramaters: pointer; begin ModificaPrivilegio('SeDebugPrivilege', TRUE); Process := OpenProcess(PROCESS_CREATE_THREAD + PROCESS_QUERY_INFORMATION + PROCESS_VM_OPERATION + PROCESS_VM_WRITE + PROCESS_VM_READ, False, PID ); Paramaters := VirtualAllocEx( Process, nil, Length(DLL), MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(Process, Paramaters, Pointer(DLL), Length(DLL), BytesWritten); Thread := CreateRemoteThread(Process, nil, 0, GetProcAddress(GetModuleHandle('KERNEL32.DLL'), 'LoadLibraryA'), Paramaters, 0, ThreadId); WaitForSingleObject(Thread, INFINITE); VirtualFreeEx(Process, Paramaters, 0, MEM_RELEASE); CloseHandle(Thread); CloseHandle(Process); end;

A questo punto non ci resta che creare una dll di esempio per completare l' opera: per rendere l' idea della mappatura della dll nel processo remoto si pu implementare il main della dll tramite una chiamata all' api GetCommandLine e verificare appunto che la linea di comando restuita relativa al processo remoto

library CommandDll; uses Windows, SysUtils, Classes; {$R *.RES} procedure EntryPointProc(reason: integer); var str: pChar; begin case reason of DLL_PROCESS_ATTACH: begin str := GetCommandLine; MessageBox(0, 'Ciao: ora sono un thread interno del processo che hai scelto tu', 'Ciaooo', 0); MessageBox(0, 'Per dimostrartelo ti restituisco la linea di comando', 'Ciaooo', 0); MessageBox(0, str, 'Ciaooo', 0); end; DLL_PROCESS_DETACH: begin end; DLL_THREAD_ATTACH: begin end; DLL_THREAD_DETACH: begin end; end; end; begin DLLProc := @EntryPointProc; EntryPointProc(DLL_PROCESS_ATTACH); end.

Per fare una prova possiamo aprire Notepad ed iniettargli la nostra dll CommandDll.dll

InjectDll(PidProcesso('notepad.exe'), 'CommandDll.dll');

 

 
 
Your Ad Here