This article introduces a small, yet important data structure of the Microsoft Windows NT kernel, the POOL_HEADER. For sure I will rely on this structure on several occasions. Also my talk at the IMF 2006 conference will be dedicated to it.
The smallest memory allocation unit at the hardware level is the page, which commonly consists of 4096 bytes. On the other hand the kernel and device drivers usually need not (and should not !) allocate large chunks of memory. So obviously a lot of memory would be wasted.
There's a simple solution to this problem: The kernel allocates some pages in advance and combines them into a pool. Whenever a routine requests some memory (less than a page), the kernel will assign it from the pool. The granularity of these assignments is 32 bytes for Windows 2000 and even only 8 bytes from XP onwards.
Each allocation will be preceeded by a small header which helps to keep track of its size and purported owner. This header is called the POOL_HEADER. For Windows 2000 it's declared as
kd> dt _POOL_HEADER +0x000 PreviousSize : UChar +0x001 PoolIndex : UChar +0x002 PoolType : UChar +0x003 BlockSize : UChar +0x000 Ulong1 : Uint4B +0x004 ProcessBilled : Ptr32 _EPROCESS +0x004 PoolTag : Uint4B +0x004 AllocatorBackTraceIndex : Uint2B +0x006 PoolTagHash : Uint2B
And now the same for Windows XP:
kd> dt _POOL_HEADER +0x000 PreviousSize : Pos 0, 9 Bits +0x000 PoolIndex : Pos 9, 7 Bits +0x002 BlockSize : Pos 0, 9 Bits +0x002 PoolType : Pos 9, 7 Bits +0x000 Ulong1 : Uint4B +0x004 ProcessBilled : Ptr32 _EPROCESS +0x004 PoolTag : Uint4B +0x004 AllocatorBackTraceIndex : Uint2B +0x006 PoolTagHash : Uint2B
BlockSize states the allocation's size plus the header (8 bytes) in units of the granularity (32 or 8 bytes). In a similiar way PreviousSize refers back to the beginning of the preceeding allocation. Notice the change in the structure's definition, which is due to the change in granularity.
Whenever a routine requests some memory through ExAllocatePoolWithTag or a similiar function, it is required to provide a tag consisting of up to four printable characters. This tag will then be stored in the header's PoolTag field. Note that there's no form of autorization involved here. Any routine might claim any tag, including tags already used by some other piece of code, either accidentally or by malicious intent.
A pool's contents can be easily inspected with Microsoft's kernel debugger.
kd> !pool e1000000 Pool page e1000000 region is Paged pool *e1000000 size: 48 previous size: 0 (Allocated) *MmDT e1000048 size: 10 previous size: 48 (Free) ... e1000058 size: 60 previous size: 10 (Allocated) Dacl e10000b8 size: 10 previous size: 60 (Allocated) ObNm e10000c8 size: 10 previous size: 10 (Free) SeSc e10000d8 size: 10 previous size: 10 (Allocated) ObDi e10000e8 size: 10 previous size: 10 (Allocated) ObDi e10000f8 size: 10 previous size: 10 (Allocated) ObDi e1000108 size: 10 previous size: 10 (Allocated) ObDi e1000118 size: 8 previous size: 10 (Free) ObNm e1000120 size: 10 previous size: 8 (Allocated) ObDi e1000130 size: 10 previous size: 10 (Allocated) ObDi e1000140 size: 20 previous size: 10 (Allocated) ObNm e1000160 size: 10 previous size: 20 (Allocated) SePa e1000170 size: 50 previous size: 10 (Allocated) Obtb
Now compare this to the following view of the raw dump file in a hex editor.
The pool tags are clearly visible, also some strings stored in the pool allocations. If you're used to Windows data structures you might also recognize some Security IDs (SIDs) in the block tagged with "Dacl".