我对内核编程相当陌生,从内核模式获取所有磁盘驱动器信息(如名称,序列号)时遇到了一点问题。我使用以下代码来获取所有磁盘符号链接,效果非常好。
static VOID DeviceInterfaceTest_Func() {
NTSTATUS Status;
PWSTR SymbolicLinkList;
PWSTR SymbolicLinkListPtr;
GUID Guid = {
0x53F5630D,
0xB6BF,
0x11D0,
{
0x94,
0xF2,
0x00,
0xA0,
0xC9,
0x1E,
0xFB,
0x8B
}
}; //Defined in mountmgr.h
Status = IoGetDeviceInterfaces( &
Guid,
NULL,
0, &
SymbolicLinkList);
if (!NT_SUCCESS(Status)) {
return;
}
KdPrint(("IoGetDeviceInterfaces results:n"));
for (SymbolicLinkListPtr = SymbolicLinkList; SymbolicLinkListPtr[0] != 0 && SymbolicLinkListPtr[1] != 0; SymbolicLinkListPtr += wcslen(SymbolicLinkListPtr) + 1) {
KdPrint(("Symbolic Link: %Sn", SymbolicLinkListPtr));
PUNICODE_STRING PTarget {};
UNICODE_STRING Input;
NTSTATUS s = 0;
Input.Length = sizeof((PWSTR) & SymbolicLinkListPtr);
Input.MaximumLength = 200 * sizeof(WCHAR);
Input.Buffer = (PWSTR) ExAllocatePool2(PagedPool, Input.MaximumLength, 0);
s = SymbolicLinkTarget( & Input, PTarget);
if (s == STATUS_SUCCESS) {
//KdPrint(("%Sn", PTarget->Buffer));
KdPrint(("Finished!n"));
}
}
ExFreePool(SymbolicLinkList);
}
但是,当我尝试使用初始化对象属性函数提取循环内部符号链接的数据时,我使用 KdPrint 检查它们的名称,因此它们都是空的,因此我无法使用 ZwOpenSymbolicLinkObject,因为当我使用它时,我会得到蓝屏。我做错了什么?我的方法是否有效获取磁盘信息,还是应该使用其他方法?下面是SymbolicLinkTarget的代码
NTSTATUS SymbolicLinkTarget(_In_ PUNICODE_STRING SymbolicLinkStr, _Out_ PUNICODE_STRING PTarget) {
OBJECT_ATTRIBUTES ObjectAtiribute {};
NTSTATUS Status = 0;
HANDLE Handle = nullptr;
InitializeObjectAttributes( & ObjectAtiribute, SymbolicLinkStr, OBJ_CASE_INSENSITIVE, 0, 0);
KdPrint(("Object length:%u n", ObjectAtiribute.Length));
KdPrint(("Object name:%s n", ObjectAtiribute.ObjectName - > Buffer));
Status = ZwOpenSymbolicLinkObject(&Handle, GENERIC_READ, &ObjectAtiribute);
if (Status != STATUS_SUCCESS)
{
KdPrint(("ZwOpenSymbolicLinkObject failed (0x%08X)n", Status));
return Status;
}
UNREFERENCED_PARAMETER(PTarget);
ULONG Tag1 = 'Tag1';
PTarget->MaximumLength = 200 * sizeof(WCHAR);
PTarget->Length = 0;
PTarget->Buffer = (PWCH)ExAllocatePool2(PagedPool, PTarget->MaximumLength, Tag1);
if (!PTarget->Buffer)
{
ZwClose(Handle);
return STATUS_INSUFFICIENT_RESOURCES;
}
Status = ZwQuerySymbolicLinkObject(Handle, PTarget, NULL);
ZwClose(Handle);
if (Status != STATUS_SUCCESS)
{
KdPrint(("ZwQuerySymbolicLinkObject failed (0x%08X)n", Status));
ExFreePool(PTarget->Buffer);
return Status;
}
return STATUS_SUCCESS;
}
非常感谢您的帮助。
您的函数中存在多个问题。让我们从他的主要一个开始:
在SymbolicLinkTarget()
:
OBJECT_ATTRIBUTES ObjectAtiribute {};
InitializeObjectAttributes( & ObjectAtiribute, SymbolicLinkStr, OBJ_CASE_INSENSITIVE, 0, 0);
您将从SymbolicLinkStr
(和其他参数)初始化ObjectAtiribute
,但实际上DeviceInterfaceTest_Func()
您实际上从未将Input
设置为包含字符串!
UNICODE_STRING Input;
NTSTATUS s = 0;
Input.Length = sizeof((PWSTR) & SymbolicLinkListPtr);
Input.MaximumLength = 200 * sizeof(WCHAR);
Input.Buffer = (PWSTR) ExAllocatePool2(PagedPool, Input.MaximumLength, 0);
s = SymbolicLinkTarget( & Input, PTarget);
输入长度
这是错误的:
Input.Length = sizeof((PWSTR) & SymbolicLinkListPtr);
Input.Length
将设置为指针的大小。根据UNICODE_STRING
(ntdef.h; subauth.h),length
是:
指定 Buffer 成员指向的字符串的长度(以字节为单位),不包括终止 NULL 字符(如果有)。
所以:
size_t str_len_no_null = wcslen(SymbolicLinkListPtr); // number of chars, not bytes!
Input.Length = str_len_no_null * sizeof(WCHAR);
请注意,wcslen()
已经在 for 循环的 init 语句中,我会训练它提取它以将其放在循环体中。
输入最大长度
Input.MaximumLength = 200 * sizeof(WCHAR);
如果字符串超过 200 个字符怎么办?
MaximumLength
的定义如下:
指定分配给缓冲区的内存的总大小(以字节为单位)。最多可以将最大长度字节写入缓冲区,而不会占用内存。
因此,只做以下操作是安全的:
size_t max_length_bytes = Input.Length + (1 * sizeof(WCHAR)); // add room for possible null.
Input.MaximumLength = max_length_bytes;
Buffer
成员的分配可以保持不变。现在,您需要将字符串复制到缓冲区中。
UNICODE_STRING初始化
size_t str_len_no_null = wcslen(SymbolicLinkListPtr); // number of chars, not bytes!
Input.Length = str_len_no_null * sizeof(WCHAR);
size_t max_length_bytes = Input.Length + (1 * sizeof(WCHAR)); // add room for possible null.
Input.MaximumLength = max_length_bytes;
Input.Buffer = (PWSTR) ExAllocatePool2(PagedPool, Input.MaximumLength, 0); // note: you should define a Tag for your Driver.
if(Input.buffer == NULL) {
// not enough memory.
return;
}
status = RtlStringCbCopyW(Input.Buffer, max_length_bytes, SymbolicLinkListPtr);
// TODO: check status
现在您知道如何手动执行此操作,请抛出代码并使用RtlUnicodeStringInit
其他内容和提示
- 始终检查您使用的函数的返回状态/值。在内核模式下,这非常重要。
NTSTATUS
检查始终使用状态宏之一(通常NT_SUCCESS
)- 使用字符串安全函数。
- 吹毛求疵:成功返回值
IoGetDeviceInterfaces
也可能表示缓冲区为空。尽管您在 for 循环 init 语句中检查了这一点,但我会在函数之后立即检查它,以便意图更清晰。
KdPrint(("Object name:%s n", ObjectAtiribute.ObjectName - > Buffer));
它是%S
(宽字符)而不是%s
(字符);请参阅格式规范。 您可以传递UNICODE_STRING
并使用%Z
格式化程序。还要警惕- >
这很奇怪(你可能的意思是->
)。
InitializeObjectAttributes( & ObjectAtiribute, SymbolicLinkStr, OBJ_CASE_INSENSITIVE, 0, 0);
- 如果生成的句柄不打算跨越内核<>用户模式边界(在您的情况下,它不必跨越该边界),请使用
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE
。否则,会将内核句柄泄漏到用户模式,这会产生安全隐患。
当您调用ZwOpenSymbolicLinkObject
并且未在系统线程中运行时,这也是必需的:
如果调用方未在系统线程上下文中运行,则必须在调用 InitializeObjectAttributes 时设置 OBJ_KERNEL_HANDLE 属性。
可以使用
DEFINE_GUID
定义 GUID ;请参阅定义和导出新的 GUID 和在驱动程序代码中包含 GUID。在您的情况下,您不需要导出它。这可能是吹毛求疵,但使用
nullptr
(c++) 或NULL
(c) 而不是 0 来传达您正在检查指针而不仅仅是整数值 0 的想法。