lpus/nonpaged-pool-range.md

6.2 KiB

If you came here for MmNonPagedPoolStart, MmNonPagedPoolEnd, you ended up at the right place.

NonPagedPool in Windows has two variables that defined the start and end of the section in kernel memory. Online blog posts and tutorials show an outdated version of these two variables.

Take a look at this old post. _DBGKD_GET_VERSION64 KdVersionBlock was a very important structure into the debugger block of Windows. However, if you try to find this structure in Windows 10, you will hit KdVersionBlock == 0 (Ouch!!!). But this structure provides offset into MmNonPagedPool{Start,End}, how can we get those?

Luckily, both MmNonPagedPoolStart and MmNonPagedPoolEnd in Windows XP, can be found by offseting from ntoskrnl.exe. Rekall team are very positive that their tools doesn't rely on profiles file like Volatility but use PDB provided by Windows to find these values.

In Rekall source code, the values of those variables are:

  • Windows XP: MmNonPagedPool{Start,End}
  • Windows 7 and maybe 8: MiNonPagedPoolStartAligned, MiNonPagedPoolEnd, and MiNonPagedPoolBitMap

In Windows 7, 8, another field was added to controll the allocation of NonPagedPool, which is also mentioned in this paper about pool tag quick scanning.

However, from Windows 10, the whole game changed around when the global offset to those (similar) variables are gone. Instead Windows 10 introduced a new variable MiState. MiState offset is available and we can get those start/end variables by either:

  • Windows 2015: (_MI_SYSTEM_INFORMATION*)(MiState)->SystemNodeInformation.NonPagedPool{First,Last}Va
  • Windows 2016: (_MI_SYSTEM_INFORMATION*)(MiState)->Hardware.SystemNodeInformation.NonPagedPool{First,Last}Va

The NonPagedBitMap was still visible untill the May 2019 Update, here, take a look at these 2 consecutive update 1809 Redstone 5 (October Update) and 1903 19H1 (May 2019 Update).

Windows OS changes quite frequently right? Tell you more, I am using the Insider version of Windows in 2020, and guess what, I found out that they put another struct to point to those value. So now we need to go like this:

  • Windows 2020 Insider preview: *(_MI_SYSTEM_INFORMATION*)(MiState)->Hardware.SystemNodeNonPagedPool.NonPagedPool{First,Last}Va

If you go with low-level, then you only care about the offset and formula to get those variables but knowing the structure is well benefit.

Anyway, I create this project to help me with my thesis, following outdated structs online yields no result. Oh, yeah, a guy seems to be asking on how to get MmNonPagedPoolStart on stackexchange, too bad the answer is not so much helpful.


Global variables offset are parsed from the PDB file and can be queried by nt! in Windbg. In a kernel driver, we need to get the kernel base address (which is nt!). Kernel base address is the loaded address of ntoskrnl.exe. There is a shellcode to get the address here, using IDT table. But when I use the shellcode with the Windows Insider preview 2020, the address is wrong (it still a loaded PE though). Other ways to get the address are listed here. And hereby I present another way to get the kernel base address.

A device driver can get a pointer to an _EPROCESS through the use of PEPROCESS IoGetCurrentProcess. And as we know, _EPROCESS has pointer to other _EPROCESS as a circular doubly linked list. If we dump them all out, we can notice a few things:

  • The image name returned by calling IoGetCurrentProcess in DriverEntry is System
  • The _EPROCESS before System is somehow empty
// in DriverEntry
PVOID eprocess = (PVOID)IoGetCurrentProcess();

// somewhere after offsets are setup
DbgPrint("eprocess : 0x%p, [%15s]\n", eprocess, (char*)((ULONG64)eprocess + ImageBaseNameOffset));
for (int i = 0; i < 100; i++) {
  eprocess = (PVOID)(*(ULONG64*)((ULONG64)eprocess + ActiveProcessLinksOffset) - ActiveProcessLinksOffset);
  DbgPrint("eprocess : 0x%p, [%15s]\n", eprocess, (char*)((ULONG64)eprocess + ImageBaseOffset));
}

// sample output
eprocess : 0xFFFFF8037401F528, [               ]
eprocess : 0xFFFF840F5A0D9080, [         System]
eprocess : 0xFFFF840F5A28C040, [  Secure System]
eprocess : 0xFFFF840F5A2EF040, [       Registry]
eprocess : 0xFFFF840F622BF040, [       smss.exe]
eprocess : 0xFFFF840F6187D080, [       smss.exe]
eprocess : 0xFFFF840F6263D140, [      csrss.exe]
eprocess : 0xFFFF840F6277F0C0, [       smss.exe]
eprocess : 0xFFFF840F627C2080, [    wininit.exe]
eprocess : 0xFFFF840F64187140, [      csrss.exe]
eprocess : 0xFFFF840F641CD080, [   services.exe]

And if we debug and compare the address of that Empty _EPROCESS+ActiveProcessLinksOffset with nt!PsActiveProcessHead, it is just the same. And with the given offset parsed from the PDB file, we can get kernel base address.

// In DriverEntry
PVOID eprocess = (PVOID)IoGetCurrentProcess();

// somwhere after offsets are setup
DbgPrint("eprocess               : 0x%p, [%15s]\n", eprocess, (char*)((ULONG64)eprocess + ImageBaseNameOffset));
PVOID processHead = (PVOID)(*(ULONG64*)((ULONG64)eprocess + ActiveProcessLinksOffset + BLinkOffset));
DbgPrint("PsActiveProcessHead    : 0x%p\n", processHead);
PVOID ntosbase = (PVOID)((ULONG64)processHead - ActiveHeadOffset);
DbgPrint("ntoskrnl.exe           : 0x%p\n", ntosbase);

From now we have successfully get the kernel base address to index into other global variables.