Kernel Mode Drivers

Part 11: Basic Technique: Directories and Files



Ensuring the ability to work with files is the most important task of any operating system. Let's see what opportunities are provided to us by operating systems of the NT family.


11.1 Kernel descriptor table

Before we go directly to the topic of the article, we will discuss one important point that has not been given due attention before. To get an object descriptor, you need to fill in the OBJECT_ATTRIBUTES structure - we have already done this many times in something like this:


 InitializeObjectAttributes addr oa, addr g_usName, OBJ_CASE_INSENSITIVE, NULL, NULL

Having initialized the OBJECT_ATTRIBUTES structure, we called the function of creating / opening an object and got its handle(hande). But this descriptor ended up in the descriptor table of the process in the context of which it was received. Because Since the descriptor table is specific to a process, such a descriptor can only be used in the context of the same process. For example, an attempt to close this handle in the context of another process will fail at best. And at worst, if a descriptor with the same value appears in the descriptor table of this process, because the descriptor is just a 32-bit number (more precisely, a bit structure), the descriptor of a completely different object may be closed. Even if the handle is received by the driver, but in the context of a user process, it will end up in the handle of this process and it can be used to access the object from user mode, intentionally or accidentally. There are situations when this is exactly what is required, but in general this is not what you want. That is why kernel components, and drivers in particular, do not "like" to use descriptors, but prefer to usereferences to objects (reference to object), which are simple pointers to the structure of the object in kernel memory. To account for the provided object references, a reference count is stored in the header of each object. If access to the object needs is a handle, as in this and the previous example, and you want to refer to it in different contexts, it is necessary to force the system to place a descriptor in the so-called descriptor table core (kernel handle table).

Starting with Windows 2000, a special kernel descriptor table has been added to the system. The descriptors in this table are only available in kernel mode in the context of any process, and differ from the process specific descriptors in the high bit set.

Even if you get the handle in the context of the System process, for example, in the DriverEntry procedure , you cannot refer to the object in the context of a user process using it. Those. the System process uses its own descriptor table, different from the kernel descriptor table.

In order for the handle to get into the kernel handle table, you need to explicitly specify the OBJ_KERNEL_HANDLE flag in the call to the InitializeObjectAttributes macro as follows:


 InitializeObjectAttributes addr oa, addr g_usName, OBJ_KERNEL_HANDLE, NULL, NULL


11.2 FileWorks Driver Source Code

