储巢HIVE及实现注册表隐藏

注册表是一组称为储巢的单独文件组成的.配置管理器从逻辑上将一个储巢分成一些称为块的分配单元.类似于文件系统中一个磁盘分成簇一样.定义是将注册表快的大小为4096字节.

储巢总是按照块的粒度来增加新的数据的.就算不够4096个字节也是一个增加块.

这个是储巢的结构定义:

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将一个储巢所存储的注册表数据组织在一种称为巢室的容器中.一个巢室可以容纳一个键,一个值,一个安全描述符,一列子键,在巢室数据开始处的一个域描述了该巢室数据的类型.当一个巢室加入到一储巢中,而且该储巢必须进行扩展才能包含该巢室的时候,系统创建一个称为巢箱的分配单元.一个巢箱式新巢室正好扩展到下一个块的边界的大小.系统将巢室尾部和巢箱的尾部之间的任何空间都看出是空闲的空间。可以在分配给其他的巢室,

下面是巢箱的数据结构。

  typedef struct _HBIN

  {

  ULONG Signature; /* 签名 ASCII-"hbin" = 0x6E696268 (小端序) */

  ULONG FileOffset; /* 本巢箱相对第一个巢箱起始的偏移 */

  ULONG Size; /* 本巢箱的大小 */

  ULONG Reserved1[2];

  LARGE_INTEGER TimeStamp;

  ULONG Spare;

  } HBIN, *PHBIN;

巢室的数据类型:

键巢室: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;

 

值巢室:

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;

值列表巢室:

typedef struct _CHILD_LIST

  {

  ULONG Count; /* ValueList.Count值的个数 */

  ULONG List; /* ValueList.List值列表相差本BIN的偏移 */

} CHILD_LIST, *PCHILD_LIST

安全描述符巢室:

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;

 

巢室.巢箱和块之间的关系如图:

 


从图中关系可知一个巢箱可以包含多个或者一个块。储巢是以巢箱为粒度进行增长的.windows总是用一块连续的区域的内存来表示一个巢箱

在内存中存储注册表数据的换页池不一定是连续的.(为啥?储巢中不断的加入新的键值之后,储巢会增长的,必须分配新的内存来存储,分配的内存不一定连续,导致储巢在内存中的不连续性)

但是如何处理这种不连续性呢?配置管理采用跟虚拟内存转换成物理内存相似的办法

从一个巢室的索引作为输入,返回该巢室索引所在的块的内存地址,以及该巢室所在的块的内存地址.

首先将一个巢室索引从逻辑上分为多个域.第一个域解释成一个储巢的巢室映射表目录中的一个索引.这个巢室映射表目录包含1024个项目.每个项目指向一张巢室映射表

每个巢室映射表包含512个表项,在巢室映射表中的表项是由巢室索引中的第二个域来指定的。巢室索引的最后一个域解释成巢室映射表项中的一个偏移量,精确定位到内存中的巢室.

 


当一个巢室初始化的时候,配置管理器动态的创建这些映射表,为储巢中的每一个块指定一个表项,当储巢的大小要改变时,会从巢室目录中删除和增加映射表.

 

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结构指针。

如何利用hive来隐藏注册表(该部分转自看雪)

http://bbs.kanxue.com/showthread.php?t=63540&highlight=hive

1.   打开指定名字的注册表键

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 就是我们要隐藏的注册表项对应的巢室索引。
   +0x010 KeyHive          : Ptr32 _HHIVE 就是我们前面刚刚讲过的HHIVE结构指针。

4。在hhive结构中获取GetCellRoutine就是计算函数,可以根据cell索引计算出cell的内存地址。

5.hook GetCellRoutine,判断如果返回的cell内存地址 等于上面计算出的隐藏的CELL地址。则让其
      返回NULL.

Code:

 

#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。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/instruder/archive/2010/06/12/5667298.aspx

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值