Windows NT Registry
2010-12-22 17:24
141 查看
文档名称:WindowsNTRegistry |
文档维护:welfear、古月今人 |
创建时间:2009.6.10 |
修改历史:2009.7.1添加古月今人文章 |
修改历史:2009.7.5添加flyingpig对FreeDisplay的分析 |
WindowsRegistry是操作系统中十分重要的组成部分。它既是十分基础又十分复杂。 |
在兼容过程中如果处理不好,就会引起性能问题,同时用户态的注册表支持也无法 |
支持Windows驱动和内核本身的应用。对注册表实现的研究不仅可以实现兼容内核, |
同时也可以学习到很多内核技术,提高编程水平。常见的注册表文件有1.3和1.5 |
两个版本(包括Vista)。WRK1.2中有1.5版本的代码,但实际的情况还有待分析,不管 |
怎么说,我们都要先从1.3这个广泛使用的版本说起。 |
注册表相关的API十分多,也很繁杂。对于Key来说,主要有Create/Open、Delete、 |
Enumerate、Flush、Query、Save这些操作,而对于Value来说,主要有Query、Set、 |
Enumerate几个操作。这里我们还是以枚举、读和写为例展开分析。 |
Hive、Cell、Bin是registry的基本概念。Hive代表了整个RootKey文件。Cell是 |
储存Key和Value的基本单位。Bin是分配的基本单位,每次分配按照block来进行的, |
x86上典型的块大小是4KB,所以Bin的大小就满足本次分配大小并按照4KB大小对齐 |
的长度。 |
通常调用者(可能是驱动或是应用)通过NativeAPI调用注册表相关服务。 |
以应用为例: |
由系统调用进入内核空间时,ZwXxxKey函数会被转化为CmXxxKey函数,这中间会通过 |
对象管理机制把名字空间转化为Key控制块。 |
以NtEnumerateKey为例: |
在经过参数和安全性检查之后,通过调用ObReferenceObjectByHandle可以得到 |
CM_KEY_BODY的数据结构,这里其中就有Key控制块指针PCM_KEY_CONTROL_BLOCK, |
之后,CmEnumerateKey接受了以KeyControlBlock为参数的调用。要完成枚举工作, |
首先需要根据找到Cell的索引,再找到对应Key的节点信息,最后是根据节点信息 |
查询相关的信息。CmpFindSubKeyByNumber就是用查找Cell索引信息的,它接受三个 |
参数,分别是Hive、KeyNode、Number。其中Hive和KeyNode都是通过 |
KeyControlBlock获得的,而Number则是调用者传递过来的。实际的查找工作是由 |
CmpDoFindSubKeyByNumber来完成的,它接受Hive、SubKeyCell索引、Number三个参数。 |
SubKeyCell是通过HvGetCell获得的,这个函数可以根据Hive和Cell索引得到这个Cell |
的储存值,在这里通过SubKeyCell索引就得到了SubKey的索引。 |
要了解软件原理就必须了解该软件各种概念之间是如何交流数据的。在初始化系统注册表 |
服务时,系统首先会读取文件的前4KB的内容。这就是该Hive文件的基本控制块,定义如下: |
typedef struct _HBASE_BLOCK{ |
ULONG Signature; |
ULONG Sequence1; |
ULONG Sequence2; |
LARGE_INTEGERTimeStamp; |
ULONG Major; |
ULONG Minor; |
ULONG Type; |
ULONG Format; |
HCELL_INDEXRootCell; |
ULONG Length; |
ULONG Cluster; |
UCHAR FileName[HBASE_NAME_ALLOC]; |
ULONG Reserved1[99]; |
ULONG CheckSum; |
ULONG Reserved2[128*7]; |
}HBASE_BLOCK,*PHBASE_BLOCK; |
结合XPSP2上的 default .sav文件: |
0000000:72656766010000000100000000000000regf............ |
0000010:00000000010000000500000000000000................ |
0000020:01000000200000000060010001000000........`...... |
0000030:00000000000000000000000000000000................ |
0000040:00000000000000000000000000000000................ |
Signature:必须为0x66676572,也就是regf,表明这是一个注册表文件; |
Sequence1和Sequence2,看起来都为1,具体用处不知 |
TimeStamp:时间戳,这里看起来为0 |
Major:主版本,1 |
Minor:次版本,5 |
Type:这里为0,表示是Primary文件,如果打开Log文件,这里看到的是1 |
Format:恒为1 |
RootCell:这个字段很重要,这是根节点的位置,这里是0x20, |
这个值是从HBASE_BLOCK最末尾开始计算。HBASE_BLOCK大小 |
为一块,即0x1000,那么根节点就是在0x1020的地方。从根节点 |
开始,就可以遍历整个注册表文件。 |
Length:注册表文件长度,这个长度不包括HBASE_BLOCK本身 |
Cluster:簇大小,通常为1 |
FileName:应该是文件名,可在这里我们没有看到 |
Reserved1:保留字段,不过看文件内容,比较规律,应该是一些有用的信息, |
不过ReactOS并没有使用这些数据 |
Checksum:位于0x1fc位置,是对前面0x200个字节的校验和后面的字段都为空, |
暂时不知道如何使用。 |
在控制块后面就是注册表基本的分配单元Bin的结构了,定义如下: |
typedef struct _HBIN{ |
ULONG Signature; |
ULONG FileOffset; |
ULONG Size; |
ULONG Reserved1[2]; |
LARGE_INTEGERTimeStamp; |
ULONG MemAlloc; |
}HBIN,*PHBIN; |
第一块bin实际内容为: |
0001000:6862696e000000000010000000000000hbin............ |
0001010:00000000000000000000000000000000................ |
Signature:标志,一定为0x6E696268,即hbin,表示这是一个HBIN块 |
FileOffset:偏移,指相对于HBASE_BLOCK末尾的偏移,所以这里为0 |
Size:当前HBIN块大小,0x1000的倍数,通常为0x1000 |
TimeStamp:时间戳,这里也为空 |
其实HBIN结构,只是HBIN块的一个头部,占用了0x20个字节,后面跟着的就是 |
真正的数据了。数据部分由2部分组成,一部分是HCELL结构,还有一部分是CELL_DATA, |
先看HCELL结构: |
typedef struct _HCELL{ |
LONG Size; |
union { |
struct { |
ULONG Last; |
union { |
ULONG UserData; |
HCELL_INDEXNext; |
}u; |
}OldCell; |
struct { |
union { |
ULONG UserData; |
HCELL_INDEXNext; |
}u; |
}NewCell; |
}u; |
}HCELL,*PHCELL; |
很简单,就是一个Size字段,说明后面的那个CELL_DATA的大小, |
不过需要注意的是,只有当Size为负数的时候,才说明这块空间是在使用中, |
CELL_DATA的大小(包括HCELL本身)为-Size。当Size为正数是,说明有Size |
个字节的空间空闲着可以使用。 |
现在我们可以回过头来看看枚举、查询、设置几个典型操作过程,通过跟踪这些操作 |
就可以慢慢搞清楚这些概念如何协调工作的。 |
CmEnumerateKey: |
第一个参数KeyControlBlock可以得到当前键对应的CM_KEY_CONTROL_BLOCK结构。当然在 |
得到它之前需要通过WindowsNT的对象管理器通过解析句柄得到这个结构。然后, |
hive=KeyControlBlock->KeyHive; |
cell=KeyControlBlock->KeyCell; |
就找到了它的Hive和Cell结构指针。接下来使用内部函数CmpFindSubKeyByNumber就可以 |
通过Hive、KeyNode、Index得到childCell索引。有了Hive和childCell索引就可以通过 |
HvGetCell得到childCell的结构。最后CmpQueryKeyData完成了调用者的请求,它把childCell |
结构也就是CM_KEY_NODE中的值返回。 |
CmQueryKey: |
分两种情况,如果是查询Key的名字,那就直接用KeyControlBlock中的NameBlock中保存的 |
名字直接返回,如果是其他要求则也调用CmpQueryKeyData来满足。 |
从Vista开始系统中出现了Win32APIRegSetKeyValue。但对于Value来说,我们还是看之前 |
的相关操作函数: |
CmEnumerateValueKey、CmQueryValueKey、CmSetValueKey。 |
除去ValueCache机制和针对Value大小的存储机制,它们和Key的实现没有本质上的区别。 |
现在关键的问题是HvGetCell是如何工作的。根据前面的介绍我们知道Hive文件在磁盘上是 |
以4KB为单位进行分配的,第一块比较特殊称为基本块。这样每个块的偏移都是0x1000的整数倍。 |
由于内存和外存之间要交换数据,那么就需要建立起两者之间的对应关系。根据块的特点,我们 |
把文件的偏移作为索引。这个索引有32位(0-31),其中最高位31代表存储类型,分为正常的和 |
易挥发的。21-30位代表Table,12-20位代表Block。0-11位就是块内偏移。这种设计思想和MMU分页 |
很类似。这样一个固定的文件偏移就变成了灵活多变的内存地址了。而从索引中获得对应数值需要 |
使用几个宏才能方便的实现: |
获得类型信息: |
#defineHCELL_TYPE_MASK0x80000000 |
#defineHCELL_TYPE_SHIFT31 |
#defineHvGetCellType(Cell)((ULONG)((Cell&HCELL_TYPE_MASK)>>HCELL_TYPE_SHIFT)) |
获得Table信息: |
#defineHCELL_TABLE_MASK0x7fe00000 |
#defineHCELL_TABLE_SHIFT21 |
获得Block信息: |
#defineHCELL_BLOCK_MASK0x001ff000 |
#defineHCELL_BLOCK_SHIFT12 |
获得块偏移: |
#defineHCELL_OFFSET_MASK0x00000fff |
不难计算出Table最多可以有1024个,而Block最多可以存在512个。 |
#defineHTABLE_SLOTS512 |
#defineHDIRECTORY_SLOTS1024 |
而HvGetCell其实就是调用HvpGetCellPaged,它根据Hive和Cell索引得到对应的数值。这里,Cell |
的类型就决定了返回指针类型,它们可以是如下结构中的任何一种: |
typedef struct _CELL_DATA{ |
union _u{ |
CM_KEY_NODEKeyNode; |
CM_KEY_VALUEKeyValue; |
CM_KEY_SECURITYKeySecurity; |
CM_KEY_INDEXKeyIndex; |
HCELL_INDEXKeyList[1]; |
WCHAR KeyString[1]; |
}u; |
}CELL_DATA,*PCELL_DATA; |
这就是CmXxx函数基本都会用到它的原因。好了,下面的代码含义非常明显了: |
Type=HvGetCellType(Cell); |
Table=(Cell&HCELL_TABLE_MASK)>>HCELL_TABLE_SHIFT; |
Block=(Cell&HCELL_BLOCK_MASK)>>HCELL_BLOCK_SHIFT; |
Offset=(Cell&HCELL_OFFSET_MASK); |
那么传说中的表格在哪呢? |
(Hive->Storage[Type].Map)->Directory
相关文章推荐
|