当前位置: 首页 > news >正文

内核哈希表RTL_DYNAMIC_HASH_TABLE的使用分析与总结

内核哈希表RTL_DYNAMIC_HASH_TABLE的使用

文章目录

  • 内核哈希表RTL_DYNAMIC_HASH_TABLE的使用
    • 前言
    • 相关数据结构和接口
      • 数据结构
        • RTL_DYNAMIC_HASH_TABLE
        • RTL_DYNAMIC_HASH_TABLE_ENTRY
        • RTL_DYNAMIC_HASH_TABLE_ENUMERATOR
        • _RTL_DYNAMIC_HASH_TABLE_CONTEXT
      • 相关接口
        • RtlCreateHashTable
        • RtlInsertEntryHashTable
        • RtlLookupEntryHashTable
        • RtlRemoveEntryHashTable
        • RtlInitWeakEnumerationHashTable
        • RtlInitStrongEnumerationHashTable
        • RtlWeaklyEnumerateEntryHashTable
        • RtlInitStrongEnumerationHashTable
        • RtlEndWeakEnumerationHashTable
        • RtlEndStrongEnumerationHashTable
    • 底层数据结构分析
      • 哈希表内存布局图
      • 数据插入
      • 数据查找
        • 数据枚举
          • 弱枚举
          • 强枚举
    • 使用示例

前言

最近项目中某些数据在查找和插入时要求间复杂度为O(1),想起早之前有位大佬和我提到过Windows内核里面有哈希表。网上相关资料很稀少,看来没多少人用这个数据结构呀?于是用IDA翻了翻顺便研究一下,最终有了这篇分享。

相关数据结构和接口

数据结构

RTL_DYNAMIC_HASH_TABLE

哈希表结构。引用WDK里面的定义:

typedef struct _RTL_DYNAMIC_HASH_TABLE {// Entries initialized at creationULONG Flags;ULONG Shift;// Entries used in bucket computation.ULONG TableSize;ULONG Pivot;ULONG DivisorMask;// CountersULONG NumEntries;ULONG NonEmptyBuckets;ULONG NumEnumerators;// The directory. This field is for internal use only.PVOID Directory;} RTL_DYNAMIC_HASH_TABLE, *PRTL_DYNAMIC_HASH_TABLE;

我们不会直接访问该结构中的字段,该结构由RtlCreateHashTable\textcolor{cornflowerblue}{RtlCreateHashTable}RtlCreateHashTable进行初始化。

RTL_DYNAMIC_HASH_TABLE_ENTRY

哈希表条目结构。引用WDK里面的定义:

typedef struct _RTL_DYNAMIC_HASH_TABLE_ENTRY {LIST_ENTRY Linkage;ULONG_PTR Signature;
} RTL_DYNAMIC_HASH_TABLE_ENTRY, *PRTL_DYNAMIC_HASH_TABLE_ENTRY;

该结构就是哈希表中的每一项数据类型,我们只关注Signature字段即可,这个字段就是我们们插入时指定的Key注意:Key不能指定为0,否则插入后就找不到了,存在内存泄漏风险。\textcolor{BrickRed}{注意:Key不能指定为0,否则插入后就找不到了,存在内存泄漏风险。}注意:Key不能指定为0,否则插入后就找不到了,存在内存泄漏风险。

RTL_DYNAMIC_HASH_TABLE_ENUMERATOR

用于枚举哈希表的枚举器结构。引用WDK的定义:

typedef struct _RTL_DYNAMIC_HASH_TABLE_ENUMERATOR {union {RTL_DYNAMIC_HASH_TABLE_ENTRY HashEntry;PLIST_ENTRY CurEntry;};PLIST_ENTRY ChainHead;ULONG BucketIndex;
} RTL_DYNAMIC_HASH_TABLE_ENUMERATOR, *PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR;

该结构字段的具体含义我们在使用的时候并不需要关心,我们不直接操作这些字段,留个印象就好了。

_RTL_DYNAMIC_HASH_TABLE_CONTEXT

用于插入或者查找时的哈希表上下文结构。引用WDK定义:

typedef struct _RTL_DYNAMIC_HASH_TABLE_CONTEXT
{PLIST_ENTRY ChainHead;PLIST_ENTRY PrevLinkage;ULONG_PTR Signature;
}RTL_DYNAMIC_HASH_TABLE_CONTEXT,*PRTL_DYNAMIC_HASH_TABLE_CONTEXT;

该结构字段的具体含义我们在使用的时候并不需要关心,我们不直接操作这些字段,留个印象就好了。

相关接口

RtlCreateHashTable

创建哈希表

BOOLEAN
NTAPI
RtlCreateHashTable(_Inout_ _When_(NULL == *HashTable, _At_(*HashTable, __drv_allocatesMem(Mem)))PRTL_DYNAMIC_HASH_TABLE *HashTable,_In_ ULONG Shift,_Reserved_ ULONG Flags);
  • HashTable - 要初始化的哈希表。
  • Shift - 位移系数,一般填0。
  • Flags - 内部保留,无意义,填NULL。
RtlInsertEntryHashTable

向哈希表中插入数据

BOOLEAN
NTAPI
RtlInsertEntryHashTable(_In_ PRTL_DYNAMIC_HASH_TABLE HashTable,_In_ __drv_aliasesMem PRTL_DYNAMIC_HASH_TABLE_ENTRY Entry,_In_ ULONG_PTR Signature,_Inout_opt_ PRTL_DYNAMIC_HASH_TABLE_CONTEXT Context);
  • HashTable - 通过RtlCreateHashTable\textcolor{cornflowerblue}{RtlCreateHashTable}RtlCreateHashTable创建的哈希表。
  • Entry - 具体数据项。
  • Signature - 相当于Key,注意:必须是可支持数值运算的类型,且不能为0,否则存在内存泄漏的风险。详情可看后面底层数据结构分析章节\textcolor{BrickRed}{注意:必须是可支持数值运算的类型,且不能为0,否则存在内存泄漏的风险。详情可看后面底层数据结构分析章节}注意:必须是可支持数值运算的类型,且不能为0,否则存在内存泄漏的风险。详情可看后面底层数据结构分析章节
  • Context - 一般用不到,填NULL。如果不为空,则Context保存的是插入后的哈希表与Signature同hash的链表状态。

有意思的是这个函数总会返回TRUE,意味着插入总是成功的。失败的情况只有一种:哈希表被破坏了,此时插入将会触发蓝屏。

注意:Context和Entry的生命周期由调用方负责,插入函数内部不会分配额外的内存存储Entry条目,而是直接使用传入的Entry和Context。

RtlLookupEntryHashTable

根据Key查找项

PRTL_DYNAMIC_HASH_TABLE_ENTRY
NTAPI
RtlLookupEntryHashTable(_In_ PRTL_DYNAMIC_HASH_TABLE HashTable,_In_ ULONG_PTR Signature,_Out_opt_ PRTL_DYNAMIC_HASH_TABLE_CONTEXT Context);
  • HashTable - 欲查找的哈希表。
  • Signature - 欲查找的Key。
  • Context - 一般用不到,填NULL。

如果找到则返回对应的项,否则返回NULL。

RtlRemoveEntryHashTable

删除项

BOOLEAN
NTAPI
RtlRemoveEntryHashTable(_In_ PRTL_DYNAMIC_HASH_TABLE HashTable,_In_ PRTL_DYNAMIC_HASH_TABLE_ENTRY Entry,_Inout_opt_ PRTL_DYNAMIC_HASH_TABLE_CONTEXT Context);
  • HashTable - 哈希表。
  • Entry - 由RtlLookupEntryHashTable\textcolor{cornflowerblue}{RtlLookupEntryHashTable}RtlLookupEntryHashTable返回。
  • Context - 一般用不到,填NULL。

该接口只会返回TRUE,如果失败就会触发蓝屏!

RtlInitWeakEnumerationHashTable

初始化弱哈希表枚举器

BOOLEAN
NTAPI
RtlInitWeakEnumerationHashTable(_In_ PRTL_DYNAMIC_HASH_TABLE HashTable,_Out_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator);
  • HashTable - 哈希表。
  • Enumerator - 枚举器。

该接口只会返回TRUE,如果失败就会触发蓝屏!

与强枚举器不同,弱枚举器每次枚举的时候都会修改哈希表内部状态,是不稳定的枚举器,不能用于多线程场景。

该接口不会分配任何内存。

RtlInitStrongEnumerationHashTable

初始化强哈希表枚举器

BOOLEAN
NTAPI
RtlInitStrongEnumerationHashTable(_In_ PRTL_DYNAMIC_HASH_TABLE HashTable,_Out_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator);
  • HashTable - 哈希表。
  • Enumerator - 枚举器。

该接口只会返回TRUE,如果失败就会触发蓝屏!

与弱枚举器不同的是,强枚举器在枚举的时候不会修改哈希表内部状态,可用于多线程只读哈希表的场景中。

该接口不会分配任何内存。

RtlWeaklyEnumerateEntryHashTable

使用弱枚举器枚举哈希表

PRTL_DYNAMIC_HASH_TABLE_ENTRY
NTAPI
RtlWeaklyEnumerateEntryHashTable(_In_ PRTL_DYNAMIC_HASH_TABLE HashTable,_Inout_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator);
  • HashTable - 哈希表。
  • Enumerator - 通过RtlInitWeakEnumerationHashTable\textcolor{cornflowerblue}{RtlInitWeakEnumerationHashTable}RtlInitWeakEnumerationHashTable得到的弱枚举器。

接口返回的是一个有效的哈希表条目,如果枚举结束则返回NULL。

注意:在弱枚举器没有调用RtlEndWeakEnumerationHashTable之前,不能在别的地方使用强枚举器,否则会出现意外结果。\textcolor{BrickRed}{注意:在弱枚举器没有调用RtlEndWeakEnumerationHashTable之前,不能在别的地方使用强枚举器,否则会出现意外结果。}注意:在弱枚举器没有调用RtlEndWeakEnumerationHashTable之前,不能在别的地方使用强枚举器,否则会出现意外结果。

RtlInitStrongEnumerationHashTable

使用强枚举器枚举哈希表

PRTL_DYNAMIC_HASH_TABLE_ENTRY
NTAPI
RtlStronglyEnumerateEntryHashTable(_In_ PRTL_DYNAMIC_HASH_TABLE HashTable,_Inout_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator);
  • HashTable - 哈希表。
  • Enumerator - 通过RtlInitStrongEnumerationHashTable\textcolor{cornflowerblue}{RtlInitStrongEnumerationHashTable}RtlInitStrongEnumerationHashTable得到的枚举器。

接口返回的是一个有效的哈希表条目,如果枚举结束则返回NULL。

RtlEndWeakEnumerationHashTable

结束弱枚举器

VOID
NTAPI
RtlEndWeakEnumerationHashTable(_In_ PRTL_DYNAMIC_HASH_TABLE HashTable,_Inout_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator);
  • HashTable - 哈希表。
  • Enumerator - 枚举器。

调用RtlInitWeakEnumerationHashTable\textcolor{cornflowerblue}{RtlInitWeakEnumerationHashTable}RtlInitWeakEnumerationHashTableRtlWeaklyEnumerateEntryHashTable\textcolor{cornflowerblue}{RtlWeaklyEnumerateEntryHashTable}RtlWeaklyEnumerateEntryHashTable函数后,如果不再使用弱枚举器,则必须调用此函数,否则哈希表内部状态发生改变,导致后续使用出现意外。

RtlEndStrongEnumerationHashTable

结束强枚举器

VOID
NTAPI
RtlEndStrongEnumerationHashTable(_In_ PRTL_DYNAMIC_HASH_TABLE HashTable,_Inout_ PRTL_DYNAMIC_HASH_TABLE_ENUMERATOR Enumerator);
  • HashTable - 哈希表。
  • Enumerator - 枚举器。

实际这个接口在内核中是个空函数,因为强枚举器不会修改哈希表内部状态,并且整个枚举流程也没有额外的内存分配,所以该接口没有什么事情要做。不过为了保持良好习惯,建议不用强枚举器的时候调用此接口。

底层数据结构分析

哈希表内存布局图

  • 当分配的哈希表大小不超过0x80字节时的结构:

    在这里插入图片描述

    HashTable−>Directory\textcolor{orange}{HashTable->Directory}HashTable>Directory指向的就是Bucket(桶),也称为一级目录。此时的一级目录大小固定为0x80字节,桶中的每一个元素都是_RTL_DYNAMIC_HASH_TABLE_ENTRY类型的指针,头部指针不存储数据。桶中每一个元素的索引值就是Signaturehash值,相同hash值的_RTL_DYNAMIC_HASH_TABLE_ENTRY条目将会以链表方式串起来。

  • 当分配的哈希表大小超过0x80字节时的结构:

    在这里插入图片描述

    HashTable−>Directory\textcolor{orange}{HashTable->Directory}HashTable>Directory指向的一级目录将不再直接存储_RTL_DYNAMIC_HASH_TABLE_ENTRY类型的条目,而是存储指向二级目录的指针,此时二级目录才是Bucket。一句目录大小依然是0x80字节,而二级目录的大小以2倍递增。第一个二级目录大小是0x800字节,第二个二级目录大小就是0x1000,第三个0x2000,以此类推。HashTable−>Size\textcolor{orange}{HashTable->Size}HashTable>Size最大值为0x7FFF80,最多只有15个二级目录,最大二级目录大小为0x2000000字节。

    这种情况下,一级目录中每一项位置也和Signature的哈希值有关,二级目录中每一项的位置也和Signature的hash值有关。

数据插入

在这里插入图片描述

插入并没有自动去重,允许存在多个同样的Signature对应的Entry条目。插入总是成功的,唯一的失败情况就是哈希表遭到了破坏,那么系统直接蓝屏。另外可以看得出使用的是链地址法解决hash冲突的。

这里注意一个地方,就是系统并没有判断插入的Signature是否为0,也就是说它允许插入Signature为0的条目!

数据查找

在这里插入图片描述

注意:查找的时候系统判断了条目的Signature为0时是无效的,但在插入的时候却允许插入一个值为0的Signature。这就导致Signature为0的条目永远也找不到了(除了手动遍历),这就存在内存泄漏的风险。\textcolor{Red}{注意:查找的时候系统判断了条目的Signature为0时是无效的,但在插入的时候却允许插入一个值为0的Signature。这就导致Signature为0的条目永远也找不到了(除了手动遍历),这就存在内存泄漏的风险。}注意:查找的时候系统判断了条目的Signature0时是无效的,但在插入的时候却允许插入一个值为0Signature。这就导致Signature0的条目永远也找不到了(除了手动遍历),这就存在内存泄漏的风险。

数据枚举
弱枚举
  • 检索一级目录,索引从0开始。如果一级目录指向的是二级目录,再依次检索二级目录,索引从0开始,得到每种hash对应的_RTL_DYNAMIC_HASH_TABLE_ENTRY类型的条目。

  • 更新枚举器,同时还会修改原哈希表的数据:

    在这里插入图片描述

    由于弱枚举器弱在枚举的时候会修改原哈希表内部状态,所以弱枚举器不适用于多线程场景。

    注意:在弱枚举器没有调用RtlEndWeakEnumerationHashTable之前,不能在别的地方使用强枚举器,否则会出现意外结果。\textcolor{Red}{注意:在弱枚举器没有调用RtlEndWeakEnumerationHashTable之前,不能在别的地方使用强枚举器,否则会出现意外结果。}注意:在弱枚举器没有调用RtlEndWeakEnumerationHashTable之前,不能在别的地方使用强枚举器,否则会出现意外结果。

强枚举
  • 检索一级目录,索引从0开始。如果一级目录指向的是二级目录,再依次检索二级目录,索引从0开始,得到每种hash对应的_RTL_DYNAMIC_HASH_TABLE_ENTRY类型的条目。

  • 只更新枚举器,不修改原哈希表的数据:

    在这里插入图片描述

    强枚举器在枚举的时候不会修改原哈希表的内部状态,可用于多线程只读哈希表的操作中。

使用示例

typedef struct _MY_DATA_ENTRY
{RTL_DYNAMIC_HASH_TABLE_ENTRY HashTableEntry;ULONG Data;
}MY_DATA_ENTRY,*PMY_DATA_ENTRY;NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegPath)
{// 演示内核哈希表的使用DriverObject->DriverUnload = DriverUnload;PRTL_DYNAMIC_HASH_TABLE myHashTable = NULL;__try{// 1.构建哈希表if (!RtlCreateHashTableEx(&myHashTable, 0x80, 0, 0)){DbgPrint("Failed to create hash table.\n");__leave;}// 2.插入数据for (ULONG i = 0; i < 10; i++){auto myData = (PMY_DATA_ENTRY)ExAllocatePool(PagedPool, sizeof(MY_DATA_ENTRY));if (myData){myData->Data = (i + 1) * 10;DbgPrint("Alloc data entry: 0x%I64x key: %u data: %u\n", myData, i + 1, myData->Data);if (!RtlInsertEntryHashTable(myHashTable, &myData->HashTableEntry, i + 1, NULL))DbgPrint("Failed to insert index: %u\n", i);}}// 3.查找并删除auto findEntry = RtlLookupEntryHashTable(myHashTable, 3, NULL);if (findEntry){auto myData = CONTAINING_RECORD(findEntry, MY_DATA_ENTRY, HashTableEntry);DbgPrint("Key: 3 - Found => %u Now it had been deleted\n", myData->Data);RtlRemoveEntryHashTable(myHashTable, findEntry, NULL);ExFreePool(findEntry);}else{DbgPrint("Key: 3 - Not Found.\n");}findEntry = RtlLookupEntryHashTable(myHashTable, 5413, NULL);if (findEntry){auto myData = CONTAINING_RECORD(findEntry, MY_DATA_ENTRY, HashTableEntry);DbgPrint("Key: 5413 - Found => %u\n", myData->Data);}else{DbgPrint("Key: 5413 - Not Found.\n");}// 4.遍历	hashEntryEnum = RtlWeaklyEnumerateEntryHashTable(myHashTable, &hashEnumerator);while (hashEntryEnum){auto myDataEntry = CONTAINING_RECORD(hashEntryEnum, MY_DATA_ENTRY, HashTableEntry);DbgPrint("myDataEntry: 0x%I64x Key: %u, Data: %u\n", myDataEntry, myDataEntry->HashTableEntry.Signature, myDataEntry->Data);hashEntryEnum = RtlWeaklyEnumerateEntryHashTable(myHashTable, &hashEnumerator);}// 5.清理DbgPrint("Clean up...\n");RTL_DYNAMIC_HASH_TABLE_ENUMERATOR hashEnumerator;RtlInitStrongEnumerationHashTable(myHashTable, &hashEnumerator);auto hashEntryEnum = RtlStronglyEnumerateEntryHashTable(myHashTable, &hashEnumerator);while (hashEntryEnum){auto myDataEntry = CONTAINING_RECORD(hashEntryEnum, MY_DATA_ENTRY, HashTableEntry);DbgPrint("myDataEntry: 0x%I64x Key: %u, Data: %u\n", myDataEntry, myDataEntry->HashTableEntry.Signature, myDataEntry->Data);hashEntryEnum = RtlStronglyEnumerateEntryHashTable(myHashTable, &hashEnumerator);ExFreePool(myDataEntry);}}__finally{if (myHashTable) RtlDeleteHashTable(myHashTable);}return STATUS_SUCCESS;
}

