Обеспечение возможности работы с файлами является важнейшей задачей любой операционной системы. Посмотрим, какие возможности предоставляют нам операционные системы семейства NT.
Прежде чем мы перейдем непосредственно к теме статьи, обсудим один важный момент, которому прежде не было уделено должного внимания. Для получения описателя объекта требуется заполнить структуру OBJECT_ATTRIBUTES - это мы делали уже много раз примерно таким образом:
InitializeObjectAttributes addr oa, addr g_usName, OBJ_CASE_INSENSITIVE, NULL, NULL
Инициализировав структуру OBJECT_ATTRIBUTES, мы вызывали функцию создания/открытия объекта и получали его описатель (hande). Но описатель этот попадал в таблицу описателей того процесса, в контексте которого был получен. Т.к. таблица описателей специфична для процесса, то использовать такой описатель можно только в контексте того же самого процесса. Например, попытка закрыть этот описатель в контексте другого процесса, в лучшем случае, завершится неудачно. А в худшем, если в таблице описателей этого процесса окажется описатель с таким же значением, ведь описатель это просто 32-битное число (точнее битовая структура), возможно, будет закрыт описатель совершенно другого объекта. Даже если описатель получен драйвером, но в контексте пользовательского процесса, он попадет в таблицу описателей этого процесса и по нему можно будет обратиться к объекту из режима пользователя, преднамеренно или случайно. Бывают ситуации, когда именно это и требуется, но в общем случае это не то, что вы хотите. Именно поэтому компоненты ядра, и драйверы в частности, не "любят" пользоваться описателями, а предпочитают использовать ссылки на объекты (reference to object), которые являются простыми указателями на структуру объекта в памяти ядра. Для учета предоставленных ссылок на объект в заголовке каждого объекта хранится счетчик ссылок (reference count). Если же для доступа к объекту нужен именно его описатель, как в этом и предыдущем примере, и вы планируете обращаться к нему из разных контекстов, нужно заставить систему поместить описатель в так называемую таблицу описателей ядра (kernel handle table).
Начиная с Windows 2000, в системе появилась специальная таблица описателей ядра. Описатели в этой таблице доступны только в режиме ядра в контексте любого процесса и отличаются от описателей специфичных для процесса установленным старшим битом.
Даже если получить описатель в контексте процесса System, например, в процедуре DriverEntry, то обратиться по нему к объекту в контексте пользовательского процесса нельзя. Т.е. процесс System использует свою собственную таблицу описателей, отличную от таблицы описателей ядра.
Для того чтобы описатель попал в таблицу описателей ядра, нужно явно указать флаг OBJ_KERNEL_HANDLE в вызове макроса InitializeObjectAttributes таким образом:
InitializeObjectAttributes addr oa, addr g_usName, OBJ_KERNEL_HANDLE, NULL, NULL
Также как и в предыдущем примере, код драйвера состоит из нескольких автономных процедур: CreateDirectory, CreateFile, WriteFile, MarkAsReadOnly, ReadFile, UnmarkAsReadOnly, AppendFile, TruncateFile, DeleteFile, DeleteDirectory и EnumerateFiles. Все они роботают почти независимо друг от друга.
;@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 не входят некоторые нужные нам константы и структуры.
include \masm32\include\w2k\ntifs.inc
В Installable File System (IFS) Kit, предназначенный для разработки драйверов файловых систем, входит заголовочный файл ntifs.h, аналогом которого и является включаемый файл ntifs.inc. Этот файл входит в состав KmdKit начиная с версии 1.5.
InitializeObjectAttributes addr oa, addr g_usDirName, \
OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL
Заполняем структуру OBJECT_ATTRIBUTES, не забывая про флаг OBJ_KERNEL_HANDLE. Хочу подчеркнуть, что в данном примере это необязательно, т.к. мы не собираемся использовать ни один из описателей в контексте какого-либо другого процесса. Но поскольку процедуры драйвера FileWorks легко могут быть использованы в других проектах в контексте любого процесса, я решил сделать именно так. Если в ваши задачи входит совместное использование описателя драйвером и пользовательским процессом, то флаг OBJ_KERNEL_HANDLE использовать не следует. Если обращения к объекту будут происходить в контексте одного и того же процесса, флаг OBJ_KERNEL_HANDLE не нужен.
Для создания, как каталога, так и файла используется функция ZwCreateFile. С точки зрения системы, каталоги являются теми же файлами, поэтому принципиальных различий в процедуре создания файла и каталога нет. Поэтому, все сказанное ниже в отношении создания каталога в равной степени относится и к файлам, за исключением флага FILE_DIRECTORY_FILE.
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
У функции ZwCreateFile довольно много параметров. Поэтому, я приведу её протопип. И вынужден сделать это, используя синтаксис языка си, чтобы были видны входные (IN) выходные (OUT) и необязательные (OPTIONAL) параметры.
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
);
При успешном завершении функции, параметр FileHandle получает описатель созданного каталога. DesiredAccess определяет тип запрашиваемого доступа к создаваемому каталогу. Мы передаем только флаг SYNCHRONIZE, значение которого станет ясно чуть позже. Что такое ObjectAttributes вы уже знаете. После завершения функции из параметра IoStatusBlock, являющегося указателем на структуру IO_STATUS_BLOCK можно извлечь кое-какую дополнительную информацию. Параметр FileAttributes определяет атрибуты (только для чтения, скрытый и т.п.) создаваемого каталога. Мы используем FILE_ATTRIBUTE_NORMAL, т.к. придавать какие-то особые свойства каталогу, в данном случае, не требуется. FILE_ATTRIBUTE_NORMAL используется по умолчанию, поэтому, можно просто передать в этом параметре 0. Нулевое значение в необязательном параметре AllocationSize определяет, что будет создан файл нулевого размера. В случае каталога это вполне естественно. Параметр ShareAccess определяет, может ли какой-то другой код открыть описатель каталога и с какими правами доступа. В данном случае, мы запрещаем кому-либо доступ к каталогу, установив этот параметр в 0. Параметр CreateDisposition определяет действия системы, в случае если такой каталог уже существует или наоборот - если такого файла нет. Мы используем флаг FILE_OPEN_IF, который означает, что если такой каталог уже существует, он будет открыт. В параметре CreateOptions передаем комбинацию флагов FILE_DIRECTORY_FILE и FILE_SYNCHRONOUS_IO_NONALERT. Первый в особых пояснениях не нуждается и означает, что будет создан каталог, а не файл. FILE_SYNCHRONOUS_IO_NONALERT определяет (не уверен, кстати, что в случае каталога этот флаг имеет значение), что все операции с файлом будут проводиться синхронно, т.е. например, вызов ZwReadFile не вернет управление до тех пор, пока данные из файла не будут фактически прочитаны. При этом диспетчер ввода-вывода поддерживает для файла так называемую текущую позицию указателя файла (file position context). Если флаг FILE_SYNCHRONOUS_IO_NONALERT установлен, то в параметре DesiredAccess так же должен быть определен флаг SYNCHRONIZE. Последние два параметра не используются драйверами.
.if eax == STATUS_SUCCESS
.if iosb.Information == FILE_CREATED
.elseif iosb.Information == FILE_OPENED
.endif
Как я только что сказал, в структуре IO_STATUS_BLOCK будет находиться дополнительная информация.
Вспомните, как мы завершаем обработку запроса ввода-вывода (см. предыдущие части). Например, в драйвере SharingMemory (часть 9) обработка IRP_MJ_CREATE и IRP_MJ_CLOSE завершается так:
mov eax, pIrp
mov (_IRP PTR [eax]).IoStatus.Status, STATUS_SUCCESS
and (_IRP PTR [eax]).IoStatus.Information, 0
fastcall IofCompleteRequest, pIrp, IO_NO_INCREMENT
Примерно то же самое делает драйвер, завершающий запрос создания файла. Только значения, помещаемые в поля структуры IO_STATUS_BLOCK, будут зависеть от типа запроса.
Т.к. мы определили флаг FILE_OPEN_IF, система может поступить двояко. По значению iosb.Information мы узнаем, что же произошло: был ли создан новый каталог или такой каталог уже существовал и поэтому был открыт.
invoke ZwClose, hDirectory
.endif
Лишний раз повторю, что в ядре необходимо явно закрывать все открытые описатели.
invoke ZwCreateFile, addr hFile, SYNCHRONIZE, addr oa, addr iosb, 0, FILE_ATTRIBUTE_NORMAL, \
0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
Как видите, файл создается практически аналогично, нужно только убрать флаг FILE_DIRECTORY_FILE. А флаг FILE_CREATE я использую для разнообразия. Он означает, что файл может быть только создан. Если такой файл уже существует, вызов ZwCreateFile окончится неудачей.
Каждому открытому описателю файла соответствует объект "файл" (file object), представляющийся в памяти ядра структурой FILE_OBJECT.
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
Например, мы можем два раза открыть один и тот же файл, но запросить разные права доступа: чтение (FILE_READ_DATA) в первом случае и запись (FILE_WRITE_DATA) - во втором. В результате в ядре будет создано две структуры FILE_OBJECT, каждая из которых будет соответствовать своему описателю файла. Но оба описателя и соответственно обе структуры соответствуют одному и тому же файлу на диске. В первой структуре FILE_OBJECT будет установлено поле ReadAccess, во второй - WriteAccess. Например, при попытке записи в файл по первому описателю, система увидит, что поле WriteAccess = FALSE и запрос будет отклонен.
Поле DeviceObject будет содержать указатель на объект "устройство" \Device\HarddiskVolume1, т.к. именно на это устройство ссылается символьная ссылка \??\c:, которую мы используем в имени создаваемого файла.
Можете после прочтения статьи поэкспериментировать с файлами и посмотреть, как система заполняет и управляет структурой FILE_OBJECT. Если возникнут проблемы с локализацией её в памяти, можно использовать функцию ObReferenceObjectByHandle примерно таким образом:
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 вернет в переменной pFileObject указатель на структуру FILE_OBJECT, соответствующую описателю файла. При этом счетчик ссылок будет увеличен на единицу. Вызовом ObfDereferenceObject необходимо вернуть ему старое значение.
К этому моменту у нас уже есть каталог и файл нулевого размера. Пора в него что-нибудь записать - это будет строка символов.
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
С помощью функции ZwCreateFile можно не только создавать файлы, но и открывать существующие (и, кстати, не только файлы, а и некоторые другие объекты). Для этого надо указать флаг FILE_OPEN.
Для записи в файл нужен соответствующий доступ - используем флаг FILE_WRITE_DATA.
Во включаемых файлах определены несколько констант для общего доступа к файлам. Например, FILE_GENERIC_WRITE позволит не только писать данные в файл, но и изменять его свойства, а также добавлять данные.
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)
Но я везде буду запрашивать только минимально необходимые, на данный момент, права доступа, т.к. использовать, например, FILE_ALL_ACCESS везде, где надо и не надо - не очень хороший прием программирования.
Очевидно, что пока мы будем писать в файл никто другой не должен делать того же. Используем флаг FILE_SHARE_READ - больше никто не сможет открыть наш файл ни для записи, ни для удаления, а сможет только получить доступ на чтение из файла.
.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
Название функции ZwWriteFile говорит само за себя.
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
);
Минимально необходимым набором параметров для функции ZwWriteFile являются описатель файла, указатель на структуру IO_STATUS_BLOCK, в которую будет помещена дополнительная информация (в частности, количество фактически записанных в файл байт), указатель на данные, которые требуется записать в файл и их размер.
Предположим, нам потребовалось предотвратить удаление файла.
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
Флаги FILE_READ_ATTRIBUTES и FILE_WRITE_ATTRIBUTES требуются для получения и изменения свойств файла соответственно.
.if eax == STATUS_SUCCESS
invoke ZwQueryInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation
.if eax == STATUS_SUCCESS
Нам нужно установить атрибут "только чтение", но поскольку у файла имеются и другие атрибуты их нужно оставить без изменений, а для этого их нужно знать.
or fbi.FileAttributes, FILE_ATTRIBUTE_READONLY
invoke ZwSetInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation
Добавляем к атрибутам файла необходимый нам флаг. Когда понадобиться произвести изменения в файле мы сбросим этот атрибут в процедуре UnmarkAsReadOnly так:
and fbi.FileAttributes, not FILE_ATTRIBUTE_READONLY
Теперь, для разнообразия, откроем файл для чтения с помощью функции ZwOpenFile, специально предназначенной для этих целей. Назначение параметров совпадает с аналогичными параметрами функции ZwCreateFile.
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 - в данный момент нам будет достаточно только доступа для чтения данных из файла. Комбинация флагов FILE_SHARE_READ, FILE_SHARE_WRITE и FILE_SHARE_DELETE позволит всем остальным получать полный доступ к файлу.
.if eax == STATUS_SUCCESS
invoke ZwQueryInformationFile, hFile, addr iosb, addr fsi, sizeof fsi, FileStandardInformation
.if eax == STATUS_SUCCESS
Мы собираемся прочитать все содержимое файла, но для этого требуется буфер, размер которого зависит от размера файла. Размер файла можно узнать вызвав функцию ZwQueryInformationFile с информационным классом FileStandardInformation, передав ей указатель на структуру FILE_BASIC_INFORMATION.
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
.endif
invoke ExFreePool, p
.endif
Выделим буфер нужного размера, обнулим его и передадим в функцию ZwReadFile. Прототип этой функции совпадает с прототипом функции ZwWriteFile, только буфер является не источником данных, а приемником. Т.к. перед использованием мы обнулили буфер и его размер на один байт больше строки, содержащейся в файле, то никаких проблем с выводом его содержимого в отладочном сообщении у нас не возникнет.
Есть несколько путей добавления данных в файл. Можно открыть его с флагом FILE_WRITE_DATA, установить текущую позицию указателя файла на его конец, передав смещение в параметре ByteOffset функции ZwWriteFile, и писать данные. Текущая позиция указателя файла, кстати, храниться в FILE_OBJECT.CurrentByteOffset. А можно открыть файл в флагом FILE_APPEND_DATA и текущая позиция указателя файла будет автоматически установлена на его конец. Так мы и поступим.
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
Размер файла будет автоматически увеличен в соответствии с размером добавленных данных.
Допустим, нам нужно уменьшить размер файла, отбросив ненужные данные. В данном случае, я, для простоты, уменьшаю размер файла в два раза.
invoke ZwOpenFile, addr hFile, FILE_WRITE_DATA + SYNCHRONIZE, addr oa, addr iosb, \
FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT
Открываем файл с доступом на запись данных.
.if eax == STATUS_SUCCESS
invoke ZwQueryInformationFile, hFile, addr iosb, \
addr fsi, sizeof fsi, FileStandardInformation
.if eax == STATUS_SUCCESS
Получаем текущий размер файла в составе структуры FILE_STANDARD_INFORMATION.
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
Устанавливаем новый размер, равный половине текущего, пользуясь информационным классом FileEndOfFileInformation.
Дело за малым - привести диск c: в первоначальное состояние. Как это ни странно, но 2000 DDK умалчивает о существовании функции ZwDeleteFile. В XP DDK говорится, что эта функция все таки существует в системе, но начиная с Windows XP. Но это не так. ZwDeleteFile есть и Windows 2000 и даже в Windows NT4. Но для удаления файла мы воспользуемся несколько более сложным способом, а вот каталог удалим с помощью ZwDeleteFile.
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
mov fdi.DeleteFile, TRUE
invoke ZwSetInformationFile, hFile, addr iosb, addr fdi, sizeof fdi, FileDispositionInformation
Воспользуемся информационным классом FileDispositionInformation и установим признак удаления файла. При этом в структуре FILE_OBJECT поле DeletePending изменит свое значение с FALSE на TRUE. Это будет означать, что файл отмечен для удаления. До тех пор, пока хотя бы один открытый описатель такого файла существует, он не будет удален.
invoke ZwClose, hFile
Теперь единственный описатель файла закрыт и файл удален.
invoke ZwDeleteFile, addr oa
Удаление каталога с помощью ZwDeleteFile проходит несколько проще и пояснений не требует.
Существует два альтернативных способа определения имени создаваемого/открываемого объекта вообще и файла в частности. Доступ к именованному объекту можно получить либо используя полный путь, либо относительный. До сих пор мы использовали только абсолютные пути. Например, путь \??\c:\FileWorks\test.txt является абсолютным. В этом случае заполнение структуры OBJECT_ATTRIBUTES с помощью макроса InitializeObjectAttributes выглядит примерно так:
InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("\??\c:\FileWorks\test.txt"), \
OBJ_CASE_INSENSITIVE, NULL, NULL
Предпоследний параметр RootDirectory равен NULL. Параметр RootDirectory макроса InitializeObjectAttributes и одноименное поле структуры OBJECT_ATTRIBUTES, которую этот макрос заполняет, определяет описатель каталога-контейнера объекта.
OBJECT_ATTRIBUTES STRUCT
. . .
RootDirectory HANDLE ?
. . .
OBJECT_ATTRIBUTES ENDS
Если каталог-контейнер объекта уже открыт, т.е. имеется его описатель, то к объекту можно обратиться по относительному к каталогу-контейнеру пути. При этом описатель каталога-контейнера должен быть помещен в поле RootDirectory. Например, если мы уже открыли каталог \??\c:\FileWorks\ и поместили его описатель в переменную hDirectory, то можем использовать относительный путь файла test.txt таким образом:
InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("test.txt"), OBJ_CASE_INSENSITIVE, hDirectory, NULL
Под каталогом-контейнером подразумевается не только каталог на диске, но и каталог в пространстве имен диспетчера объектов.
Для перечисления содержимого системного каталога \%SystemRoot%\System32\Drivers\ мы как раз и будем использовать относительный путь.
InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("\\SystemRoot"), \
OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL
Заполняем структуру OBJECT_ATTRIBUTES, используя символьную ссылку \SystemRoot - этот путь абсолютный.
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
Открываем каталог \%SystemRoot%\. Флаг FILE_LIST_DIRECTORY позволит перечислять содержимое каталога.
.if eax == STATUS_SUCCESS
InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("system32\\drivers"), \
OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, hSystemRootDirectory, NULL
Если каталог успешно открыт, то в переменной hSystemRootDirectory получим его описатель, который и будем использовать в качестве описателя каталога-контейнера. Заполняем структуру OBJECT_ATTRIBUTES, используя относительный путь "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
Открываем каталог \%SystemRoot%\System32\Drivers\ по относительному пути.
mov cb, sizeof FILE_DIRECTORY_INFORMATION + 256
invoke ExAllocatePool, PagedPool, cb
.if eax != NULL
Выделяем небольшой буфер, в который должна поместиться структура FILE_DIRECTORY_INFORMATION и имя файла.
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
Начинаем перечислять файлы каталога, используя информационный класс FileDirectoryInformation. Прототип функции ZwQueryDirectoryFile, пожалуй, будет не лишним.
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
);
И о существовании этой функции 2000 DDK тоже умалчивает. В XP DDK говорится, что эта функция существует в системе, начиная с Windows XP. И это тоже неправда.
Параметр ReturnSingleEntry устанавливаем равным TRUE, что заставляет функцию ZwQueryDirectoryFile вернуть информацию только об одном файле, причем первом. Параметр FileName указывает на строку с условием поиска "c*", т.е. будут перечисляться только файлы, имена которых начинаются с символа "c". Это позволит сократить объем выводимой через отладочные сообщения информации. При первом вызове ZwQueryDirectoryFile мы должны установить параметр RestartScan в TRUE. Это заставит функцию ZwQueryDirectoryFile начать просмотр содержимого каталога.
.while eax != STATUS_NO_MORE_FILES
Запускаем цикл, который крутится до тех пор, пока ZwQueryDirectoryFile не вернет STATUS_NO_MORE_FILES, т.е. пока все файлы в каталоге не будут перечислены.
.if ( eax == STATUS_SUCCESS )
Если вдруг в просматриваемом нами каталоге окажется файл, длина имени которого превысит 256 байт (что, кстати, практически невероятно, т.к. имена драйверов не превышают 8 символов), ZwQueryDirectoryFile вернет код ошибки отличный от STATUS_NO_MORE_FILES. В этом случае мы просто пропускаем такой файл.
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
В цикле вызываем ZwQueryDirectoryFile, но параметры ReturnSingleEntry, FileName и RestartScan равны TRUE, NULL и FALSE соответственно. Это заставит функцию ZwQueryDirectoryFile продолжить перечисление файлов.
invoke ExFreePool, pfdi
.endif
invoke ZwClose, hDriversDirectory
.endif
invoke ZwClose, hSystemRootDirectory
.endif
Отдаем все занятые ресурсы назад.
Исходный код драйвера в архиве. Для компиляции требуется версия KmdKit не ниже 1.5 - берите на сайте.
[C] Four-F