Friday, February 8, 2008

Enumerating Registry Hives

The Windows registry can be an important forensic resource. Harlan Carvey has written extensively on various aspects of registry analysis, and is even considering writing a book on the topic. I have already written about attempting to extract hives from memory; in this post, we will again look at registry hives in Windows memory, but this time in a more top-down fashion, by examining the data structures used to represent hives by the Configuration Manager.

A hive is represented by the Windows Configuration Manager using the _CMHIVE data structure. You can examine this structure in detail using the dt command in Windbg:

lkd> dt nt!_CMHIVE
+0x000 Hive : _HHIVE
+0x210 FileHandles : [3] Ptr32 Void
+0x21c NotifyList : _LIST_ENTRY
+0x224 HiveList : _LIST_ENTRY
+0x22c HiveLock : Ptr32 _FAST_MUTEX
+0x230 ViewLock : Ptr32 _FAST_MUTEX
+0x234 LRUViewListHead : _LIST_ENTRY
+0x23c PinViewListHead : _LIST_ENTRY
+0x244 FileObject : Ptr32 _FILE_OBJECT
+0x248 FileFullPath : _UNICODE_STRING
+0x250 FileUserName : _UNICODE_STRING
+0x258 MappedViews : Uint2B
+0x25a PinnedViews : Uint2B
+0x25c UseCount : Uint4B
+0x260 SecurityCount : Uint4B
+0x264 SecurityCacheSize : Uint4B
+0x268 SecurityHitHint : Int4B
+0x26c SecurityCache : Ptr32 _CM_KEY_SECURITY_CACHE_ENTRY
+0x270 SecurityHash : [64] _LIST_ENTRY
+0x470 UnloadEvent : Ptr32 _KEVENT
+0x474 RootKcb : Ptr32 _CM_KEY_CONTROL_BLOCK
+0x478 Frozen : UChar
+0x47c UnloadWorkItem : Ptr32 _WORK_QUEUE_ITEM
+0x480 GrowOnlyMode : UChar
+0x484 GrowOffset : Uint4B
+0x488 KcbConvertListHead : _LIST_ENTRY
+0x490 KnodeConvertListHead : _LIST_ENTRY
+0x498 CellRemapArray : Ptr32 _CM_CELL_REMAP_BLOCK

Some interesting things jump out to start with. First, two _UNICODE_STRINGs. These are always good as they will provide us with a human-readable name for the hive. We also notice that the very first field, Hive, is 0x210 bytes long! So let's look at its structure, _HHIVE:

lkd> dt -r1 nt!_HHIVE
+0x000 Signature : Uint4B
+0x004 GetCellRoutine : Ptr32 _CELL_DATA*
+0x008 ReleaseCellRoutine : Ptr32 void
+0x00c Allocate : Ptr32 void*
+0x010 Free : Ptr32 void
+0x014 FileSetSize : Ptr32 unsigned char
+0x018 FileWrite : Ptr32 unsigned char
+0x01c FileRead : Ptr32 unsigned char
+0x020 FileFlush : Ptr32 unsigned char
+0x024 BaseBlock : Ptr32 _HBASE_BLOCK
+0x000 Signature : Uint4B
+0x004 Sequence1 : Uint4B
+0x008 Sequence2 : Uint4B
+0x00c TimeStamp : _LARGE_INTEGER
+0x014 Major : Uint4B
+0x018 Minor : Uint4B
+0x01c Type : Uint4B
+0x020 Format : Uint4B
+0x024 RootCell : Uint4B
+0x028 Length : Uint4B
+0x02c Cluster : Uint4B
+0x030 FileName : [64] UChar
+0x070 Reserved1 : [99] Uint4B
+0x1fc CheckSum : Uint4B
+0x200 Reserved2 : [894] Uint4B
+0xff8 BootType : Uint4B
+0xffc BootRecover : Uint4B
+0x028 DirtyVector : _RTL_BITMAP
+0x000 SizeOfBitMap : Uint4B
+0x004 Buffer : Ptr32 Uint4B
+0x030 DirtyCount : Uint4B
+0x034 DirtyAlloc : Uint4B
+0x038 RealWrites : UChar
+0x03c Cluster : Uint4B
+0x040 Flat : UChar
+0x041 ReadOnly : UChar
+0x042 Log : UChar
+0x044 HiveFlags : Uint4B
+0x048 LogSize : Uint4B
+0x04c RefreshCount : Uint4B
+0x050 StorageTypeCount : Uint4B
+0x054 Version : Uint4B
+0x058 Storage : [2] _DUAL
+0x000 Length : Uint4B
+0x004 Map : Ptr32 _HMAP_DIRECTORY
+0x008 SmallDir : Ptr32 _HMAP_TABLE
+0x00c Guard : Uint4B
+0x010 FreeDisplay : [24] _RTL_BITMAP
+0x0d0 FreeSummary : Uint4B
+0x0d4 FreeBins : _LIST_ENTRY

