Home | Chi sono | Contattami
 

Progr. lineare

Delphi
 
Componenti
  Database
 
Miei articoli

Windows

Miei articoli 

 

Code Injection


In questo articolo viene affrontata la tecnica di "code injection": questa tecnica è una estensione della nota tecnica di "dll injection"; l'analisi che verrà effettuata nei paragrafi successivi, parte direttamente dall'implementazione della "dll injection", soffermandosi sulle caratteristiche e sui limiti per poi procedere per gradi a tutti i miglioramenti applicabili. A conclusione di ciò, avremo una procedura per mappare una dll nello spazio di memoria di un processo remoto (che chiamaeremo RemoteLoadLibrary) ed una procedura per eseguire l'unloading di una dll dallo spazio di memoria di un processo remoto (che chiameremo RemoteFreeLibrary).

1. Introduzione

La tecnica di dll injection consiste nel caricare una dll nello spazio di memoria di un processo remoto, estendendo di fatto la funzionalità dell'API win32 che effettua il mapping limitatamente allo spazio di memoria del processo chiamante. Il fulcro dell'implementazione è l'API win32 CreateRemoteThread (implementata in kernel32.dll) che crea e manda in esecuzione un thread nello spazio di memoria di un processo remoto. I parametri più importante di questa funzione sono i seguenti:

1) hProcess: l'handle del processo remoto

2) lpStartAddress: l'indirizzo della funzione che verrà eseguita dal thread (indirizzo nello spazio di memoria del processo remoto): funzione con un unico parametro di tipo puntatore che rappresenta l'indirizzo dei dati da passare alla funzione medesima

3) lpParameter: l'indirizzo dei dati da passare alla funzione puntata da lpStartAddress (indirizzo nello spazio di memoria del processo remoto);  è quindi il valore del parametro della funzione puntata da lpStartAddress

Nella tecnica di dll injection il valore di lpStartAddress viene settato pari all'indirizzo di LoadLibrary (identico in tutti i processi); l'argomento è il nome della dll: tale nome viene copiato nello spazio di memoria del processo remoto tramite le API win32 VirtualAllocEx (allocazione di memoria nello spazio di memoria di un processo remoto) e WriteProcessMemory (copia una sequenza di byte da locale in remoto); lpParameter è l'indirizzo di base della regione di memoria allocata e scritta (è l'output della VirtualAllocEx).

La tecnica di "code injection" consiste nell'eseguire del codice generico nello spazio di memoria di un processo remoto. In pratica il parametro lpStartAddress rappresenta qualsiasi funzione che sia presente nello spazio di memoria del processo remoto (non solo la LoadLibraryA). Nei paragrafi successivi realizzeremo come esempio una funzione il cui obiettivo è caricare una dll in memoria (prima verifica tramite GetModuleHandleA se il modulo è già caricato ed in caso negativo chiama LoadLibraryA, il tutto correlato dalla dovuta gestione degli errori e l'utilizzo dell'API win32 GetLastError) e vedremo come andare ad eseguire tale funzione nello spazio di memoria di un processo remoto. E' da osservare una certa ambiguità nell'utilizzo comune dei termini "dll injection" e "code injection" intendendosi spesso per "dll injection" il mapping di una dll nello spazio di memoria di un processo remoto indipendentemente dal tipo di implementazione. Nel seguito si utilizzerà il termine "code injection" per esprimere l'esecuzione di una qualsiasi funzione nello spazio di memoria di un processo remoto. Senza perdita di generalità prenderemo in considerazione come gia detto una funzione che si occupa di mappare una dll nello spazio di memoria di un processo remoto (con i dovuti controlli e la dovuta gestione degli errori).

2. Passo dopo passo

Come prima cosa definiamo una procedura per loggare gli errori relativi alle api win32

function ErrStr(nomeFunc: string): Boolean; //funzione che crea una stringa composta da codice errore e descrizione //relativamente all'ultimo errore che si è verificato nel thread chiamante; //il parametro nomeFunc è il nome dell' API win32 che è fallita ed ha quindi //restituito l'errore var error_code: Cardinal; error_description: string; error_string: string; begin result := False; error_code := GetLastError; error_description := SysErrorMessage(error_code); error_string := nomeFunc + ': ' + 'CODErr=' + IntToStr(error_code) + ' Descr=' + error_description; //qui può essere inserito del codice per scrivere la stringa su un file //oppure visualizzarla a video, etc... //ScriviSuFile('c:\Logger.txt', error_string); result := True; end;

Bene! Come già detto in precedenza per "Code injection" si intende l'esecuzione di una procedura nello spazio di memoria di un processo remoto. Partiremo da una procedura di esempio ed analizzeremo i vari passaggi da effettuare per tradurla in una procedura eseguibile da remoto.

2.1 Procedura

function XLoadLibrary(NomeDll: PAnsiChar; var addr: Cardinal): Boolean; var error_code: Cardinal; begin Result := False; //se il nome della dll non è specificato, esco if NomeDll = nil then Exit; //determino l'indirizzo di base del //modulo specificato; se addr <> 0 vuol dire che il modulo //è già caricato in memoria quindi esco //e addr contiene l'indirizzo di base della dll addr := GetModuleHandleA(NomeDll); if addr <> 0 then begin Result := True; Exit; end else //viene restituito 0, ma devo in ogni caso //verificare che il codice di errore //sia quello corrispondente alla situazione //"modulo non presente" ossia 126 begin error_code := GetLastError; if error_code <> 126 then begin Exit; end; end; //caricola dll in memoria addr := LoadLibraryA(NomeDll); if addr = 0 then begin error_code := GetLastError; end; Result := True; end;