As in the previous example, the driver code consists of several stand-alone procedures: CreateDirectory , CreateFile , WriteFile , MarkAsReadOnly , ReadFile , UnmarkAsReadOnly , AppendFile , TruncateFile , DeleteFile , DeleteDirectory, and EnumerateFiles . They all work almost independently of each other.


 ;@echo off
 ;goto make

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;
 ;  FileWorks - Пример различных операций с файлами.
 ;
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 .386
 .model flat, stdcall
 option casemap:none

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                               В К Л Ю Ч А Е М Ы Е    Ф А Й Л Ы
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 include \masm32\include\w2k\ntstatus.inc
 include \masm32\include\w2k\ntifs.inc
 include \masm32\include\w2k\ntoskrnl.inc

 includelib \masm32\lib\w2k\ntoskrnl.lib

 include \masm32\Macros\Strings.mac

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                             Н Е И З М Е Н Я Е М Ы Е    Д А Н Н Ы Е                                
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 .const

 CCOUNTED_UNICODE_STRING "\\??\\c:\\FileWorks\\test.txt", g_usFileName, 4
 CCOUNTED_UNICODE_STRING "\\??\\c:\\FileWorks", g_usDirName, 4

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                           К О Д                                                   
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 .code

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                      CreateDirectory                                              
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 CreateDirectory proc

 local oa:OBJECT_ATTRIBUTES
 local iosb:IO_STATUS_BLOCK
 local hDirectory:HANDLE

     ; Помни, что коды форматирования Unicode (%C, %S, %lc, %ls, %wc, %ws, %wZ), передаваемые в
     ; функцию DbgPrint могут быть использованы только на IRQL = PASSIVE_LEVEL!
     invoke DbgPrint, $CTA0("\nFileWorks: Creating %ws directory\n"), g_usDirName.Buffer

     InitializeObjectAttributes addr oa, addr g_usDirName, \
                         OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

     invoke ZwCreateFile, addr hDirectory, SYNCHRONIZE, addr oa, addr iosb, 0, FILE_ATTRIBUTE_NORMAL, \
                         0, FILE_OPEN_IF, FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
     .if eax == STATUS_SUCCESS
         .if iosb.Information == FILE_CREATED
             invoke DbgPrint, $CTA0("FileWorks: Directory created\n")
         .elseif iosb.Information == FILE_OPENED
             invoke DbgPrint, $CTA0("FileWorks: Directory exists and was opened\n")
         .endif
         invoke ZwClose, hDirectory
     .else
         invoke DbgPrint, $CTA0("FileWorks: Can't create directory. Status: %08X\n"), eax
     .endif
    
     ret

 CreateDirectory endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                        CreateFile                                                 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 CreateFile proc

 local oa:OBJECT_ATTRIBUTES
 local iosb:IO_STATUS_BLOCK
 local hFile:HANDLE

     ; Помни, что коды форматирования Unicode (%C, %S, %lc, %ls, %wc, %ws, %wZ), передаваемые в
     ; функцию DbgPrint могут быть использованы только на IRQL = PASSIVE_LEVEL!
     invoke DbgPrint, $CTA0("\nFileWorks: Creating %ws file\n"), g_usFileName.Buffer

     InitializeObjectAttributes addr oa, addr g_usFileName, \
                         OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

     invoke ZwCreateFile, addr hFile, SYNCHRONIZE, addr oa, addr iosb, 0, FILE_ATTRIBUTE_NORMAL, \
                         0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
     .if eax == STATUS_SUCCESS

         invoke DbgPrint, $CTA0("FileWorks: File created\n")
         invoke ZwClose, hFile
     .else
         invoke DbgPrint, $CTA0("FileWorks: Can't create file. Status: %08X\n"), eax
     .endif
    
     ret

 CreateFile endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                            WriteFile                                              
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 WriteFile proc

 local oa:OBJECT_ATTRIBUTES
 local iosb:IO_STATUS_BLOCK
 local hFile:HANDLE

     invoke DbgPrint, $CTA0("\nFileWorks: Opening file for writing\n")

     InitializeObjectAttributes addr oa, addr g_usFileName, \
                         OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL
    
     invoke ZwCreateFile, addr hFile, FILE_WRITE_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                         0, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
     .if eax == STATUS_SUCCESS
         invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

         CTA0 "Data can be written to an open file", g_szData, 4

         invoke ZwWriteFile, hFile, 0, NULL, NULL, addr iosb, \
                         addr g_szData, sizeof g_szData - 1, NULL, NULL
         .if eax == STATUS_SUCCESS
             invoke DbgPrint, $CTA0("FileWorks: File was written\n")
         .else
             invoke DbgPrint, $CTA0("FileWorks: Can't write to the file. Status: %08X\n"), eax
         .endif

         invoke ZwClose, hFile
     .else
         invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
     .endif

     ret

 WriteFile endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                        MarkAsReadOnly                                             
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 MarkAsReadOnly proc
 
 local oa:OBJECT_ATTRIBUTES
 local iosb:IO_STATUS_BLOCK
 local hFile:HANDLE
 local fbi:FILE_BASIC_INFORMATION

     invoke DbgPrint, $CTA0("\nFileWorks: Opening file for changing attributes\n")

     InitializeObjectAttributes addr oa, addr g_usFileName, \
                         OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL
    
     invoke ZwCreateFile, addr hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES + SYNCHRONIZE, \
                         addr oa, addr iosb, 0, 0, FILE_SHARE_READ, \
                         FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
     .if eax == STATUS_SUCCESS
         invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

         invoke ZwQueryInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation
         .if eax == STATUS_SUCCESS
             invoke DbgPrint, $CTA0("FileWorks: File attributes were: %08X\n"), fbi.FileAttributes
             or fbi.FileAttributes, FILE_ATTRIBUTE_READONLY
             invoke ZwSetInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation
             .if eax == STATUS_SUCCESS
                 invoke DbgPrint, $CTA0("FileWorks: Now file marked as read-only\n")
             .else
                 invoke DbgPrint, $CTA0("FileWorks: Can't change file attributes. Status: %08X\n"), eax
             .endif
         .else
             invoke DbgPrint, $CTA0("FileWorks: Can't query file attributes. Status: %08X\n"), eax
         .endif

         invoke ZwClose, hFile
     .else
         invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
     .endif

     ret

 MarkAsReadOnly endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                          ReadFile                                                 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 ReadFile proc

 local oa:OBJECT_ATTRIBUTES
 local iosb:IO_STATUS_BLOCK
 local hFile:HANDLE
 local p:PVOID
 local cb:DWORD
 local fsi:FILE_STANDARD_INFORMATION

     invoke DbgPrint, $CTA0("\nFileWorks: Opening file for reading\n")

     InitializeObjectAttributes addr oa, addr g_usFileName, \
                         OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

     invoke ZwOpenFile, addr hFile, FILE_READ_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                 FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_NONALERT
     .if eax == STATUS_SUCCESS

         invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

         invoke ZwQueryInformationFile, hFile, addr iosb, addr fsi, sizeof fsi, FileStandardInformation
         .if eax == STATUS_SUCCESS

             mov eax, fsi.EndOfFile.LowPart
             inc eax
             mov cb, eax

             invoke ExAllocatePool, PagedPool, cb
             .if eax != NULL
                 mov p, eax

                 invoke RtlZeroMemory, p, cb

                 invoke ZwReadFile, hFile, 0, NULL, NULL, addr iosb, p, cb, 0, NULL
                 .if eax == STATUS_SUCCESS
                     invoke DbgPrint, $CTA0("FileWorks: File content: \=%s\=\n"), p
                 .else
                     invoke DbgPrint, $CTA0("FileWorks: Can't read from the file. Status: %08X\n"), eax
                 .endif

                 invoke ExFreePool, p

             .else
                 invoke DbgPrint, $CTA0("FileWorks: Can't allocate memory. Status: %08X\n"), eax
             .endif
         .else
             invoke DbgPrint, $CTA0("FileWorks: Can't query file size. Status: %08X\n"), eax
         .endif

         invoke ZwClose, hFile

     .else
         invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
     .endif

     ret

 ReadFile endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                        UnmarkAsReadOnly                                           
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 UnmarkAsReadOnly proc

 local oa:OBJECT_ATTRIBUTES
 local iosb:IO_STATUS_BLOCK
 local hFile:HANDLE
 local fbi:FILE_BASIC_INFORMATION

     invoke DbgPrint, $CTA0("\nFileWorks: Opening file for changing attributes\n")

     InitializeObjectAttributes addr oa, addr g_usFileName, \
                         OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL
    
     invoke ZwCreateFile, addr hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES + SYNCHRONIZE, \
                         addr oa, addr iosb, 0, 0, FILE_SHARE_READ, FILE_OPEN, \
                         FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
     .if eax == STATUS_SUCCESS
         invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

         invoke ZwQueryInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation
         .if eax == STATUS_SUCCESS
             invoke DbgPrint, $CTA0("FileWorks: File attributes were: %08X\n"), fbi.FileAttributes
             and fbi.FileAttributes, not FILE_ATTRIBUTE_READONLY
             invoke ZwSetInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation
             .if eax == STATUS_SUCCESS
                 invoke DbgPrint, $CTA0("FileWorks: Now file can be written or deleted\n")
             .else
                 invoke DbgPrint, $CTA0("FileWorks: Can't change file attributes. Status: %08X\n"), eax
             .endif
         .else
             invoke DbgPrint, $CTA0("FileWorks: Can't query file attributes. Status: %08X\n"), eax
         .endif

         invoke ZwClose, hFile
     .else
         invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
     .endif

     ret

 UnmarkAsReadOnly endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                         AppendFile                                                
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 AppendFile proc

 local oa:OBJECT_ATTRIBUTES
 local iosb:IO_STATUS_BLOCK
 local hFile:HANDLE

     invoke DbgPrint, $CTA0("\nFileWorks: Opening file to append data\n")

     InitializeObjectAttributes addr oa, addr g_usFileName, \
                         OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

     invoke ZwOpenFile, addr hFile, FILE_APPEND_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                                     FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT
     .if eax == STATUS_SUCCESS
         invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

         CTA0 " using ZwWriteFile", g_szDataToAppend, 4

         invoke ZwWriteFile, hFile, 0, NULL, NULL, addr iosb, \
                         addr g_szDataToAppend, sizeof g_szDataToAppend - 1, NULL, NULL
         .if eax == STATUS_SUCCESS
             invoke DbgPrint, $CTA0("FileWorks: Data appended to the file\n")
         .else
             invoke DbgPrint, $CTA0("FileWorks: Can't append data to file. Status: %08X\n"), eax
         .endif

         invoke ZwClose, hFile
    .else
         invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
     .endif

     ret

 AppendFile endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                        TruncateFile                                               
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 TruncateFile proc
 
 local oa:OBJECT_ATTRIBUTES
 local iosb:IO_STATUS_BLOCK
 local hFile:HANDLE
 local fsi:FILE_STANDARD_INFORMATION
 local feofi:FILE_END_OF_FILE_INFORMATION

     invoke DbgPrint, $CTA0("\nFileWorks: Opening file to truncate\n")

     InitializeObjectAttributes addr oa, addr g_usFileName, \
                         OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

     invoke ZwOpenFile, addr hFile, FILE_WRITE_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                         FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT
     .if eax == STATUS_SUCCESS
         invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

         invoke ZwQueryInformationFile, hFile, addr iosb, \
                         addr fsi, sizeof fsi, FileStandardInformation
         .if eax == STATUS_SUCCESS

             invoke DbgPrint, $CTA0("FileWorks: EOF was: %08X\n"), fsi.EndOfFile.LowPart

             and feofi.EndOfFile.HighPart, 0
             mov eax, fsi.EndOfFile.LowPart
             shr eax, 1
             mov feofi.EndOfFile.LowPart, eax
             invoke ZwSetInformationFile, hFile, addr iosb, \
                         addr feofi, sizeof feofi, FileEndOfFileInformation
             .if eax == STATUS_SUCCESS
                 invoke DbgPrint, $CTA0("FileWorks: File truncated to its half size\n")
             .else
                 invoke DbgPrint, $CTA0("FileWorks: Can't truncate file. Status: %08X\n"), eax       
             .endif

         .else
             invoke DbgPrint, $CTA0("FileWorks: Can't query file info. Status: %08X\n"), eax
         .endif

         invoke ZwClose, hFile
     .else
         invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
     .endif

     ret

 TruncateFile endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                         DeleteFile                                                
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 DeleteFile proc

 local oa:OBJECT_ATTRIBUTES
 local iosb:IO_STATUS_BLOCK
 local hFile:HANDLE
 local fdi:FILE_DISPOSITION_INFORMATION

     invoke DbgPrint, $CTA0("\nFileWorks: Opening file for deletion")

     InitializeObjectAttributes addr oa, addr g_usFileName, \
                         OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

     invoke ZwCreateFile, addr hFile, DELETE + SYNCHRONIZE, addr oa, addr iosb, \
                         0, 0, FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
     .if eax == STATUS_SUCCESS
         invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

         mov fdi.DeleteFile, TRUE
         invoke ZwSetInformationFile, hFile, addr iosb, addr fdi, sizeof fdi, FileDispositionInformation
         .if eax == STATUS_SUCCESS
             ; The file has been marked for deletion. Do nothing with the file handle except closing it.
             invoke DbgPrint, $CTA0("FileWorks: File has been marked for deletion\n")
             invoke DbgPrint, $CTA0("FileWorks: It should be deleted when the last open handle is closed\n")
         .else
             invoke DbgPrint, $CTA0("FileWorks: Can't mark file for deletion. Status: %08X\n"), eax
         .endif

         invoke ZwClose, hFile
     .else
         invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
     .endif

     ret

 DeleteFile endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                       DeleteDirectory                                             
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 DeleteDirectory proc

 local oa:OBJECT_ATTRIBUTES
 local iosb:IO_STATUS_BLOCK
 local hDirectory:HANDLE

     InitializeObjectAttributes addr oa, addr g_usDirName, \
                         OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

     invoke ZwDeleteFile, addr oa
     .if eax == STATUS_SUCCESS
         invoke DbgPrint, $CTA0("\nFileWorks: Directory should be deleted\n")            
     .else
         invoke DbgPrint, $CTA0("\nFileWorks: Can't delete directory. Status: %08X\n"), eax
     .endif

     ret

 DeleteDirectory endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                      EnumerateFiles                                               
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 EnumerateFiles proc uses esi

 local status:NTSTATUS
 local oa:OBJECT_ATTRIBUTES
 local hSystemRootDirectory:HANDLE
 local hDriversDirectory:HANDLE
 local as:ANSI_STRING
 local us:UNICODE_STRING
 local iosb:IO_STATUS_BLOCK
 local tf:TIME_FIELDS
 local cb:DWORD
 local pfdi:PFILE_DIRECTORY_INFORMATION 

     invoke DbgPrint, $CTA0("\nFileWorks: Opening directory to enumerate files\n")
    
     InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("\\SystemRoot"), \
                                 OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

     invoke ZwOpenFile, addr hSystemRootDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE, addr oa, \
                         addr iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, \
                         FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT
     .if eax == STATUS_SUCCESS

         InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("system32\\drivers"), \
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, hSystemRootDirectory, NULL

         invoke ZwOpenFile, addr hDriversDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE, addr oa, \
                             addr iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, \
                             FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT
         .if eax == STATUS_SUCCESS

             mov cb, sizeof FILE_DIRECTORY_INFORMATION + 256

             invoke ExAllocatePool, PagedPool, cb
             .if eax != NULL

                 mov pfdi, eax
                 mov esi, eax
                 assume esi:ptr FILE_DIRECTORY_INFORMATION

                 invoke DbgPrint, \
                         $CTA0("\nFileWorks: ---------- Starting enumerate files ----------\n")

                 invoke ZwQueryDirectoryFile, hDriversDirectory, NULL, NULL, NULL, addr iosb, \
                             esi, cb, FileDirectoryInformation, \
                             TRUE, $CCOUNTED_UNICODE_STRING("c*"), TRUE

                 .while eax != STATUS_NO_MORE_FILES

                     .if ( eax == STATUS_SUCCESS )
 
                         mov eax, [esi].FileNameLength
                         mov us._Length, ax
                         mov us.MaximumLength, ax
                         lea eax, [esi].FileName
                         mov us.Buffer, eax
                         invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
                         .if eax == STATUS_SUCCESS

                             invoke RtlTimeToTimeFields, addr [esi].CreationTime, addr tf
                             movzx eax, tf.Day
                             movzx ecx, tf.Month
                             movzx edx, tf.Year

                             invoke DbgPrint, $CTA0("    %s   size=%d   created on %d.%02d.%04d\n"), \
                                         as.Buffer, [esi].EndOfFile.LowPart, eax, ecx, edx

                             invoke RtlFreeAnsiString, addr as
                         .endif

                     .endif

                     invoke ZwQueryDirectoryFile, hDriversDirectory, NULL, NULL, NULL, addr iosb, \
                                 esi, cb, FileDirectoryInformation, \
                                 TRUE, NULL, FALSE
                 .endw
                 invoke DbgPrint, \
                     $CTA0("FileWorks: ------------------------------------------------\n")

                 assume esi:nothing
                 invoke ExFreePool, pfdi
             .endif
             invoke ZwClose, hDriversDirectory
         .else
             invoke DbgPrint, $CTA0("FileWorks: Can't open drivers directory. Status: %08X\n"), eax
         .endif
         invoke ZwClose, hSystemRootDirectory
     .else
         invoke DbgPrint, $CTA0("FileWorks: Can't open system root directory. Status: %08X\n"), eax
     .endif

     ret

 EnumerateFiles endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                       DriverEntry                                                 
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING

     invoke DbgPrint, $CTA0("\nFileWorks: Entering DriverEntry\n")

     invoke CreateDirectory
     invoke CreateFile
     invoke WriteFile
     invoke MarkAsReadOnly
     invoke ReadFile
     invoke UnmarkAsReadOnly
     invoke AppendFile
     invoke ReadFile
     invoke TruncateFile
     invoke ReadFile
     invoke DeleteFile
     invoke DeleteDirectory
     invoke EnumerateFiles

     invoke DbgPrint, $CTA0("\nFileWorks: Leaving DriverEntry\n")

     mov eax, STATUS_DEVICE_CONFIGURATION_ERROR
     ret

 DriverEntry endp

 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 ;                                                                                                   
 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

 end DriverEntry

 :make

 set drv=FileWorks

 \masm32\bin\ml /nologo /c /coff %drv%.bat
 \masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native %drv%.obj

 del %drv%.obj

 echo.
 pause

