Home | Chi sono | Contattami
 

Progr. lineare

Delphi
 
Componenti
  Database
 
Miei articoli

Windows

Miei articoli 

 

Nascondere file e cartelle in Windows 2000/XP/2003 intercettando l' api nativa NtQueryDirectoryFile


Negli articoli precedenti ho esposto un metodo per nascondere un determinato processo in esecuzione intercettando l' api nativa NtQuerySystemInformation ed eliminando il processo in questione dalla lista restituita dalla funzione medesima. Bene ... ora invece voglio nascondere una cartella o un file. Beehhh diciamo che la strada da percorrere è praticamente identica: dobbiamo usare l' api nativa NtQueryDirectoryFile che restituisce informazioni relative al contenuto di una directory. Anche questa volta utilizziamo, per la dichiarazione delle api native, la unit Native.pas prelevabile all' indirizzo

http://members.chello.nl/m.vanbrakel2/win32api._zip

Di seguito la diachiarazione dell' api con i commenti sui vari parametri

function NtQueryDirectoryFile(//handle ad un oggetto file che rappresenta una directory; //per ricavarne il nome si può chiamare NtQueryObject //passandogli ObjectNameInformation come valore del parametro //ObjectInformationClass (*IN*)FileHandle: HANDLE; //parametro opzionale che specifica l' handle di un oggetto //di tipo Event che diventa signaled quando l' operazione è //terminata (*IN*)Event: HANDLE; //parametro opzionale che punta ad una funzione da eseguire //una volta terminata l' operazione (*IN*)ApcRoutine: PIO_APC_ROUTINE; //puntatore all' informazione da usare come primo parametro //della funzione puntata da ApcRoutine (*IN*)ApcContext: PVOID; //restituisce lo stato di terminazione della funzione ed //informazioni riguardo all' operazione (*OUT*)IoStatusBlock: PIO_STATUS_BLOCK; //output della funzione (*OUT*)FileInformation: PVOID; //dimensione del buffer che dovrà contenere l' output della //funzione (ossia FileInformation) (*IN*)FileInformationLength: ULONG; //tipo di informazione che deve essere restituita: //per le directory le classi valide sono 1, 2, 3, 12: //FileDirectoryInformation, // 1 //FileFullDirectoryInformation, // 2 //FileBothDirectoryInformation, // 3 //FileNamesInformation, //12 (*IN*)FileInformationClass: FILE_INFORMATION_CLASS; //specifica se deve essere restituita nel buffer FileInformation //un singolo elemento (True) oppure tutti gli elementi (False) (*IN*)ReturnSingleEntry: ByteBool; //specifica un eventuale filtro sui nomi dei file in una directory //ad esempio se si interroga una directory e si pone FileName come //*.txt, in FileInformation verrà restituito un elenco di //blocchi di byte ognuno dei quali corrisponde (*IN*)FileName: PUNICODE_STRING; //specifica se si deve ricominciare dall' inizio la scansione //della directory oppure si parte dalla posizione corrente (*IN*)RestartScan: ByteBool ): NTSTATUS; stdcall;

Questa api viene chiamata tutte le volte che si desidera conoscere il contenuto di una cartella: quando apriamo una cartella nella shell di windows oppure eseguiamo il comando dir dalla linea di comando. Come nel caso della NtQuerySystemInformation, anche qui si ha come output un insieme di blocchi di byte ognuno dei quali contiene informazioni su un file o una cartella relativamente ad una data cartella; come nel caso di NtQuerySystemInformation i primi 4 byte di ogni blocco definiscono la distanza dal blocco corrente al blocco successivo e nel caso dell' ultimo blocco tale valore è 0. L' output in questione viene puntato dal parametro FileInformation. In pratica data una cartella identificata dal suo handle (primo parametro FileHandle) si richiede un determinato tipo di informazione (parametro FileInformationClass) relativamente a quella cartella: l' indirizzo a partire dal quale viene scritto il risultato è definito dal parametro FileInformation. Il parametro FileInformationLength è un parametro di input ed indica la lunghezza del buffer puntato da FileInformation: in pratica la funzione che chiama NtQueryDirectoryFile deve allocare la memoria destinata a contenere il risultato dell' interrogazione effettuata sulla cartella (dimensione del buffer puntato da FileInformation): FileInformationLength definisce tale dimensione. Come già detto il tipo di informazione richiesta sulla cartella in questione è specificato dal parametro FileInformationClass: la tipologia di tale parametro è la seguente

_FILE_INFORMATION_CLASS = ( FileFiller0, FileDirectoryInformation, // 1 directory FileFullDirectoryInformation, // 2 directory FileBothDirectoryInformation, // 3 directory FileBasicInformation, // 4 FileStandardInformation, // 5 FileInternalInformation, // 6 FileEaInformation, // 7 FileAccessInformation, // 8 FileNameInformation, // 9 FileRenameInformation, // 10 FileLinkInformation, // 11 FileNamesInformation, // 12 directory FileDispositionInformation, // 13 FilePositionInformation, // 14 FileFullEaInformation, // 15 FileModeInformation, // 16 FileAlignmentInformation, // 17 FileAllInformation, // 18 FileAllocationInformation, // 19 FileEndOfFileInformation, // 20 FileAlternateNameInformation, // 21 FileStreamInformation, // 22 FilePipeInformation, // 23 FilePipeLocalInformation, // 24 FilePipeRemoteInformation, // 25 FileMailslotQueryInformation, // 26 FileMailslotSetInformation, // 27 FileCompressionInformation, // 28 FileObjectIdInformation, // 29 FileCompletionInformation, // 30 FileMoveClusterInformation, // 31 FileQuotaInformation, // 32 FileReparsePointInformation, // 33 FileNetworkOpenInformation, // 34 FileAttributeTagInformation, // 35 FileTrackingInformation, // 36 FileMaximumInformation); FILE_INFORMATION_CLASS = _FILE_INFORMATION_CLASS; PFILE_INFORMATION_CLASS = ^FILE_INFORMATION_CLASS;

il tipo enumerativo _FILE_INFORMATION_CLASS definisce varie tipologie di informazione prelevabili da un file o da una directory. Le tipologie di informazione relative alle directory sono la 1, la 2, la 3, a 12 (come si vede dall' indicazione "directory" di fianco alle singole voci). A seconda del tipo di informazione richiesta, ogni blocco di byte restituito dalla funzione nel buffer puntato da FileInformation avrà una struttura differente: di seguito la struttura di ogni elemento memorizzato nel buffer puntato da FileInformation a seconda del valore di FileInformationClass

FileInformationClass = FileDirectoryInformation

_FILE_DIRECTORY_INFORMATION = record // Information Class 1 NextEntryOffset: ULONG; Unknown: ULONG; CreationTime: LARGE_INTEGER; LastAccessTime: LARGE_INTEGER; LastWriteTime: LARGE_INTEGER; ChangeTime: LARGE_INTEGER; EndOfFile: LARGE_INTEGER; AllocationSize: LARGE_INTEGER; FileAttributes: ULONG; FileNameLength: ULONG; FileName: array [0..0] of WCHAR end; FILE_DIRECTORY_INFORMATION = _FILE_DIRECTORY_INFORMATION; PFILE_DIRECTORY_INFORMATION = ^FILE_DIRECTORY_INFORMATION;

FileInformationClass = FileFullDirectoryInformation

_FILE_FULL_DIRECTORY_INFORMATION = record // Information Class 2 NextEntryOffset: ULONG; Unknown: ULONG; CreationTime: LARGE_INTEGER; LastAccessTime: LARGE_INTEGER; LastWriteTime: LARGE_INTEGER; ChangeTime: LARGE_INTEGER; EndOfFile: LARGE_INTEGER; AllocationSize: LARGE_INTEGER; FileAttributes: ULONG; FileNameLength: ULONG; EaInformationLength: ULONG; FileName: array [0..0] of WCHAR end; FILE_FULL_DIRECTORY_INFORMATION = _FILE_FULL_DIRECTORY_INFORMATION; PFILE_FULL_DIRECTORY_INFORMATION = ^FILE_FULL_DIRECTORY_INFORMATION;

FileInformationClass = FileBothDirectoryInformation

_FILE_BOTH_DIRECTORY_INFORMATION = record // Information Class 3 NextEntryOffset: ULONG; Unknown: ULONG; CreationTime: LARGE_INTEGER; LastAccessTime: LARGE_INTEGER; LastWriteTime: LARGE_INTEGER; ChangeTime: LARGE_INTEGER; EndOfFile: LARGE_INTEGER; AllocationSize: LARGE_INTEGER; FileAttributes: ULONG; FileNameLength: ULONG; EaInformationLength: ULONG; AlternateNameLength: UCHAR; AlternateName: array [0..11] of WCHAR; FileName: array [0..0] of WCHAR; end; FILE_BOTH_DIRECTORY_INFORMATION = _FILE_BOTH_DIRECTORY_INFORMATION; PFILE_BOTH_DIRECTORY_INFORMATION = ^FILE_BOTH_DIRECTORY_INFORMATION;

FileInformationClass = FileNamesInformation

_FILE_NAMES_INFORMATION = record // Information Class 12 NextEntryOffset: ULONG; Unknown: ULONG; FileNameLength: ULONG; FileName: array [0..0] of WCHAR; end; FILE_NAMES_INFORMATION = _FILE_NAMES_INFORMATION; PFILE_NAMES_INFORMATION = ^FILE_NAMES_INFORMATION;

Per nascondere un processo abbiamo usato NtQuerySystemInformation ed abbiamo eliminato dalla lista di blocchi ottenuta, il blocco corrispondente al processo in questione. Ora faremo la stessa cosa per nascondere un file o una cartella. Utilizzando la tecnica di Detours andremo ad intercettare le chiamate all' api NtQueryDirectoryFile modificando il risultato ottenuto in maniera tale da non far risultare il file o la cartella che si desidera nascondere: di seguito ecco l' implementazione della funzione che verrà chiamata al posto della NtQueryDirectoryFile

function DetourNtQueryDirectoryFile(FileHandle: HANDLE; Event: HANDLE; ApcRoutine: PIO_APC_ROUTINE; ApcContext: PVOID; IoStatusBlock: PIO_STATUS_BLOCK; FileInformation: PVOID; FileInformationLength: ULONG; FileInformationClass: FILE_INFORMATION_CLASS; ReturnSingleEntry: ByteBool; FileName: PUNICODE_STRING; RestartScan: ByteBool ): NTSTATUS; stdcall; var i,rl,cp : dword; pfiledirectoryinformation, pfiledirectoryinformation_prec: PFILE_DIRECTORY_INFORMATION; pfilefulldirectoryinformation, pfilefulldirectoryinformation_prec: PFILE_FULL_DIRECTORY_INFORMATION; pfilebothdirectoryinformation, pfilebothdirectoryinformation_prec: PFILE_BOTH_DIRECTORY_INFORMATION; pfilenamesinformation, pfilenamesinformation_prec: PFILE_NAMES_INFORMATION; buf : Pointer; dim: dword; Name: string; NomeDir: string; begin result := TrampolineNtQueryDirectoryFile(FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock, FileInformation, FileInformationLength, FileInformationClass, ReturnSingleEntry, FileName, RestartScan ); if result <> 0 then Exit; case FileInformationClass of FileDirectoryInformation: (*1*) begin cp := 0; pfiledirectoryinformation_prec := nil; repeat pfiledirectoryinformation := PFILE_DIRECTORY_INFORMATION(Pointer(dword(FileInformation) + cp)); with pfiledirectoryinformation^ do begin if (FileName <> nil) then begin //FileNameLength è la dimensione in byte del nome del file (FileName): essendo //quest' ultimo un pWideChar (ogni carattere = 2 byte) allora il numero //di caratteri del nome del file sarà dato da FileNameLength div 2) Name := Copy(WideCharToString(FileName), 1, FileNameLength div 2); if LowerCase(Name) = 'notepad.exe' then begin if cp = 0 then //siamo sul primo elemento begin if NextEntryOffset = 0 then //oltre che essere sul primo elemento siamo anche sull' ultimo //elemento (cioè l' elencazione contiene un solo elemento) begin Result := NTSTATUS($C000000F); Exit; end else begin Result := NTSTATUS($C000000F); Exit; end; end; if NextEntryOffset = 0 then //siamo all' ultimo elemento begin if pfiledirectoryinformation_prec <> nil then pfiledirectoryinformation_prec.NextEntryOffset := 0; Exit; end else //siamo su un elemento interno qualsiasi begin pfiledirectoryinformation_prec.NextEntryOffset := pfiledirectoryinformation_prec.NextEntryOffset + NextEntryOffset; end; end else begin pfiledirectoryinformation_prec := pfiledirectoryinformation; end; end; cp := cp + NextEntryOffset; end; until (pfiledirectoryinformation.NextEntryOffset = 0); end; FileFullDirectoryInformation: (*2*) begin cp := 0; pfilefulldirectoryinformation_prec := nil; repeat pfilefulldirectoryinformation := PFILE_FULL_DIRECTORY_INFORMATION(Pointer(dword(FileInformation) + cp)); with pfilefulldirectoryinformation^ do begin if (FileName <> nil) then begin Name := Copy(WideCharToString(FileName), 1, FileNameLength div 2); if LowerCase(Name) = 'notepad.exe' then begin if cp = 0 then //siamo sul primo elemento begin if NextEntryOffset = 0 then //oltre che essere sul primo elemento siamo anche sull' ultimo //elemento (cioè l' elencazione contiene un solo elemento) begin Result := NTSTATUS($C000000F); Exit; end else begin Result := NTSTATUS($C000000F); Exit; end; end; if NextEntryOffset = 0 then //siamo all' ultimo elemento begin if pfilefulldirectoryinformation_prec <> nil then pfilefulldirectoryinformation_prec.NextEntryOffset := 0; Exit; end else //siamo su un elemento interno qualsiasi begin pfilefulldirectoryinformation_prec.NextEntryOffset := pfilefulldirectoryinformation_prec.NextEntryOffset + NextEntryOffset; end; end else begin pfilefulldirectoryinformation_prec := pfilefulldirectoryinformation; end; end; cp := cp + NextEntryOffset; end; until (pfilefulldirectoryinformation.NextEntryOffset = 0); end; FileBothDirectoryInformation: (*3*) begin cp := 0; pfilebothdirectoryinformation_prec := nil; repeat pfilebothdirectoryinformation := PFILE_BOTH_DIRECTORY_INFORMATION(Pointer(dword(FileInformation) + cp)); with pfilebothdirectoryinformation^ do begin if (FileName <> nil) then begin Name := Copy(WideCharToString(FileName), 1, FileNameLength div 2); if LowerCase(Name) = 'notepad.exe' then begin if cp = 0 then //siamo sul primo elemento begin if NextEntryOffset = 0 then //oltre che essere sul primo elemento siamo anche sull' ultimo //elemento (cioè l' elencazione contiene un solo elemento) begin Result := NTSTATUS($C000000F); Exit; end else begin Result := NTSTATUS($C000000F); Exit; end; end; if NextEntryOffset = 0 then //siamo all' ultimo elemento begin if pfilebothdirectoryinformation_prec <> nil then pfilebothdirectoryinformation_prec.NextEntryOffset := 0; Exit; end else //siamo su un elemento interno qualsiasi begin pfilebothdirectoryinformation_prec.NextEntryOffset := pfilebothdirectoryinformation_prec.NextEntryOffset + NextEntryOffset; end; end else begin pfilebothdirectoryinformation_prec := pfilebothdirectoryinformation; end; end; cp := cp + NextEntryOffset; end; until (pfilebothdirectoryinformation.NextEntryOffset = 0); end; FileNamesInformation: (*12*) begin cp := 0; pfilenamesinformation_prec := nil; repeat pfilenamesinformation := PFILE_NAMES_INFORMATION(Pointer(dword(FileInformation) + cp)); with pfilenamesinformation^ do begin if (FileName <> nil) then begin Name := Copy(WideCharToString(FileName), 1, FileNameLength div 2); if LowerCase(Name) = 'notepad.exe' then begin if cp = 0 then //siamo sul primo elemento begin if NextEntryOffset = 0 then //oltre che essere sul primo elemento siamo anche sull' ultimo //elemento (cioè l' elencazione contiene un solo elemento) begin Result := NTSTATUS($C000000F); Exit; end else begin Result := NTSTATUS($C000000F); Exit; end; end; if NextEntryOffset = 0 then //siamo all' ultimo elemento begin if pfilenamesinformation_prec <> nil then pfilenamesinformation_prec.NextEntryOffset := 0; Exit; end else //siamo su un elemento interno qualsiasi begin pfilenamesinformation_prec.NextEntryOffset := pfilenamesinformation_prec.NextEntryOffset + NextEntryOffset; end; end else begin pfilenamesinformation_prec := pfilenamesinformation; end; end; cp := cp + NextEntryOffset; end; until (pfilenamesinformation.NextEntryOffset = 0); end; end; end;

Nell' esempio abbiamo preso in esame i 4 tipi di informazione relativi alle directory ed abbiamo eliminato dalla lista dei blocchi di byte, quello corrispondente al file che vogliamo nascondere (nel caso specifico ci siamo prefissati l' obiettivo di nascondere notepad.exe): o meglio ... non lo abbiamo eliminato ma abbiamo semplicemente fatto puntare il blocco precedente al blocco successivo (sommando i valori di NextEntryOffset). Si nota che viene preso in esame il caso in cui l' elemento che si vuole eliminare sia il primo elemento della lista: non è un caso frequente in quanto quando viene enumerato il contenuto di una cartella i primi 2 elementi sono "." (identificativo della cartella corrente) e ".." (identificativo della cartella superiore). Tuttavia se apriamo una linea di comando (cmd.exe) e facciamo un "dir" seguito da un nome di file preciso oppure da un nome di file con carattere jolly (*), l' elemento da nascondere può effettivamente essere il primo elemento della lista (lo è sicuramente se si fa ad esempio un "dir notepad.exe"). In tal caso, se la lista contiene un solo elemento (quindi l' elemento che deve essere nascosto) posso far restituire alla funzione il valore $C000000F ad indicare che nessun file è stato trovato: un dir nella linea di comando restituisce "File non trovato". Nel caso in cui invece non si tratti del primo ed unico elemento ma ci siano altri elementi dopo, allora devo spostare tutti gli elementi successivi di un numero di byte pari alla dimensione del primo elemento sovrascrivendolo: tuttavia ho preferito lasciare tale operazione ad un' altra volta ed anche in questo caso faccio restituire per farla breve il valore $C000000F.

Un' altra cosa che è importante sottolineare è che il file che si vuole nascondere verrà nascosto in qualsiasi cartella esso si trovi (infatti non andiamo a fare nessuna verifica sul nome della cartella di cui vogliamo prelevare l' informazione. Per fare le cose un pò meglio si dovrebbe ottenere il nome della cartella e poi nascondere il file solo se si è nella cartella che si desidera. Come ottenere il nome di una cartella dal suo handle (parametro FileHandle della funzione NtQueryDirectoryFile)??? Si può utilizzare l' api nativa NtQueryObject che prende in input un handle di un oggetto qualsiasi (file, directory, processo, pipe, etc...) e ne restituisce l' informazione richiesta: tra le verie tipologie di informazione spiccano ObjectNameInformation ed ObjectTypeInformation che consentono di ottenere rispettivamente il nome ed il tipo dell' oggetto (in questo caso il nome della directory e "Directory"). Il problema che rimane a questo punto è che il nome della directory viene espresso nella forma "\Device\HardDiskVolume1\<percorso cartella>" e non come
"c:\<percorso cartella>". Il problema si risolve utilizzando la funzione QueryDosDevice che dato un nome di device (ad esempio appunto \Device\HardDiskVolume1) restituisce la lettera che ne identifica il volume (ad esempio c:\). Ecco allora di seguito la funzione per ottenere il nome di una directory a partire dal suo handle:

 

function DirNameFromHandle(dirHandle: THandle): string; const BUFSIZE = 1024; var pObjectNameInformation: POBJECT_NAME_INFORMATION;//Pointer; ObjectNameInformationLength: ULONG; NomeDir: string; pResponse: dword; rl: dword; devName: string; x: LongWord; DrvLetter: char; Drv: string; begin GetMem(pObjectNameInformation, 10000); NtQueryObject(dirHandle, ObjectNameInformation, pObjectNameInformation, 10000, @rl); NomeDir := WideCharToString(pObjectNameInformation.Name.Buffer); Freemem(pObjectNameInformation); for DrvLetter:= 'A' to 'Z' do begin Drv:= DrvLetter+':'; SetLength(devName, BufSize); x:= QueryDosDevice(pchar(Drv), pchar(devName), BufSize-1); SetLength(devName, x-2); if Pos(devName, NomeDir) <> 0 then begin NomeDir:= StringReplace(NomeDir, devName, Drv, []); break; end; end; result := NomeDir; end;

A questo punto nella funzione che si sostituirà alla NtQueryDirectoryFile con la tecnica di Detours basterà eseguire una semplice verifica sul nome della directory per vedere se si deve proseguire analizzandone il contenuto (la directory è quella che ci interessa) oppure uscire con un exit in quanto non è in questa cartella che si trova il file che vogliamo nascondere (o anche non è in questa cartella che vogliamo nascondere il file).

Il sorgente ed il demo consentono di verificare come sia possibile nascondere notepad.exe nella sua cartella (ossia la cartella di sistema). Se si sposta notepad.exe in un altra cartella però a quel punto sarà visibile. Come ulteriore prova ho voluto per curiosità nascondere alcuni eseguibili che vengono riconosciuti come virus da AVG: ebbene AVG non li rileva più!!!

Sorgenti

 

 

 

 
 
Your Ad Here