NTSYSCALLAPI NTSTATUS ZwCreateFile( PHANDLE FileHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER AllocationSize, ULONG FileAttributes, ULONG ShareAccess, ULONG CreateDisposition, ULONG CreateOptions, PVOID EaBuffer, ULONG EaLength );
FileHandle è il puntatore che riceverà l'indirizzo dell'handle al file o alla directory
DesiredAccess è un valore (o una combinazione di valori) che indica cosa si vuole fare del file o della directory tramite il relativo handle. Ad esempio, si può voler leggere, scrivere o entrambe le cose. Per le directory si può anche indicare se si vuole elencarne i file. Si possono inoltre passare valori per indicare che tramite l'handle il chiamante può eliminare l'oggetto kernel sottostante o anche per indicare che quest'ultimo può essere usato come oggetto di sincronizzazione (in modo simile ad un mutex o ad un semaforo) permettendo così l'attesa del completamento in operazioni asincrone sul relativo file.
ObjectAttributes è un puntatore ad una struttura di tipo OBJECT_ATTRIBUTES che deve essere inizializzata con vari attributi da associare all'handle. Tra i più importanti ci sono:
- il nome (spesso completamente qualificato) dell'oggetto kernel (file o directory) di cui si vuole ottenere l'handle.
- l'indicazione del fatto che l'handle sarà usato solo in kernel mode o meno.
- l'indicazione del fatto che la comparazione tra il nome indicato per l'oggetto kernel ed altri nomi di oggetti esistesti dello stesso tipo avvenga senza distinzione tra maiuscole o minuscole.
Per inizializzare tale struttura si può usare la macro InitializeObjectAttributes (si veda [5]).
IoStatusBlock è un puntatore ad una struttura di tipo IO_STATUS_BLOCK che riceve lo stato finale della richiesta. Ad esempio, se ZwCreateFile è stata chiamata per aprire un file e la chiamata ha successo il campo Information della struttura IO_STATUS_BLOCK riceverà il valore FILE_OPENED.
AllocationSize indica la dimensione iniziale, in byte, di un file che si vuole creare o sovrascrivere. Non rilevante al momento.
FileAttributes è un valore che indica gli attributi iniziali per un file o una directory che si vuole creare o sovrascrivere. Nei casi dove è richiesto viene specificato quasi sempre FILE_ATTRIBUTE_NORMAL.
ShareAccess è un valore (o una combinazione di valori) che indica se e come si vuole permettere ad altri thread che chiamano ZwCreateFile di condividere l'oggetto. Ad esempio, una condivisione in lettura, scrittura o entrambe. Passando zero equivale a tenersi l'accesso esclusivo all'oggetto. Inoltre si può passare un valore che permette ad altri thread di ottenere (sempre tramite ZwCreateFile) un handle all'oggetto allo scopo di eliminarlo.
CreateDisposition è un valore che specifica due azioni da intraprendere in base al fatto che il file esista o meno. Ad esempio, passando FILE_CREATE se il file esiste già la chiamata a ZwCreateFile fallisce (ritorna un codice di errore) altrimenti, se il file non esiste, la chiamata ha successo e crea il file. Passando invece FILE_OPEN si ha l'effetto opposto: successo se il file esiste, fallimento se non esiste. Passando FILE_OPEN_IF si ha successo in entrambi i casi.
CreateOptions è un valore (spesso una combinazione di valori) che indica le opzioni da applicare quando si apre o si crea un file o una directory. Tra i più importanti ci sono:
- FILE_DIRECTORY_FILE e FILE_NON_DIRECTORY_FILE che indicano se il file è una directory o no.
- FILE_SEQUENTIAL_ONLY e FILE_RANDOM_ACCESS che indicano come avverrà l'accesso al file.
- FILE_NO_INTERMEDIATE_BUFFERING indica che il file non può essere sottoposto a cache nei buffer temporanei del driver.
- FILE_OPEN_FOR_BACKUP_INTENT viene usato solitamente quando si vogliono elencare i file di una directory nel qual caso viene usato in combinazione con FILE_DIRECTORY_FILE e, allo stesso tempo, viene specificato anche FILE_LIST_DIRECTORY in DesiredAccess.
- FILE_SYNCHRONOUS_IO_ALERT e FILE_SYNCHRONOUS_IO_NONALERT si usano se è stato specificato SYNCHRONIZE in DesiredAccess. Entrambe indicano che le operazioni sul file verranno eseguite in modo sincronizzato: questo significa che se una operazione di I/O sul file è pendente tutte le altre eventuali richieste di I/O sul file (anche se fatte da altri thread) verranno messe in attesa e processate solo quando la richiesta pendente viene completata. L'unica differenza sta nel modo in cui vengono gestite le attese: specificando FILE_SYNCHRONOUS_IO_ALERT l'attesa può terminare a causa di un allerta. Al contrario, specificando FILE_SYNCHRONOUS_IO_ALERT l'attesa non può terminare a causa di un allerta ma solo quando l'oggetto su cui si è in attesa viene segnalato.
EaBuffer e EaLength non sono rilevanti.
Alla fine delle operazioni, quando l'handle non serve più, è necessario chiamare ZwClose
NTSYSCALLAPI NTSTATUS ZwClose( HANDLE Handle );
Grazie a queste prime informazioni è possibile iniziare a scrivere qualcosa. Tutti gli esempi di codice presenti in questa lezione sono tratti da [1].
BOOLEAN MyCreateFile(UNICODE_STRING ustrFilePath) { HANDLE hFile = NULL; OBJECT_ATTRIBUTES objectAttributes = { 0 }; IO_STATUS_BLOCK iosb = { 0 }; NTSTATUS status = STATUS_SUCCESS; InitializeObjectAttributes( &objectAttributes, &ustrFilePath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); // GENERIC_READ = STANDARD_RIGHTS_READ | FILE_READ_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA | SYNCHRONIZE status = ZwCreateFile( &hFile, GENERIC_READ, &objectAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); if (!NT_SUCCESS(status)) { ShowError("ZwCreateFile", status); return FALSE; } ZwClose(hFile); return TRUE; }
BOOLEAN MyCreateFileFolder(UNICODE_STRING ustrFileFolderPath) { HANDLE hFile = NULL; OBJECT_ATTRIBUTES objectAttributes = { 0 }; IO_STATUS_BLOCK iosb = { 0 }; NTSTATUS status = STATUS_SUCCESS; InitializeObjectAttributes( &objectAttributes, &ustrFileFolderPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = ZwCreateFile( &hFile, GENERIC_READ, &objectAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE, FILE_DIRECTORY_FILE, NULL, 0); if (!NT_SUCCESS(status)) { ShowError("ZwCreateFile", status); return FALSE; } ZwClose(hFile); return TRUE; }
Altre tre funzioni utili sono ZwReadFile, ZwWriteFile e ZwDeleteFile.
NTSYSCALLAPI NTSTATUS ZwReadFile( HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key );
NTSYSCALLAPI NTSTATUS ZwWriteFile( HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key );
FileHandle è l'handle al file o alla directory ottenuto tramite ZwCreateFile, IoCreateFile, ZwOpenFile o API equivalente a cui è stato passato un valore appropriato nel parametro DesiredAccess: FILE_READ_DATA o GENERIC_READ per ZwReadFile. FILE_WRITE_DATA, FILE_APPEND_DATA, o GENERIC_WRITE per ZwWriteFile.
Event, ApcRoutine e ApcContext non sono rilevanti.
IoStatusBlock è un puntatore ad una struttura di tipo IO_STATUS_BLOCK che riceve lo stato finale della richiesta. In particolare, il campo Information della struttura IO_STATUS_BLOCK riceverà il numero di byte letti (ZwReadFile) dal file o scritti (ZwWriteFile) sul file.
Buffer è un puntatore ad un buffer che conterrà i dati da leggere (ZwReadFile) dal file o da scrivere (ZwWriteFile) sul file.
Length è un valore che indica la dimensione in byte del Buffer.
ByteOffset è un puntatore ad una variabile di tipo intero che indica l'offset da cui iniziare a leggere (ZwReadFile) o scrivere (ZwWriteFile) nel file.
Key non è rilevante.
NTSYSAPI NTSTATUS ZwDeleteFile( POBJECT_ATTRIBUTES ObjectAttributes );
ObjectAttributes è un puntatore ad una struttura di tipo OBJECT_ATTRIBUTES che deve essere inizializzata con vari attributi, tra cui il nome (spesso completamente qualificato) dell'oggetto kernel (file o directory) che si vuole eliminare.
Grazie a queste ulteriori informazioni è possibile scrivere qualcosa di ancora più interessante.
BOOLEAN MyReadFile(UNICODE_STRING ustrFileName, LARGE_INTEGER liOffset, PUCHAR pReadData, PULONG pulReadDataSize) { HANDLE hFile = NULL; IO_STATUS_BLOCK iosb = { 0 }; OBJECT_ATTRIBUTES objectAttributes = { 0 }; NTSTATUS status = STATUS_SUCCESS; InitializeObjectAttributes( &objectAttributes, &ustrFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = ZwCreateFile(&hFile, GENERIC_READ, &objectAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); if (!NT_SUCCESS(status)) { ShowError("ZwCreateFile", status); return FALSE; } RtlZeroMemory(&iosb, sizeof(iosb)); status = ZwReadFile(hFile, NULL, NULL, NULL, &iosb, pReadData, *pulReadDataSize, &liOffset, NULL); if (!NT_SUCCESS(status)) { *pulReadDataSize = iosb.Information; ZwClose(hFile); ShowError("ZwCreateFile", status); return FALSE; } *pulReadDataSize = iosb.Information; ZwClose(hFile); return TRUE; }
BOOLEAN MyWriteFile(UNICODE_STRING ustrFileName, LARGE_INTEGER liOffset, PUCHAR pWriteData, PULONG pulWriteDataSize) { HANDLE hFile = NULL; IO_STATUS_BLOCK iosb = { 0 }; OBJECT_ATTRIBUTES objectAttributes = { 0 }; NTSTATUS status = STATUS_SUCCESS; InitializeObjectAttributes( &objectAttributes, &ustrFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = ZwCreateFile(&hFile, GENERIC_WRITE, &objectAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); if (!NT_SUCCESS(status)) { ShowError("ZwCreateFile", status); return FALSE; } RtlZeroMemory(&iosb, sizeof(iosb)); status = ZwWriteFile(hFile, NULL, NULL, NULL, &iosb, pWriteData, *pulWriteDataSize, &liOffset, NULL); if (!NT_SUCCESS(status)) { *pulWriteDataSize = iosb.Information; ZwClose(hFile); ShowError("ZwCreateFile", status); return FALSE; } *pulWriteDataSize = iosb.Information; ZwClose(hFile); return TRUE; }
BOOLEAN MyCopyFile(UNICODE_STRING ustrScrFile, UNICODE_STRING ustrDestFile) { ULONG ulBufferSize = 4096; LARGE_INTEGER liOffset = { 0 }; PUCHAR pBuffer = ExAllocatePool(NonPagedPool, ulBufferSize); do { ULONG ulReadDataSize = ulBufferSize; MyReadFile(ustrScrFile, liOffset, pBuffer, &ulReadDataSize); if (ulReadDataSize <= 0) { break; } MyWriteFile(ustrDestFile, liOffset, pBuffer, &ulReadDataSize); liOffset.QuadPart = liOffset.QuadPart + ulReadDataSize; } while (TRUE); ExFreePool(pBuffer); return TRUE; }
Altre due funzioni importanti sono ZwQueryInformationFile e ZwSetInformationFile che servono, rispettivamente, a recuperare ed impostare informazioni su file e directory.
NTSYSCALLAPI NTSTATUS ZwQueryInformationFile( HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, ULONG Length, FILE_INFORMATION_CLASS FileInformationClass );
NTSYSAPI NTSTATUS ZwSetInformationFile( HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, ULONG Length, FILE_INFORMATION_CLASS FileInformationClass );
FileHandle è un handle al file o alla directory ottenuto tramite ZwCreateFile, IoCreateFile, ZwOpenFile o API equivalente.
IoStatusBlock è un puntatore ad una struttura di tipo IO_STATUS_BLOCK che riceve lo stato finale della richiesta. In particolare, il campo Information della struttura IO_STATUS_BLOCK riceverà il numero di byte scritti nel buffer indicato dal parametro FileInformation (ZwQueryInformationFile) o scritti sul file (ZwSetInformationFile).
FileInformation è un puntatore ad un buffer che conterrà le informazioni da leggere dal file (ZwQueryInformationFile) o da scrivere sul file (ZwSetInformationFile).
Length è un valore che indica la dimensione in byte del buffer indicato dal parametro FileInformation.
FileInformationClass è un valore che indica il tipo del buffer indicato da FileInformation e che conterrà le informazioni da scrivere dal file su tale buffer (ZwQueryInformationFile) o da tale buffer sul file (ZwSetInformationFile). Esistono tanti valori che è possibile passare come argomento per questo parametro, si vedano [2] e [3].
A seguire un paio di esempi di codice basati su quanto appena illustrato.
/* typedef struct _FILE_STANDARD_INFORMATION { LARGE_INTEGER AllocationSize; LARGE_INTEGER EndOfFile; ULONG NumberOfLinks; BOOLEAN DeletePending; BOOLEAN Directory; } FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION; */ ULONG64 MyGetFileSize(UNICODE_STRING ustrFileName) { HANDLE hFile = NULL; OBJECT_ATTRIBUTES objectAttributes = { 0 }; IO_STATUS_BLOCK iosb = { 0 }; NTSTATUS status = STATUS_SUCCESS; FILE_STANDARD_INFORMATION fsi = { 0 }; InitializeObjectAttributes(&objectAttributes, &ustrFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = ZwCreateFile(&hFile, GENERIC_READ, &objectAttributes, &iosb, NULL, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); if (!NT_SUCCESS(status)) { ShowError("ZwCreateFile", status); return 0; } status = ZwQueryInformationFile(hFile, &iosb, &fsi, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation); if (!NT_SUCCESS(status)) { ZwClose(hFile); ShowError("ZwQueryInformationFile", status); return 0; } // EndOfFile indica l'offset in byte dall'inizio del file al byte successivo all'ultimo byte valido. return fsi.EndOfFile.QuadPart; }
/* typedef struct _FILE_RENAME_INFORMATION { BOOLEAN ReplaceIfExists; HANDLE RootDirectory; ULONG FileNameLength; WCHAR FileName[1]; } FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION; */ BOOLEAN MyRenameFileOrFileFolder(UNICODE_STRING ustrSrcFileName, UNICODE_STRING ustrDestFileName) { HANDLE hFile = NULL; OBJECT_ATTRIBUTES objectAttributes = { 0 }; IO_STATUS_BLOCK iosb = { 0 }; NTSTATUS status = STATUS_SUCCESS; PFILE_RENAME_INFORMATION pRenameInfo = NULL; // Come si può notare FileName è l'ultimo campo di FILE_RENAME_INFORMATION ed è quello che // contiene la stringa con il nome del file ma mentre nella struttura è definito come un singolo // carattere in memoria di fatto la stringa segue in maniera contigua la struttura FILE_RENAME_INFORMATION. // Per questa ragione è necessario allocare un po' più di spazio per tale stringa. ULONG ulLength = (1024 + sizeof(FILE_RENAME_INFORMATION)); pRenameInfo = (PFILE_RENAME_INFORMATION)ExAllocatePool(NonPagedPool, ulLength); if (NULL == pRenameInfo) { ShowError("ExAllocatePool", 0); return FALSE; } RtlZeroMemory(pRenameInfo, ulLength); pRenameInfo->FileNameLength = ustrDestFileName.Length; wcscpy(pRenameInfo->FileName, ustrDestFileName.Buffer); // Se ReplaceIfExists è TRUE ed esiste un file con lo stesso nome fornito in FileName // questo verrà sostituito con il file che si va a rinominare. // Se ReplaceIfExists è FALSE ed esiste un file con lo stesso nome fornito in FileName // l'operazione di rinomina fallisce. pRenameInfo->ReplaceIfExists = 0; // Se RootDirectory è NULL il campo FileName deve contenere il percorso completo del file. pRenameInfo->RootDirectory = NULL; InitializeObjectAttributes(&objectAttributes, &ustrSrcFileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = ZwCreateFile(&hFile, SYNCHRONIZE | DELETE, &objectAttributes, &iosb, NULL, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT | FILE_NO_INTERMEDIATE_BUFFERING, NULL, 0); if (!NT_SUCCESS(status)) { ExFreePool(pRenameInfo); ShowError("ZwCreateFile", status); return FALSE; } status = ZwSetInformationFile(hFile, &iosb, pRenameInfo, ulLength, FileRenameInformation); if (!NT_SUCCESS(status)) { ZwClose(hFile); ExFreePool(pRenameInfo); ShowError("ZwSetInformationFile", status); return FALSE; } ExFreePool(pRenameInfo); ZwClose(hFile); return TRUE; }
L'ultima funzione presa in esame in questa lezione è ZwQueryDirectoryFile che recupera informazioni su tutti i file di una data directory.
NTSYSCALLAPI NTSTATUS ZwQueryDirectoryFile( HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID FileInformation, ULONG Length, FILE_INFORMATION_CLASS FileInformationClass, BOOLEAN ReturnSingleEntry, PUNICODE_STRING FileName, BOOLEAN RestartScan );
FileHandle è l'handle alla directory ottenuto tramite ZwCreateFile, IoCreateFile, ZwOpenFile o API equivalente.
Event, ApcRoutine e ApcContext non sono rilevanti.
IoStatusBlock è un puntatore ad una struttura di tipo IO_STATUS_BLOCK che riceve lo stato finale della richiesta. In particolare, il campo Information della struttura IO_STATUS_BLOCK riceverà il numero di byte scritti nel buffer indicato dal parametro FileInformation.
FileInformation è un puntatore ad un buffer che conterrà le informazioni recuperate sui file della directory.
Length è un valore che indica la dimensione in byte del Buffer indicato dal parametro FileInformation.
FileInformationClass è un valore che indica il tipo di info da recuperare. Se ZwQueryDirectoryFile ha successo il buffer indicato dal parametro FileInformation conterrà una struttura di tale tipo per ogni file della directory (a meno che non si diano direttive diverse: si vedano parametri ReturnSingleEntry e FileName) disposte in modo contiguo nel buffer finché c'è spazio disponibile in quest'ultimo. Il parametro Length dovrebbe essere impostato ad un multiplo della dimensione del tipo specificato dal parametro FileInformationClass. Esistono vari valori per FileInformationClass, si veda [4] per maggiori dettagli.
ReturnSingleEntry è un booleano che se TRUE indica che verrà recuperata e messa nel buffer indicato dal parametro FileInformation solo una singola entry del tipo indicato dal parametro FileInformationClass.
FileName è un puntatore ad una stringa che, se non è NULL, indica il nome del file (o dei file: le wildcard sono ammesse) a cui si è interessati. Solo entry del tipo indicato dal parametro FileInformationClass che soddisfano tale criterio verranno recuperate e messe nel buffer indicato dal parametro FileInformation.
RestartScan è un booleano che se è TRUE indica che la scansione dei file nella directory deve ripartire dall'inizio. Se invece è FALSE la scansione riprenderà dalla chiamata precedente.
Segue un esempio di codice che permette di elencare i file contenuti in una certa directory.
/* typedef struct _FILE_BOTH_DIR_INFORMATION { ULONG NextEntryOffset; ULONG FileIndex; LARGE_INTEGER CreationTime; LARGE_INTEGER LastAccessTime; LARGE_INTEGER LastWriteTime; LARGE_INTEGER ChangeTime; LARGE_INTEGER EndOfFile; LARGE_INTEGER AllocationSize; ULONG FileAttributes; ULONG FileNameLength; ULONG EaSize; CCHAR ShortNameLength; WCHAR ShortName[12]; WCHAR FileName[1]; } FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION; */ BOOLEAN MyQueryFileAndFileFolder(UNICODE_STRING ustrPath) { HANDLE hFile = NULL; OBJECT_ATTRIBUTES objectAttributes = { 0 }; IO_STATUS_BLOCK iosb = { 0 }; NTSTATUS status = STATUS_SUCCESS; InitializeObjectAttributes(&objectAttributes, &ustrPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); status = ZwCreateFile(&hFile, FILE_LIST_DIRECTORY | SYNCHRONIZE | FILE_ANY_ACCESS, &objectAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT, NULL, 0); if (!NT_SUCCESS(status)) { ShowError("ZwCreateFile", status); return FALSE; } // FileName campo di tipo stringa che segue in maniera contigua la struttura FILE_BOTH_DIR_INFORMATION quindi // è necessario allocare qualcosa in più che un semplice multiplo di sizeof(FILE_BOTH_DIR_INFORMATION). // Le versioni UNICODE delle funzioni che fanno parte dell'API di Windows permettono // lunghezze estese per i percorsi dei file. Questo vuol dire che un percorso può avere fino a 32,767 caratteri. // Nella maggior parte dei casi non è necessario spingersi fino a tanto (4096 caratteri sembra una lunghezza // più che ragionevole). // 4096 x 2 perchè si deve allocare spazio in byte e stringhe UNICODE hanno 2 byte per carattere. // Il buffer potrà contenere fino a 0x2000 entry (quindi info per 0x2000 = 8192 file). ULONG ulLength = (2 * 4096 + sizeof(FILE_BOTH_DIR_INFORMATION)) * 0x2000; PFILE_BOTH_DIR_INFORMATION pDir = ExAllocatePool(PagedPool, ulLength); // Salva indirizzo del buffer per passarlo poi a ExFreePool e liberare la memoria allocata. PFILE_BOTH_DIR_INFORMATION pBeginAddr = pDir; status = ZwQueryDirectoryFile( hFile, NULL, NULL, NULL, &iosb, pDir, ulLength, FileBothDirectoryInformation, FALSE, NULL, FALSE); if (!NT_SUCCESS(status)) { ExFreePool(pDir); ZwClose(hFile); ShowError("ZwQueryDirectoryFile", status); return FALSE; } UNICODE_STRING ustrTemp; UNICODE_STRING ustrOne; UNICODE_STRING ustrTwo; RtlInitUnicodeString(&ustrOne, L"."); RtlInitUnicodeString(&ustrTwo, L".."); WCHAR wcFileName[1024] = { 0 }; while (TRUE) { // Salva i nomi dei file in un array di 1024 caratteri. // Per tali nomi si erano allocati 4096x2 byte alla fine di ogni entry ma in fase di // visualizzazione 1024 sono più che sufficienti. RtlZeroMemory(wcFileName, 1024); RtlCopyMemory(wcFileName, pDir->FileName, pDir->FileNameLength); RtlInitUnicodeString(&ustrTemp, wcFileName); // Esclude i file che rappresentano la directory corrente e quella genitore. if ((0 != RtlCompareUnicodeString(&ustrTemp, &ustrOne, TRUE)) && (0 != RtlCompareUnicodeString(&ustrTemp, &ustrTwo, TRUE))) { if (pDir->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { DbgPrint("[DIRECTORY]\t%wZ\n", &ustrTemp); } else { DbgPrint("[FILE]\t\t%wZ\n", &ustrTemp); } } // Ultima entry ha sempre campo NextEntryOffset azzerato; if (pDir->NextEntryOffset == 0) { DbgPrint("\n[QUERY OVER]\n\n"); break; } // NextEntryOffset contiene offset in byte di prossima entry a partire da entry corrente. pDir = (PFILE_BOTH_DIR_INFORMATION)((PUCHAR)pDir + pDir->NextEntryOffset); } ExFreePool(pBeginAddr); ZwClose(hFile); return TRUE; }
Infine, segue il codice di DriverEntry per testare gli esempi discussi fino ad ora.
VOID DriverUnload(PDRIVER_OBJECT pDriverObject) { } NTSTATUS DriverDefaultHandle(PDEVICE_OBJECT pDevObj, PIRP pIrp) { NTSTATUS status = STATUS_SUCCESS; pIrp->IoStatus.Status = status; pIrp->IoStatus.Information = 0; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return status; } NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegPath) { DbgPrint("Enter DriverEntry\n"); NTSTATUS status = STATUS_SUCCESS; pDriverObject->DriverUnload = DriverUnload; for (ULONG i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { pDriverObject->MajorFunction[i] = DriverDefaultHandle; } UNICODE_STRING ustrDirectory; RtlInitUnicodeString(&ustrDirectory, L"\\??\\C:\\MyCreateFolder"); MyCreateFileFolder(ustrDirectory); UNICODE_STRING ustrCreateFile; RtlInitUnicodeString(&ustrCreateFile, L"\\??\\C:\\MyCreateFolder\\MyCreateFile.txt"); MyCreateFile(ustrCreateFile); UNICODE_STRING ustrOldFile, ustrNewFile; RtlInitUnicodeString(&ustrOldFile, L"\\??\\C:\\MyCreateFolder\\MyCreateFile.txt"); RtlInitUnicodeString(&ustrNewFile, L"\\??\\C:\\MyCreateFolder\\MyNewFile.txt"); MyRenameFileOrFileFolder(ustrOldFile, ustrNewFile); UNICODE_STRING ustrQueryFile; RtlInitUnicodeString(&ustrQueryFile, L"\\??\\C:\\MyCreateFolder"); MyQueryFileAndFileFolder(ustrQueryFile); UNICODE_STRING ustrDeleteFile; RtlInitUnicodeString(&ustrDeleteFile, L"\\??\\C:\\MyCreateFolder\\MyNewFile.txt"); MyDeleteFileOrFileFolder(ustrDeleteFile); UNICODE_STRING ustrFileSize; RtlInitUnicodeString(&ustrFileSize, L"\\??\\C:\\MyFolder\\1.exe"); ULONG64 ullFileSize = MyGetFileSize(ustrFileSize); DbgPrint("FileSize = %I64d Bytes\n", ullFileSize); UNICODE_STRING ustrScrFile, ustrDestFile; RtlInitUnicodeString(&ustrScrFile, L"\\??\\C:\\MyFolder\\1.exe"); RtlInitUnicodeString(&ustrDestFile, L"\\??\\C:\\MyFolder\\2.exe"); MyCopyFile(ustrScrFile, ustrDestFile); DbgPrint("Leave DriverEntry\n"); return status; }
Riferimenti:
[1] https://github.com/DemonGan/Windows-Hack-Programming
[2] https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-ntqueryinformationfile
[3] https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-zwsetinformationfile
[4] https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-ntquerydirectoryfile
[5] https://docs.microsoft.com/en-us/windows/win32/api/ntdef/nf-ntdef-initializeobjectattributes
Nessun commento:
Posta un commento