Windows进程中KERNEL32.DLL是否始终是第3个加载的模块?
假设KERNEL32.DLL始终是进程加载第3个模块的可靠性分析
我尝试通过假设KERNEL32.DLL始终是Windows进程中加载的第3个模块来获取其基地址,调试输出的模块加载顺序如下:
ModLoad: 00400000 024077eb image00400000 ModLoad: 77ad0000 77c7f000 ntdll.dll ModLoad: 75c80000 75d70000 C:\WINDOWS\SysWOW64\KERNEL32.DLL ModLoad: 77160000 773d3000 C:\WINDOWS\SysWOW64\KERNELBASE.dll ...
原本我会循环遍历模块列表,实际调试中发现KERNEL32.DLL始终是第3个条目,请问这种假设是否可靠?
结论:该假设并不具备普遍可靠性,仅能在常规无干扰的场景下临时生效,存在兼容性失效风险
原因分析
- 常规默认流程中,Windows进程的模块加载顺序确实为:进程自身可执行文件 →
ntdll.dll→kernel32.dll,这是系统默认加载逻辑的常规表现 - 但存在多种例外场景会打破这个顺序:
- 第三方注入工具、安全软件的钩子DLL可能提前加载,插入到
kernel32.dll之前 - Windows版本迭代或系统配置修改,可能调整系统模块的加载优先级
- 特殊进程(如系统核心进程、被调试器附加的进程)的加载流程可能与普通进程不同
- 第三方注入工具、安全软件的钩子DLL可能提前加载,插入到
最初实现代码及输出
程序输出:
Kernel32.dll Base Address: 0x75C80000
代码实现:
#include <stdio.h> #include <stdint.h> #include <windows.h> #include <winternl.h> int main() { PPEB peb = (PPEB)__readfsdword(0x30); uintptr_t kernel32Base = 0; /* Skip first two entries as kernel32.dll is always the third entry */ PLIST_ENTRY ptr = peb->Ldr->InMemoryOrderModuleList.Flink->Flink->Flink; PLDR_DATA_TABLE_ENTRY e = CONTAINING_RECORD(ptr, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); kernel32Base = (uintptr_t)e->DllBase; printf("Kernel32.dll Base Address: 0x%p\n", (void*)kernel32Base); return 0; }
更新方案:改用InLoadOrderModuleList并自定义结构
基于建议改用InLoadOrderModuleList(按加载顺序排列的模块列表),调试后代码可正常运行,输出一致:
Kernel32.dll Base Address: 0x75C80000
自定义结构的实现代码:
#include <stdio.h> #include <stdint.h> #include <windows.h> typedef struct MY_PEB_LDR_DATA { ULONG Length; UCHAR Initialized; VOID* SsHandle; LIST_ENTRY InLoadOrderModuleList; // ... other fields } MY_PEB_LDR_DATA, *MY_PPEB_LDR_DATA; typedef struct MY_LDR_DATA_TABLE_ENTRY { LIST_ENTRY InLoadOrderLinks; LIST_ENTRY InMemoryOrderLinks; LIST_ENTRY InInitializationOrderLinks; PVOID DllBase; // ... other fields } MY_LDR_DATA_TABLE_ENTRY, *MY_PLDR_DATA_TABLE_ENTRY; typedef struct MY_PEB { BYTE Reserved1[2]; BYTE BeingDebugged; BYTE Reserved2[1]; PVOID Reserved3[2]; MY_PPEB_LDR_DATA Ldr; // ... other fields } MY_PEB, *MY_PPEB; int main() { MY_PPEB peb = (MY_PPEB)__readfsdword(0x30); uintptr_t kernel32Base = 0; // Order of modules loaded into the process: [image][ntdll][kernel32] // Skip first two entries as kernel32.dll is always the third entry. PLIST_ENTRY ptr = peb->Ldr->InLoadOrderModuleList.Flink->Flink->Flink; MY_PLDR_DATA_TABLE_ENTRY e = CONTAINING_RECORD(ptr, MY_LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); kernel32Base = (uintptr_t)e->DllBase; printf("Kernel32.dll Base Address: 0x%p\n", (void*)kernel32Base); return 0; }
精简版实现
#include <intrin.h> #include <stdint.h> #include <stdio.h> #ifdef _WIN64 #define PEB __readgsqword(0x60) #define LDR_DATA_IN_PEB_OFFSET 24 #define IN_LOAD_ORDER_MODULE_LIST_OFFSET 16 #define DLL_BASE_OFFSET 48 #else #define PEB __readfsdword(0x30) #define LDR_DATA_IN_PEB_OFFSET 12 #define IN_LOAD_ORDER_MODULE_LIST_OFFSET 12 #define DLL_BASE_OFFSET 24 #endif // The sequence of modules loaded into the process typically follows the order: // [Executable Image], [ntdll.dll], [kernel32.dll]. #define KERNEL32_BASE_ADDRESS *(uintptr_t*)(*(uintptr_t*)(*(uintptr_t*)(*(uintptr_t*)(*(uintptr_t*)(PEB + LDR_DATA_IN_PEB_OFFSET) + IN_LOAD_ORDER_MODULE_LIST_OFFSET))) + DLL_BASE_OFFSET) int main() { printf("Kernel32.dll Base Address: 0x%p\n", (void*)KERNEL32_BASE_ADDRESS); }
模块列表说明
InLoadOrderModuleList:按模块加载顺序排列,常规情况下第一个是可执行文件,随后是ntdll.dll、KERNEL32.dll等系统DLLInMemoryOrderModuleList:按模块在内存中的布局顺序排列,不一定与加载顺序完全一致,但通常相似
内容的提问来源于stack exchange,提问作者vengy
相关产品推荐
相关产品推荐

