Wednesday, February 13, 2008

Reading Open Keys

Last time, we found out how to find information about loaded registry hives in Windows memory dumps. However, just knowing what hives are loaded may not be particularly useful. To fill out our view of the state of the system's registry, we will also want to get information about registry keys currently open in the Configuration Manager. To start, though, we will need to know a little bit more about how the Configuration Manager represents keys in memory.


Each open key is represented by a Key Control Block. Key Control Blocks, or KCBs, store a number of pieces of metadata about the key, such as the name, last write time, pointers to the cached values contained in the key (recall that the registry is essentially a filesystem, where keys act as folders and values are the files). To see precisely what information we can get about a key from its KCB, we can look at the _CM_KEY_CONTROL_BLOCK structure in Windbg:


lkd> dt nt!_CM_KEY_CONTROL_BLOCK
+0x000 RefCount : Uint2B
+0x002 Flags : Uint2B
+0x004 ExtFlags : Pos 0, 8 Bits
+0x004 PrivateAlloc : Pos 8, 1 Bit
+0x004 Delete : Pos 9, 1 Bit
+0x004 DelayedCloseIndex : Pos 10, 12 Bits
+0x004 TotalLevels : Pos 22, 10 Bits
+0x008 KeyHash : _CM_KEY_HASH
+0x008 ConvKey : Uint4B
+0x00c NextHash : Ptr32 _CM_KEY_HASH
+0x010 KeyHive : Ptr32 _HHIVE
+0x014 KeyCell : Uint4B
+0x018 ParentKcb : Ptr32 _CM_KEY_CONTROL_BLOCK
+0x01c NameBlock : Ptr32 _CM_NAME_CONTROL_BLOCK
+0x020 CachedSecurity : Ptr32 _CM_KEY_SECURITY_CACHE
+0x024 ValueCache : _CACHED_CHILD_LIST
+0x02c IndexHint : Ptr32 _CM_INDEX_HINT_BLOCK
+0x02c HashKey : Uint4B
+0x02c SubKeyCount : Uint4B
+0x030 KeyBodyListHead : _LIST_ENTRY
+0x030 FreeListEntry : _LIST_ENTRY
+0x038 KcbLastWriteTime : _LARGE_INTEGER
+0x040 KcbMaxNameLen : Uint2B
+0x042 KcbMaxValueNameLen : Uint2B
+0x044 KcbMaxValueDataLen : Uint4B


So to find out what keys are open, we will be looking for a list of key control blocks in memory. To figure out where to look, I turned to the kernel debugging extensions. After all, Windbg's !reg openkeys command is able to get a list of all open keys and their KCBs, so by reverse engineering that command we can figure out how to extract the same information from a memory dump.


Luckily, the kernel debugging extensions disassemble easily, and debug information for them is available from the Microsoft symbol server, so we get information on function names and so on for free (for anyone wishing to try this at home, the appropriate file is in winxp\kdexts.dll in the Debugging Tools for Windows program directory). In general, this can actually be an excellent technique for finding out about Windows internals -- any information you get a Windbg extension can be extracted from live memory with a little effort, and since the intention is to display the information to the user, there are a lot of useful markers in the code like print statements that help you understand what particular data structures and fields mean.


By reverse engineering the regopenkeys() function, we find that it uses the unexported kernel global variables CmpCacheTable and CmpHashTableSize. CmpCacheTable points to a hash table that is used by the Configuration Manager to provide fast lookups for open keys. Each entry in the table points to a _CM_KEY_HASH structure, shown below:


lkd> dt nt!_CM_KEY_HASH
+0x000 ConvKey : Uint4B
+0x004 NextHash : Ptr32 _CM_KEY_HASH
+0x008 KeyHive : Ptr32 _HHIVE
+0x00c KeyCell : Uint4B

