HDD serial number

Hi ALL!

I have a question:
How to read HDD serial number from device driver?

Best regards,
Dmitry.

Same question after 19 years…

And the same answer I’ve been giving to such questions for that time: Did you try Google? There are, literally, 197,000 results when I entered this query.

Peter

(OK… after rethinking my wise-guy answer: Have you tried IOCTL_STORAGE_QUERY_PROPERTY?)

There are, literally, 197,000 results when I entered this query.

…and, out of these 197,000 results, IFengHuang somehow managed to find the one that points to the question that, for some reason, was left unanswered on NTDEV, and decided to “revive” the thread that had been resting in peace for 19(!!!) fucking years. Sounds incredible, but that’s the way it is…

Anton Bassov

https://i.pinimg.com/originals/0d/c0/ad/0dc0adc947663e9bf31365edde44424c.gif![](/uploads/db2714/original/1X/fa82999e884155d8afe7f709ffbf19e151c729ed.gif “”)

I’m so sorry for “Dig Grave”.

After referring to this article, I got a solution.

The following is my code, leave it for those who need it in the future.

[MODS: Updated sample provided by author below]

Thank you for coming back to us and showing the solution. That’s very nice of you! MUCH appreciated.

Peter

Where did you get this code? The “AllocRetry” thing here is an abomination that shows a fundamental misunderstanding. If your system is so short on non-paged pool that you can’t allocate these small structures, then your system is going down. Retries won’t help, and they are silly for a request as trivial as this one. Some people added these retry functions to try to work around the “low-memory simulation” in Driver Verifier, but the WHOLE POINT of that test is to make sure your driver handles a low-memory situation gracefully. By retrying like this, you never test your low-memory path.

You should eliminate AllocRetry and replace it with ExAllocatePoolXxx. It’s not clear to me why you need non-paged pool here, either. ZwCreateFile requires PASSIVE_LEVEL, so you’ll be able to handle paged pool.

@Tim_Roberts said:
Where did you get this code? The “AllocRetry” thing here is an abomination that shows a fundamental misunderstanding. If your system is so short on non-paged pool that you can’t allocate these small structures, then your system is going down. Retries won’t help, and they are silly for a request as trivial as this one. Some people added these retry functions to try to work around the “low-memory simulation” in Driver Verifier, but the WHOLE POINT of that test is to make sure your driver handles a low-memory situation gracefully. By retrying like this, you never test your low-memory path.

You should eliminate AllocRetry and replace it with ExAllocatePoolXxx. It’s not clear to me why you need non-paged pool here, either. ZwCreateFile requires PASSIVE_LEVEL, so you’ll be able to handle paged pool.

The code is referring to this article, then I modify it into kernel mode.

Thank you for pointing out the shortcomings. I’ll modify the code later.

@Tim_Roberts said:
Where did you get this code? The “AllocRetry” thing here is an abomination that shows a fundamental misunderstanding. If your system is so short on non-paged pool that you can’t allocate these small structures, then your system is going down. Retries won’t help, and they are silly for a request as trivial as this one. Some people added these retry functions to try to work around the “low-memory simulation” in Driver Verifier, but the WHOLE POINT of that test is to make sure your driver handles a low-memory situation gracefully. By retrying like this, you never test your low-memory path.

You should eliminate AllocRetry and replace it with ExAllocatePoolXxx. It’s not clear to me why you need non-paged pool here, either. ZwCreateFile requires PASSIVE_LEVEL, so you’ll be able to handle paged pool.

By the way, but I saw many alloc-retry-like macro inside WFPSampler codes, why do they write like that?

I find that I can’t edit my comment anymore. :cry:

I can’t edit my comment anymore

Yup! By design. You have one hour to edit something you post. After that, you’ve got to me a mod to edit anything.

alloc-retry-like macro inside WFPSampler codes, why do they write like that

If the screen cap you posted is what you’re referring to, this isn’t an alloc-retry… it’s using a “safe multiplier” function to detect/avoid an overrun during the multiply.

