x32 PEB: 获取Kernel32基地址的原理及实现
字数 1651 2025-08-24 07:48:33
获取Kernel32基地址的原理及实现
0x00 前言
在Windows程序开发和安全研究中,获取自身加载的DLL基地址是一个重要技术。这项技术广泛应用于ShellCode编写,用于定位动态API地址。本文将详细介绍两种主要方法:暴力搜索和基于PEB的搜索。
0x01 暴力搜索方法
暴力搜索方法虽然简单但存在诸多缺陷,主要包括三种实现方式:
方法一:固定地址范围搜索
- 在32位系统中,kernel32.dll通常加载在0x70000000-0x80000000范围内
- 加载地址是64KB对齐的,最多需要搜索4097次
- 需要双重验证:先检查"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
方法二:利用线程返回地址
- Windows创建进程时,主线程初始化时将ExitThread地址压入堆栈
- ExitThread位于kernel32.dll中,可以从栈顶地址开始递减搜索"MZ"头
局限性:
- 受编译器包装代码影响,内联汇编难以使用
- 通用性差,不适合作为通用寻址手段
方法三:利用异常处理链表
- 通过fs:[0]获取异常处理链表(ExceptionList)
- 链表最后一项指向Kernel32.dll中的UnhandledExceptionFilter
- 从该地址递减搜索"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空间
暴力搜索小结
- 共同缺陷:兼容性差,需要额外判断条件
- 优化方向:从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结构分析
关键结构成员:
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;
模块加载顺序:
- PebTest.exe
- ntdll.dll
- KERNEL32.DLL
- KERNELBASE.dll
- 其他依赖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 总结
- 暴力搜索方法已过时,存在兼容性问题
- 基于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 |