Output:

Alloc data entry: 0xffffe503a02f0bb0 key: 1 data: 10
Alloc data entry: 0xffffe503a02f0c40 key: 2 data: 20
Alloc data entry: 0xffffe503a02f2b30 key: 3 data: 30
Alloc data entry: 0xffffe503a02f3df0 key: 4 data: 40
Alloc data entry: 0xffffe503a02f53b0 key: 5 data: 50
Alloc data entry: 0xffffe503a02f5110 key: 6 data: 60
Alloc data entry: 0xffffe503a02f5650 key: 7 data: 70
Alloc data entry: 0xffffe503a02f57d0 key: 8 data: 80
Alloc data entry: 0xffffe503a02f6640 key: 9 data: 90
Alloc data entry: 0xffffe503a02f7b70 key: 10 data: 100
Key: 3 - Found => 30 Now it had been deleted
Key: 5413 - Not Found.
myDataEntry: 0xffffe503a02f0c40 Key: 2, Data: 20
myDataEntry: 0xffffe503a02f3df0 Key: 4, Data: 40
myDataEntry: 0xffffe503a02f5110 Key: 6, Data: 60
myDataEntry: 0xffffe503a02f57d0 Key: 8, Data: 80
myDataEntry: 0xffffe503a02f7b70 Key: 10, Data: 100
myDataEntry: 0xffffe503a02f0bb0 Key: 1, Data: 10
myDataEntry: 0xffffe503a02f53b0 Key: 5, Data: 50
myDataEntry: 0xffffe503a02f5650 Key: 7, Data: 70
myDataEntry: 0xffffe503a02f6640 Key: 9, Data: 90
Clean up…
myDataEntry: 0xffffe503a02f0c40 Key: 2, Data: 20
myDataEntry: 0xffffe503a02f3df0 Key: 4, Data: 40
myDataEntry: 0xffffe503a02f5110 Key: 6, Data: 60
myDataEntry: 0xffffe503a02f57d0 Key: 8, Data: 80
myDataEntry: 0xffffe503a02f7b70 Key: 10, Data: 100
myDataEntry: 0xffffe503a02f0bb0 Key: 1, Data: 10
myDataEntry: 0xffffe503a02f53b0 Key: 5, Data: 50
myDataEntry: 0xffffe503a02f5650 Key: 7, Data: 70
myDataEntry: 0xffffe503a02f6640 Key: 9, Data: 90

