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

   +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:

   +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.

Pool tags are clearly visible in this hex view.

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".


What are the actions I should take to correct a BAD_POOL_HEADER? I have never received this error before and was hoping to fing some guidance here. Let me know if anyone can help


your question does not seem to be related to the usage of pool headers in forensic analysis. I beg your pardon that I'm not an experienced kernelprogrammer on the Windows platform.

BAD_POOL_HEADER is a generic bugcheck. Microsoft provides some help on interpreting the additional parameters given on the blue screen (or in the memory dump). Please see

If you're a programmer and it happens in your code, then I might suggest to run your driver under the pool verifier.

Hope that'll help!




This blog is a project of:
Andreas Schuster
Im Äuelchen 45
D-53177 Bonn

Copyright © 2005-2012 by
Andreas Schuster
All rights reserved.
Powered by Movable Type 5.12