轉:Windows註冊表HIVE文件格式解析

Windows註冊表HIVE文件格式解析
文章作者:fahrenheit
引言

  相信大家對Windows系統的註冊表(registry)一定都不陌生了,我們可以用系統提供的註冊表編輯器(regedit)來訪問和修改註冊表中的數據。直觀的講,註冊表呈現出來的是圖1所示的形式,它由根鍵(rootkey)、子鍵(subkey)、鍵值(value)和數據(data)組成。數據之間有類型的分別,常見的有:REG_SZ、字符串型,REG_BINARY、二進制型, REG_DWORD、雙字型,REG_MULTI_SZ、多字符串值型和REG_EXPAND_SZ、長度可變的數據串型。

  註冊表相當於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格式的分析程序。



註冊表API工作原理簡述

  Windows系統提供了大量的API給用戶訪問和修改註冊表中的數據,regedit就是基於這些API所實現的。註冊表API大致分爲用戶空間的與內核空間的兩類,一般用戶調用前者,層層調用轉移,由內核的註冊表API再調用文件系統的驅動等,去訪問磁盤上的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 TimeStamp; /* 最後一次寫操作的時間戳 */
  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 TimeStamp;
  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。

  巢室,巢箱和塊之間的區別很容易讓人混淆,所以我們來看一個簡單的註冊表儲巢的佈局示例,如圖5。該示例中包含了一個基本塊和兩個巢箱,第一個巢箱是空的,第二個巢箱包含了幾個巢室。該巢室有兩個鍵,一個是根鍵Root,另一個是Root的子鍵——SubKey。Root有兩個值,Val1和Val2,通過一個子鍵列表巢室,可以定位到根鍵的子鍵,通過一個值列表巢室,可以定位到根鍵的值。第二個巢箱中,空閒的空間屬於空的巢室。



獲得HIVE文件

  知道了HIVE們的存放位置,自然就想到要把它們抓過來,逐個解剖,好好研究一番了。但是如果直接去打開或者複製C:/WINDOWS/system32/config/SYSTEM,就會看到圖6的出錯提示,這是系統的獨佔資源。

  抓捕工作看似棘手,但解決起來也很簡單。HIVE是Windows的重要資源,自啓動以來就只能被系統獨佔訪問。我們換一種思路,在另外一個系統中啓動,如同一臺機器上的Linux,那目前系統的HIVE文件不是就可以訪問了嗎?但是這裏如果你非要在當前系統訪問這個HIVE文件,就只有求助於文件系統驅動了。後者已經超出了本文的討論範圍,故我們不作考慮。
不過,爲了示例學習的需要,我們總希望HIVE文件能相對簡單一些,讓我們把它的結構看個清楚明白。系統內部的HIVE文件一般都不太適合,從其文件尺寸已經達到MB級別,就可看出其數據量的巨大。因此,初期我們需要自己建立一個小型的HIVE以供學習之用。
爲了以後敘述的方便,我們在HKLM/SAM下建立了子鍵test_root,然後在test_root下再建立兩個子鍵1test和2test,並且在 1test下新建了五種不同的值,並填寫了相應的數據.然後,用RegSaveKey函數編個小程序,把test_root保存爲HIVE文件。這樣 test_root就變成這個HIVE文件的根鍵。小程序已經放在了文章的附件裏。接下來,我們就可以細細剖析HIVE文件中每一個部分的結構和功能了。



HIVE格式實例分析

  我們用一個16進制編輯器打開test_root文件,首先就可以看到基本塊的簽名——“regf”字符串,這是registry file的縮寫,標誌它是個註冊表文件...

  本節中用圖片和例子詳細解釋了一個名爲test_root的註冊表HIVE文件,具體情況可以參見雜誌。



HIVE文件讀取程序

  基於上面的結構解釋和分析,我們可以寫出一個HIVE文件的讀取程序,放在文章附件裏。針對上述分析示例的讀取效果,如圖13所示,test_root下有子鍵1test_subkey和2test,前者有1_REG_SZ、2_REG_BINARY、3_REG_DWORD、 4_REG_MULIT_SZ和5_REG_EXPAND_SZ 5個鍵值。[]內的是鍵值的類型,()內的是值的長度,以字節爲單位,對於REG_SZ型數據是打印出其unicode的編碼。圖14是針對本機的名爲 Software的HIVE文件的分析結果,首先看到的第一項是360安全衛士的註冊表信息。


後記

  本文中HIVE的數據格式大部分來源於網絡和自己的整理,因爲缺乏Microsoft官方的文檔支持,所以我們並不能保證分析程序在所有情況下都是正確的。Petter Nordahl-Hagen曾經寫過一個NT Registry Hive access library,但年代稍顯久遠,Windows可能會隨版本變化修改這些結構的組織形式,其對XP系統已有局部不再適用。註冊表在安全方面有很多應用場景,如果需要更加深入的學習該領域的知識,可以藉助windbg,利用其導出的Windows內核數據結構進一步瞭解註冊表文件的組織形式,同時也歡迎大家和我一起探討,共同學習,共同進步。 

發佈了30 篇原創文章 · 獲贊 5 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章