Ntddk.inc does not include some of the constants and structures we need.


 include \masm32\include\w2k\ntifs.inc

The Installable File System (IFS) Kit for developing file system drivers includes the ntifs.h header file, which is analogous to the ntifs.inc include file. This file has been included with KmdKit since version 1.5.


11.3 Create directory and file


     InitializeObjectAttributes addr oa, addr g_usDirName, \
                         OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

We fill in the OBJECT_ATTRIBUTES structure, not forgetting about the OBJ_KERNEL_HANDLE flag. I want to emphasize that in this example this is not necessary, since we are not going to use any of the descriptors in the context of any other process. But since the FileWorks driver routines can easily be used in other projects in the context of any process, I decided to do just that. If your task is to share the handle with the driver and the user process, then the OBJ_KERNEL_HANDLE flag should not be used. If the object will be accessed in the context of the same process, the OBJ_KERNEL_HANDLE flag is not needed.

The ZwCreateFile function is used to create both the directory and the file . From the point of view of the system, directories are the same files, so there are no fundamental differences in the procedure for creating a file and a directory. Therefore, everything said below regarding directory creation applies equally to files, with the exception of the FILE_DIRECTORY_FILE flag.


     invoke ZwCreateFile, addr hDirectory, SYNCHRONIZE, addr oa, addr iosb, 0, FILE_ATTRIBUTE_NORMAL, \
                         0, FILE_OPEN_IF, FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0

