I still think this will create a BSOD, what's more I think that this is deliberate. It makes perfect sense to assume that this is deliberate once piecing all the puzzle pieces together. The source incompatibility will inevitably force that the developer notices the change of type for KeNumberProcessors
from PCCHAR
to CCHAR
. The likeliest error is: error C2100: illegal indirection
.
According to the PE header the file was created using WDK 7600.16385.1 (OS version), targeting XP (subsystem version), assuming we can trust best practices having been used to create the driver. However, the driver does run on Windows 2000 Professional (with SP4) as I verified. This is possible whenever the author is careful enough not to use functions unavailable on Windows 2000, but available at compile and link time for the nominal target.
Okay, so say you have an old driver targeting pre-XP Windows versions. You'll write this (some more code was necessary to force this code not to be optimized away):
CCHAR nCpus = *KeNumberProcessors;
PVOID lpBuf = ExAllocatePool(NonPagedPool, nCpus * 7);
when building this for Windows 2000 (free) as target (using WDK 6001.18002), we get for the line assigning nCpus
and the subsequent one:
.text:00010519 mov eax, ds:__imp__KeNumberProcessors
; opcodes unrelated to processor number
.text:0001052E movsx eax, byte ptr [eax]
.text:00010531 imul eax, 7
.text:00010534 push edi
.text:00010535 push eax ; NumberOfBytes
.text:00010536 push ebx ; PoolType
.text:00010537 call ds:__imp__ExAllocatePool@8 ; ExAllocatePool(x,x)
.text:0001053D mov [ebp+lpBuf], eax
and when targeting Windows XP using the WDK 7600.16385.1 we get an error during compilation:
KNPs.cpp(102) : error C2100: illegal indirection
... which we fix by removing the *
that dereferences KeNumberProcessors
, which then gives us a successful compilation and the following code:
.text:00010519 mov eax, ds:__imp__KeNumberProcessors
; opcodes unrelated to processor number
.text:0001052E movsx eax, byte ptr [eax]
.text:00010531 imul eax, 7
.text:00010534 push edi
.text:00010535 push eax ; NumberOfBytes
.text:00010536 push ebx ; PoolType
.text:00010537 call ds:__imp__ExAllocatePool@8 ; ExAllocatePool(x,x)
.text:0001053D mov [ebp+lpBuf], eax
that's identical. And there is no indication of anything like in the .sys
for DebugView that would deliberately cause a BSOD.
So my money is on: this is meant to cause a BSOD, knowing full well that this situation could realistically never occur.
Original answer below:
With the information I could dig up, I concur. This code would seem to lead to an inevitable BSOD.
Let's start by stating that the maximum available number of processors for Windows 2000 Server (Datacenter edition) was 32.
Based on the declaration for KeNumberProcessors
, which is by the way deprecated in favor of KeQueryActiveProcessors
:
#if (NTDDI_VERSION >= NTDDI_VISTA)
extern NTSYSAPI volatile CCHAR KeNumberProcessors;
#elif (NTDDI_VERSION >= NTDDI_WINXP)
extern NTSYSAPI CCHAR KeNumberProcessors;
#else
extern PCCHAR KeNumberProcessors;
#endif
that variable used to be a pointer prior to Windows XP. Looking at the above linked documentation you will find (relevant excerpts):
The KeNumberProcessors
kernel variable is obsolete in Windows Vista
with Service Pack 1 (SP1), Windows Server 2008, and later versions of
Windows. KeNumberProcessors
does not appear in WDK headers for WDK
releases starting with Windows Vista SP1; however, the variable is
still exported from the kernel, so drivers built for earlier platforms
will not break
... and:
Starting with Windows XP, KeNumberProcessors
is an 8-bit integer value
that indicates the number of processors in the platform. In earlier
versions of Windows, KeNumberProcessors
is a pointer to an 8-bit
integer value that indicates the number of processors in the platform.
Now, while I certainly may not have all the answers that the folks from Sysinternals/Microsoft have by glancing at the Windows source code, my guess is the following. This was meant to be a clever trick - relying on circumstantial knowledge - to accommodate both Windows XP and newer (where the variable is a CCHAR
) as well as prior Windows versions, where it's a PCCHAR
.
Older MSDN documentation is even more verbose:
Change in the Definition of KeNumberProcessors
The kernel variable KeNumberProcessors
indicates the number of active CPUs in the system
on which a driver is running. In the Microsoft Windows XP versions of
ntddk.h and wdm.h, the definition of KeNumberProcessors
has been
changed from a pointer to a value. The Microsoft® Windows® 2000
definition of this variable required that KeNumberProcessors
be
dereferenced (for example, *KeNumberProcessors
). As a result of the
changed definition of this variable, drivers built in the Windows XP
build environment must not dereference this variable (for example,
KeNumberProcessors
).
Drivers that fail to use KeNumberProcessors
correctly will get an
"illegal indirection" error at compile time.
Note that regardless of the declaration used, drivers that properly
reference KeNumberProcessors
according to the environment in which
they are built will work properly on both Windows 2000 and Windows XP.
Thus, for instance, a driver built in the Windows 2000 build
environment that dereferences KeNumberProcessors
will get the proper
value for this variable when the driver is run on either Windows 2000
or Windows XP.
Problem seems to be that they seem to have gotten it wrong, if I am not all mistaken. There's, however, also a chance they got it right and I am lacking some vital information they have access to :)
You can test the scenario with MSVC by using:
DWORD_PTR KeNumberProcessors = 0x80564321;
DWORD_PTR dsKeNumberProcessors = (DWORD_PTR)&KeNumberProcessors;
__asm {
push eax
mov eax, dsKeNumberProcessors
mov al, [eax] // only affects AL -> AL := 0x21
cmp al, 0x40
movsx eax, al // garbles EAX
pop eax
};
Stepping through it gives you a clue as to what's going on. I have to admit the movsx
throws me off here.
Here's how I think it may have been meant to work
Read the byte at the address specified by KeQueryActiveProcessors
. See if it's a value smaller than 0x40 and if so, stick with the interpretation as byte.
If bigger than 0x40, which happens whenever the least significant byte of the (Little Endian) address contained in KeQueryActiveProcessors
(i.e. on Windows prior to XP) is big enough, we may assume that this is in fact a pointer and dereference it.
NB: re-read it: the least significant byte. So it's not the 0x80 in the base address of the kernel (the kernel is loaded at an address higher than 0x80000000) that matters here. For Windows 2000 I have found the following values for the least significant byte:
- 0x70 (ntkrnlpa.exe, SP4)
- 0xF0 (ntoskrnl.exe, SP4)
- 0xC8 (ntkrnlpa.exe, RTM)
- 0x30 (ntoskrnl.exe, RTM)
I didn't check any other kernel version, though. Problem here is that we already have a value smaller than 0x40 among the lot.
By the way: having an address beyond 0x40 could reasonably be enforced by having the byte be a member of a struct that is page-aligned.
Now the part that would in any case destroy this scenario is the movsx
(sign extend). It overwrites what could have been the address of the CCHAR
unconditionally, thereby creating a bogus address that should for all practical purposes cause a BSOD.
Anyway, the mechanism behind this may have been meant to be something like with the resources IDs which can be 16-bit unsigned integers or pointers to zero-terminated strings with the resource name.