|
Nei giorni addietro abbiamo parlato di dll injection e via dicendo; ora però
facciamo i tipi fighi e vediamo se siamo in grado di eseguire codice in un
processo remoto senza ricorrere ad una dll esterna. Bene, bene, direi di passare
direttamente ad un esempio pratico per presentare questa tecnica. Si è visto
come eseguire l' unmapping di una dll dallo spazio di memoria di un processo
remoto, determinandone l' indirizzo base di caricamento tramite le "Tool help
api" ed eseguendo la FreeLibrary nel contesto del processo remoto (tramite
CreateRemoteThread) passandogli come argomento l' indirizzo base precedentemente
ottenuto. Bene, supponiamo di non voler usare le "tool help api": c'è una
alternativa???? Cerrtooo. Anzitutto come dovrei procedere se volessi eliminare
dalla memoria del mio processo una dll caricata precedentemente???
Beehhh
var
Dll_handle: LongWord;
begin
DllHandle := GetModuleHandle('NomeDll.dll');
if DllHandle <> 0 then
//la dll 'NomeDll.dll' è caricata nello spazio di memoria del mio processo
FreeLibrary(DllHandle);
end;
Bene; ora dovremmo eseguire tale procedura nel processo remoto (il processo in
cui vogliamo eseguire il FreeLibrary della dll). Dovrei in sostanza copiare la
procedura sopra nello spazio di memoria del processo remoto e poi eseguirla con
il classico CreateRemoteThread. CreateRemoteThread prende in input (tra i vari
argomenti) un puntatore alla procedura la cui implementazione definisce il
codice di esecuzione del thread ed il parametro di input della
procedura medesima (valore dword). Inizialmente il fatto di poter passare un solo parametro potrebbe sembrare una limitazione: e che cavolo, posso eseguire in remoto solo procedure con un solo parametro???? Ehhh si, però proviamo a pensare: dunque quel parametro potrebbe essere anche ad esempio un puntatore ad un record; se la procedura che voglio eseguire in remoto deve avere ad esempio 10 argomenti in input, allora posso crearmi un record con 10 campi ognuno dei quali corrisponde ad ognuno dei 10 argomenti in questione; passerò come argomento un puntatore a tale record ed il gioco è fatto. A questo punto bisogna dire che quando andremo ad eseguire il codice nel contesto del processo remoto, bisognerà specificare direttamente l' indirizzo delle varie api che vengono chiamate dalla procedura medesima. Facendo riferimento alla procedura sopra dovremo specificare
direttamente l' indirizzo (naturalmente nel contesto del processo remoto) delle
funzioni GetModuleHandle e FreeLibrary. Entrambe le funzioni sono implementate
nella dll kernel32.dll: essa viene caricata in tutti i processi win32 ed in
particolare non è soggetta a rilocazione, ossia viene caricata sempre allo
stesso indirizzo in tutti i processi. Questo significa che, se nel mio processo
vado a determinare l' indirizzo delle 2 funzioni in questione, tale indirizzo
sarà valido anche nel contesto del processo remoto. Di conseguenza io posso
determinare i 2 indirizzi e poi assegnarli ai campi di un record che andrò a
passare come argomento della procedura da eseguire in remoto. Ci possiamo quindi
creare un record
type
TRemoteData = record
//indirizzo della GetModuleHandle
pGetModuleHandle: function (lpModuleName: PAnsiChar): LongWord; stdcall;
//indirizzo della FreeLibrary
pFreeLibrary: function (hLibModule: LongWord): LongBool; stdcall;
end;
PRemoteData = ^TRemoteData;
Ora dal mio processo scriverò
var
RemoteData: TRemoteData;
begin
RemoteData.pGetModuleHandle := GetProcAddress(GetModuleHandle('kernel32'), 'GetModuleHandleA');
RemoteData.pFreeLibrary := GetProcAddress(GetModuleHandle('kernel32'), 'FreeLibrary');
...
...
...
end;
Inoltre dobbiamo anche copiare nello spazio di memoria del processo remoto il
nome della dll da scaricare (nell' esempio iniziale 'NomeDll.dll'). L' indirizzo a
partire
dal quale (nel contesto del processo remoto) sarà presente la stringa con il
nome della dll, verrà memorizzato in un ulteriore campo del record TRemoteData.
Avremo quindi
type
TRemoteData = record
//indirizzo della GetModuleHandle
pGetModuleHandle: function (lpModuleName: PAnsiChar): LongWord; stdcall;
//indirizzo della FreeLibrary
pFreeLibrary: function (hLibModule: LongWord): LongBool; stdcall;
//indirizzo del nome della dll da scaricare
pNomeDll: pChar;
end;
PRemoteData = ^TRemoteData;
E' opportuno scrivere una funzione per copiare una stringa nello spazio di
memoria di un altro processo
function InjectString(hProcess: LongWord; stringa: pChar): pChar;
var
BytesWritten: dword;
begin
//vado ad allocare nel processo remoto (di cui ho l' handle hProcess
//opportunamente ottenuto tramite una chiamata ad OpenProcess) una striscia
//di memoria di dimensione pari alla lunghezza della stringa incrementata di 1
//(carattere di terminazione stringa)
Result := VirtualAllocEx(Process,
nil,
Length(stringa) + 1,
MEM_COMMIT or MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
//scrivo, nell' area di memoria appena allocata, il nome della dll
WriteProcessMemory(Process, Result, stringa, Length(stringa) + 1, BytesWritten);
//l' output della funzione è appunto l' indirizzo di memoria a partire dal quale
//è memorizzato (nel contesto del processo remoto) il nome della stringa
end;
A questo punto dal mio processo scriverò
var
RemoteData: TRemoteData;
process: THandle;
begin
Process := OpenProcess(PROCESS_CREATE_THREAD +
PROCESS_QUERY_INFORMATION +
PROCESS_VM_OPERATION +
PROCESS_VM_WRITE +
PROCESS_VM_READ,
False,
PID
);
RemoteData.pGetModuleHandle := GetProcAddress(GetModuleHandle('kernel32'),
'GetModuleHandleA');
RemoteData.pFreeLibrary := GetProcAddress(GetModuleHandle('kernel32'),
'FreeLibrary');
RemoteData.pNomeDll := InjectString(Process, 'NomeDll.dll');
...
...
...
end;
A questo punto la procedura da eseguire in remoto diventerà
function RemoteFreeLibrary(RemoteData: PRemoteData): Bool; stdcall;
var
Dll_handle:LongWord;
begin
Result := False;
Dll_handle := RemoteData^.pGetModuleHandle(RemoteData^.pNomeDll);
if Dll_handle <> 0 then
//la dll in questione è caricata nello spazio di memoria del mio processo
Result := RemoteData^.pFreeLibrary(Dll_handle);
end;
Ho pensato bene di renderla una funzione che restituisce il valore restituito
dalla FreeLibrary
Ok ora dobbiamo copiare sia la procedura (RemoteFreeLibrary) sia le informazioni
puntate dal suo parametro di input (RemoteData) nello spazio di memoria del
processo remoto; anzitutto scriviamoci una bella funzioncina (la sorella della
InjectString) che mi consenta di copiare qualsiasi dato (e quindi anche il
nostro record TRemoteData) nello spazio di memoria di un processo remoto.
function InjectDati(Process: LongWord; Dati: Pointer; Dimensione: dword): pointer;
var
BytesWritten: dword;
begin
Result := VirtualAllocEx(Process,
nil,
Dimensione,
MEM_COMMIT or MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
WriteProcessMemory(Process, Result, Dati, Dimensione, BytesWritten);
end;
La funzione è analoga alla precedente solo che in questo caso non è possibile
determinare la dimensione del dato da copiare e quindi la dimensione va passata
come parametro. Quando vado a chiamare InjectDati userò la funzione SizeOf per
determinare la dimensione del dato da copiare.
Ora dobbiamo copiare la procedura: mmmhhh interessante ... il codice di
implementazione di una procedura è di per sè una sequenza di byte e come tale
può essere copiato nello spazio di memoria di un processo remoto tramite la
InjectDati; l' unica cosa un pò tostina diventa la determinazione della
dimensione della procedura: dunnnqueeeee ... si può fare in questo modo:
si può definire subito dopo la definizione della funzione RemoteFreeLibrary un
altra procedura che non esegue praticamente nulla e che chiameremo
RemoteFreeLibraryEnd;
procedure EndRemoteFreeLibrary();
begin
end;
la dimensione della RemoteFreeLibrary sarà data dalla differenza tra l'
indirizzo a partire dal quale è memorizzato il codice di implementazione di
EndRemoteFreeLibrary (e cioè @EndRemoteFreeLibrary) e l' indirizzo a partire dal
quale è memorizzato il codice di implementazione di RemoteFreeLibrary (e cioè
@RemoteFreeLibrary); in sostanza la dimensione della RemoteFreeLibrary sarà:
dword(@EndRemoteFreeLibrary) - dword(@RemoteFreeLibrary
Ok, riassumiamo un attimo: sappiamo copiare una procedura in remoto, sappiamo
copiare il suo input in remoto ... non resta altro da fare che chiamare
CreateRemoteThread usando i 2 indirizzi (della procedura e del parametro
relativamente al processo remoto) come argomenti: nel mio processo scriverò
function ExecuteRemoteFreeLibrary(PID: DWORD; Dll: pChar): Bool;
var
RemoteData: TRemoteData;
process, thread: THandle;
ptrInput, ptrProc: Pointer;
ThreadId: DWORD;
Coduscita: dword;
begin
//ottengo un handle al processo remoto
Process := OpenProcess(PROCESS_CREATE_THREAD +
PROCESS_QUERY_INFORMATION +
PROCESS_VM_OPERATION +
PROCESS_VM_WRITE +
PROCESS_VM_READ,
False,
PID
);
//definisco l' argomento di input della procedura da eseguire in remoto
RemoteData.pGetModuleHandle := GetProcAddress(GetModuleHandle('kernel32'),
'GetModuleHandleA');
RemoteData.pFreeLibrary := GetProcAddress(GetModuleHandle('kernel32'),
'FreeLibrary');
RemoteData.pNomeDll := InjectString(Process, Dll);
//copio RemoteData in remoto
ptrInput := InjectDati(Process, @RemoteData, SizeOf(RemoteData));
//copio RemoteFreeLibrary in remoto
ptrProc := InjectDati(Process, @RemoteData, SizeOfProcedure(RemoteFreeLibrary));
//eseguo RemoteFreeLibrary nel contesto del processo remoto passandogli
//l' indirizzo di RemoteData come parametro
Thread := CreateRemoteThread(Process, nil, 0, ptrProc, ptrInput, 0, ThreadId);
//attendo la terminazione del thread remoto ossia lo scaricamento della dll
//in questione dallo spazio di memoria del processo remoto
WaitForSingleObject(Thread, INFINITE);
//rilevo il codice di uscita del thread remoto che altro non è che il valore restituito
//dalla RemoteFreeLibrary ossia False in caso di fallimento, True in caso di successo
//essendo esso stesso l' output della FreeLibrary. Essendo il secondo parametro della
// GetExitCodeThread un dword allora si avrà 0 per False ed un valore <> 0 per True
GetExitCodeThread(Thread, CodUscita);
if CodUscita = 0 then
Result := False
else
Result := True;
//chiudo l' handle al thread creato in remoto (Thread)
CloseHandle(Thread);
end;
Come ulteriore rifinitura è opportuno abilitare il privilegio di debug prima
dell' OpenProcess tramite la procedura ModificaPrivilegio già esaminata nei
precedenti articoli; anche il PID può essere ottenuto a partire dal nome dell'
eseguibile tramite la funzione PidProcesso già esposta precedentemente.
In definitiva questo è il codice per scaricare una dll dallo spazio di memoria
di un processo remoto:
unit RemoteFreeUnit;
interface
uses
Windows, Sysutils, TlHelp32;
function PidProcesso(NomeProcesso: string): LongWord;
function ExecuteRemoteFreeLibrary(PID: DWORD; Dll: pChar): bool;
implementation
type
TRemoteData = record
pGetModuleHandle: function (lpModuleName: PAnsiChar): LongWord; stdcall;
pFreeLibrary: function (hLibModule: LongWord): LongBool; stdcall;
pNomeDll: pChar;
end;
PRemoteData = ^TRemoteData;
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;
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;
function InjectString(Process: LongWord; Text: pchar): pchar;
var
BytesWritten: dword;
begin
Result := VirtualAllocEx(Process,
nil,
Length(Text) + 1,
MEM_COMMIT or MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
WriteProcessMemory(Process, Result, Text, Length(Text) + 1, BytesWritten);
end;
function InjectDati(Process: LongWord; Dati: Pointer; Dimensione: dword): pointer;
var
BytesWritten: dword;
begin
Result := VirtualAllocEx(Process,
nil,
Dimensione,
MEM_COMMIT or MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
WriteProcessMemory(Process, Result, Dati, Dimensione, BytesWritten);
end;
function RemoteFreeLibrary(RemoteData: PRemoteData): Bool; stdcall;
var
Dll_handle:LongWord;
begin
Result := False;
Dll_handle := RemoteData^.pGetModuleHandle(RemoteData^.pNomeDll);
if Dll_handle <> 0 then
//la dll in questione è caricata nello spazio di memoria del mio processo
Result := RemoteData^.pFreeLibrary(Dll_handle);
end;
procedure EndRemoteFreeLibrary(); stdcall;
begin
end;
function ExecuteRemoteFreeLibrary(PID: DWORD; Dll: pChar): bool;
var
RemoteData: TRemoteData;
process, thread: THandle;
ptrInput, ptrProc: Pointer;
ThreadId: DWORD;
CodUscita: dword;
begin
ModificaPrivilegio('SeDebugPrivilege', TRUE);
Process := OpenProcess(PROCESS_CREATE_THREAD +
PROCESS_QUERY_INFORMATION +
PROCESS_VM_OPERATION +
PROCESS_VM_WRITE +
PROCESS_VM_READ,
False,
PID
);
RemoteData.pGetModuleHandle := GetProcAddress(GetModuleHandle('kernel32'),
'GetModuleHandleA');
RemoteData.pFreeLibrary := GetProcAddress(GetModuleHandle('kernel32'),
'FreeLibrary');
RemoteData.pNomeDll := InjectString(Process, Dll);
ptrInput := InjectDati(Process, @RemoteData, SizeOf(RemoteData));
ptrProc := InjectDati(Process,
@RemoteFreeLibrary,
dword(@EndRemoteFreeLibrary) - dword(@RemoteFreeLibrary)
);
Thread := CreateRemoteThread(Process, nil, 0, ptrProc, ptrInput, 0, ThreadId);
WaitForSingleObject(Thread, INFINITE);
GetExitCodeThread(Thread, CodUscita);
if CodUscita = 0 then
Result := False
else
Result := True;
CloseHandle(Thread);
end;
end.
|