The ZwCreateFile function has quite a few parameters. Therefore, I will cite her prototype. And I have to do it using the syntax of the C language, so that the input (IN), output (OUT) and optional (OPTIONAL) parameters are visible.


NTSTATUS  
  ZwCreateFile(
    OUT PHANDLE             FileHandle,
    IN  ACCESS_MASK         DesiredAccess,
    IN  POBJECT_ATTRIBUTES  ObjectAttributes,
    OUT PIO_STATUS_BLOCK    IoStatusBlock,
    IN  PLARGE_INTEGER      AllocationSize  OPTIONAL,
    IN  ULONG               FileAttributes,
    IN  ULONG               ShareAccess,
    IN  ULONG               CreateDisposition,
    IN  ULONG               CreateOptions,
    IN  PVOID               EaBuffer        OPTIONAL,
    IN  ULONG               EaLength
    );

On successful completion of the function, the FileHandle parameter gets the handle of the created directory. DesiredAccess determines the type of access requested to the created directory. We only pass the SYNCHRONIZE flag, the meaning of which will become clear a little later. You already know what ObjectAttributes are. After the function completes, some additional information can be retrieved from the IoStatusBlock parameter, which is a pointer to the IO_STATUS_BLOCK structure. The FileAttributes parameter specifies the attributes (read-only, hidden, etc.) of the created directory. We use FILE_ATTRIBUTE_NORMAL because in this case, it is not required to give any special properties to the directory. FILE_ATTRIBUTE_NORMAL is used by default, so you can simply pass 0 in this parameter. A zero value in the optional AllocationSize parameter specifies, that a file of zero size will be created. In the case of a directory, this is quite natural. The ShareAccess parameter determines whether some other code can open the directory handle and with what access rights. In this case, we prohibit someone from accessing the directory by setting this parameter to 0. The CreateDisposition parameter determines the actions of the system if such a directory already exists, or vice versa - if such a file does not exist. We use the FILE_OPEN_IF flag, which means that if such a directory already exists, it will be opened. In the CreateOptions parameter, we pass a combination of the FILE_DIRECTORY_FILE and FILE_SYNCHRONOUS_IO_NONALERT flags. The first does not need any special explanation and means that a directory, not a file, will be created. FILE_SYNCHRONOUS_IO_NONALERT defines (not sure, by the way, if this flag matters in the case of a directory), that all operations with the file will be carried out synchronously, i.e. for example, calling ZwReadFile will not return until the data from the file is actually read. In this case, the I / O manager supports the so-calledthe current file position context. If the FILE_SYNCHRONOUS_IO_NONALERT flag is set, then the SYNCHRONIZE flag must also be specified in the DesiredAccess parameter. The last two parameters are not used by drivers.


     .if eax == STATUS_SUCCESS
         .if iosb.Information == FILE_CREATED
         .elseif iosb.Information == FILE_OPENED
         .endif

