x32 PEB: 获取Kernel32基地址的原理及实现
字数 1651 2025-08-24 07:48:33

获取Kernel32基地址的原理及实现

0x00 前言

在Windows程序开发和安全研究中,获取自身加载的DLL基地址是一个重要技术。这项技术广泛应用于ShellCode编写,用于定位动态API地址。本文将详细介绍两种主要方法:暴力搜索和基于PEB的搜索。

0x01 暴力搜索方法

暴力搜索方法虽然简单但存在诸多缺陷,主要包括三种实现方式:

方法一:固定地址范围搜索

  1. 在32位系统中,kernel32.dll通常加载在0x70000000-0x80000000范围内
  2. 加载地址是64KB对齐的,最多需要搜索4097次
  3. 需要双重验证:先检查"MZ"头,再解析PE结构验证DLL名称
#include <Windows.h>
#include <stdio.h>
int main() {
    HANDLE kernelA = LoadLibrary(L"kernel32.dll");
    printf("0x:%p\n", kernelA);
    system("pause");
}

不同系统下的典型加载地址:

  • Win11: 0x76640000
  • Win10: 0x75710000
  • Win7: 0x754A0000
  • Windows 2003: 0x7c800000

方法二:利用线程返回地址

  1. Windows创建进程时,主线程初始化时将ExitThread地址压入堆栈
  2. ExitThread位于kernel32.dll中,可以从栈顶地址开始递减搜索"MZ"头

局限性

  • 受编译器包装代码影响,内联汇编难以使用
  • 通用性差,不适合作为通用寻址手段

方法三:利用异常处理链表

  1. 通过fs:[0]获取异常处理链表(ExceptionList)
  2. 链表最后一项指向Kernel32.dll中的UnhandledExceptionFilter
  3. 从该地址递减搜索"MZ"头
#include <stdio.h>
#include <windows.h>
int main() {
    unsigned int kernelAddr;
    __asm {
        mov edx, fs:[0];
    Foreach:
        cmp [edx], 0xffffffff;
        je Handle;
        mov edx, [edx];
        jmp Foreach;
    Handle:
        mov edx, [edx+4];
    _Loop:
        cmp word ptr [edx], 'ZM';
        jz Kernel;
        dec edx;
        xor dx, dx;
        jmp _Loop;
    Kernel:
        mov kernelAddr, edx;
    }
    printf("Kernel32.dll address: %x\n", kernelAddr);
    printf("LoadLibrary Kernel32.dll address: %x\n", 
           LoadLibrary("kernel32.dll"));
    return 0;
}

兼容性问题

  • 仅适用于Win2003/XP
  • Win7/Win10中最后过滤函数位于ntdll.dll空间

暴力搜索小结

  1. 共同缺陷:兼容性差,需要额外判断条件
  2. 优化方向:从ntdll.dll回溯到kernel32.dll,检查PE结构
  3. 不推荐使用:内存访问不可预测,代码复杂度高

0x02 基于PEB的搜索方法

2.1 TEB与PEB结构

  1. TEB (Thread Environment Block):

    • 存储线程相关数据
    • 用户模式下通过FS寄存器访问(fs:[0])
  2. PEB (Process Environment Block):

    • 存储进程信息
    • 位于TEB结构偏移0x30处(fs:[0x30])

2.2 PEB结构分析

关键结构成员:

typedef struct _PEB {
    BYTE Reserved1[2];
    BYTE BeingDebugged;
    // ...
    PPEB_LDR_DATA Ldr;  // 偏移0xC处
    // ...
} PEB, *PPEB;

PEB_LDR_DATA结构:

typedef struct _PEB_LDR_DATA {
    BYTE Reserved1[8];
    PVOID Reserved2[3];
    LIST_ENTRY InLoadOrderModuleList;    // 偏移0xC
    LIST_ENTRY InMemoryOrderModuleList;  // 偏移0x14
    LIST_ENTRY InInitializationOrderModuleList; // 偏移0x1C
} PEB_LDR_DATA, *PPEB_LDR_DATA;

模块加载顺序:

  1. PebTest.exe
  2. ntdll.dll
  3. KERNEL32.DLL
  4. KERNELBASE.dll
  5. 其他依赖DLL

2.3 搜索实现方案

方案一:InLoadOrderModuleList

#include <Windows.h>
#include <stdio.h>
int main() {
    unsigned int address;
    __asm {
        xor eax, eax;
        mov eax, fs:[eax+30h];  // PEB指针
        mov eax, [eax+0ch];     // PEB_LDR_DATA
        mov eax, [eax+0ch];     // InLoadOrderModuleList
        mov esi, [eax];
        lodsd;
        mov eax, [eax+18h];     // Kernel32.dll基地址(偏移0x18)
        mov address, eax;
    }
    printf("0x:%p\n", address);
    HANDLE kernelA = LoadLibrary(L"kernel32.dll");
    printf("0x:%p\n", kernelA);
    system("pause");
    return 0;
}

方案二:InMemoryOrderModuleList

