Introduction
the configuration manager, the executive subsystem that implements the registry,organizes the registry`s on-disk files.
windows organizes the registry data that a hive stores in containers called cells. a cell can hold a key, a value,a security descriptor,a list of subkeys, or a list of key values.
config manager ntapi
NtCreateKey
函数中开始处先验证用户参数是否可访问,然后调用对象管理器来得到对应的句柄
/* Do the create */
Status = ObOpenObjectByName(ObjectAttributes,
CmpKeyObjectType,
PreviousMode,
NULL,
DesiredAccess,
&ParseContext,
&Handle)
这里面我们创建的就是一个key的内核对象。我们得到的是对象句柄,将句柄传递出去,给用户使用,当然NtOpenKey也是做了如此的操作。
NtDeleteKey
然后我们看看NtDeleteKey,这个函数中通过参数句柄值使用对象管理器来实际获得到对象体,然后包装对象体的信息,调用配置管理器的回调函数。
/* Setup the callback */
PostOperationInfo.Object = (PVOID)KeyObject;
DeleteKeyInfo.Object = (PVOID)KeyObject;
Status = CmiCallRegisteredCallbacks(RegNtPreDeleteKey, &DeleteKeyInfo);
然后我们调用内部实际的函数CmDeleteKey()来删除键,在这个之前我们要确认这个键值我们有权限删除。
我们如下定义着key的对象体
//
// Key Body
//
typedef struct _CM_KEY_BODY
{
ULONG Type;
struct _CM_KEY_CONTROL_BLOCK *KeyControlBlock;
struct _CM_NOTIFY_BLOCK *NotifyBlock;
HANDLE ProcessID;
LIST_ENTRY KeyBodyList;
} CM_KEY_BODY, *PCM_KEY_BODY;
CmDeleteKey
我们来分析一下CmDeleteKey,相对来说有点麻烦。
NTSTATUS
NTAPI
CmDeleteKey(IN PCM_KEY_BODY KeyBody)
{
NTSTATUS Status;
PHHIVE Hive;
PCM_KEY_NODE Node, Parent;
HCELL_INDEX Cell, ParentCell;
PCM_KEY_CONTROL_BLOCK Kcb;
/* Acquire hive lock */
CmpLockRegistry();
/* Get the kcb */
Kcb = KeyBody->KeyControlBlock;
/* Don't allow deleting the root */
if (!Kcb->ParentKcb)
{
/* Fail */
CmpUnlockRegistry();
return STATUS_CANNOT_DELETE;
}
/* Lock parent and child */
CmpAcquireTwoKcbLocksExclusiveByKey(Kcb->ConvKey, Kcb->ParentKcb->ConvKey);
/* Check if we're already being deleted */
if (Kcb->Delete)
{
/* Don't do it twice */
Status = STATUS_SUCCESS;
goto Quickie2;
}
/* Get the hive and node */
Hive = Kcb->KeyHive;
Cell = Kcb->KeyCell;
/* Lock flushes */
CmpLockHiveFlusherShared((PCMHIVE)Hive);
/* Get the key node */
Node = (PCM_KEY_NODE)HvGetCell(Hive, Cell);
ASSERT(Node);
/* Sanity check */
ASSERT(Node->Flags == Kcb->Flags);
/* Check if we don't have any children */
if (!(Node->SubKeyCounts[Stable] + Node->SubKeyCounts[Volatile]) &&
!(Node->Flags & KEY_NO_DELETE))
{
/* Send notification to registered callbacks */
CmpReportNotify(Kcb, Hive, Cell, REG_NOTIFY_CHANGE_NAME);
/* Get the parent and free the cell */
ParentCell = Node->Parent;
Status = CmpFreeKeyByCell(Hive, Cell, TRUE);
if (NT_SUCCESS(Status))
{
/* Flush any notifications */
CmpFlushNotifiesOnKeyBodyList(Kcb, FALSE);
/* Clean up information we have on the subkey */
CmpCleanUpSubKeyInfo(Kcb->ParentKcb);
/* Get the parent node */
Parent = (PCM_KEY_NODE)HvGetCell(Hive, ParentCell);
if (Parent)
{
/* Update the maximum name length */
Kcb->ParentKcb->KcbMaxNameLen = (USHORT)Parent->MaxNameLen;
/* Make sure we're dirty */
ASSERT(HvIsCellDirty(Hive, ParentCell));
/* Update the write time */
KeQuerySystemTime(&Parent->LastWriteTime);
Kcb->ParentKcb->KcbLastWriteTime = Parent->LastWriteTime;
/* Release the cell */
HvReleaseCell(Hive, ParentCell);
}
/* Set the KCB in delete mode and remove it */
Kcb->Delete = TRUE;
CmpRemoveKeyControlBlock(Kcb);
/* Clear the cell */
Kcb->KeyCell = HCELL_NIL;
}
}
else
{
/* Fail */
Status = STATUS_CANNOT_DELETE;
}
/* Release the cell */
HvReleaseCell(Hive, Cell);
/* Release flush lock */
CmpUnlockHiveFlusher((PCMHIVE)Hive);
/* Release the KCB locks */
Quickie2:
CmpReleaseTwoKcbLockByKey(Kcb->ConvKey, Kcb->ParentKcb->ConvKey);
/* Release hive lock */
CmpUnlockRegistry();
return Status;
}
参数是key的对象,在key的对象体中我们能获得相关的核心信息,如cell 和hive。
函数开始申请互斥锁,获得key的kcb,即核心信息,针对于root key,我们不处理,因为不允许删除,然后我们判断这个键是否已经删除,如果没有,我们继续操作,这些都是前期的操作,通过kcb我们获得要操作的hive和cell的地址。
/* Get the hive and node */
Hive = Kcb->KeyHive;
Cell = Kcb->KeyCell;
然后调用HvGetCell 函数来获得cell所在地址的数据信息PCM_KEY_NODE,在hvGetCell函数中,cell是一个地址偏移值。
CellType = (CellIndex & HCELL_TYPE_MASK) >> HCELL_TYPE_SHIFT;
CellBlock = (CellIndex & HCELL_BLOCK_MASK) >> HCELL_BLOCK_SHIFT;
CellOffset = (CellIndex & HCELL_OFFSET_MASK) >> HCELL_OFFSET_SHIFT;
ASSERT(CellBlock < RegistryHive->Storage[CellType].Length);
Block = (PVOID)RegistryHive->Storage[CellType].BlockList[CellBlock].BlockAddress;
ASSERT(Block != NULL);
return (PVOID)((ULONG_PTR)Block + CellOffset);
无非是在hive中找到cell所在的地址,在这个地址上操作。然后我们调用CmpFreeKeyByCell来进行具体的释放cell。然后我们跟着操作一下父key。
RegCreateKeyExW
我们从advapi32开始研究,这个dll中提供用户层面的注册表调用,内部和对象管理器,配置管理器进行交互,我们在分析配置管理器的时候首先看一下接口。
这个函数用来创建键,函数中我们根据第一个参数来实际的打开根键。
LONG
WINAPI
RegCreateKeyExW(
_In_ HKEY hKey,
_In_ LPCWSTR lpSubKey,
_In_ DWORD Reserved,
_In_opt_ LPWSTR lpClass,
_In_ DWORD dwOptions,
_In_ REGSAM samDesired,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_Out_ PHKEY phkResult,
_Out_opt_ LPDWORD lpdwDisposition)
然后我们判断是否在hkcr下创建新键,如果是我们调用CreateHKCRKey来进行特殊的创建。
如果是其他情况,我们使用CreateNestedKey来进行创建。
CreateNestedKey
函数中通过两种方式创建,首先全路径创建,如果无法创建成功,我们去掉最后一个键名称然后创建,之后再添加上最后一个键名,全路径键创建。
static NTSTATUS
CreateNestedKey(PHKEY KeyHandle,
POBJECT_ATTRIBUTES ObjectAttributes,
PUNICODE_STRING ClassString,
DWORD dwOptions,
REGSAM samDesired,
DWORD *lpdwDisposition)
{
OBJECT_ATTRIBUTES LocalObjectAttributes;
UNICODE_STRING LocalKeyName;
ULONG Disposition;
NTSTATUS Status;
ULONG FullNameLength;
ULONG Length;
PWCHAR Ptr;
HANDLE LocalKeyHandle;
Status = NtCreateKey((PHANDLE) KeyHandle,
samDesired,
ObjectAttributes,
0,
ClassString,
dwOptions,
(PULONG)lpdwDisposition);
TRACE("NtCreateKey(%wZ) called (Status %lx)\n", ObjectAttributes->ObjectName, Status);
if (Status != STATUS_OBJECT_NAME_NOT_FOUND)
return Status;
/* Copy object attributes */
RtlCopyMemory(&LocalObjectAttributes,
ObjectAttributes,
sizeof(OBJECT_ATTRIBUTES));
RtlCreateUnicodeString(&LocalKeyName,
ObjectAttributes->ObjectName->Buffer);
LocalObjectAttributes.ObjectName = &LocalKeyName;
FullNameLength = LocalKeyName.Length / sizeof(WCHAR);
LocalKeyHandle = NULL;
/* Remove the last part of the key name and try to create the key again. */
while (Status == STATUS_OBJECT_NAME_NOT_FOUND)
{
Ptr = wcsrchr(LocalKeyName.Buffer, '\\');
if (Ptr == NULL || Ptr == LocalKeyName.Buffer)
{
Status = STATUS_UNSUCCESSFUL;
break;
}
*Ptr = (WCHAR)0;
LocalKeyName.Length = wcslen(LocalKeyName.Buffer) * sizeof(WCHAR);
Status = NtCreateKey(&LocalKeyHandle,
KEY_CREATE_SUB_KEY,
&LocalObjectAttributes,
0,
NULL,
0,
&Disposition);
TRACE("NtCreateKey(%wZ) called (Status %lx)\n", &LocalKeyName, Status);
}
if (!NT_SUCCESS(Status))
{
RtlFreeUnicodeString(&LocalKeyName);
return Status;
}
/* Add removed parts of the key name and create them too. */
Length = wcslen(LocalKeyName.Buffer);
while (TRUE)
{
if (LocalKeyHandle)
NtClose (LocalKeyHandle);
LocalKeyName.Buffer[Length] = L'\\';
Length = wcslen (LocalKeyName.Buffer);
LocalKeyName.Length = Length * sizeof(WCHAR);
if (Length == FullNameLength)
{
Status = NtCreateKey((PHANDLE) KeyHandle,
samDesired,
ObjectAttributes,
0,
ClassString,
dwOptions,
(PULONG)lpdwDisposition);
break;
}
Status = NtCreateKey(&LocalKeyHandle,
KEY_CREATE_SUB_KEY,
&LocalObjectAttributes,
0,
NULL,
0,
&Disposition);
TRACE("NtCreateKey(%wZ) called (Status %lx)\n", &LocalKeyName, Status);
if (!NT_SUCCESS(Status))
break;
}
RtlFreeUnicodeString(&LocalKeyName);
return Status;
}
当然函数内部调用的是NtCreateKey.
NtQueryValueKey
我们来看看查询,我们如何查询键值呢。当然上层是调用RegQueryValueExW来查询键值,在其函数内部我们调用系统提供的API来处理,这个API是NtQueryValueKey在这个函数内部,我们接着调用对象管理器和配置管理器来进行实际的查询值处理。
函数中我们通过key句柄去对象管理器中查询到key对象的对象体,然后再去配置管理器中进行具体的查找。
所以其真实的内部查询函数式CmQueryValueKey函数,用来查询具体的信息,这个是配置管理器的API函数。
NTSTATUS
NTAPI
CmQueryValueKey(IN PCM_KEY_CONTROL_BLOCK Kcb,
IN UNICODE_STRING ValueName,
IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
IN PVOID KeyValueInformation,
IN ULONG Length,
IN PULONG ResultLength)
{
NTSTATUS Status;
PCM_KEY_VALUE ValueData;
ULONG Index;
BOOLEAN ValueCached = FALSE;
PCM_CACHED_VALUE *CachedValue;
HCELL_INDEX CellToRelease;
VALUE_SEARCH_RETURN_TYPE Result;
PHHIVE Hive;
PAGED_CODE();
/* Acquire hive lock */
CmpLockRegistry();
/* Lock the KCB shared */
CmpAcquireKcbLockShared(Kcb);
/* Don't touch deleted keys */
DoAgain:
if (Kcb->Delete)
{
/* Undo everything */
CmpReleaseKcbLock(Kcb);
CmpUnlockRegistry();
return STATUS_KEY_DELETED;
}
/* We don't deal with this yet */
if (Kcb->ExtFlags & CM_KCB_SYM_LINK_FOUND)
{
/* Shouldn't happen */
ASSERT(FALSE);
}
/* Get the hive */
Hive = Kcb->KeyHive;
/* Find the key value */
Result = CmpFindValueByNameFromCache(Kcb,
&ValueName,
&CachedValue,
&Index,
&ValueData,
&ValueCached,
&CellToRelease);
if (Result == SearchNeedExclusiveLock)
{
/* Check if we need an exclusive lock */
ASSERT(CellToRelease == HCELL_NIL);
ASSERT(ValueData == NULL);
/* Try with exclusive KCB lock */
CmpConvertKcbSharedToExclusive(Kcb);
goto DoAgain;
}
if (Result == SearchSuccess)
{
/* Sanity check */
ASSERT(ValueData != NULL);
/* User data, protect against exceptions */
_SEH2_TRY
{
/* Query the information requested */
Result = CmpQueryKeyValueData(Kcb,
CachedValue,
ValueData,
ValueCached,
KeyValueInformationClass,
KeyValueInformation,
Length,
ResultLength,
&Status);
if (Result == SearchNeedExclusiveLock)
{
/* Release the value cell */
if (CellToRelease != HCELL_NIL)
{
HvReleaseCell(Hive, CellToRelease);
CellToRelease = HCELL_NIL;
}
/* Try with exclusive KCB lock */
CmpConvertKcbSharedToExclusive(Kcb);
_SEH2_YIELD(goto DoAgain);
}
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
}
else
{
/* Failed to find the value */
Status = STATUS_OBJECT_NAME_NOT_FOUND;
}
/* If we have a cell to release, do so */
if (CellToRelease != HCELL_NIL) HvReleaseCell(Hive, CellToRelease);
/* Release locks */
CmpReleaseKcbLock(Kcb);
CmpUnlockRegistry();
return Status;
}
依旧是要求互斥化访问,这个是老生常谈,锁定hive,锁定KCB。
然后我们根据键名字在缓存中查询位置。
hive file list management
hive 文件的列表位于注册表中。如下主键,我们要修改hive文件列表,其实就是修改这个键下的键和键值\REGISTRY\MACHINE\SYSTEM\CurrentControlSet\Control\hivelist,首先我们先创建或者是打开这个主键,然后通过参数hive获得要加入列表的文件路径,然后我们调用ZwSetValueKey 来设置信息。当我们要删除hive文件列表时,也是一样,先找到主键,然后使用键删除函数,来删除hive文件键值.
cell values
CmpGetValueData
我们先看看如何获取值,内部调用CmpGetValueData 来进行值的获取。
BOOLEAN
NTAPI
CmpGetValueData(IN PHHIVE Hive,
IN PCM_KEY_VALUE Value, 这个是获取标志信息,根据它来获取信息。
IN PULONG Length,
OUT PVOID *Buffer,
OUT PBOOLEAN BufferAllocated,
OUT PHCELL_INDEX CellToRelease)
{
PAGED_CODE();
/* Sanity check */
ASSERT(Value->Signature == CM_KEY_VALUE_SIGNATURE);
/* Set failure defaults */
*BufferAllocated = FALSE;
*Buffer = NULL;
*CellToRelease = HCELL_NIL;
/* Check if this is a small key */
if (CmpIsKeyValueSmall(Length, Value->DataLength))
{
/* Return the data immediately */
*Buffer = &Value->Data;
return TRUE;
}
/* Unsupported */
ASSERT_VALUE_BIG(Hive, *Length);
/* Get the data from the cell */
*Buffer = HvGetCell(Hive, Value->Data);
if (!(*Buffer)) return FALSE;
/* Return success and the cell to be released */
*CellToRelease = Value->Data;
return TRUE;
}
核心的地方是我们使用HvGetCell来获取值。当然其函数内部开始已经介绍过,通过cell的索引值来获取。
我们来详细说说PCM_KEY_VALUE 这是一个value cell,是cell的一种类型,在这个cell中存储的是一个键值。我们通过这个结构里面的cell 的偏移值来得到值的缓存地址。
typedef struct _CM_KEY_VALUE
{
USHORT Signature;
USHORT NameLength;
ULONG DataLength;
HCELL_INDEX Data;
ULONG Type;
USHORT Flags;
USHORT Unused1;
WCHAR Name[ANYSIZE_ARRAY];
} CM_KEY_VALUE, *PCM_KEY_VALUE;
在windows internals 中也介绍到关于cell 的类型,我们暂时不一一介绍,在碰到的时候,我们在介绍。
目前这个结构中的HCELL_INDEX Data;就是一个键值的偏移值。
之前这些是值的获取,那么我们怎么添加一个值呢。
其实我们只需要在hive中添加一个cell就可以,重要的是我们需要进行数据的操作。
CmpAddValueToList
下面我们看看如何往列表中添加值,这里的意思就是如何添加一个cell。
NTSTATUS
NTAPI
CmpAddValueToList(IN PHHIVE Hive,
IN HCELL_INDEX ValueCell,
IN ULONG Index,
IN ULONG Type,
IN OUT PCHILD_LIST ChildList)
{
HCELL_INDEX ListCell;
ULONG ChildCount, Length, i;
PCELL_DATA CellData;
PAGED_CODE();
/* Sanity check */
ASSERT((((LONG)Index) >= 0) && (Index <= ChildList->Count));
/* Get the number of entries in the child list */
ChildCount = ChildList->Count;
ChildCount++;
if (ChildCount > 1)
{
/* The cell should be dirty at this point */
ASSERT(HvIsCellDirty(Hive, ChildList->List));
/* Check if we have less then 100 children */
if (ChildCount < 100)
{
/* Allocate just enough as requested */
Length = ChildCount * sizeof(HCELL_INDEX);
}
else
{
/* Otherwise, we have quite a few, so allocate a batch */
Length = ROUND_UP(ChildCount, 100) * sizeof(HCELL_INDEX);
if (Length > HBLOCK_SIZE)
{
/* But make sure we don't allocate beyond our block size */
Length = ROUND_UP(Length, HBLOCK_SIZE);
}
}
/* Perform the allocation */
ListCell = HvReallocateCell(Hive, ChildList->List, Length);
}
else
{
/* This is our first child, so allocate a single cell */
ListCell = HvAllocateCell(Hive, sizeof(HCELL_INDEX), Type, HCELL_NIL);
}
/* Fail if we couldn't get a cell */
if (ListCell == HCELL_NIL) return STATUS_INSUFFICIENT_RESOURCES;
/* Set this cell as the child list's list cell */
ChildList->List = ListCell;
/* Get the actual key list memory */
CellData = HvGetCell(Hive, ListCell);
ASSERT(CellData != NULL);
/* Loop all the children */
for (i = ChildCount - 1; i > Index; i--)
{
/* Move them all down */
CellData->u.KeyList[i] = CellData->u.KeyList[i - 1];
}
/* Insert us on top now */
CellData->u.KeyList[Index] = ValueCell;
ChildList->Count = ChildCount;
/* Release the list cell and make sure the value cell is dirty */
HvReleaseCell(Hive, ListCell);
ASSERT(HvIsCellDirty(Hive, ValueCell));
/* We're done here */
return STATUS_SUCCESS;
}
我们先计算出列表中子节点的计数,如果里面已经有一个以上的cell,我们计算总体cell大小重新申请。如果没有子节点,说明是第一个节点,那么我们使用HvAllocateCell 来申请节点。修改链表中子节点的链接指针,修改cell结构体中链表指针(此处就是数组内数据后移,将cell插入到数组内指定位置)
在这里面我们使用hvAllocateCell来申请一个cell index,然后我们在通过HvGetCell函数来实际获取一个cell,当然这个函数之前说过,用来实际获得cell的内存地址,我们将这个地址转换为数据结构,操作数据结构就是操作这个地址(事实上就是用cell结构来表示这块内存)。
下面是一个cell的结构,在windows internals 中也实际的介绍了关于cell的结构,其有几种类型,如下可以清楚的看出各种类型。之前的分析中我们包含了CM_KEY_VALUE。
typedef struct _CELL_DATA
{
union
{
CM_KEY_NODE KeyNode;
CM_KEY_VALUE KeyValue;
CM_KEY_SECURITY KeySecurity;
CM_KEY_INDEX KeyIndex;
HCELL_INDEX KeyList[ANYSIZE_ARRAY];
WCHAR KeyString[ANYSIZE_ARRAY];
} u;
} CELL_DATA, *PCELL_DATA;
CmpRemoveValueFromList
前面我们说到了值的获取和添加,这里该到值的删除了。这里面论及的是内核配置管理器的内部函数,也就是说它不是api,是内部数据结构核心操作函数,api会调用这些函数来完成具体的功能。
这个函数用通过NtDeleteValueKey来具体调用。我们追本溯源,探索数据结构和算法的具体实现。
NTSTATUS
NTAPI
CmpRemoveValueFromList(IN PHHIVE Hive,
IN ULONG Index,
IN OUT PCHILD_LIST ChildList)
{
ULONG Count;
PCELL_DATA CellData;
HCELL_INDEX NewCell;
PAGED_CODE();
/* Sanity check */
ASSERT((((LONG)Index) >= 0) && (Index <= ChildList->Count));
/* Get the new count after removal */
Count = ChildList->Count - 1;
if (Count > 0)
{
/* Get the actual list array */
CellData = HvGetCell(Hive, ChildList->List);
if (!CellData) return STATUS_INSUFFICIENT_RESOURCES;
/* Make sure cells data have been made dirty */
ASSERT(HvIsCellDirty(Hive, ChildList->List));
ASSERT(HvIsCellDirty(Hive, CellData->u.KeyList[Index]));
/* Loop the list */
while (Index < Count)
{
/* Move everything up */
CellData->u.KeyList[Index] = CellData->u.KeyList[Index + 1];
Index++;
}
/* Re-allocate the cell for the list by decreasing the count */
NewCell = HvReallocateCell(Hive,
ChildList->List,
Count * sizeof(HCELL_INDEX));
ASSERT(NewCell != HCELL_NIL);
HvReleaseCell(Hive,ChildList->List);
/* Update the list cell */
ChildList->List = NewCell;
}
else
{
/* Otherwise, we were the last entry, so free the list entirely */
HvFreeCell(Hive, ChildList->List);
ChildList->List = HCELL_NIL;
}
/* Update the child list with the new count */
ChildList->Count = Count;
return STATUS_SUCCESS;
}
函数中我们首先判断列表中节点数是否大于0,对于空节点,很简单,我们使用HvFreeCell(Hive, ChildList->List); free掉这个父节点。如果我们列表中子节点数大于零,我们根据ChildList->List来获取到这个节点的cell结构,修改内部keylist,指定位置,其后的数据前移。然后重新根据计算数据大小,申请内存拷贝数据。
config manager init
我们来看看配置管理器config manager的初始化,这个函数位于cmsysini.c文件中。
BOOLEAN
NTAPI
INIT_FUNCTION
CmInitSystem1(VOID)
这个函数在执行体ex Phase1InitializationDiscard 被调用,也就是说在系统阶段1被初始化。
对于CmInitSystem1函数,在函数开始,我们初始化hive list
/* Initialize the hive list and lock */
InitializeListHead(&CmpHiveListHead);
然后初始化缓存cache,初始化回调。
比较重要的一步是调用对象管理器初始化key的对象类型,使得对象管理器中可以按照我们key的模板初始化我们自己的key对象。这一步在cm中使用的是CmpCreateObjectTypes()函数。
然后初始化hive,这里使用的函数是CmpInitializeHive(),这个函数是我们主要要分析的函数之一。下面我们来看看其相关的实现。
NTSTATUS
NTAPI
CmpInitializeHive(OUT PCMHIVE *RegistryHive,
IN ULONG OperationType,
IN ULONG HiveFlags,
IN ULONG FileType,
IN PVOID HiveData OPTIONAL,
IN HANDLE Primary,
IN HANDLE Log,
IN HANDLE External,
IN PCUNICODE_STRING FileName OPTIONAL,
IN ULONG CheckFlags)
在这个函数中其实就是在初始化PCMHIVE 结构。函数里面做了前期的参数检查,然后就是设置hive内部的数据。在cm初始化函数中如下调用hive初始化函数。
/* Build the master hive */
Status = CmpInitializeHive((PCMHIVE*)&CmiVolatileHive,
HINIT_CREATE,
HIVE_VOLATILE,
HFILE_TYPE_PRIMARY,
NULL,
NULL,
NULL,
NULL,
NULL,
0);
由此我们可以获知CmiVolatileHive这个全局的hive的重要性。下面我们研究一下这个函数。其实我们可以直接看PCMHIVE结构即可。上面这个函数就是填写这个数据。
//
// Configuration Manager Hive Structure
//
typedef struct _CMHIVE
{
HHIVE Hive;
HANDLE FileHandles[HFILE_TYPE_MAX];
LIST_ENTRY NotifyList;
LIST_ENTRY HiveList;
EX_PUSH_LOCK HiveLock;
PKTHREAD HiveLockOwner;
PKGUARDED_MUTEX ViewLock;
PKTHREAD ViewLockOwner;
EX_PUSH_LOCK WriterLock;
PKTHREAD WriterLockOwner;
PERESOURCE FlusherLock;
EX_PUSH_LOCK SecurityLock;
PKTHREAD HiveSecurityLockOwner;
LIST_ENTRY LRUViewListHead;
LIST_ENTRY PinViewListHead;
PFILE_OBJECT FileObject;
UNICODE_STRING FileFullPath;
UNICODE_STRING FileUserName;
USHORT MappedViews;
USHORT PinnedViews;
ULONG UseCount;
ULONG SecurityCount;
ULONG SecurityCacheSize;
LONG SecurityHitHint;
PCM_KEY_SECURITY_CACHE_ENTRY SecurityCache;
LIST_ENTRY SecurityHash[CMP_SECURITY_HASH_LISTS];
PKEVENT UnloadEvent;
PCM_KEY_CONTROL_BLOCK RootKcb;
BOOLEAN Frozen;
PWORK_QUEUE_ITEM UnloadWorkItem;
BOOLEAN GrowOnlyMode;
ULONG GrowOffset;
LIST_ENTRY KcbConvertListHead;
LIST_ENTRY KnodeConvertListHead;
PCM_CELL_REMAP_BLOCK CellRemapArray;
CM_USE_COUNT_LOG UseCountLog;
CM_USE_COUNT_LOG LockHiveLog;
ULONG Flags;
LIST_ENTRY TrustClassEntry;
ULONG FlushCount;
BOOLEAN HiveIsLoading;
PKTHREAD CreatorOwner;
} CMHIVE, *PCMHIVE;
我们首先为这个数据申请空间,使用的是ExAllocatePoolWithTag函数。
然后初始化起步的链表,置空某些成员,为内部成员申请空间比如锁。初始化锁,初始化访问资源锁,这些都是在初始化这个CMHIVE数据。
/* Initialize it */
Status = HvInitialize(&Hive->Hive,
OperationType,
FileType,
HiveFlags,
HiveData,
CmpAllocate,
CmpFree,
CmpFileSetSize,
CmpFileWrite,
CmpFileRead,
CmpFileFlush,
Cluster,
FileName);
当我们初始化完hive后,我们特别的去初始化hive的第一个成员HHIVE,我们第一次调用这个函数是去创建hive中的baseblock.
CmpCreateRegistryRoot
我们回到开始的config manager 初始化的地方,当hive初始化结束后我们开始创建注册表的根节点。
BOOLEAN
NTAPI
INIT_FUNCTION
CmpCreateRegistryRoot(VOID)
{
UNICODE_STRING KeyName;
OBJECT_ATTRIBUTES ObjectAttributes;
PCM_KEY_BODY RootKey;
HCELL_INDEX RootIndex;
NTSTATUS Status;
PCM_KEY_NODE KeyCell;
PSECURITY_DESCRIPTOR SecurityDescriptor;
PCM_KEY_CONTROL_BLOCK Kcb;
PAGED_CODE();
/* Setup the root node */
if (!CmpCreateRootNode(&CmiVolatileHive->Hive, L"REGISTRY", &RootIndex))
{
/* We failed */
return FALSE;
}
/* Create '\Registry' key. */
RtlInitUnicodeString(&KeyName, L"\\REGISTRY");
SecurityDescriptor = CmpHiveRootSecurityDescriptor();
InitializeObjectAttributes(&ObjectAttributes,
&KeyName,
OBJ_CASE_INSENSITIVE,
NULL,
NULL);
Status = ObCreateObject(KernelMode,
CmpKeyObjectType,
&ObjectAttributes,
KernelMode,
NULL,
sizeof(CM_KEY_BODY),
0,
0,
(PVOID*)&RootKey);
ExFreePoolWithTag(SecurityDescriptor, TAG_CM);
if (!NT_SUCCESS(Status)) return FALSE;
/* Sanity check, and get the key cell */
ASSERT((&CmiVolatileHive->Hive)->ReleaseCellRoutine == NULL);
KeyCell = (PCM_KEY_NODE)HvGetCell(&CmiVolatileHive->Hive, RootIndex);
if (!KeyCell) return FALSE;
/* Create the KCB */
RtlInitUnicodeString(&KeyName, L"\\REGISTRY");
Kcb = CmpCreateKeyControlBlock(&CmiVolatileHive->Hive,
RootIndex,
KeyCell,
NULL,
0,
&KeyName);
if (!Kcb)
{
ObDereferenceObject(RootKey);
return FALSE;
}
/* Initialize the object */
RootKey->KeyControlBlock = Kcb;
RootKey->Type = CM_KEY_BODY_TYPE;
RootKey->NotifyBlock = NULL;
RootKey->ProcessID = PsGetCurrentProcessId();
/* Link with KCB */
EnlistKeyBodyWithKCB(RootKey, 0);
/* Insert the key into the namespace */
Status = ObInsertObject(RootKey,
NULL,
KEY_ALL_ACCESS,
0,
NULL,
&CmpRegistryRootHandle);
if (!NT_SUCCESS(Status))
{
ObDereferenceObject(RootKey);
return FALSE;
}
/* Reference the key again so that we never lose it */
Status = ObReferenceObjectByHandle(CmpRegistryRootHandle,
KEY_READ,
NULL,
KernelMode,
(PVOID*)&RootKey,
NULL);
if (!NT_SUCCESS(Status))
{
ObDereferenceObject(RootKey);
return FALSE;
}
/* Completely sucessful */
return TRUE;
}
创建根节点,然后在对象管理器中创建根结点key对象,创建根节点的KCB,接着我们在对象管理器中获取到的key对象体,我们设置key对象体内部数据
//
// Key Body
//
typedef struct _CM_KEY_BODY
{
ULONG Type;
struct _CM_KEY_CONTROL_BLOCK *KeyControlBlock;
struct _CM_NOTIFY_BLOCK *NotifyBlock;
HANDLE ProcessID;
LIST_ENTRY KeyBodyList;
} CM_KEY_BODY, *PCM_KEY_BODY;、
接着还是对象管理器操作,我们使用ObInsertObject来将根节点key对象插入到对象管理器中,如此我们就获得了一个可访问的句柄CmpRegistryRootHandle,引用这个句柄,使得对象管理器中对其增加引用计数,这样我们就不能失去这个对象,因为引用计数无法自动降低,只有我们手动去销毁这个对象时,才释放这个对象。
然后在config manager中我们继续创建其他键,这些键的创建就没有根键那么麻烦,使用的是NtCreateKey来创建的。
如下键值先被创建出来,初始化对象属性,然后使用NtCreateKey来创建。
L”\REGISTRY\MACHINE”
L”\REGISTRY\USER”
如此看来可以知道根结点的创建还是不同寻常的。 下面函数在CmpCreateRegistryRoot开始处被调用用来创建根节点。
BOOLEAN
NTAPI
INIT_FUNCTION
CmpCreateRootNode(IN PHHIVE Hive,
IN PCWSTR Name,
OUT PHCELL_INDEX Index)
{
UNICODE_STRING KeyName;
PCM_KEY_NODE KeyCell;
LARGE_INTEGER SystemTime;
PAGED_CODE();
/* Initialize the node name and allocate it */
RtlInitUnicodeString(&KeyName, Name);
*Index = HvAllocateCell(Hive,
FIELD_OFFSET(CM_KEY_NODE, Name) +
CmpNameSize(Hive, &KeyName),
Stable,
HCELL_NIL);
if (*Index == HCELL_NIL) return FALSE;
/* Set the cell index and get the data */
Hive->BaseBlock->RootCell = *Index;
KeyCell = (PCM_KEY_NODE)HvGetCell(Hive, *Index);
if (!KeyCell) return FALSE;
/* Setup the cell */
KeyCell->Signature = (USHORT)CM_KEY_NODE_SIGNATURE;
KeyCell->Flags = KEY_HIVE_ENTRY | KEY_NO_DELETE;
KeQuerySystemTime(&SystemTime);
KeyCell->LastWriteTime = SystemTime;
KeyCell->Parent = HCELL_NIL;
KeyCell->SubKeyCounts[Stable] = 0;
KeyCell->SubKeyCounts[Volatile] = 0;
KeyCell->SubKeyLists[Stable] = HCELL_NIL;
KeyCell->SubKeyLists[Volatile] = HCELL_NIL;
KeyCell->ValueList.Count = 0;
KeyCell->ValueList.List = HCELL_NIL;
KeyCell->Security = HCELL_NIL;
KeyCell->Class = HCELL_NIL;
KeyCell->ClassLength = 0;
KeyCell->MaxNameLen = 0;
KeyCell->MaxClassLen = 0;
KeyCell->MaxValueNameLen = 0;
KeyCell->MaxValueDataLen = 0;
/* Copy the name (this will also set the length) */
KeyCell->NameLength = CmpCopyName(Hive, (PWCHAR)KeyCell->Name, &KeyName);
/* Check if the name was compressed */
if (KeyCell->NameLength < KeyName.Length)
{
/* Set the flag */
KeyCell->Flags |= KEY_COMP_NAME;
}
/* Return success */
HvReleaseCell(Hive, *Index);
return TRUE;
}
函数中使用HvAllocateCell 申请一个cell,这个返回的是cell的索引地址,我们根据这个偏移地址加上首地址计算出cell的地址,转换成要操作的数据结构,当然这个cell是一个节点数据。其结构如下:
这个是cell的类型之一,之前介绍过一种是值的类型。
//
// Node Key
//
typedef struct _CM_KEY_NODE
{
USHORT Signature;
USHORT Flags;
LARGE_INTEGER LastWriteTime;
ULONG Spare;
HCELL_INDEX Parent;
ULONG SubKeyCounts[HTYPE_COUNT];
union
{
struct
{
HCELL_INDEX SubKeyLists[HTYPE_COUNT];
CHILD_LIST ValueList;
};
CM_KEY_REFERENCE ChildHiveReference;
};
HCELL_INDEX Security;
HCELL_INDEX Class;
ULONG MaxNameLen;
ULONG MaxClassLen;
ULONG MaxValueNameLen;
ULONG MaxValueDataLen;
ULONG WorkVar;
USHORT NameLength;
USHORT ClassLength;
WCHAR Name[ANYSIZE_ARRAY];
} CM_KEY_NODE, *PCM_KEY_NODE;
我们在结构中设置根节点的名字,和数据。
我们回归到config manager 初始化。继续CmInitSystem1函数会初始化系统hive,并设置这个hive的文件名为L”\SystemRoot\System32\Config\SYSTEM”
然后创建硬件hive。设置这个硬件hive的key名字,然后连接到主hive上。
/* Attach it to the machine key */
RtlInitUnicodeString(&KeyName, L"\\Registry\\Machine\\HARDWARE");
Status = CmpLinkHiveToMaster(&KeyName,
NULL,
(PCMHIVE)HardwareHive,
TRUE,
SecurityDescriptor);
最后针对于硬件key做对应自己的处理。
cell的申请、释放、使用
这里更加倾向于cell的管理。
对于cell的管理,其代码位于lib\cmlib,这个令我有点意外,位于这个目录,难道不是一个系统文件吗?或许这是操作系统内核库,更加底层。因为我在lib文件夹中还发现了rtl的实现。
或许这个lib文件夹也放着比较核心的东西。应该放置的是系统库。
我们需要进一步分析cell的各种管理方式来准确了解config manager的核心。
cell的申请
HCELL_INDEX CMAPI
HvAllocateCell(
PHHIVE RegistryHive,
ULONG Size,
HSTORAGE_TYPE Storage,
HCELL_INDEX Vicinity)
{
PHCELL FreeCell;
HCELL_INDEX FreeCellOffset;
PHCELL NewCell;
PHBIN Bin;
ASSERT(RegistryHive->ReadOnly == FALSE);
CMLTRACE(CMLIB_HCELL_DEBUG, "%s - Hive %p, Size %x, %s, Vicinity %08lx\n",
__FUNCTION__, RegistryHive, Size, (Storage == 0) ? "Stable" : "Volatile", Vicinity);
/* Round to 16 bytes multiple. */
Size = ROUND_UP(Size + sizeof(HCELL), 16);
/* First search in free blocks. */
FreeCellOffset = HvpFindFree(RegistryHive, Size, Storage);
/* If no free cell was found we need to extend the hive file. */
if (FreeCellOffset == HCELL_NIL)
{
Bin = HvpAddBin(RegistryHive, Size, Storage);
if (Bin == NULL)
return HCELL_NIL;
FreeCellOffset = Bin->FileOffset + sizeof(HBIN);
FreeCellOffset |= Storage << HCELL_TYPE_SHIFT;
}
FreeCell = HvpGetCellHeader(RegistryHive, FreeCellOffset);
/* Split the block in two parts */
/* The free block that is created has to be at least
sizeof(HCELL) + sizeof(HCELL_INDEX) big, so that free
cell list code can work. Moreover we round cell sizes
to 16 bytes, so creating a smaller block would result in
a cell that would never be allocated. */
if ((ULONG)FreeCell->Size > Size + 16)
{
NewCell = (PHCELL)((ULONG_PTR)FreeCell + Size);
NewCell->Size = FreeCell->Size - Size;
FreeCell->Size = Size;
HvpAddFree(RegistryHive, NewCell, FreeCellOffset + Size);
if (Storage == Stable)
HvMarkCellDirty(RegistryHive, FreeCellOffset + Size, FALSE);
}
if (Storage == Stable)
HvMarkCellDirty(RegistryHive, FreeCellOffset, FALSE);
FreeCell->Size = -FreeCell->Size;
RtlZeroMemory(FreeCell + 1, Size - sizeof(HCELL));
CMLTRACE(CMLIB_HCELL_DEBUG, "%s - CellIndex %08lx\n",
__FUNCTION__, FreeCellOffset);
return FreeCellOffset;
}
对于cell的申请,我们先是将cell需要的大小按16位对齐,然后我们查找空闲的bin,如果没有找到我们要在hive中添加bin,HvpAddBin具体来实现如何添加bin,然后我们通过bin的首地址,如下计算获得cell的偏移
FreeCellOffset = Bin->FileOffset + sizeof(HBIN);
FreeCellOffset |= Storage << HCELL_TYPE_SHIFT;
然后我们获得cell的实际大小,看看是否已经超过我们需要的大小,如果超过了,我们去掉超出的部分,将超出的部分放入bin中。然后判断这块cell存储是stable还是volatile,如果是stable的,我们要设置标志位。
bin 的申请
对于bin的申请,我们可以从中知道注册表的存储方式,这里面使用的是bin的这种数据结构。
typedef struct _HHIVE
{
ULONG Signature;
PGET_CELL_ROUTINE GetCellRoutine;
PRELEASE_CELL_ROUTINE ReleaseCellRoutine;
PALLOCATE_ROUTINE Allocate;
PFREE_ROUTINE Free;
PFILE_READ_ROUTINE FileRead;
PFILE_WRITE_ROUTINE FileWrite;
PFILE_SET_SIZE_ROUTINE FileSetSize;
PFILE_FLUSH_ROUTINE FileFlush;
PHBASE_BLOCK BaseBlock;
RTL_BITMAP DirtyVector;
ULONG DirtyCount;
ULONG DirtyAlloc;
ULONG BaseBlockAlloc;
ULONG Cluster;
BOOLEAN Flat;
BOOLEAN ReadOnly;
BOOLEAN Log;
BOOLEAN DirtyFlag;
ULONG HvBinHeadersUse;
ULONG HvFreeCellsUse;
ULONG HvUsedcellsUse;
ULONG CmUsedCellsUse;
ULONG HiveFlags;
ULONG LogSize;
ULONG RefreshCount;
ULONG StorageTypeCount;
ULONG Version;
DUAL Storage[HTYPE_COUNT];
} HHIVE, *PHHIVE;
其实对于bin的申请,这里面主要操作的是hhive这个数据结构,其位于cmhive中第一个数据成员里,我们可以看一下如下这个截图就可以知道。
这里介绍的是config manager 的核心数据结构。我们接着讨论hhive,其中红色标示的是数据的申请释放函数操作,蓝色标示的是bin的实际数据存储地址和表示。当然不例外的是一切内存地址都有个数据结构来表示,更优化的是还有数据头。我们看一下bin申请的代码就可以知道,bin所在空间的数据结构表示和算法申请。
PHBIN CMAPI
HvpAddBin(
PHHIVE RegistryHive,
ULONG Size,
HSTORAGE_TYPE Storage)
{
PHMAP_ENTRY BlockList;
PHBIN Bin;
SIZE_T BinSize;
ULONG i;
ULONG BitmapSize;
ULONG BlockCount;
ULONG OldBlockListSize;
PHCELL Block;
BinSize = ROUND_UP(Size + sizeof(HBIN), HV_BLOCK_SIZE);
BlockCount = (ULONG)(BinSize / HV_BLOCK_SIZE);
Bin = RegistryHive->Allocate(BinSize, TRUE, TAG_CM);
if (Bin == NULL)
return NULL;
RtlZeroMemory(Bin, BinSize);
Bin->Signature = HV_BIN_SIGNATURE;
Bin->FileOffset = RegistryHive->Storage[Storage].Length *
HV_BLOCK_SIZE;
Bin->Size = (ULONG)BinSize;
/* Allocate new block list */
OldBlockListSize = RegistryHive->Storage[Storage].Length;
BlockList = RegistryHive->Allocate(sizeof(HMAP_ENTRY) *
(OldBlockListSize + BlockCount),
TRUE,
TAG_CM);
if (BlockList == NULL)
{
RegistryHive->Free(Bin, 0);
return NULL;
}
if (OldBlockListSize > 0)
{
RtlCopyMemory(BlockList, RegistryHive->Storage[Storage].BlockList,
OldBlockListSize * sizeof(HMAP_ENTRY));
RegistryHive->Free(RegistryHive->Storage[Storage].BlockList, 0);
}
RegistryHive->Storage[Storage].BlockList = BlockList;
RegistryHive->Storage[Storage].Length += BlockCount;
for (i = 0; i < BlockCount; i++)
{
RegistryHive->Storage[Storage].BlockList[OldBlockListSize + i].BlockAddress =
((ULONG_PTR)Bin + (i * HV_BLOCK_SIZE));
RegistryHive->Storage[Storage].BlockList[OldBlockListSize + i].BinAddress = (ULONG_PTR)Bin;
}
/* Initialize a free block in this heap. */
Block = (PHCELL)(Bin + 1);
Block->Size = (LONG)(BinSize - sizeof(HBIN));
if (Storage == Stable)
{
/* Calculate bitmap size in bytes (always a multiple of 32 bits). */
BitmapSize = ROUND_UP(RegistryHive->Storage[Stable].Length,
sizeof(ULONG) * 8) / 8;
/* Grow bitmap if necessary. */
if (BitmapSize > RegistryHive->DirtyVector.SizeOfBitMap / 8)
{
PULONG BitmapBuffer;
BitmapBuffer = RegistryHive->Allocate(BitmapSize, TRUE, TAG_CM);
RtlZeroMemory(BitmapBuffer, BitmapSize);
if (RegistryHive->DirtyVector.SizeOfBitMap > 0)
{
ASSERT(RegistryHive->DirtyVector.Buffer);
RtlCopyMemory(BitmapBuffer,
RegistryHive->DirtyVector.Buffer,
RegistryHive->DirtyVector.SizeOfBitMap / 8);
RegistryHive->Free(RegistryHive->DirtyVector.Buffer, 0);
}
RtlInitializeBitMap(&RegistryHive->DirtyVector, BitmapBuffer,
BitmapSize * 8);
}
/* Mark new bin dirty. */
RtlSetBits(&RegistryHive->DirtyVector,
Bin->FileOffset / HV_BLOCK_SIZE,
BlockCount);
/* Update size in the base block */
RegistryHive->BaseBlock->Length += BinSize;
}
return Bin;
}
bin的申请函数开始处,先在用户要申请的大小加上bin头的大小后做个HV_BLOCK_SIZE大小的对齐。然后呢,我们使用hive已经初始化过的申请空间函数来申请内存,实际上这个函数就是内存管理器的内存申请函数,当然我们申请完空间后判断是否申请成功,之后将这块空间置零。然后我们获得到原来bin块列表的大小,需要重新申请bin块列表,这里仅仅是重新申请表示bin块的列表的空间而不是bin块。然后我们修改bin块列表,使其新增加块的地址指向我们新申请的bin地址,如下操作。
for (i = 0; i < BlockCount; i++)
{
RegistryHive->Storage[Storage].BlockList[OldBlockListSize + i].BlockAddress =
((ULONG_PTR)Bin + (i * HV_BLOCK_SIZE));
RegistryHive->Storage[Storage].BlockList[OldBlockListSize + i].BinAddress = (ULONG_PTR)Bin;
}
如果我们申请的bin是Stable类型的,我们还要设置标志位。
空闲cell的添加
好的,我们回到cell申请处,去看看我们如何在bin中添加一个空闲的cell,当我们申请完一个bin的时候,我们就可以在这个bin上切出一块我们需要的空间当成cell,然而多数时候我们的cell要比bin小很多,这样bin中就会留有空余空间,我们此时就就这空余空间放置到空闲列表中,当我们下次要申请cell的时候优先查找空闲列表,如果空闲列表中没有可用cell空间时,我们再去申请bin。
so,我们来看看怎么添加空闲内存到bin的空闲列表中吧。
static NTSTATUS CMAPI
HvpAddFree(
PHHIVE RegistryHive,
PHCELL FreeBlock,
HCELL_INDEX FreeIndex)
{
PHCELL_INDEX FreeBlockData;
HSTORAGE_TYPE Storage;
ULONG Index;
ASSERT(RegistryHive != NULL);
ASSERT(FreeBlock != NULL);
Storage = (FreeIndex & HCELL_TYPE_MASK) >> HCELL_TYPE_SHIFT;
Index = HvpComputeFreeListIndex((ULONG)FreeBlock->Size);
FreeBlockData = (PHCELL_INDEX)(FreeBlock + 1);
*FreeBlockData = RegistryHive->Storage[Storage].FreeDisplay[Index];
RegistryHive->Storage[Storage].FreeDisplay[Index] = FreeIndex;
/* FIXME: Eventually get rid of free bins. */
return STATUS_SUCCESS;
}
函数中我们根据块大小计算出索引位置。
FreeBlockData = (PHCELL_INDEX)(FreeBlock + 1);
*FreeBlockData = RegistryHive->Storage[Storage].FreeDisplay[Index];
RegistryHive->Storage[Storage].FreeDisplay[Index] = FreeIndex;
hive 内存与文件的同步
BOOLEAN CMAPI
HvSyncHive(
PHHIVE RegistryHive)
{
ASSERT(RegistryHive->ReadOnly == FALSE);
if (RtlFindSetBits(&RegistryHive->DirtyVector, 1, 0) == ~0U)
{
return TRUE;
}
/* Update hive header modification time */
KeQuerySystemTime(&RegistryHive->BaseBlock->TimeStamp);
/* Update log file */
if (!HvpWriteLog(RegistryHive))
{
return FALSE;
}
/* Update hive file */
if (!HvpWriteHive(RegistryHive, TRUE))
{
return FALSE;
}
/* Clear dirty bitmap. */
RtlClearAllBits(&RegistryHive->DirtyVector);
RegistryHive->DirtyCount = 0;
return TRUE;
}
我们遍历hivelist,然后判断出hive是否符合刷新的条件,如果符合,那么我们刷新,调用上述函数进行刷新,将内存数据刷新到文件上
为了防止在刷新的时候出现崩溃,我们先将数据存储到log中进行备份,然后再刷新数据。
static BOOLEAN CMAPI
HvpWriteHive(
PHHIVE RegistryHive,
BOOLEAN OnlyDirty)
{
ULONG FileOffset;
ULONG BlockIndex;
ULONG LastIndex;
PVOID BlockPtr;
BOOLEAN Success;
ASSERT(RegistryHive->ReadOnly == FALSE);
ASSERT(RegistryHive->BaseBlock->Length ==
RegistryHive->Storage[Stable].Length * HV_BLOCK_SIZE);
DPRINT("HvpWriteHive called\n");
if (RegistryHive->BaseBlock->Sequence1 !=
RegistryHive->BaseBlock->Sequence2)
{
return FALSE;
}
/* Update first update counter and CheckSum */
RegistryHive->BaseBlock->Type = HFILE_TYPE_PRIMARY;
RegistryHive->BaseBlock->Sequence1++;
RegistryHive->BaseBlock->CheckSum =
HvpHiveHeaderChecksum(RegistryHive->BaseBlock);
/* Write hive block */
FileOffset = 0;
Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY,
&FileOffset, RegistryHive->BaseBlock,
sizeof(HBASE_BLOCK));
if (!Success)
{
return FALSE;
}
BlockIndex = 0;
while (BlockIndex < RegistryHive->Storage[Stable].Length)
{
if (OnlyDirty)
{
LastIndex = BlockIndex;
BlockIndex = RtlFindSetBits(&RegistryHive->DirtyVector, 1, BlockIndex);
if (BlockIndex == ~0U || BlockIndex < LastIndex)
{
break;
}
}
BlockPtr = (PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress;
FileOffset = (BlockIndex + 1) * HV_BLOCK_SIZE;
/* Write hive block */
Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY,
&FileOffset, BlockPtr,
HV_BLOCK_SIZE);
if (!Success)
{
return FALSE;
}
BlockIndex++;
}
Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_PRIMARY, NULL, 0);
if (!Success)
{
DPRINT("FileFlush failed\n");
}
/* Update second update counter and CheckSum */
RegistryHive->BaseBlock->Sequence2++;
RegistryHive->BaseBlock->CheckSum =
HvpHiveHeaderChecksum(RegistryHive->BaseBlock);
/* Write hive block */
FileOffset = 0;
Success = RegistryHive->FileWrite(RegistryHive, HFILE_TYPE_PRIMARY,
&FileOffset, RegistryHive->BaseBlock,
sizeof(HBASE_BLOCK));
if (!Success)
{
return FALSE;
}
Success = RegistryHive->FileFlush(RegistryHive, HFILE_TYPE_PRIMARY, NULL, 0);
if (!Success)
{
DPRINT("FileFlush failed\n");
}
return TRUE;
}
我们主要重点看如何同步到文件中的。通过如下方式,我们就能知道在log的备份中是否发生了崩溃。
RegistryHive->BaseBlock->Sequence1 !=
RegistryHive->BaseBlock->Sequence2
如果没有的话,我们计算检验和,递增计数Sequence1 ,设置同步类型,然后调用操作系统的写函数,首先将basebock拷贝到文件系统的缓冲区中,然后我们逐一读取(PVOID)RegistryHive->Storage[Stable].BlockList[BlockIndex].BlockAddress,block的地址,分别复制到文件系统的缓冲区中,当我们把所有的block和基本baseblock复制到系统缓冲区的时候,我们Flush,强制将缓冲区内容刷入到文件中,然后重新计算baseblock的检验和,递增Sequence2,将新的baseblock刷新到文件中,这里使用的是Sequence2,所以在刷新前检查计数是否相等,就可以知道是否中途发生过崩溃。
而这里面查看是否是脏页,并设置标志位的时候,设置的成员变量位于RegistryHive->DirtyVector,即hhive中的DirtyVector。
config manager 文件系统接口
既然注册表是从文件中读取出来的,那么不可避免的要访问文件系统,那么怎么方便的访问呢,这里面cm其实做的还算不错,C语言的系统却是面向对象的思维。
我们去看一下HHIVE结构就能明白其中的奥秘,其实我们在HHIVE中记录了文件系统函数指针,我们在访问文件系统的接口上进行自己的包装,然后将这些基本函数地址在cm初始化的时候记录到HHIVE中,当要使用这些文件函数地址的时候,我们通过hhive去获取就可以了。
typedef struct _HHIVE
{
ULONG Signature;
PGET_CELL_ROUTINE GetCellRoutine;
PRELEASE_CELL_ROUTINE ReleaseCellRoutine;
PALLOCATE_ROUTINE Allocate;
PFREE_ROUTINE Free;
PFILE_READ_ROUTINE FileRead;
PFILE_WRITE_ROUTINE FileWrite;
PFILE_SET_SIZE_ROUTINE FileSetSize;
PFILE_FLUSH_ROUTINE FileFlush;
。。。。。。。
}
我们如下初始化,记录函数地址到hive中
/* Initialize it */
Status = HvInitialize(&Hive->Hive,
OperationType,
FileType,
HiveFlags,
HiveData,
CmpAllocate,
CmpFree,
CmpFileSetSize,
CmpFileWrite,
CmpFileRead,
CmpFileFlush,
Cluster,
FileName);
下面是两个例子函数,包装系统调用接口
PVOID
NTAPI
CmpAllocate(IN SIZE_T Size,
IN BOOLEAN Paged,
IN ULONG Tag)
{
return ExAllocatePoolWithTag(Paged ? PagedPool : NonPagedPool,
Size,
Tag);
}
BOOLEAN
NTAPI
CmpFileRead(IN PHHIVE RegistryHive,
IN ULONG FileType,
IN PULONG FileOffset,
OUT PVOID Buffer,
IN SIZE_T BufferLength)
{
PCMHIVE CmHive = (PCMHIVE)RegistryHive;
HANDLE HiveHandle = CmHive->FileHandles[FileType];
LARGE_INTEGER _FileOffset;
IO_STATUS_BLOCK IoStatusBlock;
NTSTATUS Status;
_FileOffset.QuadPart = *FileOffset;
Status = ZwReadFile(HiveHandle, 0, 0, 0, &IoStatusBlock,
Buffer, (ULONG)BufferLength, &_FileOffset, 0);
return NT_SUCCESS(Status) ? TRUE : FALSE;
}
windbg config manager dbg
下面我们看看windbg中如何调试系统的reg。
我们首先显示出系统的hivelist,这样打印出所有的hive,有了hive的地址,我们相当于有了一切。
然后我们可以使用openkeys 来查询在一个hive中已经被打开的keys。
kd> !reg hivelist
-------------------------------------------------------------------------------------------------------------
| HiveAddr |Stable Length|Stable Map|Volatile Length|Volatile Map|MappedViews|PinnedViews|U(Cnt)| BaseBlock | FileName
-------------------------------------------------------------------------------------------------------------
| 832414e8 | 5000 | 83241564 | 1000 | 832416a0 | 0 | 0 | 0| 83242000 | emRoot\System32\Config\SECURITY
| 832ae100 | 4000 | 832ae17c | 0 | 00000000 | 0 | 0 | 0| 832bb000 | \SystemRoot\System32\Config\SAM
| 8333b3f8 | 3b000 | 8333b474 | 0 | 00000000 | 0 | 0 | 0| 8333c000 | files\NetworkService\NTUSER.DAT
| 833c9008 | 3a000 | 833c9084 | 0 | 00000000 | 0 | 0 | 0| 833ca000 | rofiles\LocalService\NTUSER.DAT
| 8940c440 | 1000 | 8940c4bc | 1000 | 8940c5f8 | 0 | 0 | 0| 8940e000 | <NONAME>
| 8941c008 | b17000 | 89422000 | 46000 | 8941c1c0 | 0 | 0 | 0| 8941d000 | SYSTEM
| 89444008 | 23000 | 89444084 | b000 | 894441c0 | 0 | 0 | 0| 89445000 | <NONAME>
| 894d48d0 | 2ba000 | 92e13000 | 1000 | 894d4a88 | 0 | 0 | 0| 894d5000 | temRoot\System32\Config\DEFAULT
| 89b229c8 | 388000 | 89b4c000 | 3000 | 89b22b80 | 0 | 0 | 0| 89aff000 | \Users\Administrator\ntuser.dat
| 8b242008 | 47000 | 8b242084 | 0 | 00000000 | 0 | 0 | 0| 8b243000 | \Microsoft\Windows\UsrClass.dat
| 8ec9d900 | 7000 | 8ec9d97c | 0 | 00000000 | 0 | 0 | 0| 8ec9e000 | Device\HarddiskVolume1\Boot\BCD
| 8ecb29c8 | 20d1000 | 8fdde000 | 2000 | 8ecb2b80 | 0 | 0 | 0| 8ecb3000 | emRoot\System32\Config\SOFTWARE
-------------------------------------------------------------------------------------------------------------
kd> !reg openkeys 832414e8
Hive: \REGISTRY\MACHINE\SECURITY
===========================================================================================
Index 13e: 4359d65c kcb=990c4a08 cell=000000a8 f=00200000 \REGISTRY\MACHINE\SECURITY\RXACT
Index 13f: ddd42113 kcb=894f5968 cell=80000020 f=00300008 \REGISTRY\MACHINE\SECURITY\SAM
Index 15c: b2bf03a2 kcb=89506828 cell=000007f8 f=00200000 \REGISTRY\MACHINE\SECURITY\POLICY
Index 1f6: e09d303e kcb=8959b828 cell=00000020 f=002c0004 \REGISTRY\MACHINE\SECURITY
0x4 keys found
==========================================================================================
在这里我们可以使用hive的地址显示出hive的数据,我们也可以进一步的查看HHIVE的数据。
kd> dt nt!_cmhive 832414e8=
+0x000 Hive : _HHIVE
+0x2ec FileHandles : [6] 0x8000027c Void
+0x304 NotifyList : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x30c HiveList : _LIST_ENTRY [ 0x832ae40c - 0x841bc44c ]
+0x314 PreloadedHiveList : _LIST_ENTRY [ 0x832417fc - 0x832417fc ]
+0x31c HiveRundown : _EX_RUNDOWN_REF
+0x320 ParseCacheEntries : _LIST_ENTRY [ 0x8323cb4c - 0x83243cfc ]
+0x328 KcbCacheTable : 0x8324a000 _CM_KEY_HASH_TABLE_ENTRY
+0x32c KcbCacheTableSize : 0x200
+0x330 Identity : 6
+0x334 HiveLock : 0x86cdb3c8 _FAST_MUTEX
+0x338 ViewLock : _EX_PUSH_LOCK
+0x33c ViewLockOwner : (null)
+0x340 ViewLockLast : 0xd
+0x344 ViewUnLockLast : 0x19
+0x348 WriterLock : 0x86cdb408 _FAST_MUTEX
+0x34c FlusherLock : 0x86cdb428 _ERESOURCE
+0x350 FlushDirtyVector : _RTL_BITMAP
+0x358 FlushOffsetArray : (null)
+0x35c FlushOffsetArrayCount : 0
+0x360 FlushHiveTruncated : 0
+0x364 FlushLock2 : 0x86cdb3e8 _FAST_MUTEX
+0x368 SecurityLock : _EX_PUSH_LOCK
+0x36c MappedViewList : _LIST_ENTRY [ 0x83241854 - 0x83241854 ]
+0x374 PinnedViewList : _LIST_ENTRY [ 0x8324185c - 0x8324185c ]
+0x37c FlushedViewList : _LIST_ENTRY [ 0x83241864 - 0x83241864 ]
+0x384 MappedViewCount : 0
+0x386 PinnedViewCount : 0
+0x388 UseCount : 0
+0x38c ViewsPerHive : 0x100
+0x390 FileObject : (null)
+0x394 LastShrinkHiveSize : 0
+0x398 ActualFileSize : _LARGE_INTEGER 0x40000
+0x3a0 FileFullPath : _UNICODE_STRING ""
+0x3a8 FileUserName : _UNICODE_STRING "\SystemRoot\System32\Config\SECURITY"
+0x3b0 HiveRootPath : _UNICODE_STRING "\REGISTRY\MACHINE\SECURITY"
+0x3b8 SecurityCount : 3
+0x3bc SecurityCacheSize : 0x12
+0x3c0 SecurityHitHint : 0n2
+0x3c4 SecurityCache : 0x83248398 _CM_KEY_SECURITY_CACHE_ENTRY
+0x3c8 SecurityHash : [64] _LIST_ENTRY [ 0x832418b0 - 0x832418b0 ]
+0x5c8 UnloadEventCount : 0
+0x5cc UnloadEventArray : (null)
+0x5d0 RootKcb : (null)
+0x5d4 Frozen : 0 ''
+0x5d8 UnloadWorkItem : (null)
+0x5dc UnloadWorkItemHolder : _CM_WORKITEM
+0x5f0 GrowOnlyMode : 0 ''
+0x5f4 GrowOffset : 0
+0x5f8 KcbConvertListHead : _LIST_ENTRY [ 0x83241ae0 - 0x83241ae0 ]
+0x600 KnodeConvertListHead : _LIST_ENTRY [ 0x83241ae8 - 0x83241ae8 ]
+0x608 CellRemapArray : (null)
+0x60c Flags : 0x10c
+0x610 TrustClassEntry : _LIST_ENTRY [ 0x83241af8 - 0x83241af8 ]
+0x618 FlushCount : 0
+0x61c CmRm : 0x8ec96300 _CM_RM
+0x620 CmRmInitFailPoint : 0
+0x624 CmRmInitFailStatus : 0n0
+0x628 CreatorOwner : (null)
+0x62c RundownThread : (null)
+0x630 LastWriteTime : _LARGE_INTEGER 0x1d065fa`73e7bdad
kd> dt nt!_hhive 832414e8
+0x000 Signature : 0xbee0bee0
+0x004 GetCellRoutine : 0x8409b292 _CELL_DATA* nt!HvpGetCellPaged+0
+0x008 ReleaseCellRoutine : (null)
+0x00c Allocate : 0x8405ca31 void* nt!CmpAllocate+0
+0x010 Free : 0x8405cc0b void nt!CmpFree+0
+0x014 FileSetSize : 0x840328ae unsigned char nt!CmpFileSetSize+0
+0x018 FileWrite : 0x84060914 unsigned char nt!CmpFileWrite+0
+0x01c FileRead : 0x8401d4c7 unsigned char nt!CmpFileRead+0
+0x020 FileFlush : 0x84060951 unsigned char nt!CmpFileFlush+0
+0x024 HiveLoadFailure : (null)
+0x028 BaseBlock : 0x83242000 _HBASE_BLOCK
+0x02c DirtyVector : _RTL_BITMAP
+0x034 DirtyCount : 8
+0x038 DirtyAlloc : 8
+0x03c BaseBlockAlloc : 0x1000
+0x040 Cluster : 1
+0x044 Flat : 0 ''
+0x045 ReadOnly : 0 ''
+0x046 DirtyFlag : 0x1 ''
+0x048 HvBinHeadersUse : 0xa0
+0x04c HvFreeCellsUse : 0x498
+0x050 HvUsedCellsUse : 0x4ac8
+0x054 CmUsedCellsUse : 0
+0x058 HiveFlags : 0x10200
+0x05c CurrentLog : 4
+0x060 LogSize : [2] 0x40000
+0x068 RefreshCount : 0
+0x06c StorageTypeCount : 2
+0x070 Version : 3
+0x074 Storage : [2] _DUAL
kd> dt -b nt!_hhive 832414e8
+0x000 Signature : 0xbee0bee0
+0x004 GetCellRoutine : 0x8409b292
+0x008 ReleaseCellRoutine : (null)
+0x00c Allocate : 0x8405ca31
+0x010 Free : 0x8405cc0b
+0x014 FileSetSize : 0x840328ae
+0x018 FileWrite : 0x84060914
+0x01c FileRead : 0x8401d4c7
+0x020 FileFlush : 0x84060951
+0x024 HiveLoadFailure : (null)
+0x028 BaseBlock : 0x83242000
+0x02c DirtyVector : _RTL_BITMAP
+0x000 SizeOfBitMap : 0x28
+0x004 Buffer : 0x832408c0
+0x034 DirtyCount : 8
+0x038 DirtyAlloc : 8
+0x03c BaseBlockAlloc : 0x1000
+0x040 Cluster : 1
+0x044 Flat : 0 ''
+0x045 ReadOnly : 0 ''
+0x046 DirtyFlag : 0x1 ''
+0x048 HvBinHeadersUse : 0xa0
+0x04c HvFreeCellsUse : 0x498
+0x050 HvUsedCellsUse : 0x4ac8
+0x054 CmUsedCellsUse : 0
+0x058 HiveFlags : 0x10200
+0x05c CurrentLog : 4
+0x060 LogSize :
[00] 0x40000
[01] 0x40000
+0x068 RefreshCount : 0
+0x06c StorageTypeCount : 2
+0x070 Version : 3
+0x074 Storage :
[00] _DUAL
+0x000 Length : 0x5000
+0x004 Map : 0x83241564
+0x008 SmallDir : 0x83246000
+0x00c Guard : 0xffffffff
+0x010 FreeDisplay :
[00] _FREE_DISPLAY
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83243b00
[01]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83243980
[02]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83243800
[03]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83243680
[04]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83243500
[05]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83243380
[06]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83243200
[07]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83243080
[08]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83244040
[09]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83244ec0
[10]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83244d40
[11]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83244bc0
[12]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83244a40
[13]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x832448c0
[14]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83244740
[15]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x832445c0
[16]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83244440
[17]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x832442c0
[18]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83245040
[19]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83245ec0
[20]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83245d40
[21]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83245bc0
[22]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x83245a40
[23]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 5
+0x004 Buffer : 0x832458c0
+0x130 FreeSummary : 0x80003
+0x134 FreeBins : _LIST_ENTRY [ 0x83241690 - 0x83241690 ]
+0x000 Flink : 0x83241690
+0x004 Blink : 0x83241690
[01]
+0x000 Length : 0x1000
+0x004 Map : 0x832416a0
+0x008 SmallDir : 0x83252000
+0x00c Guard : 0xffffffff
+0x010 FreeDisplay :
[00] _FREE_DISPLAY
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x8324b880
[01]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83248a80
[02]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83248900
[03]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83248780
[04]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83248600
[05]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83248480
[06]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83254040
[07]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83254ec0
[08]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83254d40
[09]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83254bc0
[10]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83254a40
[11]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x832548c0
[12]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83254740
[13]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x832545c0
[14]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83254440
[15]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x832542c0
[16]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83255040
[17]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83255ec0
[18]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83255d40
[19]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83255bc0
[20]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83255a40
[21]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x832558c0
[22]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x83255740
[23]
+0x000 RealVectorSize : 0x100
+0x004 Display : _RTL_BITMAP
+0x000 SizeOfBitMap : 1
+0x004 Buffer : 0x832555c0
+0x130 FreeSummary : 0x100000
+0x134 FreeBins : _LIST_ENTRY [ 0x832417cc - 0x832417cc ]
+0x000 Flink : 0x832417cc
+0x004 Blink : 0x832417cc