La procedura sopra è un involucro alla LoadLibraryA: in pratica se il modulo che si vuol caricare è già presente in memoria (la verifica viene fatta tramite la GetModuleHandleA), il parametro addr riceve il valore dell'indirizzo di base attuale, altrimenti esegue la LoadLibraryA; il tutto correlato con la dovuta gestione degli errori.

2.2 Procedure di supporto

La prima cosa da fare è definire delle procedure che vadano a definire il blocchi fondamentali per la realizzazione del "code injection"

2.2.1 InjectData

Questa funzione si occupa di allocare una regione di memoria nello spazio di memoria di un processo remoto e copiarvi una sequenza di byte.

function InjectData(hProcess: Cardinal; localData: Pointer; DataSize: Cardinal; Esecuzione: Boolean; var remoteData: Pointer): Boolean; var BytesWritten: Cardinal; protezione: Cardinal; begin Result := False; //controllo la validità dei valori dei parametri if ((hProcess = 0) or (localData = nil) or (DataSize = 0)) then Exit; try if Esecuzione then protezione := PAGE_EXECUTE_READWRITE else protezione := PAGE_READWRITE; remoteData := VirtualAllocEx( hProcess, nil, DataSize, MEM_COMMIT, protezione); if remoteData = nil then begin ErrStr('VirtualAllocEx'); Exit; end; if not WriteProcessMemory(hProcess, remoteData, localData, DataSize, BytesWritten) then begin ErrStr('WriteProcessMemory'); Exit; end; Result := True; finally if not result then begin if remoteData <> nil then begin if VirtualFreeEx(hProcess, remoteData, 0, MEM_RELEASE) = nil then begin ErrStr('VirtualFreeEx'); end; end; end; end; end;

Analizziamo nel dettaglio i parametri:

  • hProcess:[input] handle del processo remoto; nel contesto della tecnica di code injection l'handle deve avere i seguenti diritti di accesso al processo in questione: PROCESS_CREATE_THREAD + PROCESS_QUERY_INFORMATION + PROCESS_VM_OPERATION + PROCESS_VM_WRITE + PROCESS_VM_READ
  • localData:[input] puntatore alla sequenza di byte in locale che devo copiare in remoto
  • DataSize:[input] dimensione in byte della sequenza di byte in locale che devo copiare in remoto (dimensione dell'informazione puntata da localData)
  • Esecuzione:[input] boolean che indica se la sequenza di byte da copiare in remoto, rappresenta codice da eseguire; in caso positivo l'API VirtualAllocEx verrà chiamata con livello di protezione (valore dell'ultimo parametro) pari a PAGE_EXECUTE_READWRITE; in caso contrario il livello di protezione sarà PAGE_READWRITE
  • remoteData:[ouptut] indirizzo, nello spazio di memoria del processo remoto, della sequenza di byte che è stata copiata

2.2.2 UnloadData

Questa funzione ha il compito di deallocare la memoria precedentemente allocata tramite la InjectData

function UnloadData(hProcess: Cardinal; remoteData: Pointer): Boolean; begin Result := False; //controllo la validità dei valori dei parametri if (hProcess = 0) or (remoteData = nil) then Exit; try if VirtualFreeEx(hProcess, remoteData, 0, MEM_RELEASE) = nil then begin ErrStr('VirtualFreeEx'); Exit; end; Result := True; finally end; end;

Analizziamo i singoli parametri:

  • hProcess:[input] handle al processo remoto
  • remoteData:[input] indirizzo di base della regione di memoria da deallocare (valore precedentemente ottenuto tramite chiamata alla InjectData)

2.2.3 Come creare la funzione da copiare ed eseguire in remoto

Il nostro obiettivo (a titolo esemplificativo) è eseguire la procedura al paragrafo 2 nell'ambito di un thread creato nel contesto del processo remoto. Ci sono vari accorgimenti; il primo e più importante è la sostituzione delle chiamate alle varie API win32 con dei puntatori che hanno come valore l'indirizzo delle funzioni corrispondenti. Facendo riferimento sempre alla funzione definita nel paragrafo 2 abbiamo le seguenti API win32

  • GetModuleHandleA
  • LoadLibraryA
  • GetLastError

Bisogna definire una variabile di tipo puntatore a funzione per ognuno delle suddette funzioni ed assegnare ad ognuna di esse l'indirizzo corrispondente, nella seguente maniera

var //puntatore a GetModuleHandleA pGetModuleHandleA: function(lpModuleName: PAnsiChar): Cardinal; stdcall; //puntatore a LoadLibraryA pLoadLibraryA: function(pLibFileName: PAnsiChar): Cardinal; stdcall; //puntatore a GetLastError pGetLastError: function(): Cardinal; stdcall; ... ... ... begin ... ... pGetModuleHandleA := GetProcAddress(GetModuleHandle('kernel32'), 'GetModuleHandleA'); pLoadLibraryA := GetProcAddress(GetModuleHandle('kernel32'), 'LoadLibraryA'); pGetLastError := GetProcAddress(GetModuleHandle('kernel32'), 'GetLastError');

Una cosa importante da osservare (a titolo informativo) è che le 3 funzioni in questione sono implementate in kernel32.dll; kernel32.dll viene sempre caricata al medesimo indirizzo di base (valore restituito da GetModuleHandle) nello spazio di memoria di qualsiasi processo e di conseguenza anche l'indirizzo di tutte le funzioni in essa implementate (valore restituito dalla GetProcAddress) sarà sempre lo stesso in tutti i processi; di conseguenza tali indirizzi possono essere calcolati una volta per tutte ed assegnati direttamente senza stare tutte le volte a ricalcolarli tramite GetModuleHandle e GetProcAddress. Più precisamente si ha

