Wednesday, April 16, 2008

Finding Kernel Global Variables in Windows

When performing memory analysis of Windows systems, there are a number of kernel variables that are extremely helpful in determining the state of the operating system. For example, the global variable PsActiveProcessHead is a pointer to the start of the kernel's list of _EPROCESS structures, and PsLoadedModuleList points to the list of currently loaded kernel modules (i.e., drivers). Unfortunately, these variables are not among those exported by the kernel, so finding them isn't as simple as looking them up in the export table of ntoskrnl.exe.


How, then, do tools like Volatility find these addresses so that they can produce process listings from memory dumps? The simplest technique is just to hard-code the address of each variable. The "basic" version of Volatools does exactly this; if you open up vsyms.py, you will see the hardcoded values for PsLoadedModuleList, PsActiveProcessHead, PsIdleProcess, and HandleTableListHead. The downside of this is that the addresses of these variables are only constant for a particular version of Windows at a given patch level, and even a single hotfix can change them and render your tools useless.


Much more reliable is what's known as the KPCR trick. First discovered by Edgar Barbosa (Opc0de) in his paper "Finding some non-exported kernel variables in Windows XP", and expanded upon by Alex Ionescu in a post entitled "Getting Kernel Variables from KdVersionBlock, Part 2", the trick takes advantage of a data structure in Windows known as the Kernel Processor Control Region, or KPCR. The KPCR is a data structure used by the Windows kernel to store information about each processor, and it is located at virtual address 0xffdff000 in XP, 2003, and Vista. What Opc0de noticed is that between Windows 2000 and Windows XP, one of the fields, Reserved2 changed to KdVersionBlock. After some investigation, it turned out that this field pointed to a _DBGKD_GET_VERSION64 structure, which in turn contained a linked list of _KDDEBUGGER_DATA64 structures.


The _KDDEBUGGER_DATA64 structure is used by the kernel debugger to easily find out the state of the operating system -- in order to provide meaningful information about the cause of a crash, the debugger needs many of the same pieces of information that we do when performing forensics: what processes were active, which handles to objects were held, and so on. In addition, Microsoft would prefer to only support one version of WinDbg, rather than having to deal with many different builds for different versions and service packs of the operating system, so the data in the structure is relatively stable. And so this data structure contains the memory addresses of a large number of kernel variables (around 70 in Windows Vista).


The fact that this structure can be found through a constant offset and contains such a wealth of data makes the KPCR method a very attractive option on XP and above. Also, fields inside the structure do not move around between versions of the operating system (there is even a stern warning in WDbgExts.h not to add or remove fields in the middle of the structure), though fields may be added at the end. This technique is used by Volatility to find many kernel global variables while supporting different versions of Windows XP SP2. For a full list of the variables that can be found using this method, consult WDbgExts.h in the WinDbg SDK.


Unfortunately, as mentioned above, Windows 2000 does not have a pointer to the _KDDEBUGGER_DATA64 stored in the KPCR. However, I have found that this structure still exists in Windows 2000 and contains the same useful data, but we must do a little bit more work to find it. To locate the data we need in Windows 2000, we will develop a signature that reliably locates the structure, and then simply do a linear scan of physical memory for it.


The debugger data block starts with a standard header that looks like (reproduced here from WDbgExts.h, minus comments):


typedef struct _DBGKD_DEBUG_DATA_HEADER64 {
LIST_ENTRY64 List;
ULONG OwnerTag;
ULONG Size;
} DBGKD_DEBUG_DATA_HEADER64, *PDBGKD_DEBUG_DATA_HEADER64;

Since the LIST_ENTRY is 64-bit, on 32-bit systems the last 8 bytes of the list structure will be 0. Perhaps more importantly, the OwnerTag is set to the constant value "KDBG". Finally, the Size is 0x290 bytes on XP and above, and 0x208 bytes on Windows 2000 (thus on Windows 2000 only the variables up through MmLoadedUserImageList in the structure are available).


To search for the debugger data block, then, we can just look for the constant string:

\x00\x00\x00\x00\x00\x00\x00\x00KDBG

If we wish to restrict our search to a specific operating system, we can append \x08\x02 or \x90\x02 to specify the size for Windows 2000 or XP, respectively.


In theory, it should even be possible to extend this technique to Windows NT4, though I do not have any NT4 memory images to test with. According to WDbgExts.h, however, the structures used would be 32-bit (_KDDEBUGGER_DATA32 and _DBGKD_DEBUG_DATA_HEADER32), so the signature would need some small modifications.


So there you have it! Using either the KPCR trick or a signature based-scan, we can find the kernel debugger data block, and from it obtain the addresses of crucial kernel variables. It is worth noting, though, that not all of the kernel's global variables are available through this technique. For example, one variable which we used recently, CmpCacheTable, is nowhere to be found in the debugger data block. In this case, either a heuristic or the public debug symbols will be needed to find the address of the variable.

3 comments:

Sippy said...

Brendan,

Thanks for sharing the technique, I was wondering if you guys were using it in Volatility :)

BTW, I'm not sure if this was before 0pcode, but Sven Schreiber also posted a huge article in 2001 about Win2k memory forensics and talks about KPCR. See page 5 below:

http://www.informit.com/articles/article.aspx?p=167857&seqNum=5

Thanks!

AW said...

"I was wondering if you guys were using it in Volatility :)"

May the source be with you! No surprises.

AW

printf said...

Do you know how to find the KPCR on the Windows 64bit?