You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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版本迭代或系统配置修改,可能调整系统模块的加载优先级
    • 特殊进程(如系统核心进程、被调试器附加的进程)的加载流程可能与普通进程不同

最初实现代码及输出

程序输出:

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等系统DLL
  • InMemoryOrderModuleList:按模块在内存中的布局顺序排列,不一定与加载顺序完全一致,但通常相似

内容的提问来源于stack exchange,提问作者vengy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 06:56:02