Indirizzi di base dll:

  • Kernel32.dll: $7C800000
  • Ntdll.dll: $7C910000

Offset API nelle dll corrispondenti:

  • GetModuleHandleA (Kernel32.dll): $0000B6A1
  • LoadLibraryA (Kernel32.dll): $00001D77
  • GetProcAddress (Kernel32.dll): $0000ADA0
  • FreeLibrary (Kernel32.dll): $0000ABDE
  • GetLastError (Kernel32.dll) forward --> RtlGetLastWin32Error (Ntdll.dll): $00010331

Quindi:

Indirizzi delle API nello spazio di memoria di un processo: (indirizzo di base dll) + (offset nella dll)

  • GetModuleHandleA = $7C800000 + $0000B6A1 = $7C80B6A1
  • LoadLibraryA = $7C800000 + $00001D77 = $7C801D77
  • GetProcAddress = $7C800000 + $0000ADA0 = $7C80ADA0
  • FreeLibrary = $7C800000 + $0000ABDE = $7C80ABDE
  • GetLastError = RtlGetLastWin32Error = $7C910000 + $00010331 = $7C920331

Chiusa questa breve parentesi, torniamo all'obiettivo che ci siamo posti ossia tradurre la procedura al paragrafo 2 in una versione eseguibile da remoto. Vediamo direttamente il codice che andremo poi ad analizzare

type TRemoteLoadLibraryData = record errorcod: Cardinal; //codice di errore restituito dalla GetLastError output: Cardinal; //risultato significativo nella funzione eseguita dal thread //qui iniziano i campi specifici relativi alla nostra operazione //prima di tutto gli indirizzi delle API usate pExitThread: procedure (dwExitCode: Cardinal); stdcall; //API obbligatoria pGetLastError: function(): Cardinal; stdcall; pGetModuleHandleA: function(lpModuleName: PAnsiChar): Cardinal; stdcall; pLoadLibraryA: function(pLibFileName: PAnsiChar): Cardinal; stdcall; //ora mettiamo i puntatori ai parametri //(che copiamo e scriviamo tramite InjectData) pNomeDll: Pointer; end; ... ... procedure RemoteLoadLibraryThread(lpParameter: pointer); stdcall; begin with TRemoteLoadLibraryData(lpParameter^) do begin if pNomeDll = nil then pExitThread(0); //determino l'indirizzo di base del modulo specificato; //se output <> 0 vuol dire che il modulo è già caricato //in memoria quindi esco e output contiene //l'indirizzo di base della dll output := pGetModuleHandleA(pNomeDll); if output <> 0 then begin pExitThread(1); end else //viene restituito 0, ma devo in ogni caso //verificare che il codice di errore //sia quello corrispondente alla situazione //"modulo non presente" ossia 126 begin errorcod := pGetLastError; if errorcod <> 126 then begin pExitThread(0); end; end; //carico la dll in memoria output := pLoadLibraryA(pNomeDll); if output = 0 then begin errorcod := pGetLastError; pExitThread(0); end; end; end;

RemoteLoadLibraryThread è la funzione che verrà eseguita in remoto mentre TRemoteLoadLibraryData è il tipo del parametro della funzione stessa. La funzione RemoteLoadLibraryThread ha come parametro il puntatore lpParameter: il valore di lpParameter sarà l'indirizzo, nello spazio di memoria del processo remoto, di un oggetto di tipo TRemoteLoadLibraryData. Il tutto nel rispetto della definizione dell'API win32 CreateRemoteThread. Si perviene alla seguente sequenza di passaggi per realizzare il "code injection"

1) copio la funzione RemoteLoadLibraryThread nello spazio di memoria del processo remoto tramite la InjectData: sia pRemoteLoadLibraryThread l'indirizzo della funzione nello spazio di memoria del processo remoto

2) copio un oggetto di tipo TRemoteLoadLibraryData nello spazio di memoria del processo remoto tramite la InjectData: sia pRemoteLoadLibraryData l'indirizzo dell'oggetto nello spazio di memoria del proceso remoto

3) eseguo l'API win32 CreateRemoteThread con i seguenti valori:

  • lpStartAddress (indirizzo della funzione che verrà eseguita dal thread che vado a creare) =  pRemoteLoadLibraryThread
  • lpParameter (indirizzo del parametro che verrà preso in input dalla funzione eseguita dal thread che vado a creare) = pRemoteLoadLibraryData

Come determinare la dimensione dei 2 suddetti valori che mi accingo a copiare nello spazio di memoria del processo remoto? Tale dimensione è infatti uno dei parametri di input della InjectData. Nel caso dell' oggetto di tipo TRemoteLoadLibrary, la dimensione si ottiene tramite la nota funzione SizeOf: dimensione=SizeOf(TRemoteLoadLibraryData). Nel caso della funzione (RemoteLoadLibraryThread) si possono seguire 2 strade:

Dimensione di una funzione

1) Definisco una funzione dummy subito dopo la definizione della funzione di cui desidero sapere la dimensione: tale funzione dummy non fa nulla ed ha la seguente definizione

procedure Dummy; begin end;

In pratica scriverò:

function MyFunction(//parametri... //implementazione ); procedure Dummy; begin end;

la dimensione di MyFunction sarà data da

@Dummy - @MyFunction

2) Ogni funzione termina con l'istruzione assembly "ret" la cui rappresentazione binaria (in esadecimale) è data dal byte $C3. Si può quindi partire dall'inizio della funzione (l'indirizzo della funzione ossia @nomefunzione) e scorrere tutti i byte fino a trovare il valore $C3

function SizeOfProc(pAddr: pointer): Cardinal; var dwSize: Cardinal; begin dwSize := 0; repeat inc(dwSize); until PByte(Cardinal(pAddr)+dwSize-1)^ = $C3; Result := dwSize; end;

2.2.4 InjectThread

A questo punto andiamo a definire una funzione che incapsula la chiamata all'API win32 CreateRemoteThread. Prima di mostrare il codice ed analizzarlo, occorre fare una breve osservazione riguardo al record TRemoteLoadLibraryData definito al paragrafo 2.3.3: i primi 2 campi hanno il seguente significato

  • errorcod: è il codice dell'eventuale errore generato all'interno della RemoteLoadLibraryThread (codice di errore che può essere sia il valore restituito dalla GetLastError come conseguenza del fallimento di una delle API win32, sia naturalmente un qualsiasi altro valore che rappresenti un determinato tipo di errore nel contesto del programma che andiamo a sviluppare)
  • output: rappresenta un valore significativo nell'ambito della RemoteLoadLibraryThread: ad esempio nel caso specifico di cui sopra, esso riceve il valore dell'indirizzo base di caricamento della dll (e quindi l'output di GetModuleHandleA o di LoadLibraryA)

Nel seguito si useranno sempre questi 2 campi come i primi 2 campi del record che andremo a definire come parametro della funzione da eseguire in remoto (qualsiasi sia la funzione da eseguire in remoto). Detto questo andiamo a definire la funzione InjectThread che si occupa di incapsulare la chiamata alla CreateRemoteThread con l'adeguata gestione degli errori e dei valori di output (ivi compresi i valori dei primi 2 campi del record)

function InjectThread(hProcess: Cardinal; remoteFunction: Pointer; remoteParameter: Pointer; var outputValue: Cardinal; var errorCode: Cardinal; Synch: Boolean): Boolean; var hThread: Cardinal; TID: Cardinal; lpExitCode: Cardinal; bytesRead: Cardinal; begin //N.B. i parametri outputValue e errorCode hanno senso solo se Synch=True Result := False; //controllo la valdità dei valori dei parametri if ((hProcess = 0) or (remoteFunction = nil) or (remoteParameter = nil)) then Exit; try hThread := CreateRemoteThread(hProcess, nil, 0, remoteFunction, remoteParameter, 0, TID); if hThread = 0 then begin ErrStr('CreateRemoteThread'); Exit; end; //mi metto in attesa della terminazione del thread if Synch then begin case WaitForSingleObject(hThread, INFINITE) of WAIT_FAILED: begin ErrStr('WaitForSingleObject'); Exit; end; end; if not GetExitCodeThread(hThread, lpExitCode) then begin ErrStr('GetExitCodeThread'); Exit; end; //c'è stato un errore (lpExitCode è l'output //della funzione eseguita dal thread remoto //che nel nostro caso è RemoteLoadLibraryThread; //più precisamente è l'argomento //della ExitThread) if lpExitCode = 0 then //ExitThread(0) begin //leggo il codice dell' errore //che si è verificato nel thread remoto //nel nostro caso TRemoteLoadLibraryData.errorcod if not ReadProcessMemory(hProcess, remoteParameter, @errorcode, 4, bytesRead) then begin ErrStr('ReadProcessMemory'); Exit; end; end else begin //leggo il valore significativo generato nel thread remoto //nel nostro caso TRemoteLoadLibraryData.output if not ReadProcessMemory(hProcess, Pointer(Cardinal(remoteParameter)+4), @outputvalue, 4, bytesRead) then begin ErrStr('ReadProcessMemory'); Exit; end; end; end; Result := True; finally if hThread <> 0 then begin if not CloseHandle(hThread) then begin ErrStr('CloseHandle'); end; end; end; end;

Analizziamo nel dettaglio i singoli parametri

  • hProcess:[input] handle al processo remoto
  • remoteFunction:[input] indirizzo della funzione (nel nostro caso la RemoteLoadLibraryThread) nello spazio di memoria del processo remoto (risultato della InjectData); è il valore del parametro lpStartaddress della CreateRemoteThread
  • remoteParameter:[input] indirizzo del parametro della funzione da eseguire in remoto (nel nostro caso è l'indirizzo di un oggetto di tipo TRemoteLoadLibrary) (risultato anch'esso della InjectData); è il valore del parametro lpParameter della CreateRemoteThread
  • outputvalue:[output] valore del campo output del record da usare come parametro della funzione da eseguire in remoto (nel nostro caso TRemoteLoadLibrary.output)
  • errorcode:[output] valore del campo errorcod del record da usare come parametro della funzione da eseguire in remoto (nel nostro caso TRemoteLoadLibrary.errorcod)
  • Synch:[input] indica se l'esecuzione del thread deve essere sincrona (ossia ci si deve mettere in attesa della sua terminazione tramite chiamata alla WaitForSingleObject sull'handle del thread creato dalla CreateRemoteThread)

A questo punto dobbiamo definire 2 funzioni, una che si occupa di inizializzare i campi del record, allocare regioni di memoria nello spazio di memoria del processo remoto e copiarvi dati da locale, l'altra che si occupa della deallocazione complessiva. Facendo sempre riferimento alla procedura al paragrafo 2 avremo:

var hProcess: Cardinal; //handle al processo remoto RemoteLoadLibraryData: TRemoteLoadLibraryData; //record da usare come parametro pRemoteLoadLibraryData: Pointer; //indirizzo del record nello spazio di memoria //del processo remoto pRemoteLoadLibraryThread: Pointer; //indirizzo della funzione del thread //nello spazio di memoria del processo remoto ... ... ... //dealloco la memoria in remoto function RemoteLoadLibraryUnloadData(): Boolean; begin Result := False; with RemoteLoadLibraryData do begin UnloadData(hProcess, pNomeDll); end; //deallocazione spazio per il parametro UnloadData(hProcess, pRemoteLoadLibraryData); //deallocazione spazio per la funzione UnloadData(hProcess, pRemoteLoadLibraryThread); Result := True; end; //definisco i valori dei campi del record e copio i dati in remoto function RemoteLoadLibraryInjectData(): Boolean; begin Result := False; try //inizializzazione valori campi: with RemoteLoadLibraryData do begin errorcod := 0; output := 0; pGetLastError := GetProcAddress(GetModuleHandle('kernel32'), 'GetLastError'); pGetModuleHandleA := GetProcAddress(GetModuleHandle('kernel32'), 'GetModuleHandleA'); pLoadLibraryA := GetProcAddress(GetModuleHandle('kernel32'), 'LoadLibraryA'); if not InjectData(hProcess, NomeDll, Length(NomeDll), False, RemoteLoadLibraryData.pNomeDll) then begin Exit; end; end; //copio il parametro parameterSize := SizeOf(RemoteLoadLibraryData); if not InjectData(hProcess, @RemoteLoadLibraryData, parameterSize, False, pRemoteLoadLibraryData) then begin Exit; end; //copio la funzione functionSize := SizeOfProc(@RemoteLoadLibraryThread); if not InjectData(hProcess, @RemoteLoadLibraryThread, functionSize, True, pRemoteLoadLibraryThread) then begin Exit; end; Result := True; finally if not Result then begin RemoteLoadLibraryUnloadData; end; end; end;

Dal sorgente si può vedere che le chiamate ad InjectData sono racchiuse in blocco try finally: in caso di insuccesso della InjectData il flusso del programma viene rediretto al blocco finally dove lo aspetta la UnloadData; tutte le operazioni di InjectData devono terminare con successo altrimenti viene fatto un rollback totale ripristinando la memoria del processo remoto alla situazione iniziale.

3 Approccio generale

Di seguito andremo a definire la versione definitiva della RemoteLoadLibrary; successivamente definiremo lo pseudocodice della procedura da utilizzare come scheletro di base tutte le volte che si vuole eseguire codice nello spazio di memoria di un processo remoto. Per finire daremo un ulteriore esempio di "code injection" implementando l'unloading di una dll nello spazio di memoria di un processo remoto. Per prima cosa però è opportuno definire una funzione che faccia da wrapper all'api win32 OpenProcess e che chiameremo OpenProcessEx.

3.1 OpenProcessEx

Per ottenere un handle a determinati processi tipo ad esempio winlogon.exe, è necessario avere abilitato il Privilegio di Debug. La funzione OpenProcessEx che vado a presentare, prova ad ottenere un handle al processo remoto e, in caso di insuccesso tenta l'abilitazione del Privilegio di Debug e, in caso di successo, chiama nuovamente la OpenProcess.

function ModificaPrivilegio(szPrivilege: pChar; fEnable: Boolean): Boolean; var NewState: TTokenPrivileges; luid: TLargeInteger; hToken: Cardinal; ReturnLength: Cardinal; begin Result := False; hToken := 0; try if not OpenThreadToken( GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, False, hToken) then begin if GetLastError = ERROR_NO_TOKEN then begin //non ho ottenuto il token asociato al thread //e allora provo col processo if not OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, hToken) then begin ErrStr('OpenProcessExToken'); Exit; end; end else begin ErrStr('OpenThreadToken'); Exit; end; end; //ricavo il LUID (Locally Unique Identifier) corrispondente al privilegio //specificato: si tratta in sostanza di un identificativo univoco del privilegio; //varia da sessione a sessione ed anche tra un riavvio e l' altro del sistema if not LookupPrivilegeValue(nil, szPrivilege, luid) then begin ErrStr('LookupPrivilegeValue'); Exit; end; //lavoro su NewState (di tipo TTokenPrivileges). Rappresenta un elenco di privilegi; //nel caso specifico conterrà un solo privilegio (ProvilegeCount = 1). L' arrary //Privileges contiene oggetti con 2 campi: il luid del privilegio (Luid) ed //il livello di abilitazione del medesimo (Attributes) NewState.PrivilegeCount := 1; NewState.Privileges[0].Luid := luid; if fEnable then //abilitiamo il privilegio NewState.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED else //disabilitiamo il privilegio NewState.Privileges[0].Attributes := 0; //eseguiamo la modifica sullo stato di abilitazione del privilegio //nel contesto del token di accesso aperto if not AdjustTokenPrivileges( hToken, FALSE, NewState, sizeof(NewState), nil, ReturnLength) then begin ErrStr('AdjustTokenPrivileges'); Exit; end; Result := True; finally //chiudo l' handle al token di accesso aperto if hToken <> 0 then begin if not CloseHandle(hToken) then begin ErrStr('CloseHandle'); end; end; end; end; function OpenProcessEx(dwDesiredAccess: Cardinal; bInheritableHandle: LongBool; dwProcessId: Cardinal): Cardinal; var hProcess: Cardinal; begin hProcess := OpenProcess(dwDesiredAccess, bInheritableHandle, dwProcessId); //provo ad ottenere un handle al processo if hProcess = 0 then //se non ci riesco provo ad assegnare il privilegio di debug begin if ModificaPrivilegio('SeDebugPrivilege', True) then begin hProcess := OpenProcess(dwDesiredAccess, bInheritableHandle, dwProcessId); end; end; result := hProcess; end;

3.1 RemoteLodLibrary

Si perviene alla seguente funzione mirata all'esecuzione della LoadLibraryA nello spazio di memoria del processo remoto

function RemoteLoadLibrary(PID: Cardinal; NomeDll: PAnsiChar; var addr: Cardinal; var lastError: Cardinal; synch: Boolean): Boolean; type TRemoteLoadLibraryData = record errorcod: Cardinal; //codice di errore restituito dalla GetLastError output: Cardinal; //risultato significativo nella funzione eseguita dal thread //qui iniziano i campi specifici relativi alla nostra operazione //prima di tutto gli indirizzi delle API usate pExitThread: procedure (dwExitCode: Cardinal); stdcall; //API obbligatoria pGetLastError: function(): Cardinal; stdcall; pGetModuleHandleA: function(lpModuleName: PAnsiChar): Cardinal; stdcall; pLoadLibraryA: function(pLibFileName: PAnsiChar): Cardinal; stdcall; //ora mettiamo i puntatori ai parametri //(che copiamo e scriviamo tramite InjectData) pNomeDll: Pointer; end; var hProcess: Cardinal; RemoteLoadLibraryData: TRemoteLoadLibraryData; //indirizzo del record nello spazio di memoria del processo remoto pRemoteLoadLibraryData: Pointer; //indirizzo della funzione del thread nello spazio //di memoria del processo remoto pRemoteLoadLibraryThread: Pointer; output_value: Cardinal; error_code: Cardinal; functionSize, parameterSize: Cardinal; procedure RemoteLoadLibraryThread(lpParameter: pointer); stdcall; begin with TRemoteLoadLibraryData(lpParameter^) do begin if pNomeDll = nil then pExitThread(0); //determino l'indirizzo di base del modulo specificato; //se output <> 0 vuol dire che il modulo //è già caricato in memoria quindi esco e //output contiene l'indirizzo di base della dll output := pGetModuleHandleA(pNomeDll); if output <> 0 then begin pExitThread(1); end else //viene restituito 0, ma devo in ogni caso //verificare che il codice di errore //sia quello corrispondente alla situazione //"modulo non presente" ossia 126 begin errorcod := pGetLastError; if errorcod <> 126 then begin pExitThread(0); end; end; //carico la dll in memoria output := pLoadLibraryA(pNomeDll); if output = 0 then begin errorcod := pGetLastError; pExitThread(0); end; pExitThread(1); end; end; //dealloco la memoria in remoto function RemoteLoadLibraryUnloadData(): Boolean; begin Result := False; with RemoteLoadLibraryData do begin UnloadData(hProcess, pNomeDll); end; //deallocazione spazio per il parametro UnloadData(hProcess, pRemoteLoadLibraryData); //deallocazione spazio per la funzione UnloadData(hProcess, pRemoteLoadLibraryThread); Result := True; end; //definisco i valori dei campi del record e copio i dati in remoto function RemoteLoadLibraryInjectData(): Boolean; begin Result := False; try //inizializzazione valori campi: with RemoteLoadLibraryData do begin errorcod := 0; output := 0; pExitThread := GetProcAddress(GetModuleHandle('kernel32'), 'ExitThread'); pGetLastError := GetProcAddress(GetModuleHandle('kernel32'), 'GetLastError'); pGetModuleHandleA := GetProcAddress(GetModuleHandle('kernel32'), 'GetModuleHandleA'); pLoadLibraryA := GetProcAddress(GetModuleHandle('kernel32'), 'LoadLibraryA'); if not InjectData(hProcess, NomeDll, Length(NomeDll), False, pNomeDll) then begin Exit; end; end; //copio il parametro parameterSize := SizeOf(RemoteLoadLibraryData); if not InjectData(hProcess, @RemoteLoadLibraryData, parameterSize, False, pRemoteLoadLibraryData) then begin Exit; end; //copio la funzione functionSize := SizeOfProc(@RemoteLoadLibraryThread); if not InjectData(hProcess, @RemoteLoadLibraryThread, functionSize, True, pRemoteLoadLibraryThread) then begin Exit; end; Result := True; finally if not Result then begin RemoteLoadLibraryUnloadData; end; end; end; begin Result := False; //controllo la validità dei valori dei parametri: if ((PID = 0) or (NomeDll = nil)) then Exit; //inizializzo a zero le variabili locali hProcess := 0; pRemoteLoadLibraryData := nil; pRemoteLoadLibraryThread := nil; output_value := 0; error_code := 0; try hProcess := OpenProcessEx(PROCESS_CREATE_THREAD + PROCESS_QUERY_INFORMATION + PROCESS_VM_OPERATION + PROCESS_VM_WRITE + PROCESS_VM_READ, False, PID ); if hProcess = 0 then begin ErrStr('OpenProcess'); Exit; end; if not RemoteLoadLibraryInjectData() then Exit; if not InjectThread(hProcess, pRemoteLoadLibraryThread, pRemoteLoadLibraryData, output_value, error_code, synch) then Exit; //output_value e error_code sono stati inizializzati a 0. //sono stati modificati dalla InjectThread solo se synch = true; //se synch=false sono rimasti uguali a zero addr := output_value; lastError := error_code; Result := True; finally if synch or (not Result) then begin RemoteLoadLibraryUnloadData; end; if hProcess <> 0 then begin if not CloseHandle(hProcess) then begin ErrStr('CloseHandle'); end; end; end; end;

3.2 Pseudocodice da usare come scheletro di base per il "code injection"

Sia CodeInjectionFunc il nome della funzione che implementa il "code injection", il seguente pseudocodice definisce l'implementazione di tale procedura

function CodeInjectionFunc( PID: Cardinal; //PID del processo remoto ...; //dichiarazione di tutti i parametri che vogliamo var outValue: Cardinal; //valore importante ottenuto dalla funzione remota var codError: Cardinal; //errore riscontrato dalla funzione remota synch: Boolean): Boolean; //esecuzione sincrona del thread remoto type TCodeInjectionFuncData = record errorcod: Cardinal; //codice di errore rilevato nella funzione remota output: Cardinal; //risultato significativo nella funzione remota //Indirizzi delle API usate: elenco puntatori a funzioni; N.B. rigorosamente stdcall //Es. pLoadLibraryA: function(pLibFileName: PAnsiChar): Cardinal; stdcall; pExitThread: procedure (dwExitCode: Cardinal); stdcall; //API obbligatoria //Puntatori a dati; Es. tutte le stringhe vanno messe qui dentro //Es. pNomeDll: Pointer; end; var hProcess: Cardinal; //handle al processo remoto CodeInjectionFuncData: TCodeInjectionFuncData; //indirizzo del record nello spazio di memoria del processo remoto pCodeInjectionFuncData: Pointer; //indirizzo della funzione del thread nello //spazio di memoria del processo remoto pCodeInjectionFuncThread: Pointer; output_value: Cardinal; error_code: Cardinal; functionSize, parameterSize: Cardinal; procedure CodeInjectionFuncThread(lpParameter: pointer); stdcall; //var //qui posso definire delle variabili locali begin with TCodeInjectionFuncData(lpParameter^) do begin //implementazione: tutte le API vengono chiamate //tramite i puntatori nel record ... ... //N.B. chiamare sempre pExitCodeThread //per definire il codice di uscita del thread //Es. pExitCodeThread(1) significa successo // pExitCodeThread(0) significa fallimento end; end; //dealloco la memoria in remoto function CodeInjectionFuncUnloadData(): Boolean; begin Result := False; with CodeInjectionFuncData do begin //chiamo UnloadData su tutti i puntatori //a dati inclusi nel record //Es. UnloadData(hProcess, pNomeDll); end; //deallocazione spazio per il parametro UnloadData(hProcess, pCodeInjectionFuncData); //deallocazione spazio per la funzione UnloadData(hProcess, pCodeInjectionFuncThread); Result := True; end; //definisco i valori dei campi del record e copio i dati in remoto function CodeInjectionFuncInjectData(): Boolean; begin Result := False; try //inizializzazione valori campi del record: with CodeInjectionFuncData do begin errorcod := 0; output := 0; //assegno i valori ai puntatori //a funzione (tramite GetModuleHandle e GetProcAddres) //Es. pLoadLibraryA := GetProcAddress(GetModuleHandle('kernel32'), 'LoadLibraryA'); pExitThread := GetProcAddress(GetModuleHandle('kernel32'), 'ExitThread'); //API Obbligatoria ... ... //assegno i valori ai puntatori ai dati (tramite InjectData); //N.B. se una InjectData ritorna False allora bisogna chiamare Exit end; //copio il parametro parameterSize := SizeOf(CodeInjectionFuncData); if not InjectData(hProcess, @CodeInjectionFuncData, parameterSize, False, pCodeInjectionFuncData) then begin Exit; end; //copio la funzione functionSize := SizeOfProc(@CodeInjectionFuncThread); if not InjectData(hProcess, @CodeInjectionFuncThread, functionSize, True, pCodeInjectionFuncThread) then begin Exit; end; Result := True; finally if not Result then begin CodeInjectionFuncUnloadData; end; end; end; begin //inizializzo a zero le variabili locali hProcess := 0; pCodeInjectionFuncData := nil; pCodeInjectionFuncThread := nil; output_value := 0; error_code := 0; try hProcess := OpenProcessEx(PROCESS_CREATE_THREAD + PROCESS_QUERY_INFORMATION + PROCESS_VM_OPERATION + PROCESS_VM_WRITE + PROCESS_VM_READ, False, PID ); if hProcess = 0 then begin ErrStr('OpenProcess'); Exit; end; if not CodeInjectionFuncInjectData() then Exit; if not InjectThread(hProcess, pCodeInjectionFuncThread, pCodeInjectionFuncData, output_value, error_code, synch) then Exit; //output_value e error_code sono stati inizializzati a 0. //sono stati modificati dalla InjectThread solo se synch = true; //se synch=false sono rimasti uguali a zero outvalue := output_value; codError := error_code; Result := True; finally if synch or (not Result) then begin CodeInjectionFuncUnloadData; end; if hProcess <> 0 then begin if not CloseHandle(hProcess) then begin ErrStr('CloseHandle'); end; end; end; end;

Si noti anche l'utilizzo di una standardizzazione nella definizione dei nomi delle funzioni, dei tipi e delle variabili. Questo pseudocodice può essere usato come base per qualsiasi implementazione di "code injection" (si può salvare la procedura in un file di testo e poi creare una semplice procedura che sostituisce la stringa "CodeInjectionFunc" con una qualsiasi altra stringa su tutto il testo per ottenere versioni personalizzate) .

3.3 RemoteFreeLibrary

Come ulteriore esempio della tecnica di "code injection" di seguito la funzione per eseguire l'unloading di una dll nello spazio di memoria di un processo remoto.

function RemoteFreeLibrary( PID: Cardinal; //PID del processo remoto NomeDll: PAnsiChar; var outValue: Cardinal; //valore importante ottenuto dalla funzione remota var codError: Cardinal; //errore riscontrato dalla funzione remota synch: Boolean): Boolean; //esecuzione sincrona del thread remoto type TRemoteFreeLibraryData = record errorcod: Cardinal; //codice di errore rilevato nella funzione remota output: Cardinal; //risultato significativo nella funzione remota //Indirizzi delle API usate: elenco puntatori a funzioni; N.B. rigorosamente stdcall //Es. pLoadLibraryA: function(pLibFileName: PAnsiChar): Cardinal; stdcall; pExitThread: procedure (dwExitCode: Cardinal); stdcall; //API obbligatoria pGetLastError: function(): Cardinal; stdcall; pGetModuleHandleA: function(lpModuleName: PAnsiChar): Cardinal; stdcall; pFreeLibrary: function (hLibModule: Cardinal): Cardinal; stdcall; //ora mettiamo i puntatori ai parametri //(che copiamo e scriviamo tramite InjectData) pNomeDll: Pointer; end; var hProcess: Cardinal; //handle al processo remoto RemoteFreeLibraryData: TRemoteFreeLibraryData; //indirizzo del record nello spazio di memoria del processo remoto pRemoteFreeLibraryData: Pointer; //indirizzo della funzione del thread nello //spazio di memoria del processo remoto pRemoteFreeLibraryThread: Pointer; output_value: Cardinal; error_code: Cardinal; functionSize, parameterSize: Cardinal; procedure RemoteFreeLibraryThread(lpParameter: pointer); stdcall; var hDll: Cardinal; begin with TRemoteFreeLibraryData(lpParameter^) do begin if pNomeDll = nil then pExitThread(0); //determino l'indirizzo di base del modulo specificato; //se hDll = 0 vuol dire che il modulo //non è caricato in memoria quindi esco hDll := pGetModuleHandleA(pNomeDll); if hDll = 0 then begin errorcod := pGetLastError; pExitThread(0); end; //libero la dll dalla memoria output := pFreeLibrary(hDll); if output = 0 then begin errorcod := pGetLastError; pExitThread(0); end; pExitThread(1); end; end; //dealloco la memoria in remoto function RemoteFreeLibraryUnloadData(): Boolean; begin Result := False; with RemoteFreeLibraryData do begin //chiamo UnloadData su tutti //i puntatori a dati inclusi nel record UnloadData(hProcess, pNomeDll); end; //deallocazione spazio per il parametro UnloadData(hProcess, pRemoteFreeLibraryData); //deallocazione spazio per la funzione UnloadData(hProcess, pRemoteFreeLibraryThread); Result := True; end; //definisco i valori dei campi del record e copio i dati in remoto function RemoteFreeLibraryInjectData(): Boolean; begin Result := False; try //inizializzazione valori campi del record: with RemoteFreeLibraryData do begin errorcod := 0; output := 0; //assegno i valori ai puntatori //a funzione (tramite GetModuleHandle e GetProcAddres) //Es. pLoadLibraryA := GetProcAddress(GetModuleHandle('kernel32'), 'LoadLibraryA'); pExitThread := GetProcAddress(GetModuleHandle('kernel32'), 'ExitThread'); //API Obbligatoria pGetLastError := GetProcAddress(GetModuleHandle('kernel32'), 'GetLastError'); pGetModuleHandleA := GetProcAddress(GetModuleHandle('kernel32'), 'GetModuleHandleA'); pFreeLibrary := GetProcAddress(GetModuleHandle('kernel32'), 'FreeLibrary'); //assegno i valori ai puntatori ai dati (tramite InjectData); //N.B. se una InjectData ritorna False allora bisogna chiamare Exit if not InjectData(hProcess, NomeDll, Length(NomeDll), False, pNomeDll) then begin Exit; end; end; //copio il parametro parameterSize := SizeOf(RemoteFreeLibraryData); if not InjectData(hProcess, @RemoteFreeLibraryData, parameterSize, False, pRemoteFreeLibraryData) then begin Exit; end; //copio la funzione functionSize := SizeOfProc(@RemoteFreeLibraryThread); if not InjectData(hProcess, @RemoteFreeLibraryThread, functionSize, True, pRemoteFreeLibraryThread) then begin Exit; end; Result := True; finally if not Result then begin RemoteFreeLibraryUnloadData; end; end; end; begin //inizializzo a zero le variabili locali hProcess := 0; pRemoteFreeLibraryData := nil; pRemoteFreeLibraryThread := nil; output_value := 0; error_code := 0; try hProcess := OpenProcessEx(PROCESS_CREATE_THREAD + PROCESS_QUERY_INFORMATION + PROCESS_VM_OPERATION + PROCESS_VM_WRITE + PROCESS_VM_READ, False, PID ); if hProcess = 0 then begin ErrStr('OpenProcessEx'); Exit; end; if not RemoteFreeLibraryInjectData() then Exit; if not InjectThread(hProcess, pRemoteFreeLibraryThread, pRemoteFreeLibraryData, output_value, error_code, synch) then Exit; //output_value e error_code sono stati inizializzati a 0. //sono stati modificati dalla InjectThread solo se synch = true; //se synch=false sono rimasti uguali a zero outvalue := output_value; codError := error_code; Result := True; finally if synch or (not Result) then begin RemoteFreeLibraryUnloadData; end; if hProcess <> 0 then begin if not CloseHandle(hProcess) then begin ErrStr('CloseHandle'); end; end; end; end;

RemoteLoadUnload

  

 
 
Your Ad Here