在驱动程序中使用文件
内核模式组件通过其对象名称引用文件,该对象名称是连接到文件的完整路径的 \DosDevices 。 在 Microsoft Windows 2000 及更高版本的操作系统上, \?? 等效于 \DosDevices。例如,C:\WINDOWS\example.txt 文件的对象名称 \DosDevices\C:\WINDOWS\example.txt,我们可以使用对象名称打开文件的句柄。
打开/读写文件
若要打开文件的句柄,请执行以下步骤:
- 创建 OBJECT_ATTRIBUTES 结构,并调用 InitializeObjectAttributes 宏来初始化结构。 将文件的对象名称指定为 InitializeObjectAttributes 的 ObjectName 参数;
- 通过将 OBJECT_ATTRIBUTES 结构传递给 IoCreateFile、 ZwCreateFile 或 ZwOpenFile,打开文件的句柄;
- 如果文件不存在, IoCreateFile 和 ZwCreateFile 将创建该文件,而 ZwOpenFile 将返回STATUS_OBJECT_NAME_NOT_FOUND;
请注意,驱动程序几乎始终使用 ZwCreateFile 或 ZwOpenFile ,而不是 IoCreateFile。
调用 IoCreateFile、 ZwCreateFile 或 ZwOpenFile 时,Windows 主管会创建一个新的文件对象来表示文件,并为对象提供一个打开的句柄。 此文件对象将一直保留,直到你关闭所有打开的句柄。
无论调用哪个例程,都必须传递所需的访问权限作为 DesiredAccess 参数。 这些权限必须涵盖驱动程序将执行的所有操作。 请求的相应访问权限如下:
- 从文件中读取:FILE_READ_DATA或GENERIC_READ
- 向文件写入数据:FILE_WRITE_DATA或GENERIC_WRITE
- 仅写入文件末尾:FILE_APPEND_DATA
- 读取文件的元数据,例如文件的创建时间:FILE_READ_ATTRIBUTES或GENERIC_READ
- 写入文件的元数据,例如文件的创建时间:FILE_WRITE_ATTRIBUTES或GENERIC_WRITE
下表列出了驱动程序可以对文件句柄执行的操作以及执行这些操作的相应例程。
- 从文件读取数据:ZwReadFile
- 将数据写入文件:ZwWriteFile
- 读取文件或文件句柄的元数据:ZwQueryInformationFile
- 写入文件或文件句柄的元数据:ZwSetInformationFile
若要指示开始读取或写入数据的文件中的位置,请分别将 ByteOffset 参数传递给 ZwReadFile 或 ZwWriteFile。
如果使用FILE_APPEND_DATA访问权限打开句柄,则所有数据将写入文件末尾,并忽略 ByteOffset 参数。
在某些情况下,I/O 管理器维护文件的当前文件位置指针。 可以通过为 ByteOffset 指定 NULL,在该位置开始读取或写入操作。
若要检查或更改有关文件的信息,请分别调用 ZwQueryInformationFile 或 ZwSetInformationFile。 将特定类型的信息指定为每个例程的 FileInformationClass 参数。 例如,将 FileInformationClass 设置为 FileBasicInformation 可以检查或更改 FILE_BASIC_INFORMATION 结构,该结构包含文件创建时间和上次访问时间的成员等。
使用当前文件位置
创建或打开文件时,可能导致 I/O 管理器创建与文件句柄关联的当前文件位置指针。 执行此操作后,可以读取和写入当前文件位置的数据,I/O 管理器将按读取或写入的字节数自动更新位置。
默认情况下,I/O 管理器不维护当前文件位置指针。 此默认值可提供效率,因为正确维护当前文件位置需要 I/O 管理器同步文件对象上的每个读取和写入操作。
若要创建具有关联的当前文件位置指针的句柄,请在 DesiredAccess 参数中将 SYNCHRONIZE 访问权限指定为 ZwCreateFile、 IoCreateFile 或 ZwOpenFile,并在 CreateOptions 或 OpenOptions 参数中指定FILE_SYNCHRONOUS_IO_ALERT或FILE_SYNCHRONOUS_IO_NONALERT。 请确保不要同时指定FILE_APPEND_DATA访问权限。
ZwReadFile 和 ZwWriteFile 自动更新当前文件位置指针,使其指向受操作影响的数据之外。 例如,如果从字节偏移量 101 开始读取 20 个字节, 则 ZwReadFile 会将当前文件位置更新为 121。
可以通过分别调用 ZwQueryInformationFile 或 ZwSetInformationFile 来检查或更改当前文件位置。 在任一情况下,将 FileInformationClass 参数设置为 FilePositionInformation。
注册表的使用
Windows 将注册表项表示为由对象管理器管理的执行对象。具体而言,每个键都有一个对象名称,你可以打开键的句柄。
用户模式应用程序访问与全局句柄相关的密钥,例如HKEY_LOCAL_MACHINE或HKEY_CURRENT_USER。 但是,这些句柄不适用于内核模式代码。 相反,可以按键的对象名称来引用键。 所有注册表项的根目录都是 \Registry 对象。 全局句柄对应于 \Registry 对象的后代,如下所示:
- HKEY_LOCAL_MACHINE:\Registry\Machine
- HKEY_USERS:\Registry\User
- HKEY_CLASSES_ROOT:没有等效的内核模式;
- HKEY_CURRENT_USER:没有简单的内核模式等效项;
驱动程序可以通过执行以下步骤操作注册表项对象:
- 打开注册表项对象的句柄;
- 通过调用相应的 ZwXxx密钥 例程来执行预期操作。 有关如何执行此操作的信息,请参阅 使用 Registry-Key 对象的句柄;
- 通过调用 ZwClose 关闭句柄;
打开/读写注册表
若要打开注册表项对象的句柄,请执行以下两个步骤:
- 创建 OBJECT_ATTRIBUTES 结构,并通过调用 InitializeObjectAttributes 对其进行初始化。 指定要操作的键的名称作为 InitializeObjectAttributes 的 ObjectName 参数。如果将 NULL 作为 RootDirectory 参数传递给 InitializeObjectAttributes, 则 ObjectName 必须是注册表项的完整路径,从 \Registry 开始。 否则, RootDirectory 必须是键的打开句柄,而 ObjectName 是相对于该键的路径;
- 通过调用 ZwCreateKey 或 ZwOpenKey 打开密钥对象的句柄,并将 OBJECT_ATTRIBUTES 结构传递给该对象。 如果该键尚不存在, ZwCreateKey 将创建密钥,而 ZwOpenKey 将返回STATUS_OBJECT_NAME_NOT_FOUND;
将 DesiredAccess 参数传递给包含所请求的访问权限的 ZwCreateKey 或 ZwOpenKey 。 必须指定允许驱动程序执行的操作的访问权限。 下面列出了可以执行的操作以及要请求的相应访问权限:
- 获取注册表项值: KEY_QUERY_VALUE或KEY_READ
- 设置注册表项值: KEY_SET_VALUE或KEY_WRITE
- 循环访问键的所有子项:KEY_ENUMERATE_SUB_KEYS或KEY_READ
- 创建子项: KEY_CREATE_SUB_KEY或KEY_WRITE
- 删除密钥: DELETE
还可以调用 IoOpenDeviceRegistryKey 和 IoOpenDeviceInterfaceRegistryKey ,分别打开特定于设备的注册表项和设备接口特定的句柄。
注意 对于对 ZwCreateKey、 ZwOpenKey、 IoOpenDeviceRegistryKey 和 IoOpenDeviceInterfaceRegistryKey 的调用,泛型访问权限GENERIC_READ和GENERIC_WRITE在含义上等效于键特定的访问权限,分别KEY_READ和KEY_WRITE,并且可用作这些特定于密钥的访问权限的替代项。
下面列出了驱动程序可以对打开键执行的操作以及要调用的相应例程。
- 检查密钥的属性,例如其名称或其子项的数目:ZwQueryKey
- 循环访问密钥的子项,检查每个子项的属性:ZwEnumerateKey
- 检查键值的属性,包括值的数据:ZwQueryValueKey
- 循环访问键的值,检查每个键的属性:ZwEnumerateValueKey
- 为与键关联的值设置数据:ZwSetValueKey
- 删除密钥:ZwDeleteKey
- 删除键值:ZwDeleteValueKey
驱动程序完成其操作后,必须调用 ZwClose 以关闭句柄,即使它已调用 ZwDeleteKey 来删除密钥。 (删除密钥后,其所有打开的句柄将变为无效,但驱动程序仍必须关闭 handle.)
下面的代码示例演示了如何为名为 \Registry\Machine\Software\MyCompanyMyApp 打开句柄,然后检索密钥数据并关闭句柄:
//
// Get the frame location from the registry key
// HKLM\SOFTWARE\MyCompany\MyApp.
// For example: "FrameLocation"="X:\\MyApp\\Frames"
//
HANDLE handleRegKey = NULL;
for (int n = 0; n < 1; n++)
{
NTSTATUS status = NULL;
UNICODE_STRING RegistryKeyName;
OBJECT_ATTRIBUTES ObjectAttributes;
RtlInitUnicodeString(&RegistryKeyName, L"\\Registry\\Machine\\Software\\MyCompany\\MyApp");
InitializeObjectAttributes(&ObjectAttributes,
&RegistryKeyName,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL, // handle
NULL);
status = ZwOpenKey(&handleRegKey, KEY_READ, &ObjectAttributes);
// If the driver cannot open the key, the driver cannot continue.
// In this situation, the driver was probably set up incorrectly
// and worst case, the driver cannot stream.
if( NT_SUCCESS(status) == FALSE )
{
break;
}
// The driver obtained the registry key.
PKEY_VALUE_FULL_INFORMATION pKeyInfo = NULL;
UNICODE_STRING ValueName;
ULONG ulKeyInfoSize = 0;
ULONG ulKeyInfoSizeNeeded = 0;
// The driver requires the following value.
RtlInitUnicodeString(&ValueName, L"FrameLocation");
// Determine the required size of keyInfo.
status = ZwQueryValueKey( handleRegKey,
&ValueName,
KeyValueFullInformation,
pKeyInfo,
ulKeyInfoSize,
&ulKeyInfoSizeNeeded );
// The driver expects one of the following errors.
if( (status == STATUS_BUFFER_TOO_SMALL) || (status == STATUS_BUFFER_OVERFLOW) )
{
// Allocate the memory required for the key.
ulKeyInfoSize = ulKeyInfoSizeNeeded;
pKeyInfo = (PKEY_VALUE_FULL_INFORMATION) ExAllocatePoolWithTag( NonPagedPool, ulKeyInfoSizeNeeded, g_ulTag);
if( NULL == pKeyInfo )
{
break;
}
RtlZeroMemory( pKeyInfo, ulKeyInfoSize );
// Get the key data.
status = ZwQueryValueKey( handleRegKey,
&ValueName,
KeyValueFullInformation,
pKeyInfo,
ulKeyInfoSize,
&ulKeyInfoSizeNeeded );
if( (status != STATUS_SUCCESS) || (ulKeyInfoSizeNeeded != ulKeyInfoSize) || (NULL == pKeyInfo) )
{
break;
}
// Fill in the frame location if it has not been filled in already.
if ( NULL == m_szwFramePath )
{
m_ulFramePathLength = pKeyInfo->DataLength;
ULONG_PTR pSrc = NULL;
pSrc = (ULONG_PTR) ( (PBYTE) pKeyInfo + pKeyInfo->DataOffset);
m_szwFramePath = (LPWSTR) ExAllocatePoolWithTag( NonPagedPool, m_ulFramePathLength, g_ulTag);
if ( NULL == m_szwFramePath )
{
m_ulFramePathLength = 0;
break;
}
// Copy the frame path.
RtlCopyMemory(m_szwFramePath, (PVOID) pSrc, m_ulFramePathLength);
}
// The driver is done with the pKeyInfo.
xFreePoolWithTag(pKeyInfo, g_ulTag);
} // if( (status == STATUS_BUFFER_TOO_SMALL) || (status == STATUS_BUFFER_OVERFLOW) )
} // Get the Frame location from the registry key.
// All done with the registry.
if (NULL != handleRegKey)
{
ZwClose(handleRegKey);
}
系统缓存内存中的密钥更改,并每隔几秒将其写入磁盘。 若要强制更改磁盘的密钥,请调用 ZwFlushKey。
若要通过更简单的接口操作注册表,驱动程序还可以调用 RtlXxx注册表Xxx 例程。
注册表运行时库例程
若要操作注册表项,驱动程序可以调用 RtlXxxRegistryXxx 例程,该例程提供比 ZwXxxKey 例程更简单的接口。 执行此操作时,驱动程序不需要打开和关闭句柄;相反,驱动程序按名称引用密钥。
将 RelativeTo 和 Path 参数传递给每个 RtlXxx注册表Xxx 例程。 如果 RelativeTo 是RTL_REGISTRY_ABSOLUTE, 则 Path 指定项的完整路径,从 \Registry 根目录开始。 如果 RelativeTo 是RTL_REGISTRY_HANDLE, 则 Path 实际上是一个打开的句柄。Relative 的其他 RTL_REGISTRY_XXX 值 指定 键的公共根路径;在这些情况下, Path 指定相对于该根的路径。 例如,RTL_REGISTRY_USER要求 Path 相对于当前用户的注册表设置。 此值等效于在用户模式应用程序中指定HKEY_CURRENT_USER。)
下面列出了驱动程序可以通过调用 RtlXxx注册表Xxx 例程执行的操作:
- 创建注册表项:RtlCreateRegistryKey
- 检查是否存在注册表项:RtlCheckRegistryKey
- 检查一个或多个注册表项值:RtlQueryRegistryValues
- 编写注册表项值:RtlWriteRegistryValue
- 删除注册表项值:RtlDeleteRegistryValue
下面的代码示例演示如何将 \Registry\Machine\System\KeyName 的 ValueName 设置为 ULONG 值0xFF:
NTSTATUS status;
ULONG data = 0xFF;
status = RtlWriteRegistryValue(RTL_REGISTRY_ABSOLUTE,
(PWCSTR)L"\\Registry\\Machine\\System\\KeyName",
(PWCSTR)L"ValueName",
REG_DWORD,
&data,
sizeof(ULONG));
尽管使用 RtlXxxRegistryXxx 例程(而不是 Zw Xxx Key 例程)时写入的代码行更少,但执行某些操作需要后一行代码。 例如,不存在与 ZwEnumerateKey 对应的 RtlXxxRegistryXxx 例程。
如果对同一键执行多个操作, 则 ZwXxx密钥 例程会更高效—你可以对每个操作使用相同的打开句柄。 相比之下, RtlXxx注册表Xxx 例程打开和关闭每个操作的新句柄。