Once again we notice a few fields that might be of interest to us. The Storage member contains information that the configuration manager uses to translate cell indices (see my earlier article for more details on cell indices) into virtual addresses in memory. The BaseBlock member gives us a direct pointer to the base block structure of the registry file, as it exists in memory. Reading memory there shows us exactly what we expect; the usual registry file signature "regf" followed by the timestamp, filename, etc.

Knowing what the structure looks like is all well and good, but how do we actually find them in memory? It turns out that we can create a highly effective signature by noting two facts. First, the _CMHIVE data structure is allocated in paged pool, and thus has a pool tag: "CM10" (listed in pooltag.txt as "Internal Configuration manager allocations"). We also know from the the structure listing above that it is 0x49c bytes long; by including the pool header (8 bytes) and padding it out to an 8 byte alignment, we get a total size of 0x4a8 for the whole pool block. This will appear in the pool header as 0x95 (since the size is listed as the number of 8-byte blocks in the allocation, and 0x95 * 0x8 = 0x4a8). Finally, the pool type (paged pool) is encoded as 0x0c.

So we can already create a signature for the pool header (regular expression notation): "..\x95\x0cCM10" that should be fairly accurate in finding the pool allocations we want. But we can do even better! Notice the first field of the _HHIVE structure -- Signature. Each _HHIVE, and therefore each _CMHIVE, begins with a constant signature, 0xbee0bee0 as a little-endian integer. So armed with this knowledge, we can build the full signature for use with something like FTimes:


Once we have found just a single hive, we can use it to find all of the others in virtual memory. Each _CMHIVE has a member HiveList. This _LIST_ENTRY links together all the hives in the kernel address space. We can traverse this linked list to find all the hives currently loaded by the configuration manager (remember that each _LIST_ENTRY will point to the HiveList member of the _CMHIVE, not the beginning of the structure -- you will have to subtract 0x224 from the value of the Flink or Blink to obtain the correct value). Note that the addresses given by the HiveList member are virtual addresses; you will need a page directory to translate them. Luckily, all Configuration Manager structures live in kernel space, so the page directory of any process will do -- I generally use the Idle process.

As you traverse this list, you may notice a couple oddities. First, there will be one entry in the list that doesn't seem to match the others. Its address will be somewhere around 0x8067b3a8, and its signature won't be 0xbee0bee0. This is because it actually isn't a hive at all! Rather, this is actually the list head. It exists as a global variable within the NT kernel; you can get its value by issuing ? CmpHiveListHead from within Windbg. If all you want is a list of the hives, though, it's save to just skip over it as you traverse the list.

The other slightly strange thing you will encounter is that there are many more hive structures found in the physical memory dump than can be found by traversing the linked list. Upon closer inspection, this is because there are actually a number of duplicate structures in physical memory -- they have exactly the same data as other entries that can be found in the linked list. A similar phenomenon can be seen when searching for process structures in physical memory; ptfinder calculates the MD5 of the structures to avoid such duplicates. It is probably safe to ignore such duplicates and focus only on the entries that are in the in-memory list.

Hopefully this article has given you a taste for what kinds of information about the registry can be extracted from Windows memory dumps. But perhaps you feel let down -- after all, a simple list of the hives loaded into memory doesn't tell you much that's forensically useful. Sure, you might be able to say when the hive was last modified by looking at the timestamp in the base block, or find out who was logged in by looking at what ntuser.dat hives were open, but that's not that exciting. What you'd really like to do is get information about actual registry keys that are currently open. This can also be accomplished using memory analysis, and we'll see how in an upcoming article.

1 comment:

makitos said...

I love these articles that go so deep into the ins and outs of memory, I love to understand how these things work. However there is something I cannot understand.

Your phrase: "Finally, the pool type (paged pool) is encoded as 0x0c."
And me staring intently here:

WHY 0x0C?