#include <Windows.h>
#include <stdio.h>
int main() {
    unsigned int address;
    __asm {
        xor eax, eax;
        mov eax, fs:[eax+30h];  // PEB指针
        mov eax, [eax+0ch];     // PEB_LDR_DATA
        mov eax, [eax+14h];     // InMemoryOrderModuleList
        mov esi, [eax];
        lodsd;
        mov eax, [eax+10h];     // Kernel32.dll基地址(偏移0x10)
        mov address, eax;
    }
    printf("0x:%p\n", address);
    HANDLE kernelA = LoadLibrary(L"kernel32.dll");
    printf("0x:%p\n", kernelA);
    system("pause");
    return 0;
}

优化方案:增加模块名称验证

__asm {
    xor eax, eax;
    mov eax, fs:[eax+30h];  // PEB指针
    mov eax, [eax+0ch];     // PEB_LDR_DATA
    mov eax, [eax+0ch];     // InLoadOrderModuleList
    push 0x001a0018;        // "KERNEL32.DLL"长度
    mov edi, [esp];
Next:
    mov eax, [eax];         // 遍历链表
    cmp edi, [eax+0x2c];    // 比较名称长度
    jne Next;
    mov eax, [eax+18h];     // 获取基地址
    mov address, eax;
    add esp, 0x4;           // 平衡堆栈
}

0x03 总结

  1. 暴力搜索方法已过时,存在兼容性问题
  2. 基于PEB的方法是当前最佳实践:
    • 通过TEB/PEB结构定位模块列表
    • 遍历模块列表获取kernel32.dll基地址
  3. 推荐使用InLoadOrderModuleList或InMemoryOrderModuleList
  4. 可进一步优化增加模块名称验证

0x04 附录:关键偏移量

结构 成员 偏移量
TEB PEB指针 0x30
PEB PEB_LDR_DATA 0xC
PEB_LDR_DATA InLoadOrderModuleList 0xC
PEB_LDR_DATA InMemoryOrderModuleList 0x14
LDR_DATA_TABLE_ENTRY (InLoadOrder) DllBase 0x18
LDR_DATA_TABLE_ENTRY (InMemoryOrder) DllBase 0x10
LDR_DATA_TABLE_ENTRY BaseDllName 0x2C
获取Kernel32基地址的原理及实现 0x00 前言 在Windows程序开发和安全研究中,获取自身加载的DLL基地址是一个重要技术。这项技术广泛应用于ShellCode编写,用于定位动态API地址。本文将详细介绍两种主要方法:暴力搜索和基于PEB的搜索。 0x01 暴力搜索方法 暴力搜索方法虽然简单但存在诸多缺陷,主要包括三种实现方式: 方法一:固定地址范围搜索 在32位系统中,kernel32.dll通常加载在0x70000000-0x80000000范围内 加载地址是64KB对齐的,最多需要搜索4097次 需要双重验证:先检查"MZ"头,再解析PE结构验证DLL名称 不同系统下的典型加载地址: Win11: 0x76640000 Win10: 0x75710000 Win7: 0x754A0000 Windows 2003: 0x7c800000 方法二:利用线程返回地址 Windows创建进程时,主线程初始化时将ExitThread地址压入堆栈 ExitThread位于kernel32.dll中,可以从栈顶地址开始递减搜索"MZ"头 局限性 : 受编译器包装代码影响,内联汇编难以使用 通用性差,不适合作为通用寻址手段 方法三:利用异常处理链表 通过fs:[ 0 ]获取异常处理链表(ExceptionList) 链表最后一项指向Kernel32.dll中的UnhandledExceptionFilter 从该地址递减搜索"MZ"头 兼容性问题 : 仅适用于Win2003/XP Win7/Win10中最后过滤函数位于ntdll.dll空间 暴力搜索小结 共同缺陷:兼容性差,需要额外判断条件 优化方向:从ntdll.dll回溯到kernel32.dll,检查PE结构 不推荐使用:内存访问不可预测,代码复杂度高 0x02 基于PEB的搜索方法 2.1 TEB与PEB结构 TEB (Thread Environment Block): 存储线程相关数据 用户模式下通过FS寄存器访问(fs:[ 0 ]) PEB (Process Environment Block): 存储进程信息 位于TEB结构偏移0x30处(fs:[ 0x30 ]) 2.2 PEB结构分析 关键结构成员: PEB_LDR_DATA 结构: 模块加载顺序: PebTest.exe ntdll.dll KERNEL32.DLL KERNELBASE.dll 其他依赖DLL 2.3 搜索实现方案 方案一:InLoadOrderModuleList 方案二:InMemoryOrderModuleList 优化方案:增加模块名称验证 0x03 总结 暴力搜索方法已过时,存在兼容性问题 基于PEB的方法是当前最佳实践: 通过TEB/PEB结构定位模块列表 遍历模块列表获取kernel32.dll基地址 推荐使用InLoadOrderModuleList或InMemoryOrderModuleList 可进一步优化增加模块名称验证 0x04 附录:关键偏移量 | 结构 | 成员 | 偏移量 | |------|------|--------| | TEB | PEB指针 | 0x30 | | PEB | PEB_ LDR_ DATA | 0xC | | PEB_ LDR_ DATA | InLoadOrderModuleList | 0xC | | PEB_ LDR_ DATA | InMemoryOrderModuleList | 0x14 | | LDR_ DATA_ TABLE_ ENTRY (InLoadOrder) | DllBase | 0x18 | | LDR_ DATA_ TABLE_ ENTRY (InMemoryOrder) | DllBase | 0x10 | | LDR_ DATA_ TABLE_ ENTRY | BaseDllName | 0x2C |