The NextHash member points to the next entry in the given hash bucket. To read the entire table, we just read CmpHashTableSize pointers at CmpCacheTable, and then for each of those, read in all the entries in that bucket using the NextHash field (the last entry in a given bucket will have its NextHash set to 0).


However, the !reg openkeys command gives much more information about each key than we find in a _CM_KEY_HASH structure -- it also lists the KCB, name, and cell. Digging a little deeper, we discover the secret to how it does this: each _CM_KEY_HASH is in fact embedded in its corresponding _CM_KEY_CONTROL_BLOCK (see the KeyHash member at offset 0x8 up there?). By subtracting 8 from the address of the _CM_KEY_HASH, we get the address of the KCB, which in turn gives us all the information we could want: last write time, name, cell index, and pointers to parent KCBs.


Throughout this discussion, you may have noticed that unlike the debugger, we don't know in what value CmpCacheTable has. Unfortunately, the location of this variable changes with each version of Windows and even each service pack or hotfix. Was all our hard work figuring this out for naught, then, since we can't implement it in an offline analysis framework like Volatility?


Well, not really. Just as we came up with a signature for the registry hive data structure in the last post, we can come up with a heuristic for determining the address of CmpCacheTable in memory as well. Since the hash table is just a big list of pointers, however, a static signature probably won't work. So let's look at the things we know about CmpCacheTable:


  1. It's a kernel global variable, so it should reside inside the mapped executable image for ntoskrnl.exe.

  2. It points to a list of pointers, and each of those points to a _CM_KEY_HASH structure.

  3. The _CM_KEY_HASH structure has a pointer to a the hive the key belongs to at offset 0x8.


Point (3) is the critical one, because we already know how to identify valid _HHIVE structures! We can leverage this to find the table of cached registry keys.


Our algorithm for finding this table is going to look something like this, then:


  1. Find the base address of the kernel in memory. This can be done using the KPCR trick.

  2. Parse the PE header to get the offset (RVA) of each section, and add that to the kernel base address to get its virtual address. (I find pefile quite useful for this.)

  3. Loop over each section, and treat each dword in that section as a potential pointer.

  4. At the pointed-to memory location, treat each dword as a pointer to a _CM_KEY_HASH.
  5. Follow the KeyHive pointer to the _HHIVE candidate, and check that it matches the signature we developed earlier.

  6. As a bonus, once we've found CmpCacheTable this way, CmpHashTableSize is the next dword after it, at least on XP SP2 (the variables are most likely declared next to each other in the Windows kernel source.)


If you want to cheat a bit, the variable is in the PAGEDATA section, which should simplify your search. It is up to you how many entries in the candidate hash table you want to check; checking more will take longer, but should reduce the chance of false positives. Also, some entries will be 0 (if that hash bucket is not filled), so don't throw out a candidate just because some of its entries are null.


Armed with this information, you should now be able to read the full list of registry keys opened by the configuration manager. So how many keys can you expect to find using this method? Here are some quick stats from some images I have (the first two are publicly available test images, the third is just an image I took with dd from my own XP SP2 machine):






Image# keys
xp-laptop-2005-06-25.img3367
xp-laptop-2005-07-04-1430.img3998
my_own_machine.img3251


Although we have now looked pretty deeply into the internals of the Configuration Manager and figured out how to extract several important pieces of information about the registry from memory, we aren't quite done with the topic yet. For one thing, we've so far only really dealt with metadata, rather than the registry data itself. In addition to things like key key names and timestamps, we may want to pull out as much information as possible about each key and its values. To do this, we will have to start looking at the pieces of the raw hive itself that are mapped into memory, which will involve finally figuring out how to make use of cell indexes and where they fit into virtual address space. Stay tuned!

4 comments:

Tao Xiao said...
This comment has been removed by the author.
Tao Xiao said...

How can you location "_CM_KEY_CONTROL_BLOCK" sucessfully?

Tao Xiao said...
This comment has been removed by the author.
Tao Xiao said...
This comment has been removed by the author.