As I just said, there will be additional information in the IO_STATUS_BLOCK structure.

Remember how we complete the processing of the I / O request (see the previous sections). For example, in the SharingMemory driver (part 9), the processing of IRP_MJ_CREATE and IRP_MJ_CLOSE ends like this:


 mov eax, pIrp
 mov (_IRP PTR [eax]).IoStatus.Status, STATUS_SUCCESS
 and (_IRP PTR [eax]).IoStatus.Information, 0
 fastcall IofCompleteRequest, pIrp, IO_NO_INCREMENT

The driver does the same when it completes the file creation request. Only the values ​​placed in the fields of the IO_STATUS_BLOCK structure will depend on the type of request.

Because we have defined the FILE_OPEN_IF flag, the system can do two things. By the value of iosb.Information, we find out what happened: whether a new directory was created or such a directory already existed and therefore was opened.


         invoke ZwClose, hDirectory
     .endif

Once again, I repeat that in the kernel, you must explicitly close all open descriptors.


     invoke ZwCreateFile, addr hFile, SYNCHRONIZE, addr oa, addr iosb, 0, FILE_ATTRIBUTE_NORMAL, \
                         0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0

As you can see, the file is created in almost the same way, you just need to remove the FILE_DIRECTORY_FILE flag. And I use the FILE_CREATE flag for a change. It means that the file can only be created. If such a file already exists, the call to ZwCreateFile will fail.


11.4 The file object

Each open file descriptor corresponds to a file object, represented in kernel memory by a FILE_OBJECT structure.


 FILE_OBJECT STRUCT                              ; sizeof = 070h
     _Type                   SWORD       ?       ; 0000h  IO_TYPE_FILE 
     _Size                   SWORD       ?       ; 0002h
     DeviceObject            PVOID       ?       ; 0004h  PTR DEVICE_OBJECT
     Vpb                     PVOID       ?       ; 0008h  PTR VPB
     FsContext               PVOID       ?       ; 000Ch
     FsContext2              PVOID       ?       ; 0010h
     SectionObjectPointer    PVOID       ?       ; 0014h  PTR SECTION_OBJECT_POINTERS
     PrivateCacheMap         PVOID       ?       ; 0018h
     FinalStatus             SDWORD      ?       ; 001Ch
     RelatedFileObject       PVOID       ?       ; 0020h  PTR FILE_OBJECT
     LockOperation           BYTE        ?       ; 0024h  BOOLEAN
     DeletePending           BYTE        ?       ; 0025h  BOOLEAN
     ReadAccess              BYTE        ?       ; 0026h  BOOLEAN
     WriteAccess             BYTE        ?       ; 0027h  BOOLEAN
     DeleteAccess            BYTE        ?       ; 0028h  BOOLEAN
     SharedRead              BYTE        ?       ; 0029h  BOOLEAN
     SharedWrite             BYTE        ?       ; 002Ah  BOOLEAN
     SharedDelete            BYTE        ?       ; 002Bh  BOOLEAN
     Flags                   DWORD       ?       ; 002Ch
     FileName                UNICODE_STRING  <>  ; 0030h
     CurrentByteOffset       LARGE_INTEGER   <>  ; 0038h
     Waiters                 DWORD       ?       ; 0040h
     Busy                    DWORD       ?       ; 0044h
     LastLock                PVOID       ?       ; 0048h
     _Lock                   KEVENT      <>      ; 004Ch
     Event                   KEVENT      <>      ; 005Ch
     CompletionContext       PVOID       ?       ; 006Ch  PTR IO_COMPLETION_CONTEXT
 FILE_OBJECT ENDS
 PFILE_OBJECT typedef ptr FILE_OBJECT

