相信大家对Windows系统的注册表一定都不陌生了,我们可以用系统提供的注册表编辑器regedit来访问和修改注册表中的数据。它由根键 (rootkey)、子键(subkey)、键值(value)和数据 (data)组成。数据类型如下:
注册表相当于Windows系统中所有32位硬件/驱动和32位应用程序的数据文件,是一个系统信息的数据库。既然是数据文件,那在磁盘上就一定有注册表 的影子的存在。Windows 2000/XP的注册表文件在系统设置和缺省用户配置数据的情况下,是存放在 \系统文件夹\SYSTEM32\CONFIG目录下的6个文 件DEFAULT、SAM、SECURITY、SOFTWARE、USERDIFF和SYSTEM中,而用户的配置信息存放在系统所在磁盘 的\Documents and Setting\目录,包括ntuser.dat,ntuser.ini和ntuser.dat.log。其中每个文件的路径都由注册表项HKLM \SYSTEM\CurrentControlSet\Control\HIVElist下的键值指出。
我们看到的注册表结构是经过注册表编辑器读取之后呈现给我们的,其磁盘形式并不是一个简单的大文件,而是一组称被为 HIVE的单独文件形式,HIVE中文名曰“储巢”。每个HIVE文件可以被理解为一棵单独注册表树,就像Windows的PE格式一样,它也有自己的组 织形式。本文的任务就是要分析HIVE文件的组织形式并完成一个HIVE格式的分析程序。
HIVE结构解析
在认识真正的HIVE文件之前,我们先列举HIVE文件的几个主要特征。先入为主的将它们呈现出来将有助于我们对其文件组织和数据结构的理解。
注册表由多个HIVE文件组成。
一个HIVE文件由多个巢箱(BIN)组成,HIVE文件的首部有一个文件头(基本块、base block),用于描述这个HIVE文件的一些全局信息
一个BIN由多个巢室(CELL)组成,CELL可以分为具体的5种(后面介绍),用于存储不同的注册表数据。
本文中,我们并不统一使用HIVE、BIN和CELL的英文单词,而是和对应的中文词汇交替出现。在中文里,它们分别对应储巢、巢箱和巢室三个名词。
一个储巢被看成是一些称为块(block)的分配单元,类似于将磁盘分为簇的形式。根据定义,每一个注册表块的大小为4096字节(4KB),当新的数据要 加入到一个储巢中来时,该储巢总是按照块的粒度来增加。一个储巢的第一个块是基本块(base block),包含了有关该储巢的全局信息,包括一个特征签名“regf”,更新序列号,储巢上一次写操作发生的时间戳,储巢格式版本号、检验和,以及该 储巢文件的内部文件名等等。下面的_HBASE_BLOCK就是一个基本块的数据结构还原。
typedef struct _HBASE_BLOCK
{
ULONG Signature; /* 签名ASCII-"regf" = 0x66676572 (小端序)*/
ULONG Sequence1;
ULONG Sequence2;
LARGE_INTEGER TimeSp; /* 最后一次写操作的时间戳 */
ULONG Major; /* 主版本号 */
ULONG Minor; /* 次版本号 */
ULONG Type;
ULONG Format;
ULONG RootCell; /* 第一个键记录的偏移 */
ULONG Length; /* 数据块长度 */
ULONG Cluster;
UCHAR name[64]; /* 储巢文件名 */
ULONG Reserved1[99];
ULONG CheckSum; /* 校验和 */
ULONG Reserved2[894];
ULONG BootType;
ULONG BootRecover;
} HBASE_BLOCK, *PHBASE_BLOCK;
Windows将一个储巢所存储的注册表条目 组织在一种称为巢室的容器中,当一个巢室加入到一个储巢中,而且该巢室必须经过扩展才能容纳该巢室时,系统将创建一个巢箱的分配单元。巢箱是新巢室正好扩 展到下一个块的边界的大小,系统将巢室的尾部和巢箱的尾部之间的任何空间都看作是空闲空间,因而可以分配其他的巢室。
巢箱也有头部的标识,包含了一个特殊的签名“hbin”,一个记录了该巢箱在储巢文件中偏移量的域,以及该巢箱的大小。下面是巢箱的数据结构。
typedef struct _HBIN
{
ULONG Signature; /* 签名 ASCII-"hbin" = 0x6E696268 (小端序) */
ULONG FileOffset; /* 本巢箱相对第一个巢箱起始的偏移 */
ULONG Size; /* 本巢箱的大小 */
ULONG Reserved1[2];
LARGE_INTEGER TimeSp;
ULONG Spare;
} HBIN, *PHBIN;
一个巢室可以容纳一个键、一个值、一个安全描述符、一列子键或者一列键值,分别有对应的巢室来存储数据。在巢室数据的开始之处,有一个数据域描述了该巢室 数据的类型,具体的数据结构如下:
键巢室,包含了一个注册表键(也称为键节点)的巢室,一个键巢室包含一个特征签名(对于一个键是kn,一个符号链接是kl)、该键最近一次更新的时间戳、 该键父键巢室的巢室索引、代表该键的子键的子键列表巢室的索引、该键的安全描述符巢室索引、一个代表该键类名的字符串键巢室索引,以及该键的名称。
typedef struct _CM_KEY_NODE
{
USHORT Signature; /* 签名ASCII-"kn" = 0x6B6E (小端序)*/
USHORT Flags; /* 根键标识: 0x2C, 其他为 0x20 */
LARGE_INTEGER LastWriteTime;
ULONG Spare;
ULONG Parent; /* 父键的偏移 */
ULONG SubKeyCounts[2]; /* SubKeyCounts[0] 为子键的个数 */
union /* 偏 移为0x001C 联合体 */
{
struct
{
ULONG SubKeyLists[2]; /* SubKeyLists[0]为子键列表相差本BIN的偏移 */
CHILD_LIST ValueList; /* ValueList结构体 */
};
ULONG ChildHiveReference[4];
};
ULONG Security; /* 安 全描述符记录的偏移 */
ULONG Class; /* 类 名的偏移 */
ULONG MaxNameLen: 16;
ULONG UserFlags: 4;
ULONG VirtControlFlags: 4;
ULONG Debug: 8;
ULONG MaxClassLen;
ULONG MaxValueNameLen;
ULONG MaxValueDataLen;
ULONG WorkVar;
USHORT NameLength; /* 键名长度 */
USHORT ClassLength; /* 类名长度 */
PBYTE Name; /* 键 名称 */
}CM_KEY_NODE, *PCM_KEY_NODE;
值巢室,一个巢室,包含了关于一个键的值的信息,该巢室包含一个签名kv,该值的类型,如REG_DWORD或REG_BINARY,以及该值的名称。一 个值巢室也包含了另一个值巢室的索引,后者包含了对前者的数据。
typedef struct _CM_KEY_VALUE
{
WORD Signature; /* 签名ASCII-"kv" = 0x6B76(小端序) */
WORD NameLength; /* 名称长度 */
ULONG DataLength; /* 数据长度 */
ULONG Data; /*数据偏移或数据, 如果DataLength最高位为1,那么它就是数据,
且 DataLenth&0x7FFFFFFF为数据长度;否则 */
ULONG Type; /* 值类型 */
WORD Flags;
WORD Spare;
PWCHAR Name; /* 值名称 */
} CM_KEY_VALUE, *PCM_KEY_VALUE;
子键列表巢室,有一系列的键巢室的巢室索引构成的巢室,这些键巢室是同一个父键下面的所有子键。
typedef struct _CM_KEY_INDEX
{
WORD Signature;
WORD Count;
ULONG List[1];
} CM_KEY_INDEX, *PCM_KEY_INDEX;
如果 Signature==CM_KEY_FAST_LEAF,签名为“fl”,或者Signature==CM_KEY_HASH_LEAF,签名为 “hl”,那么List后是一个结构体:
struct
{
ULONG offset;
ULONG HashKey;
}
否则为:ULONG offset;
值列表巢室,有一系列的值巢室的巢室索引构成的巢室,这些值巢室是同一个父键下面的所有值。其数据结构即上文说到的结构。即上面_CM_KEY_NODE 的联合体中ValueList数据域。
typedef struct _CHILD_LIST
{
ULONG Count; /* ValueList.Count 值的个数 */
ULONG List; /* ValueList.List 值列表相差本BIN的偏移 */
} CHILD_LIST, *PCHILD_LIST;
安全描述符巢室,包含了一个安全描述符巢室,其首部的特征签名为ks,以及一个引用计数,该引用计数值记录了所有共享安全描述符的键节点数目,多个键巢室 可以共享同样的安全描述符巢室。
typedef struct _CM_KEY_SECURITY
{
WORD Signature; /* 签名ASCII-"sk" = 0x6B73 (小端序)*/
WORD Reserved;
ULONG Flink; /*上一个"sk"记录的偏 移 */
ULONG Blink; /*下一个"sk"记录的偏 移 */
ULONG ReferenceCount; /* 引用计数 */
ULONG DescriptorLength; /* 数据大小 */
SECURITY_DESCRIPTOR_RELATIVE Descriptor; /* 数据 */
} CM_KEY_SECURITY, *PCM_KEY_SECURITY;
储巢的结构是通过一些链接建立起来的,这些链接称为巢室索引(cell index)。每个巢室索引是一个巢室在储巢文件中的偏移。因此,巢室索引就像是一个指针,从一个巢室指向另一个巢室,配置管理器将巢室索引解释为相对于储巢起始处的偏移。因此,假如你想找到子键A的键巢室,并且A的父键是B,那么就必须先利用B的巢室中的子键列表巢室索引,找到包含B的所有子键列表的那 个巢室,然后再利用该子键列表巢室中的巢室索引列表,找到B的每个子键的巢室,随即找到A。
获得HIVE文件
知道了HIVE们的存放位置,自然就想到要把它们抓过来,逐个解剖,好好研究一番了。但是如果直接去打开或者复制 C:\WINDOWS\system32\config\SYSTEM,就会看到出错提示,这是系统的独占资源。
抓捕工作看似棘手,但解决起来也很简单。HIVE是Windows的重要资源,自启动以来就只能被系统独占访问。我们换一种思路,在另外一个系统中启动, 如同一台机器上的Linux,那目前系统的HIVE文件不是就可以访问了吗?但是这里如果你非要在当前系统访问这个HIVE文件,就只有求助于文件系统驱 动了。后者已经超出了本文的讨论范围,故我们不作考虑。
![](https://i-blog.csdnimg.cn/blog_migrate/902c6197ade91e77b84737ef82a145eb.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/38403f10e5e3013c8238d058f501fba1.jpeg)
注册表相当于Windows系统中所有32位硬件/驱动和32位应用程序的数据文件,是一个系统信息的数据库。既然是数据文件,那在磁盘上就一定有注册表 的影子的存在。Windows 2000/XP的注册表文件在系统设置和缺省用户配置数据的情况下,是存放在 \系统文件夹\SYSTEM32\CONFIG目录下的6个文 件DEFAULT、SAM、SECURITY、SOFTWARE、USERDIFF和SYSTEM中,而用户的配置信息存放在系统所在磁盘 的\Documents and Setting\目录,包括ntuser.dat,ntuser.ini和ntuser.dat.log。其中每个文件的路径都由注册表项HKLM \SYSTEM\CurrentControlSet\Control\HIVElist下的键值指出。
我们看到的注册表结构是经过注册表编辑器读取之后呈现给我们的,其磁盘形式并不是一个简单的大文件,而是一组称被为 HIVE的单独文件形式,HIVE中文名曰“储巢”。每个HIVE文件可以被理解为一棵单独注册表树,就像Windows的PE格式一样,它也有自己的组 织形式。本文的任务就是要分析HIVE文件的组织形式并完成一个HIVE格式的分析程序。
HIVE结构解析
在认识真正的HIVE文件之前,我们先列举HIVE文件的几个主要特征。先入为主的将它们呈现出来将有助于我们对其文件组织和数据结构的理解。
注册表由多个HIVE文件组成。
一个HIVE文件由多个巢箱(BIN)组成,HIVE文件的首部有一个文件头(基本块、base block),用于描述这个HIVE文件的一些全局信息
一个BIN由多个巢室(CELL)组成,CELL可以分为具体的5种(后面介绍),用于存储不同的注册表数据。
本文中,我们并不统一使用HIVE、BIN和CELL的英文单词,而是和对应的中文词汇交替出现。在中文里,它们分别对应储巢、巢箱和巢室三个名词。
一个储巢被看成是一些称为块(block)的分配单元,类似于将磁盘分为簇的形式。根据定义,每一个注册表块的大小为4096字节(4KB),当新的数据要 加入到一个储巢中来时,该储巢总是按照块的粒度来增加。一个储巢的第一个块是基本块(base block),包含了有关该储巢的全局信息,包括一个特征签名“regf”,更新序列号,储巢上一次写操作发生的时间戳,储巢格式版本号、检验和,以及该 储巢文件的内部文件名等等。下面的_HBASE_BLOCK就是一个基本块的数据结构还原。
typedef struct _HBASE_BLOCK
{
ULONG Signature; /* 签名ASCII-"regf" = 0x66676572 (小端序)*/
ULONG Sequence1;
ULONG Sequence2;
LARGE_INTEGER TimeSp; /* 最后一次写操作的时间戳 */
ULONG Major; /* 主版本号 */
ULONG Minor; /* 次版本号 */
ULONG Type;
ULONG Format;
ULONG RootCell; /* 第一个键记录的偏移 */
ULONG Length; /* 数据块长度 */
ULONG Cluster;
UCHAR name[64]; /* 储巢文件名 */
ULONG Reserved1[99];
ULONG CheckSum; /* 校验和 */
ULONG Reserved2[894];
ULONG BootType;
ULONG BootRecover;
} HBASE_BLOCK, *PHBASE_BLOCK;
Windows将一个储巢所存储的注册表条目 组织在一种称为巢室的容器中,当一个巢室加入到一个储巢中,而且该巢室必须经过扩展才能容纳该巢室时,系统将创建一个巢箱的分配单元。巢箱是新巢室正好扩 展到下一个块的边界的大小,系统将巢室的尾部和巢箱的尾部之间的任何空间都看作是空闲空间,因而可以分配其他的巢室。
巢箱也有头部的标识,包含了一个特殊的签名“hbin”,一个记录了该巢箱在储巢文件中偏移量的域,以及该巢箱的大小。下面是巢箱的数据结构。
typedef struct _HBIN
{
ULONG Signature; /* 签名 ASCII-"hbin" = 0x6E696268 (小端序) */
ULONG FileOffset; /* 本巢箱相对第一个巢箱起始的偏移 */
ULONG Size; /* 本巢箱的大小 */
ULONG Reserved1[2];
LARGE_INTEGER TimeSp;
ULONG Spare;
} HBIN, *PHBIN;
一个巢室可以容纳一个键、一个值、一个安全描述符、一列子键或者一列键值,分别有对应的巢室来存储数据。在巢室数据的开始之处,有一个数据域描述了该巢室 数据的类型,具体的数据结构如下:
键巢室,包含了一个注册表键(也称为键节点)的巢室,一个键巢室包含一个特征签名(对于一个键是kn,一个符号链接是kl)、该键最近一次更新的时间戳、 该键父键巢室的巢室索引、代表该键的子键的子键列表巢室的索引、该键的安全描述符巢室索引、一个代表该键类名的字符串键巢室索引,以及该键的名称。
typedef struct _CM_KEY_NODE
{
USHORT Signature; /* 签名ASCII-"kn" = 0x6B6E (小端序)*/
USHORT Flags; /* 根键标识: 0x2C, 其他为 0x20 */
LARGE_INTEGER LastWriteTime;
ULONG Spare;
ULONG Parent; /* 父键的偏移 */
ULONG SubKeyCounts[2]; /* SubKeyCounts[0] 为子键的个数 */
union /* 偏 移为0x001C 联合体 */
{
struct
{
ULONG SubKeyLists[2]; /* SubKeyLists[0]为子键列表相差本BIN的偏移 */
CHILD_LIST ValueList; /* ValueList结构体 */
};
ULONG ChildHiveReference[4];
};
ULONG Security; /* 安 全描述符记录的偏移 */
ULONG Class; /* 类 名的偏移 */
ULONG MaxNameLen: 16;
ULONG UserFlags: 4;
ULONG VirtControlFlags: 4;
ULONG Debug: 8;
ULONG MaxClassLen;
ULONG MaxValueNameLen;
ULONG MaxValueDataLen;
ULONG WorkVar;
USHORT NameLength; /* 键名长度 */
USHORT ClassLength; /* 类名长度 */
PBYTE Name; /* 键 名称 */
}CM_KEY_NODE, *PCM_KEY_NODE;
值巢室,一个巢室,包含了关于一个键的值的信息,该巢室包含一个签名kv,该值的类型,如REG_DWORD或REG_BINARY,以及该值的名称。一 个值巢室也包含了另一个值巢室的索引,后者包含了对前者的数据。
typedef struct _CM_KEY_VALUE
{
WORD Signature; /* 签名ASCII-"kv" = 0x6B76(小端序) */
WORD NameLength; /* 名称长度 */
ULONG DataLength; /* 数据长度 */
ULONG Data; /*数据偏移或数据, 如果DataLength最高位为1,那么它就是数据,
且 DataLenth&0x7FFFFFFF为数据长度;否则 */
ULONG Type; /* 值类型 */
WORD Flags;
WORD Spare;
PWCHAR Name; /* 值名称 */
} CM_KEY_VALUE, *PCM_KEY_VALUE;
子键列表巢室,有一系列的键巢室的巢室索引构成的巢室,这些键巢室是同一个父键下面的所有子键。
typedef struct _CM_KEY_INDEX
{
WORD Signature;
WORD Count;
ULONG List[1];
} CM_KEY_INDEX, *PCM_KEY_INDEX;
如果 Signature==CM_KEY_FAST_LEAF,签名为“fl”,或者Signature==CM_KEY_HASH_LEAF,签名为 “hl”,那么List后是一个结构体:
struct
{
ULONG offset;
ULONG HashKey;
}
否则为:ULONG offset;
值列表巢室,有一系列的值巢室的巢室索引构成的巢室,这些值巢室是同一个父键下面的所有值。其数据结构即上文说到的结构。即上面_CM_KEY_NODE 的联合体中ValueList数据域。
typedef struct _CHILD_LIST
{
ULONG Count; /* ValueList.Count 值的个数 */
ULONG List; /* ValueList.List 值列表相差本BIN的偏移 */
} CHILD_LIST, *PCHILD_LIST;
安全描述符巢室,包含了一个安全描述符巢室,其首部的特征签名为ks,以及一个引用计数,该引用计数值记录了所有共享安全描述符的键节点数目,多个键巢室 可以共享同样的安全描述符巢室。
typedef struct _CM_KEY_SECURITY
{
WORD Signature; /* 签名ASCII-"sk" = 0x6B73 (小端序)*/
WORD Reserved;
ULONG Flink; /*上一个"sk"记录的偏 移 */
ULONG Blink; /*下一个"sk"记录的偏 移 */
ULONG ReferenceCount; /* 引用计数 */
ULONG DescriptorLength; /* 数据大小 */
SECURITY_DESCRIPTOR_RELATIVE Descriptor; /* 数据 */
} CM_KEY_SECURITY, *PCM_KEY_SECURITY;
储巢的结构是通过一些链接建立起来的,这些链接称为巢室索引(cell index)。每个巢室索引是一个巢室在储巢文件中的偏移。因此,巢室索引就像是一个指针,从一个巢室指向另一个巢室,配置管理器将巢室索引解释为相对于储巢起始处的偏移。因此,假如你想找到子键A的键巢室,并且A的父键是B,那么就必须先利用B的巢室中的子键列表巢室索引,找到包含B的所有子键列表的那 个巢室,然后再利用该子键列表巢室中的巢室索引列表,找到B的每个子键的巢室,随即找到A。
获得HIVE文件
知道了HIVE们的存放位置,自然就想到要把它们抓过来,逐个解剖,好好研究一番了。但是如果直接去打开或者复制 C:\WINDOWS\system32\config\SYSTEM,就会看到出错提示,这是系统的独占资源。
抓捕工作看似棘手,但解决起来也很简单。HIVE是Windows的重要资源,自启动以来就只能被系统独占访问。我们换一种思路,在另外一个系统中启动, 如同一台机器上的Linux,那目前系统的HIVE文件不是就可以访问了吗?但是这里如果你非要在当前系统访问这个HIVE文件,就只有求助于文件系统驱 动了。后者已经超出了本文的讨论范围,故我们不作考虑。