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核心資料結構進一步瞭解註冊表檔案的組織形式,同時也歡迎大家和我一起探討,共同學習,共同進步。