Home | Chi sono | Contattami
 

Progr. lineare

Delphi
 
Componenti
  Database
 
Miei articoli

Windows

Miei articoli 

 

Nascondere un processo dal Task Manager in Windows 2000/XP


Sotto Windows 98, nacondere un processo dal Task Manager era questione di secondi, tramite una chiamata alla funzione RegisterServiceProcess. Da Windows 2000 in poi è un compito decisamente più arduo. Esistono varie tecniche e quella che vado a descrivere è quella usata dal programma BackOrifice 2000(BO2K): è interessante perchè consente anche di raggiungere numerosi altri obiettivi oltre quello di nascondere il processo dal Task Manager. Il concetto a grandi linee consiste nel copiare l' immagine in memoria dell' eseguibile che si vuole nascondere nello spazio di memoria di un altro processo (tramite la funzione WriteProcessMemory) e poi eseguire il codice copiato come thread interno del processo destinazione tramite la funzione CreateRemoteThread. Anzitutto è bene fare una (breve) descrizione del formato PE (Portable Executable) che è il formato di tutti gli eseguibili Windows. Un ottimo riferimento al riguardo è il sito

http://win32assembly.online.fr/

La struttura di un eseguibile PE (Portable Executable) è la seguente

Se si tenta di lanciare il programma DOS, allora bisogna stampare a video un messaggio di errore in cui si informa che è necessario Windows per eseguire il programma. Ma per poter eseguire qualsiasi operazione (come ad esempio la stessa stampa a video) bisogna che il programma stesso sia eseguibile dal DOS: la presenza del DOS MZ Header fa si che il DOS riconosca l' eseguibile come un valido eseguibile per DOS; l' esecuzione sotto DOS consiste nell' esecuzione del DOS Stub: in genere questo consiste nella stampa a video di un messaggio del tipo "Questo programma richiede Windows".

Il DOS MZ Header può essere rappresentato dal seguente record

TImageDosHeader = packed record { DOS .EXE header } e_magic: Word; { Magic number } e_cblp: Word; { Bytes on last page of file } e_cp: Word; { Pages in file } e_crlc: Word; { Relocations } e_cparhdr: Word; { Size of header in paragraphs } e_minalloc: Word; { Minimum extra paragraphs needed } e_maxalloc: Word; { Maximum extra paragraphs needed } e_ss: Word; { Initial (relative) SS value } e_sp: Word; { Initial SP value } e_csum: Word; { Checksum } e_ip: Word; { Initial IP value } e_cs: Word; { Initial (relative) CS value } e_lfarlc: Word; { File address of relocation table } e_ovno: Word; { Overlay number } e_res: array [0..3] of Word; { Reserved words } e_oemid: Word; { OEM identifier (for e_oeminfo) } e_oeminfo: Word; { OEM information; e_oemid specific} e_res2: array [0..9] of Word; { Reserved words } _lfanew: LongInt; { File address of new exe header } end;

I primi 2 byte del DOS MZ Header (quindi il campo e_magic) sono i caratteri MZ.

Il campo _lfanew ci indica l' offest a cui si trova il PE Header

Il PE Header a sua volta contiene 3 elementi importanti

e può essere rappresentato dal seguente record

TImageNtHeaders = packed record Signature: DWORD; FileHeader: TImageFileHeader; OptionalHeader: TImageOptionalHeader; end;

dove Signature contiene i caratteri PE00 che indicano appunto che il file è un Portable Executable (PE)

TImageFileHeader = packed record Machine: Word; NumberOfSections: Word; TimeDateStamp: DWORD; PointerToSymbolTable: DWORD; NumberOfSymbols: DWORD; SizeOfOptionalHeader: Word; Characteristics: Word; end;

TImageOptionalHeaders = packed record { Standard fields. } Magic: Word; MajorLinkerVersion: Byte; MinorLinkerVersion: Byte; SizeOfCode: DWORD; SizeOfInitializedData: DWORD; SizeOfUninitializedData: DWORD; AddressOfEntryPoint: DWORD; BaseOfCode: DWORD; BaseOfData: DWORD; { NT additional fields. } ImageBase: DWORD; SectionAlignment: DWORD; FileAlignment: DWORD; MajorOperatingSystemVersion: Word; MinorOperatingSystemVersion: Word; MajorImageVersion: Word; MinorImageVersion: Word; MajorSubsystemVersion: Word; MinorSubsystemVersion: Word; Win32VersionValue: DWORD; SizeOfImage: DWORD; SizeOfHeaders: DWORD; CheckSum: DWORD; Subsystem: Word; DllCharacteristics: Word; SizeOfStackReserve: DWORD; SizeOfStackCommit: DWORD; SizeOfHeapReserve: DWORD; SizeOfHeapCommit: DWORD; LoaderFlags: DWORD; NumberOfRvaAndSizes: DWORD; DataDirectory: packed array[0..IMAGE_NUMBEROF_DIRECTORY_ENTRIES-1] of TImageDataDirectory; end;

per brevità possiamo omettere la descrizione del record TImageDataDirectory che può essere prelevata, come tutte le altre, dalla unit Windows. Inoltre sempre nella unit Windows c' è la dichiarazione di tipi puntatore ai record appena descritti:

