Windows PE结构
7、重定位表解析
1、课程目标
- 理解PE重定位的原理和必要性
- 掌握重定位表的结构和解析方法
- 学会手动进行重定位修复
- 理解重定位在Shellcode和注入中的应用
2、名词解释
| 名词 | 全称 | 解释 |
|---|---|---|
| Relocation | 重定位 | 调整代码中的地址引用 |
| Base Relocation | 基址重定位 | PE加载到非首选地址时的修复 |
| Delta | 差值 | 实际加载地址与首选地址的差 |
| ASLR | Address Space Layout Randomization | 地址空间布局随机化 |
| Fixup | 修复项 | 需要重定位的位置 |
| IMAGE_REL_BASED_HIGHLOW | - | 32位绝对地址重定位 |
| IMAGE_REL_BASED_DIR64 | - | 64位绝对地址重定位 |
3、使用工具
- CFF Explorer
- PE-bear
- x64dbg
- IDA Pro
4、技术原理
4.1、为什么需要重定位
场景:DLL编译时ImageBase = 0x10000000
代码中的绝对地址:
mov eax, [0x10001234] ; 访问全局变量
call 0x10002000 ; 调用函数
如果DLL被加载到0x20000000:
Delta = 0x20000000 - 0x10000000 = 0x10000000
需要修正为:
mov eax, [0x20001234] ; 0x10001234 + Delta
call 0x20002000 ; 0x10002000 + Delta
4.2、重定位表结构
// 重定位块
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; // 页面RVA(4KB对齐)
DWORD SizeOfBlock; // 块大小(包括头)
// 后跟 WORD 数组,每个 WORD 描述一个重定位项
} IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION;
// 重定位项格式(WORD)
// 高4位:重定位类型
// 低12位:页内偏移
// 重定位类型
#define IMAGE_REL_BASED_ABSOLUTE 0 // 无操作(用于对齐)
#define IMAGE_REL_BASED_HIGH 1 // 高16位
#define IMAGE_REL_BASED_LOW 2 // 低16位
#define IMAGE_REL_BASED_HIGHLOW 3 // 完整32位地址 ★常用★
#define IMAGE_REL_BASED_HIGHADJ 4 // 高16位调整
#define IMAGE_REL_BASED_MIPS_JMPADDR 5 // MIPS跳转地址
#define IMAGE_REL_BASED_ARM_MOV32 5 // ARM MOV32
#define IMAGE_REL_BASED_THUMB_MOV32 7 // Thumb MOV32
#define IMAGE_REL_BASED_MIPS_JMPADDR16 9 // MIPS16跳转
#define IMAGE_REL_BASED_DIR64 10 // 64位地址 ★常用★
4.3、重定位表布局
+-------------------------+
| VirtualAddress = 0x1000 |
| SizeOfBlock = 0x0014 |
| Entry[0] = 0x3010 | Type=3(HIGHLOW), Offset=0x010
| Entry[1] = 0x3024 | Type=3(HIGHLOW), Offset=0x024
| Entry[2] = 0x3038 | Type=3(HIGHLOW), Offset=0x038
| Entry[3] = 0x0000 | Type=0(ABSOLUTE), 用于对齐
+-------------------------+
| VirtualAddress = 0x2000 |
| SizeOfBlock = 0x000C |
| Entry[0] = 0x3100 |
| Entry[1] = 0x3200 |
+-------------------------+
| VirtualAddress = 0x0000 | 结束标志
| SizeOfBlock = 0x0000 |
+-------------------------+
5、代码实现
5.1、解析重定位表
#include <windows.h>
#include <stdio.h>
// 获取重定位类型名称
const char* GetRelocTypeName(WORD type) {
switch (type) {
case IMAGE_REL_BASED_ABSOLUTE: return "ABSOLUTE";
case IMAGE_REL_BASED_HIGH: return "HIGH";
case IMAGE_REL_BASED_LOW: return "LOW";
case IMAGE_REL_BASED_HIGHLOW: return "HIGHLOW";
case IMAGE_REL_BASED_HIGHADJ: return "HIGHADJ";
case IMAGE_REL_BASED_DIR64: return "DIR64";
default: return "UNKNOWN";
}
}
// 解析重定位表
void ParseRelocationTable(LPVOID fileData) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
(PBYTE)fileData + dosHeader->e_lfanew
);
BOOL is64 = (ntHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC);
PIMAGE_DATA_DIRECTORY relocDir;
ULONGLONG imageBase;
if (is64) {
PIMAGE_OPTIONAL_HEADER64 opt = (PIMAGE_OPTIONAL_HEADER64)&ntHeaders->OptionalHeader;
relocDir = &opt->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
imageBase = opt->ImageBase;
} else {
PIMAGE_OPTIONAL_HEADER32 opt = (PIMAGE_OPTIONAL_HEADER32)&ntHeaders->OptionalHeader;
relocDir = &opt->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
imageBase = opt->ImageBase;
}
if (relocDir->VirtualAddress == 0) {
printf("[-] 没有重定位表\n");
return;
}
printf("\n========== Relocation Table ==========\n");
printf("RVA: 0x%08X, Size: 0x%08X\n",
relocDir->VirtualAddress, relocDir->Size);
printf("ImageBase: 0x%llX\n\n", imageBase);
DWORD relocFoa = RvaToFoa(fileData, relocDir->VirtualAddress);
PIMAGE_BASE_RELOCATION relocBlock = (PIMAGE_BASE_RELOCATION)(
(PBYTE)fileData + relocFoa
);
int blockIndex = 0;
int totalEntries = 0;
// 遍历所有重定位块
while (relocBlock->VirtualAddress != 0) {
printf("Block #%d: PageRVA=0x%08X, BlockSize=0x%04X\n",
blockIndex, relocBlock->VirtualAddress, relocBlock->SizeOfBlock);
// 计算条目数量
DWORD numEntries = (relocBlock->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
WORD* entries = (WORD*)(relocBlock + 1);
for (DWORD i = 0; i < numEntries; i++) {
WORD type = entries[i] >> 12;
WORD offset = entries[i] & 0x0FFF;
// 跳过ABSOLUTE类型(用于对齐)
if (type == IMAGE_REL_BASED_ABSOLUTE) {
continue;
}
DWORD targetRva = relocBlock->VirtualAddress + offset;
printf(" [%3d] Type: %d (%s), Offset: 0x%03X, TargetRVA: 0x%08X\n",
i, type, GetRelocTypeName(type), offset, targetRva);
// 显示需要重定位的原始值
DWORD targetFoa = RvaToFoa(fileData, targetRva);
if (type == IMAGE_REL_BASED_HIGHLOW) {
DWORD* ptr = (DWORD*)((PBYTE)fileData + targetFoa);
printf(" Original Value: 0x%08X\n", *ptr);
} else if (type == IMAGE_REL_BASED_DIR64) {
ULONGLONG* ptr = (ULONGLONG*)((PBYTE)fileData + targetFoa);
printf(" Original Value: 0x%016llX\n", *ptr);
}
totalEntries++;
}
printf(" Entries in block: %d\n\n", numEntries);
// 移动到下一个块
relocBlock = (PIMAGE_BASE_RELOCATION)(
(PBYTE)relocBlock + relocBlock->SizeOfBlock
);
blockIndex++;
}
printf("[*] 共 %d 个重定位块, %d 个重定位项\n", blockIndex, totalEntries);
}
5.2、执行重定位
// 重定位信息
typedef struct _RELOC_INFO {
DWORD rva; // 需要修复的位置RVA
WORD type; // 重定位类型
} RELOC_INFO;
// 收集所有重定位信息
int CollectRelocations(LPVOID fileData, RELOC_INFO* relocArray, int maxCount) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
(PBYTE)fileData + dosHeader->e_lfanew
);
PIMAGE_DATA_DIRECTORY relocDir = &ntHeaders->OptionalHeader
.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (relocDir->VirtualAddress == 0) {
return 0;
}
DWORD relocFoa = RvaToFoa(fileData, relocDir->VirtualAddress);
PIMAGE_BASE_RELOCATION relocBlock = (PIMAGE_BASE_RELOCATION)(
(PBYTE)fileData + relocFoa
);
int count = 0;
while (relocBlock->VirtualAddress != 0 && count < maxCount) {
DWORD numEntries = (relocBlock->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
WORD* entries = (WORD*)(relocBlock + 1);
for (DWORD i = 0; i < numEntries && count < maxCount; i++) {
WORD type = entries[i] >> 12;
WORD offset = entries[i] & 0x0FFF;
if (type != IMAGE_REL_BASED_ABSOLUTE) {
relocArray[count].rva = relocBlock->VirtualAddress + offset;
relocArray[count].type = type;
count++;
}
}
relocBlock = (PIMAGE_BASE_RELOCATION)(
(PBYTE)relocBlock + relocBlock->SizeOfBlock
);
}
return count;
}
// 对内存中的PE映像执行重定位
BOOL ApplyRelocations(LPBYTE imageBase, ULONGLONG newBase, ULONGLONG oldBase) {
LONGLONG delta = newBase - oldBase;
if (delta == 0) {
return TRUE; // 不需要重定位
}
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)imageBase;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
imageBase + dosHeader->e_lfanew
);
PIMAGE_DATA_DIRECTORY relocDir = &ntHeaders->OptionalHeader
.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (relocDir->VirtualAddress == 0) {
printf("[-] 没有重定位表,无法重定位\n");
return FALSE;
}
PIMAGE_BASE_RELOCATION relocBlock = (PIMAGE_BASE_RELOCATION)(
imageBase + relocDir->VirtualAddress
);
int totalFixed = 0;
while (relocBlock->VirtualAddress != 0) {
DWORD numEntries = (relocBlock->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
WORD* entries = (WORD*)(relocBlock + 1);
for (DWORD i = 0; i < numEntries; i++) {
WORD type = entries[i] >> 12;
WORD offset = entries[i] & 0x0FFF;
LPBYTE targetAddr = imageBase + relocBlock->VirtualAddress + offset;
switch (type) {
case IMAGE_REL_BASED_ABSOLUTE:
// 无操作
break;
case IMAGE_REL_BASED_HIGHLOW: {
// 32位修复
DWORD* ptr = (DWORD*)targetAddr;
*ptr += (DWORD)delta;
totalFixed++;
break;
}
case IMAGE_REL_BASED_DIR64: {
// 64位修复
ULONGLONG* ptr = (ULONGLONG*)targetAddr;
*ptr += delta;
totalFixed++;
break;
}
case IMAGE_REL_BASED_HIGH: {
WORD* ptr = (WORD*)targetAddr;
*ptr += HIWORD(delta);
totalFixed++;
break;
}
case IMAGE_REL_BASED_LOW: {
WORD* ptr = (WORD*)targetAddr;
*ptr += LOWORD(delta);
totalFixed++;
break;
}
default:
printf("[!] 未知重定位类型: %d\n", type);
break;
}
}
relocBlock = (PIMAGE_BASE_RELOCATION)(
(PBYTE)relocBlock + relocBlock->SizeOfBlock
);
}
printf("[+] 重定位完成,修复了 %d 处\n", totalFixed);
printf(" Delta: 0x%llX\n", delta);
return TRUE;
}
5.3、手动PE加载器中的重定位
// 简单PE加载器(包含重定位)
typedef BOOL (WINAPI* DllMain_t)(HINSTANCE, DWORD, LPVOID);
LPVOID ManualMapDll(LPBYTE dllData) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)dllData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
dllData + dosHeader->e_lfanew
);
BOOL is64 = (ntHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC);
DWORD imageSize;
ULONGLONG preferredBase;
if (is64) {
imageSize = ((PIMAGE_OPTIONAL_HEADER64)&ntHeaders->OptionalHeader)->SizeOfImage;
preferredBase = ((PIMAGE_OPTIONAL_HEADER64)&ntHeaders->OptionalHeader)->ImageBase;
} else {
imageSize = ntHeaders->OptionalHeader.SizeOfImage;
preferredBase = ntHeaders->OptionalHeader.ImageBase;
}
// 尝试在首选地址分配内存
LPBYTE imageBase = (LPBYTE)VirtualAlloc(
(LPVOID)preferredBase,
imageSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
// 如果失败,在任意地址分配
if (!imageBase) {
imageBase = (LPBYTE)VirtualAlloc(
NULL,
imageSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
}
if (!imageBase) {
printf("[-] 无法分配内存\n");
return NULL;
}
printf("[+] 映像分配地址: 0x%p (首选: 0x%llX)\n",
imageBase, preferredBase);
// 复制PE头
memcpy(imageBase, dllData, ntHeaders->OptionalHeader.SizeOfHeaders);
// 复制区段
PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION(ntHeaders);
for (WORD i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) {
if (sections[i].SizeOfRawData > 0) {
memcpy(
imageBase + sections[i].VirtualAddress,
dllData + sections[i].PointerToRawData,
sections[i].SizeOfRawData
);
}
}
// 执行重定位
if ((ULONGLONG)imageBase != preferredBase) {
printf("[*] 需要重定位...\n");
if (!ApplyRelocations(imageBase, (ULONGLONG)imageBase, preferredBase)) {
VirtualFree(imageBase, 0, MEM_RELEASE);
return NULL;
}
}
// 处理导入表(略)
// ...
// 调用DllMain
DWORD entryPoint = ntHeaders->OptionalHeader.AddressOfEntryPoint;
if (entryPoint) {
DllMain_t DllMain = (DllMain_t)(imageBase + entryPoint);
DllMain((HINSTANCE)imageBase, DLL_PROCESS_ATTACH, NULL);
}
return imageBase;
}
5.4、重定位在Shellcode中的应用
// 位置无关代码(PIC)技术
// 通过获取当前EIP/RIP来计算相对地址
// x86 获取EIP
__declspec(naked) DWORD GetEIP32() {
__asm {
call next
next:
pop eax
ret
}
}
// x64 获取RIP(使用LEA)
ULONGLONG GetRIP64() {
ULONGLONG rip;
// lea rax, [rip]
// 在实际shellcode中使用内联汇编
return rip;
}
// 运行时重定位shellcode
void RelocateShellcode(LPBYTE shellcode, DWORD size,
ULONGLONG oldBase, ULONGLONG newBase) {
LONGLONG delta = newBase - oldBase;
// 扫描shellcode中的绝对地址并修复
// 这需要了解shellcode的结构
// 示例:修复内嵌的字符串指针
for (DWORD i = 0; i < size - sizeof(ULONGLONG); i++) {
ULONGLONG* ptr = (ULONGLONG*)(shellcode + i);
// 检查是否看起来像旧基址范围内的地址
if (*ptr >= oldBase && *ptr < oldBase + size) {
*ptr += delta;
}
}
}
6、课后作业
-
编写重定位表解析器
- 显示所有重定位块
- 显示每个重定位项详情
- 统计各类型数量
-
实现PE手动加载器
- 加载PE到内存
- 执行重定位
- 解析导入表
- 调用入口点
-
分析重定位与安全的关系
- 研究ASLR实现原理
- 分析无重定位表的PE
- 了解重定位表滥用技术