I am fairly new to kernel programming and I have a little problem getting all disk drives information like name,serialnumber from kernel mode. I use below code to get all disks symbolic links which works perfectly fine.
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: %S\n", 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(("%S\n", PTarget->Buffer));
KdPrint(("Finished!\n"));
}
}
ExFreePool(SymbolicLinkList);
}
However when i try to use InitializeObjectAttributes function to extract data of symbolic link inside for loop I checking their names with KdPrint and all them are null as a result i can't use ZwOpenSymbolicLinkObject, because when i use it i get BSOD. What am I doing wrong? Is my method valid to get disk information or I should use another method? Below is the code of 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;
}
Thank you very much for helping.
There are multiple problems in your functions. Let start with he main one:
In SymbolicLinkTarget()
:
OBJECT_ATTRIBUTES ObjectAtiribute {};
InitializeObjectAttributes( & ObjectAtiribute, SymbolicLinkStr, OBJ_CASE_INSENSITIVE, 0, 0);
You are going to initialize ObjectAtiribute
from SymbolicLinkStr
(and the other parameters) but in DeviceInterfaceTest_Func()
you actually never set Input
to contain a string!
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);
This is wrong:
Input.Length = sizeof((PWSTR) & SymbolicLinkListPtr);
Input.Length
will be set to the size of a pointer. According to the UNICODE_STRING
(ntdef.h; subauth.h) the length
is:
Specifies the length, in bytes, of the string pointed to by the Buffer member, not including the terminating NULL character, if any.
So:
size_t str_len_no_null = wcslen(SymbolicLinkListPtr); // number of chars, not bytes!
Input.Length = str_len_no_null * sizeof(WCHAR);
Notice the wcslen()
is already in the init-statement of the for loop, I would train to extract it to have it in the loop body.
Input.MaximumLength = 200 * sizeof(WCHAR);
What if the string is more lager than 200 characters?
MaximumLength
is defined as such:
Specifies the total size, in bytes, of memory allocated for Buffer. Up to MaximumLength bytes may be written into the buffer without trampling memory.
Thus it's safe to just do:
size_t max_length_bytes = Input.Length + (1 * sizeof(WCHAR)); // add room for possible null.
Input.MaximumLength = max_length_bytes;
The allocation for the Buffer
member can be kept in place. Now you need to copy the string into the buffer.
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
Now that you know how to do it manually, throw your code and use RtlUnicodeStringInit
NTSTATUS
check is always done using one of the status macros (usually NT_SUCCESS
)IoGetDeviceInterfaces
may also indicate an empty buffer. Although you check that in the for loop init-statement, I would have checked that right after the function so the intent is clearer.KdPrint(("Object name:%s \n", ObjectAtiribute.ObjectName - > Buffer));
It's %S
(wide char) not %s
(char); see format specification. you can pass a UNICODE_STRING
and use the %Z
formatter. Also be wary of - >
which is strange (you probably meant ->
).
InitializeObjectAttributes( & ObjectAtiribute, SymbolicLinkStr, OBJ_CASE_INSENSITIVE, 0, 0);
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE
if the resulting handle is not meant to cross the kernel <-> user-mode boundary (in your case, it doesn't have to cross that boundary). Otherwise you leak a kernel handle to user-mode, which has security implications.This is also required when you call ZwOpenSymbolicLinkObject
and you are not running in a system thread:
If the caller is not running in a system thread context, it must set the OBJ_KERNEL_HANDLE attribute when it calls InitializeObjectAttributes.
You can define GUIDs with DEFINE_GUID
; see Defining and Exporting New GUIDs and Including GUIDs in Driver Code. In your case you don't need to export it.
This is probably nitpicking, but use nullptr
(c++) or NULL
(c) instead of 0 to convey the idea that you are checking for a pointer and not just the integral value of 0.