PImageDosHeader = ^TImageDosHeader; PImageNTHeaders = ^TImageNTHeaders; PImageFileHeader = ^TImageFileHeader; PImageOptionalHeaders = ^TImageOptionalHeaders

II campo da prendere in considerazione è il campo SizeOfImage del record TImageOptionalHeader: esso definisce la dimensione totale dell' immagine dell' eseguibile in memoria. Ci servirà in fase di copia dell' immagine nel processo destinazione

Bene procediamo alla lettura di queste informazioni. Anzitutto per avere le suddette informazioni relativamente all' eseguibile corrente, devo prima ottenere un handle all' immagine in memoria dell' eseguibile con una chiamata del tipo

GetModuleHandle(nil);

(nil come argomento indica che ci riferiamo all' eseguibile corrente). Senza entrare troppo nei dettagli, l' immagine in memoria dell' eseguibile corrente avrà una struttura del tipo definito in precedenza (PE). Il risultato di GetModuleHandle(nil) è un puntatore all' inizio dell' immagine.

var Module: Pointer; ... Module := GetModuleHandle(nil);

Per ottenere il puntatore al PE Header, allora dovrò sommare a Module il valore di _lfanew

Quindi

PImageNTHeaders(Pointer(integer(Module) + PImageDosHeader(Module)._lfanew))

Per ottenere il puntatore all' Optional Header dovrò poi sommare al valore precedentemente ottenuto la dimensione di Signature e File Header

Quindi

PImageOptionalHeader(Pointer(integer(Module) + PImageDosHeader(Module)._lfanew + sizeof(DWORD) + sizeof(TImageFileHeader)))

Infine per ricavare la dimensione dell' immagine in memoria dell' eseguibile corrente scriverò

PImageOptionalHeader(Pointer(integer(Module) + PImageDosHeader(Module)._lfanew + sizeof(DWORD) + sizeof(TImageFileHeader))).SizeOfImage

A questo punto si può passare ad analizzari i vari passaggi che portano all' esecuzione del nostro eseguibile come thread interno di un altro processo. Anzitutto devo ottenere un handle al processo destinazione e questo lo posso fare tramite una chiamata alla funzione OpenProcess

var hProcess: THandle; begin hProcess := OpenProcess(PROCESS_ALL_ACCESS, False, PID); end.

dove PID è il PID del processo destinazione. A questo punto è bene creare una procedura che restituisca il PID di un processo dato il nome del processo

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;

Una volta ottenuto un handle al processo destinazione, si può procedere a copiare l' immagine del nostro eseguibile (punto di partenza Module, dimensione SizeOfImage) nello spazio di memoria del processo destinazione: realizziamo il tutto con la seguente procedura:

procedure CopiaImmagine(hProcess: THandle; Module: Pointer; SizeOfImage: DWORD); var pMem: Pointer; NumBytes, dwOldProt, i: longword; mbi: TMemoryBasicInformation; ptr: Pointer; begin VirtualFreeEx(hProcess, Module, 0, MEM_RELEASE); pMem := VirtualAllocEx(hProcess, Module, SizeOfImage, MEM_COMMIT or MEM_RESERVE, PAGE_EXECUTE_READWRITE); VirtualQueryEx(hProcess, pMem, mbi ,sizeof(TMemoryBasicInformation)); while((mbi.Protect <> PAGE_NOACCESS) and (mbi.RegionSize <> 0)) do begin if (mbi.Protect <> PAGE_GUARD) then begin i := 0; while (i < mbi.RegionSize) do begin ptr := Pointer(integer(pMem) + i); VirtualProtectEx(hProcess, ptr, $1000, PAGE_EXECUTE_READWRITE, dwOldProt); WriteProcessMemory(hProcess, ptr, ptr, $1000, NumBytes); i := i + $1000; end; end; pMem := Pointer(integer(pMem) + mbi.RegionSize); VirtualQueryEx(hProcess, pMem, mbi, sizeof(TMemoryBasicInformation)); end; end;

A questo punto posso chiamare la funzione CreateRemoteThread che consente la creazione di un thread nel processo destinazione:

CreateRemoteThread(hProcess, nil, 0, @Main, Module, 0, TID);

Main è il nome della procedura che deve essere eseguita dal thread creato nel processo destinazione: essa rappresenta ciò che deve fare il nostro eseguibile. Per rendere l' idea che effettivamente il nostro codice (implementazione della procedura Main in questo caso) viene eseguito nel contesto del processo destinazione, si può chiamare la funzione GetCommandLine e verificare che restituisce la linea di comando con cui è stato lanciato il processo destinazione. Quindi

function Main(dwEntryPoint: Pointer): longword; stdcall; var str: pChar; begin LoadLibrary('kernel32.dll'); LoadLibrary('user32.dll'); 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); Result := 0; end;

Si noti che abbiamo dovuto chiamare LoadLibrary per assicurarci il caricamento delle dll in cui sono implementate le funzioni che andiamo a chiamare nella function Main (in questo caso GetCommandLine e MessageBox). Si noti anche (molto importante) che la procedura Main può avere solo un parametro.

Dopo la chiamata a CreateRemoteThread andremo ad inserire una chiamata a CloseHandle(hProcess) che chiude l' handle al processo destinazione (ottenuto in precedenza con OpenProcess).

Esempio

 

 

 

 

 


 

 

 
 
Your Ad Here