http://www.dtcms.com/a/597922.html

相关文章:

  • 网站的管理更新维护做网站用什么语言比较简单
  • “湖湘杯”——湖南网安基地的四年进化论
  • 网站里自动切换图片怎么做烟台百度网站推广
  • Kafka Partition 深度解析:原理、策略与实战优化
  • 基于深度学习的车辆动态红外特性预测研究
  • 不仅仅是 AI:PawSQL 如何实现“可信 AI SQL 优化”?
  • 网站的备案号网站维护 公司简介
  • Qt之信号和槽
  • Matlab编写压缩感知重建算法集
  • QT-- 理解项目文件
  • app外包网站网站建设是固定资产吗
  • MySQL核心知识点梳理
  • 天长做网站的电子商务网站基础建设
  • 【论文阅读】Hypercomplex Prompt-aware Multimodal Recommendation
  • 邵阳优秀网站建设有什么网站可以做数学题
  • Linux 内存管理 (4):buddy 管理系统的建立
  • 华为、思科、锐捷、华三定时备份配置命令对照表
  • 网站的404如何做湖北做网站的
  • C# 桌面框架与 Qt 对比分析
  • 更新网站要怎么做呢聊天软件
  • 自己开一个网站怎么赚钱广州互联网公司有哪些
  • MATLAB实现图像菲涅尔衍射
  • Linux开源代码汇总
  • stable-diffusion安装EasyPhoto启动报错解决
  • 做网站标题业之峰装饰官网
  • 做网站挂广告赚多少钱wordpress布局主题
  • PCB之电源完整性之电源网络的PDN仿真CST---08
  • AXI-5.3.1 Memory type requirements
  • Redis vs MongoDB:内存字典与文档库对决
  • 长沙手机网站首页设计公司淮南网络宾馆