为何仅KERNEL32.DLL的BaseNameHashValue与计算哈希匹配?
BaseNameHashValue是LDR_DATA_TABLE_ENTRY结构中的字段,用于存储BaseDllName通过RtlHashUnicodeString计算得到的哈希值(参考《Windows Internals 第七版 第一部分》)。
运行下方x86程序后,输出结果显示仅KERNEL32.DLL的BaseNameHashValue与通过RtlHashUnicodeString计算出的结果匹配,其余模块两者均不匹配:
Name: peb2.exe Hash: 0x29413AE9 Calculated: 0x264362E9 Address: 0x00F10000 --------------------------------------------- Name: ntdll.dll Hash: 0xF46857D4 Calculated: 0xD22E2014 Address: 0x77AD0000 --------------------------------------------- Name: KERNEL32.DLL Hash: 0x536CD652 Calculated: 0x536CD652 Address: 0x75C80000 --------------------------------------------- Name: KERNELBASE.dll Hash: 0x0235BEC4 Calculated: 0x1217B6E4 Address: 0x77160000 --------------------------------------------- Name: ucrtbase.dll Hash: 0xAEF34AF7 Calculated: 0x2E1D6317 Address: 0x77430000 --------------------------------------------- Name: VCRUNTIME140.dll Hash: 0xB61775F8 Calculated: 0xC5F96E18 Address: 0x75710000 ---------------------------------------------
对应的测试代码如下(peb2.cpp):
#include <stdio.h> #include <stdint.h> #include <windows.h> #include <ntstatus.h> #include <winternl.h> #define HASH_STRING_ALGORITHM_DEFAULT 0 typedef struct { PVOID Reserved1[2]; LIST_ENTRY InMemoryOrderLinks; PVOID Reserved2[2]; PVOID DllBase; PVOID EntryPoint; ULONG SizeOfImage; UNICODE_STRING FullDllName; UNICODE_STRING BaseDllName; uint8_t pad[92]; /* union _union_9066 field_0x24; ushort ObsoleteLoadCount; ushort TlsIndex; struct _LIST_ENTRY HashLinks; ulong TimeDateStamp; struct _ACTIVATION_CONTEXT* EntryPointActivationContext; void* Lock; struct _LDR_DDAG_NODE* DdagNode; struct _LIST_ENTRY NodeModuleLink; struct _LDRP_LOAD_CONTEXT* LoadContext; void* ParentDllBase; void* SwitchBackContext; struct _RTL_BALANCED_NODE BaseAddressIndexNode; struct _RTL_BALANCED_NODE MappingInfoIndexNode; ulong OriginalBase; long Padding_84; union _LARGE_INTEGER LoadTime; */ ULONG BaseNameHashValue; /* enum _LDR_DLL_LOAD_REASON LoadReason; ulong ImplicitPathOptions; ulong ReferenceCount; ulong DependentLoadFlags; uchar SigningLevel; char __PADDING__[3]; */ } MY_LDR_DATA_TABLE_ENTRY; typedef VOID(NTAPI* pRtlInitUnicodeString)(PUNICODE_STRING, PCWSTR); typedef NTSTATUS(NTAPI* pRtlHashUnicodeString)(const UNICODE_STRING*, BOOLEAN, ULONG, PULONG); ULONG GetHashFromBaseName(PCWSTR BaseName) { ULONG hash = 0; HMODULE hNtdll = GetModuleHandle(L"ntdll.dll"); pRtlInitUnicodeString RtlInitUnicodeString = (pRtlInitUnicodeString)GetProcAddress(hNtdll, "RtlInitUnicodeString"); pRtlHashUnicodeString RtlHashUnicodeString = (pRtlHashUnicodeString)GetProcAddress(hNtdll, "RtlHashUnicodeString"); UNICODE_STRING uStr; RtlInitUnicodeString(&uStr, BaseName); RtlHashUnicodeString(&uStr, FALSE, HASH_STRING_ALGORITHM_DEFAULT, &hash); return hash; } int main() { PPEB peb = (PPEB)__readfsdword(0x30); PLIST_ENTRY ptr = peb->Ldr->InMemoryOrderModuleList.Flink; while (ptr != &peb->Ldr->InMemoryOrderModuleList) { MY_LDR_DATA_TABLE_ENTRY* e = CONTAINING_RECORD(ptr, MY_LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); if (e->BaseDllName.Buffer != NULL) { int len = e->BaseDllName.Length / sizeof(wchar_t); wchar_t BaseName[256]; wcsncpy_s(BaseName, sizeof(BaseName) / sizeof(wchar_t), e->BaseDllName.Buffer, len); BaseName[len] = L'\0'; // BaseDllName - The name of the module itself, without the full path. wprintf(L"Name: %s\n", BaseName); // BaseNameHashValue - Hash of BaseDllName using RtlHashUnicodeString. wprintf(L"Hash: 0x%08X Calculated: 0x%08X\n", e->BaseNameHashValue, GetHashFromBaseName(BaseName)); // Holds the base address at which the module was loaded. wprintf(L"Address: 0x%p\n", (void*)e->DllBase); wprintf(L"---------------------------------------------\n"); } ptr = ptr->Flink; } return 0; }
出现这个问题的核心原因有两点:
1. 手动定义的结构体偏移错误
你手动定义的MY_LDR_DATA_TABLE_ENTRY结构中,用uint8_t pad[92]占位的方式完全不可靠。Windows不同版本(甚至同版本的更新补丁)中,LDR_DATA_TABLE_ENTRY的内部成员布局、大小都会发生变化,硬编码的pad[92]无法匹配真实的内存偏移,导致你读取的BaseNameHashValue并非结构中真正的哈希字段,而是其他无关内存位置的值。
KERNEL32.DLL的哈希匹配只是巧合——读取到的错误内存值刚好和计算结果一致,其他模块没有这种巧合,所以出现哈希不匹配的情况。
正确做法是使用系统公开的结构定义,或通过动态计算偏移的方式获取BaseNameHashValue的位置,避免硬编码占位长度。
2. 哈希计算的大小写敏感性差异
系统计算BaseNameHashValue时,调用RtlHashUnicodeString传入的大小写敏感参数可能为TRUE(忽略大小写),而你的代码中传入的是FALSE(大小写敏感)。
例如,对于ntdll.dll,系统可能基于大写的NTDLL.DLL计算忽略大小写的哈希,而你的代码用小写的ntdll.dll计算大小写敏感的哈希,结果自然不同。KERNEL32.DLL的名称本身是大写,无论是否忽略大小写,计算出的哈希值都一致,这也进一步导致它的结果看起来匹配。
内容的提问来源于stack exchange,提问作者vengy

