21 Mar 2026
Labs 10-1 and 10-3 from Practical Malware Analysis include drivers which must be copied to C:\Windows\System32. The original drivers were 32-bit and written for Windows XP. Since XP is practically unusable at this point, new 64-bit drivers written for Windows 7 were made available on the PMA GitHub repo. This is again starting to become a problem because I encountered difficulties finding a Windows 7 VM image and the Lab10-03.sys driver crashes on Windows 10. I discovered the source of this problem is hard-coded offsets used as pointers to Windows structures that change with each version (sometimes more than once per version).
No Starch Press has given me permission to distribute the patched version of the Lab 10-03 driver which can be downloaded here. WARNING: DO NOT INSTALL THIS DRIVER ON YOUR HOST SYSTEM. These samples are intended to be run in sandbox VMs. This particular driver only hides the process making calls to it, but it's still a bad idea to install anything designed to imitate malware. The archive password is "malware".
Stepping through the executable portion of the sample with OllyDbg showed that the crash occurs on the DeviceIoControl call. Setting a user-mode breakpoint on that call allowed me to try various kernel-mode breakpoints within the driver. To obtain a list of the driver's function addresses I first had to locate the device object in memory:
Then, view the details of the object:
And finally, view the memory at the MajorFunction offset:

After a few tries, I was able to narrow it down to this function:
Stepping through the function in kernel-mode revealed the specific instruction causing the crash:
Stepping through a third time, I examined the contents of R8 before executing the instruction:
The driver is attempting to move a value to an empty pointer, which is what causes the crash. Why is the pointer empty? Looking back at the function, R8 was last defined by an offset from RAX which was the address of the current process returned by IoGetCurrentProcess. After confirming that call successfully returned a valid memory address, I had to do some research to find out why the offset was empty. What I found is that the offsets being moved into R8 and RCX are addresses that point inside the ActiveProcessLinks structure of the current process in Windows 7. This structure is a list which holds flink (pointer to the next process) and blink (pointer to the previous process). The problem is that the offset to this structure has changed several times since Windows 7. Instead of 0x188, the offset in the version of Windows 10 that I'm using is at 0x448:
| Version | Offset |
|---|---|
| Windows 7 | 0x188 |
| Windows 10 1507-1709 | 0x2e8 |
| Windows 10 1803-1909 | 0x2f0 |
| Windows 10 2004+ / Windows 11 | 0x448 |
The offset can also be viewed using the dt nt!_EPROCESS command, which shows the layout of _EPROCESS structures on the current system:
Before patching the driver I had to confirm my suspicions. After the offsets were written, I read the two values located at offsets 0x448 and 0x450 which did look like nearby memory addresses. Then I wrote those addresses to RCX and R8 and continued stepping through the function. I had to repeat this a couple times because there are some redundant moves:

Stepping through got me through the end of the function with no errors! After resuming execution I saw that the malicious executable in the VM was generating pop-ups like it was supposed to and the process was not listed in Task Manager!
Now I had to open the driver in a hex editor and replace the incorrect values:

Then, I saved the patched driver and transferred it back to the VM.
Getting the driver to run on the VM took a little more work. Patching the binary changed its signature and Windows 10 refused to run it due to Driver Signature Enforcement, resulting in File not found errors on StartService. First I ensured test signing was enabled:
After installing the Microsoft SDK and WDK packages, I then used the MakeCert tool to create a testing certificate:
Then, signed the driver with the new certificate using SignTool:
Next I moved the patched and signed driver to C:\Windows\System32 and stepped through with OllyDbg:

Success! StartService returned a non-zero value and continued execution!
Here the process can be seen in Process Explorer before the call to DeviceIoControl:
And after, it's gone:

I successfully patched the driver for a different Windows version and it is working as intended! Unfortunately, execution freezes in the loop when trying to open the URL due to a failed call to CoCreateInstance, but the important thing is that the driver patch was successful.
The hard-coded values in the original driver for Lab10-03 made it difficult to complete this lab initially. Fortunately this gave me the opportunity to get some practice with WinDbg to actually debug and patch a kernel driver. This was a valuable learning experience that taught me even more about Windows internals than the lab intended. I look forward to diving even deeper into researching Windows internals like Driver Signature Enforcement in the future.