As you may or may not know, some anti-cheats query serials of certain devices from their kernel-mode drivers. One of most popular serials to check apart from SMBIOS are disk serials.

If you want to change them, you can try to replace them in some structs such as RAID_UNIT_EXTENSION of the storport.sys driver. The problem with this approach is that most modern disks have SMART feature which also reports serials. You can’t statically change those serials since Windows sends SMART command to the disk each time you query them (SMART_RCV_DRIVE_DATA IOCTL to disk driver). The only way to change them is to hook the IOCTL somehow or make in non-functional.

Now it is the time to present my incredibly easy to do, meme worthy method:

NTSTATUS Disks::ChangeDispatch()
{
	PVOID base = Utils::GetModuleBase("CLASSPNP.SYS");
	if (!base)
		return STATUS_UNSUCCESSFUL;

	PVOID scan = Utils::FindPatternImage(base, "<sig here>", "<pattern here>"); // ClassMpdevInternalDeviceControl
	if (!scan)
		return STATUS_NOT_FOUND;
	
	UNICODE_STRING driverDisk;
	RtlInitUnicodeString(&driverDisk, L"\\Driver\\Disk");

	PDRIVER_OBJECT driverObject = nullptr;
	auto status = ObReferenceObjectByName(&driverDisk, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, nullptr, 0, *IoDriverObjectType, KernelMode, nullptr, reinterpret_cast<PVOID*>(&driverObject));
	if (!NT_SUCCESS(status))
		return STATUS_OBJECT_NAME_INVALID;

	driverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = (PDRIVER_DISPATCH)scan;

	ObDereferenceObject(driverObject);
	return STATUS_SUCCESS;
}

Now you might be asking, what the hell is this? Isn’t it just simple pointer replace hook? Well, let me explain…

Most anti-cheats that I have worked with check device control (or other IRP major functions) by simply checking boundaries, something like this:

DWORD64 pointer = (DWORD64)driver->MajorFunction[IRP_MJ_DEVICE_CONTROL];
if (pointer < (DWORD64)driverStartAddress || pointer > (DWORD64)driverEndAddress) 
{
    // very bad
}

Now look back at the first code, I am replacing the original function pointer with a pointer to the same driver. That means the checks will pass!

Now the question is, what is the function that I am changing the pointer to? Well simply said, it can be any function that will return non-zero value when called and not crash the system by reading non-existing or invalid arguments. Bonus points if you can find something that will even call IofCompleteRequest on the IRP. After a quick search in classpnp.sys (which is where the pointer address originally is), I found ClassMpdevInternalDeviceControl. Not only that it instantly returns with invalid status, but it also calls IofCompleteRequest on the IRP and has the same arguments as the original function.

After pointing to this function, the system will effectively think there are no disks and will fail to receive any serials both through IOCTL_STORAGE_QUERY_PROPERTY and SMART_RCV_DRIVE_DATA IOCTL.

As you can see the system is now not aware of the disks since the IOCTL always fails.

Is it stable? I can say that it is as stable as it can get. From a detection standpoint, it should be pretty easy to flag since a system without disks is just a bit weird and you can always do more advanced checks on the major function’s pointer, but I thought it quite funny you can do it this way and it currently works for few games.

Also a small pro tip, you can do this with basically any driver’s major function, so you can do the same for MAC addresses for example.