内核哈希表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}RtlInitWeakEnumerationHashTable和RtlWeaklyEnumerateEntryHashTable\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类型的指针,头部指针不存储数据。桶中每一个元素的索引值就是Signature的hash值,相同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的条目永远也找不到了(除了手动遍历),这就存在内存泄漏的风险。}注意:查找的时候系统判断了条目的Signature为0时是无效的,但在插入的时候却允许插入一个值为0的Signature。这就导致Signature为0的条目永远也找不到了(除了手动遍历),这就存在内存泄漏的风险。
数据枚举
弱枚举
-
检索一级目录,索引从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