For example, we can open the same file twice, but request different access rights: read (FILE_READ_DATA) in the first case and write (FILE_WRITE_DATA) in the second. As a result, two FILE_OBJECT structures will be created in the kernel, each of which will correspond to its own file descriptor. But both descriptors and, accordingly, both structures correspond to the same file on disk. The first FILE_OBJECT structure will have the ReadAccess field set, the second - WriteAccess. For example, when trying to write to a file using the first descriptor, the system will see that the WriteAccess = FALSE field and the request will be rejected.

The DeviceObject field will contain a pointer to the "device" object \ Device \ HarddiskVolume1, since it is this device that is referenced by the \ ?? \ c: symbolic link, which we use in the name of the created file.

After reading the article, you can experiment with the files and see how the system fills in and manages the FILE_OBJECT structure. If you have problems with localizing it in memory, you can use the ObReferenceObjectByHandle function like this:


 local pFileObject:PFILE_OBJECT
 . . .
 invoke ObReferenceObjectByHandle, hFile, FILE_READ_DATA, NULL, KernelMode, addr pFileObject, NULL
 .if eax == STATUS_SUCCESS
     ; pFileObject указывает на структуру FILE_OBJECT соответствующую описателю hFile
     fastcall ObfDereferenceObject, pFileObject
 .endif

ObReferenceObjectByHandle will return a pointer to the FILE_OBJECT structure corresponding to the file descriptor in the pFileObject variable. This will increase the reference count by one. Challenge ObfDereferenceObject need to return to it the old value.


11.5 Write to file

At this point, we already have a directory and a file of zero size. It's time to write something into it - it will be a string of characters.


     invoke ZwCreateFile, addr hFile, FILE_WRITE_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                         0, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0

Using the ZwCreateFile function, you can not only create files, but also open existing ones (and, by the way, not only files, but also some other objects). To do this, you need to specify the FILE_OPEN flag.

To write to the file, you need the appropriate access - we use the FILE_WRITE_DATA flag.

The include files define several constants for file sharing. For example, FILE_GENERIC_WRITE will allow not only writing data to a file, but also changing its properties, as well as adding data.


 FILE_GENERIC_WRITE equ (STANDARD_RIGHTS_WRITE or FILE_WRITE_DATA or FILE_WRITE_ATTRIBUTES or FILE_WRITE_EA or FILE_APPEND_DATA or SYNCHRONIZE)

But everywhere I will only ask for the minimum necessary, at the moment, access rights, tk. using, for example, FILE_ALL_ACCESS wherever it is necessary and not necessary is not a very good programming technique.

Obviously, while we are writing to the file, no one else should be doing the same. We use the FILE_SHARE_READ flag - no one else can open our file either for writing or for deleting, but can only get read access from the file.


     .if eax == STATUS_SUCCESS

         CTA0 "Data can be written to an open file", g_szData, 4

         invoke ZwWriteFile, hFile, 0, NULL, NULL, addr iosb, \
                         addr g_szData, sizeof g_szData - 1, NULL, NULL

The function name ZwWriteFile speaks for itself.


 NTSTATUS 
   ZwWriteFile(
     IN  HANDLE            FileHandle,
     IN  HANDLE            Event       OPTIONAL,
     IN  PIO_APC_ROUTINE   ApcRoutine  OPTIONAL,
     IN  PVOID             ApcContext  OPTIONAL,
     OUT PIO_STATUS_BLOCK  IoStatusBlock,
     IN  PVOID             Buffer,
     IN  ULONG             Length,
     IN  PLARGE_INTEGER    ByteOffset  OPTIONAL,
     IN  PULONG            Key  OPTIONAL
     );

The minimum required set of parameters for the ZwWriteFile function are a file descriptor, a pointer to the IO_STATUS_BLOCK structure, into which additional information will be placed (in particular, the number of bytes actually written to the file), a pointer to the data that needs to be written to the file and its size.


11.6 Changing file properties

Suppose we wanted to prevent the deletion of a file.


     invoke ZwCreateFile, addr hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES + SYNCHRONIZE, \
                         addr oa, addr iosb, 0, 0, FILE_SHARE_READ, \
                         FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0

The FILE_READ_ATTRIBUTES and FILE_WRITE_ATTRIBUTES flags are required to get and change file properties, respectively.


     .if eax == STATUS_SUCCESS
         invoke ZwQueryInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation
         .if eax == STATUS_SUCCESS

We need to set the read-only attribute, but since the file has other attributes, we need to leave them unchanged, and for this we need to know them.


             or fbi.FileAttributes, FILE_ATTRIBUTE_READONLY
             invoke ZwSetInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation

Add the flag we need to the file attributes. When we need to make changes to the file, we will reset this attribute in the UnmarkAsReadOnly procedure like this:


             and fbi.FileAttributes, not FILE_ATTRIBUTE_READONLY


11.7 Reading from a file

Now, for a change, let's open the file for reading using the ZwOpenFile function specially designed for this purpose. The purpose of the parameters is the same as those of the ZwCreateFile function .


 NTSTATUS
   ZwOpenFile(
     OUT PHANDLE             FileHandle,
     IN  ACCESS_MASK         DesiredAccess,
     IN  POBJECT_ATTRIBUTES  ObjectAttributes,
     OUT PIO_STATUS_BLOCK    IoStatusBlock,
     IN  ULONG               ShareAccess,
     IN  ULONG               OpenOptions
     );


     invoke ZwOpenFile, addr hFile, FILE_READ_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                 FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_NONALERT