Having said that, I’m not sure why – after calling RtlSizeTMult and checking for success – he needs to check that the allocated size is >= the plain multiplied (count * sizeof) size… but “whatever”…

Peter

OTOH, if you’re referring to this:

That is some of the dumbest code I’ve seen in a very long time. Whoever wrote that tight loop calling ExAllocatePool over and over again should be … I dunno. Something.

Peter

@“Peter_Viscarola_(OSR)” said:

I can’t edit my comment anymore

Yup! By design. You have one hour to edit something you post. After that, you’ve got to me a mod to edit anything.

I want to fix my code above for removing AllocRetry… How can I do that now ?

Just call ExAllocatePool instead of AllocRetry. I think Mr. Roberts May have suggested this already.

Peter

@“Peter_Viscarola_(OSR)” said:
Just call ExAllocatePool instead of AllocRetry. I think Mr. Roberts May have suggested this already.

Peter

:smiley: I means I want to fix my comment above for those who need it in the future easy to read.

That is very considerate. Why not post an updated version below?

Peter

Concise, neat and centralized code which showing in the forum is very important for me, especially for those who are eager to find an off-the-shelf solution (like me).

Here is the fixed version:

...
#include <ntddstor.h>
#define DEVICE_DESCRIPTOR 'CDVD'
#define SERIAL_NUMBER 'BNRS'
...
NTSTATUS GetHardDiskDriveSerialNumber(_Out_ PCHAR *pSerialNumber, _Out_ SIZE_T *pLength) {
    NTSTATUS status = STATUS_SUCCESS;
    STORAGE_PROPERTY_QUERY storagePropertyQuery = { StorageDeviceProperty, PropertyStandardQuery, 0 };
    STORAGE_DESCRIPTOR_HEADER storageDescriptorHeader = { 0 };
    PSTORAGE_DEVICE_DESCRIPTOR pDeviceDescriptor = nullptr;

    UNICODE_STRING fileName;
    RtlInitUnicodeString(&fileName, L"\\DosDevices\\PhysicalDrive0");

    OBJECT_ATTRIBUTES attributes;
    InitializeObjectAttributes(&attributes, &fileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, nullptr, nullptr);

    HANDLE fileHandle = nullptr;
    IO_STATUS_BLOCK ioStatus;

    // open PhysicalDrive0
    status = ZwCreateFile(
        &fileHandle,
        GENERIC_READ,
        &attributes,
        &ioStatus,
        nullptr,
        FILE_ATTRIBUTE_NORMAL,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        FILE_OPEN,
        FILE_NON_DIRECTORY_FILE,
        nullptr,
        0);

    if (!NT_SUCCESS(status) || !NT_SUCCESS(ioStatus.Status)) {
        KdPrint(("Open PhysicalDrive0 failed, status: %!STATUS!, ioStatus: %!STATUS!, information: %llu.\n", 
            status, ioStatus.Status, ioStatus.Information));

        if (!NT_SUCCESS(ioStatus.Status))
            status = ioStatus.Status;

        goto END;
    }

    KdPrint(("Open PhysicalDrive0 success: %p.\n", fileHandle));

    // first call for getting size
    status = ZwDeviceIoControlFile(
        fileHandle,
        nullptr,
        nullptr,
        nullptr,
        &ioStatus,
        IOCTL_STORAGE_QUERY_PROPERTY,
        &storagePropertyQuery,
        sizeof(STORAGE_PROPERTY_QUERY),
        &storageDescriptorHeader,
        sizeof(STORAGE_DESCRIPTOR_HEADER));

    if (!NT_SUCCESS(status) || !NT_SUCCESS(ioStatus.Status)) {
        KdPrint(("ZwDeviceIoControlFile first call failed, status: %!STATUS!, ioStatus: %!STATUS!, information: %llu.\n", 
            status, ioStatus.Status, ioStatus.Information));

        if (!NT_SUCCESS(ioStatus.Status))
            status = ioStatus.Status;

        goto END;
    }

    // alloc for descriptor
    pDeviceDescriptor = (PSTORAGE_DEVICE_DESCRIPTOR)ExAllocatePoolWithTag(NonPagedPoolNx, storageDescriptorHeader.Size, DEVICE_DESCRIPTOR);
    if (!pDeviceDescriptor) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        goto END;
    }
    RtlZeroMemory(pDeviceDescriptor, storageDescriptorHeader.Size);

    // second call for getting serial number
    status = ZwDeviceIoControlFile(
        fileHandle,
        nullptr,
        nullptr,
        nullptr,
        &ioStatus,
        IOCTL_STORAGE_QUERY_PROPERTY,
        &storagePropertyQuery,
        sizeof(STORAGE_PROPERTY_QUERY),
        pDeviceDescriptor,
        storageDescriptorHeader.Size);

    if (!NT_SUCCESS(status) || !NT_SUCCESS(ioStatus.Status)) {
        KdPrint(("ZwDeviceIoControlFile second call failed, status: %!STATUS!, ioStatus: %!STATUS!, information: %llu.\n", 
            status, ioStatus.Status, ioStatus.Information));

        if (!NT_SUCCESS(ioStatus.Status))
            status = ioStatus.Status;

        goto END;
    }

    if (pDeviceDescriptor->SerialNumberOffset > 0) {
        PCHAR originSerialNumber = WDF_PTR_ADD_OFFSET_TYPE(pDeviceDescriptor, pDeviceDescriptor->SerialNumberOffset, PCHAR);

        KdPrint(("SerialNumber: %s.\n", originSerialNumber));

        status = RtlStringCbLengthA(originSerialNumber, 128, pLength);
        if (!NT_SUCCESS(status)) {
            KdPrint(("RtlStringCbLengthA failed, string: %s.\n", originSerialNumber));
            goto END;
        }

        SIZE_T bufferSize = (*pLength) + 1; // include terminating null character
        PCHAR serialNumber = (PCHAR)ExAllocatePoolWithTag(NonPagedPoolNx, bufferSize, SERIAL_NUMBER);
        if (!serialNumber) {
            status = STATUS_INSUFFICIENT_RESOURCES;
            goto END;
        }
        RtlZeroMemory(serialNumber, bufferSize);

        status = RtlStringCbCopyA(serialNumber, bufferSize, originSerialNumber);
        if (!NT_SUCCESS(status)) {
            KdPrint(("RtlStringCbCopyA failed, status: %!STATUS!, string: %s.\n", status, originSerialNumber));

            if (serialNumber)
                ExFreePoolWithTag(serialNumber, SERIAL_NUMBER);

            goto END;
        }

        *pSerialNumber = serialNumber;

    } else {
        KdPrint(("SerialNumberOffset: %u.\n", pDeviceDescriptor->SerialNumberOffset));
        status = STATUS_QUERY_STORAGE_ERROR;

        *pSerialNumber = nullptr;
        *pLength = 0;
    }

END:
    if (fileHandle)
        ZwClose(fileHandle);
    if (pDeviceDescriptor)
        ExFreePoolWithTag(pDeviceDescriptor, DEVICE_DESCRIPTOR);

    return status;
}

VOID FreeHardDiskDriveSerialNumber(_In_ PCHAR serialNumber) {
    ExFreePoolWithTag(serialNumber, SERIAL_NUMBER);
}

And I have removed the code sample from your earlier post and pointed people to this post.

Thank you for your concern for future people searching for this info. It is very good of you to do this… too few people are willing to share with others. They only care for their question to be answered. So… thanks!

Peter

Concise, neat and centralized code which showing in the forum is very important for me, especially for those who are eager
to find an off-the-shelf solution (like me).

Well, the example of AllocRetry() in your code seems to be a pretty good demo of why copy-pasting “off-the-shelf solutions” may be not- so- good idea - after all, you did not come up with this “pearl” yourself, did you. Instead, you have modeled it upon some existing sample…

Anton Bassov