FILE_READ_DATA - at the moment we will only need access to read data from the file. The combination of the FILE_SHARE_READ, FILE_SHARE_WRITE and FILE_SHARE_DELETE flags will allow everyone else to have full access to the file.


     .if eax == STATUS_SUCCESS
         invoke ZwQueryInformationFile, hFile, addr iosb, addr fsi, sizeof fsi, FileStandardInformation
         .if eax == STATUS_SUCCESS

We're going to read the entire contents of the file, but this requires a buffer, the size of which depends on the size of the file. The file size can be found by calling the ZwQueryInformationFile function with the FileStandardInformation information class, passing it a pointer to the FILE_BASIC_INFORMATION structure.


             mov eax, fsi.EndOfFile.LowPart
             inc eax
             mov cb, eax

Add one byte to the trailing zero.


             invoke ExAllocatePool, PagedPool, cb
             .if eax != NULL
                 mov p, eax

                 invoke RtlZeroMemory, p, cb

                 invoke ZwReadFile, hFile, 0, NULL, NULL, addr iosb, p, cb, 0, NULL
                 .if eax == STATUS_SUCCESS
                     invoke DbgPrint, $CTA0("FileWorks: File content: \=%s\=\n"), p
                 .endif

                 invoke ExFreePool, p
             .endif

Allocate a buffer of the required size, zero it and pass it to the ZwReadFile function . The prototype of this function is the same as the prototype of the ZwWriteFile function , only the buffer is not a data source, but a destination. Because before using, we zeroed the buffer and its size is one byte larger than the line contained in the file, then we will not have any problems with displaying its contents in a debug message.


11.8 Adding data to a file

There are several ways to add data to a file. You can open it with FILE_WRITE_DATA flag, set the current position of the file pointer at the end of it, passing the offset parameter ByteOffset function ZwWriteFile , and write data. The current file pointer position, by the way, is stored in FILE_OBJECT.CurrentByteOffset. And you can open the file with the FILE_APPEND_DATA flag and the current position of the file pointer will be automatically set to its end. This is what we will do.


     invoke ZwOpenFile, addr hFile, FILE_APPEND_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                                     FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT
     .if eax == STATUS_SUCCESS

         CTA0 " using ZwWriteFile", g_szDataToAppend, 4

         invoke ZwWriteFile, hFile, 0, NULL, NULL, addr iosb, \
                         addr g_szDataToAppend, sizeof g_szDataToAppend - 1, NULL, NULL

The file size will be automatically increased according to the size of the added data.


11.9 Truncating a file

Let's say we want to reduce the file size by discarding unnecessary data. In this case, I, for simplicity, reduce the file size in half.


     invoke ZwOpenFile, addr hFile, FILE_WRITE_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                         FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT

Open a file with write access.


     .if eax == STATUS_SUCCESS
         invoke ZwQueryInformationFile, hFile, addr iosb, \
                         addr fsi, sizeof fsi, FileStandardInformation
         .if eax == STATUS_SUCCESS

We get the current file size as part of the FILE_STANDARD_INFORMATION structure.


             and feofi.EndOfFile.HighPart, 0
             mov eax, fsi.EndOfFile.LowPart
             shr eax, 1
             mov feofi.EndOfFile.LowPart, eax
             invoke ZwSetInformationFile, hFile, addr iosb, \
                         addr feofi, sizeof feofi, FileEndOfFileInformation

Set the new size to half the current size using the FileEndOfFileInformation information class.


11.10 Deleting a file and directory

The only thing left is to bring the c: drive back to its original state. Oddly enough, but the 2000 DDK is silent about the existence of the ZwDeleteFile function . The XP DDK says that this feature still exists in the system, but since Windows XP. But this is not the case. ZwDeleteFile is available in Windows 2000 and even Windows NT4. But to delete the file, we will use a slightly more complex method, but we will delete the directory using ZwDeleteFile .


     invoke ZwCreateFile, addr hFile, DELETE + SYNCHRONIZE, addr oa, addr iosb, \
                         0, 0, FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0

Open the file for deletion. At the same time, everyone else is also allowed only to delete it.


     .if eax == STATUS_SUCCESS

         mov fdi.DeleteFile, TRUE
         invoke ZwSetInformationFile, hFile, addr iosb, addr fdi, sizeof fdi, FileDispositionInformation

Let's use the FileDispositionInformation information class and set the file deletion flag. In this case, in the FILE_OBJECT structure, the DeletePending field will change its value from FALSE to TRUE. This will indicate that the file is marked for deletion. As long as at least one open descriptor of such a file exists, it will not be removed.


         invoke ZwClose, hFile

The single file descriptor is now closed and the file is deleted.


     invoke ZwDeleteFile, addr oa

Deleting a directory using ZwDeleteFile is somewhat easier and requires no explanation.


11.11 Listing directory contents

There are two alternative ways to determine the name of the created / opened object in general and the file in particular. A named object can be accessed using either a full path or a relative path. So far, we've only used absolute paths. For example, \ ?? \ c: \ FileWorks \ test.txt is absolute. In this case, filling in the OBJECT_ATTRIBUTES structure using the InitializeObjectAttributes macro looks like this:


 InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("\??\c:\FileWorks\test.txt"), OBJ_CASE_INSENSITIVE, NULL, NULL

The penultimate RootDirectory parameter is NULL. The RootDirectory parameter of the InitializeObjectAttributes macro and the field of the same name in the OBJECT_ATTRIBUTES structure that this macro fills in define the handle of the object's container directory.


 OBJECT_ATTRIBUTES STRUCT
 . . .
     RootDirectory               HANDLE          ?
 . . .
 OBJECT_ATTRIBUTES ENDS

If the object's container directory is already open, i.e. if there is a handle to it, then the object can be accessed using the path relative to the container directory. In this case, the handle to the container directory must be placed in the RootDirectory field. For example, if we have already opened the \ ?? \ c: \ FileWorks \ directory and placed its handle in the hDirectory variable, we can use the relative path of the test.txt file like this:


 InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("test.txt"), OBJ_CASE_INSENSITIVE, hDirectory, NULL

A container directory refers not only to a directory on disk, but also to a directory in the Object Manager namespace.

To list the contents of the system directory \% SystemRoot% \ System32 \ Drivers \, we will just use the relative path.


     InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("\\SystemRoot"), \
                                 OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

We fill in the OBJECT_ATTRIBUTES structure using the \ SystemRoot symbolic link - this path is absolute.


     invoke ZwOpenFile, addr hSystemRootDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE, addr oa, \
                         addr iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, \
                         FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT

Open the \% SystemRoot% \ directory. The FILE_LIST_DIRECTORY flag will allow listing the contents of the directory.


     .if eax == STATUS_SUCCESS

         InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("system32\\drivers"), \
                             OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, hSystemRootDirectory, NULL

If the directory is successfully opened, then in the hSystemRootDirectory variable we get its handle, which we will use as a handle to the container directory. Fill in the OBJECT_ATTRIBUTES structure using the relative path "system32 \ drivers".


         invoke ZwOpenFile, addr hDriversDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE, addr oa, \
                             addr iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, \
                             FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT
         .if eax == STATUS_SUCCESS

Open the \% SystemRoot% \ System32 \ Drivers \ directory using a relative path.


             mov cb, sizeof FILE_DIRECTORY_INFORMATION + 256

             invoke ExAllocatePool, PagedPool, cb
             .if eax != NULL

Allocate a small buffer into which the FILE_DIRECTORY_INFORMATION structure and file name should fit.


                 mov pfdi, eax
                 mov esi, eax
                 assume esi:ptr FILE_DIRECTORY_INFORMATION

                 invoke ZwQueryDirectoryFile, hDriversDirectory, NULL, NULL, NULL, addr iosb, \
                             esi, cb, FileDirectoryInformation, \
                             TRUE, $CCOUNTED_UNICODE_STRING("c*"), TRUE

We begin by listing the directory files using the FileDirectoryInformation information class. The prototype of the ZwQueryDirectoryFile function , perhaps, will not be superfluous.


 NTSTATUS 
   ZwQueryDirectoryFile(
     IN  HANDLE                  FileHandle,
     IN  HANDLE                  Event       OPTIONAL,
     IN  PIO_APC_ROUTINE         ApcRoutine  OPTIONAL,
     IN  PVOID                   ApcContext  OPTIONAL,
     OUT PIO_STATUS_BLOCK        IoStatusBlock,
     OUT PVOID                   FileInformation,
     IN  ULONG                   Length,
     IN  FILE_INFORMATION_CLASS  FileInformationClass,
     IN  BOOLEAN                 ReturnSingleEntry,
     IN  PUNICODE_STRING         FileName    OPTIONAL,
     IN  BOOLEAN                 RestartScan
     );

And the existence of this function in 2000 DDK is also silent. The XP DDK says this feature has existed since Windows XP. And this is also not true.

The ReturnSingleEntry parameter is set to TRUE, which causes the ZwQueryDirectoryFile function to return information about only one file, the first one. The FileName parameter points to a string with the search term "c *", i.e. only files with names starting with "c" will be listed. This will reduce the amount of information displayed through debug messages. The first time we call ZwQueryDirectoryFile, we must set the RestartScan parameter to TRUE. This will cause the ZwQueryDirectoryFile function to start browsing the contents of the directory.


                 .while eax != STATUS_NO_MORE_FILES

We start a loop that spins until ZwQueryDirectoryFile returns STATUS_NO_MORE_FILES, i.e. until all files in the directory are listed.


                     .if ( eax == STATUS_SUCCESS )

If suddenly in the directory we are looking at there is a file whose name length exceeds 256 bytes (which, by the way, is almost unbelievable, since driver names do not exceed 8 characters), ZwQueryDirectoryFile will return an error code other than STATUS_NO_MORE_FILES. In this case, we just skip such a file.


                         mov eax, [esi].FileNameLength
                         mov us._Length, ax
                         mov us.MaximumLength, ax
                         lea eax, [esi].FileName
                         mov us.Buffer, eax
                         invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
                         .if eax == STATUS_SUCCESS

                             invoke RtlTimeToTimeFields, addr [esi].CreationTime, addr tf
                             movzx eax, tf.Day
                             movzx ecx, tf.Month
                             movzx edx, tf.Year

                             invoke DbgPrint, $CTA0("    %s   size=%d   created on %d.%02d.%04d\n"), \
                                         as.Buffer, [esi].EndOfFile.LowPart, eax, ecx, edx

                             invoke RtlFreeAnsiString, addr as
                         .endif

We format the received information, displaying the file name, size and creation date.


                     .endif

                     invoke ZwQueryDirectoryFile, hDriversDirectory, NULL, NULL, NULL, addr iosb, \
                                 esi, cb, FileDirectoryInformation, \
                                 TRUE, NULL, FALSE
                 .endw

In a loop, call ZwQueryDirectoryFile , but the ReturnSingleEntry, FileName, and RestartScan parameters are TRUE, NULL, and FALSE, respectively. This will cause the ZwQueryDirectoryFile function to continue listing the files.


                 invoke ExFreePool, pfdi
             .endif
             invoke ZwClose, hDriversDirectory
         .endif
         invoke ZwClose, hSystemRootDirectory
     .endif

We give all the occupied resources back.

The source code of the driver in the archive . To compile, you need a version of KmdKit at least 1.5 - take it on the site.