PE32格式学习之手动创建一个简单的Windows程序
字数 3480 2025-08-05 11:39:43
PE32格式学习之手动创建一个简单的Windows程序
一、项目目标
手动创建一个简单的PE.exe程序,功能是弹出一个对话框。
二、基础知识
1. 进程虚拟地址空间
Windows运行程序时会创建一个"进程"容器,程序被加载到"虚拟内存地址空间"而非直接物理内存。32位进程有4GB虚拟内存空间。
2. 内存页和物理内存映射
- 虚拟内存和物理内存被"裁减"成4KB大小的内存片
- 只有需要使用的虚拟内存片才会映射到物理内存片
- 系统DLL的物理内存片可映射到所有进程的虚拟内存空间
3. PE32可执行文件基本结构
典型exe程序包含:
- 文件头:PE32文件头是多个结构的组合
- .code节:包含用户代码编译后的二进制指令流
- .data节:包含预初始化数据
- .idata节:包含导入表
4. 从磁盘File到内存Image
- 程序加载时有"拉伸"过程
- 磁盘对齐:512字节(传统磁盘扇区大小)
- 内存对齐:4096字节(内存页大小)
- FOA(File Offset Address):字段在文件中相对文件起始位置的偏移量
- RVA(Relative Virtual Address):字段在内存Image中相对Image起始位置的偏移量
三、创建PE32可执行文件框架
创建2048字节(512*4)的全0二进制文件,保存为PE.exe,分为:
- 文件头(512字节)
- .code节(512字节)
- .data节(512字节)
- .idata节(512字节)
四、编辑PE.exe文件头
1. DOS Header
| FOA | RVA | Size | Field | Value | Description |
|---|---|---|---|---|---|
| 0 | 0 | 2 | e_magic | 0x5A4D | DOS头标志 'MZ' |
| 0x3C | 0x3C | 4 | e_lfanew | 0x80 | 指向PE Header头部的偏移量 |
2. PE File Header
| FOA | RVA | Size | Field | Value | Description |
|---|---|---|---|---|---|
| 0x80 | 0x80 | 4 | PE Signature | 0x4550 | PE头标志 'PE\x00\x00' |
| 0x84 | 0x84 | 2 | Machine | 0x014C | Intel 386或兼容处理器 |
| 0x86 | 0x86 | 2 | NumberOfSections | 3 | 3个节:.code、.data、.idata |
| 0x94 | 0x94 | 2 | SizeOfOptionalHeader | 0xE0 | PE32的OptionalHeader大小 |
| 0x96 | 0x96 | 2 | Characteristics | 0x0102 | 32位可执行文件 |
3. PE Optional Header
| FOA | RVA | Size | Field | Value | Description |
|---|---|---|---|---|---|
| 0x98 | 0x98 | 2 | Magic | 0x010B | PE32文件标识 |
| 0x9C | 0x9C | 4 | SizeOfCode | 0x1000 | .code节内存大小 |
| 0xA8 | 0xA8 | 4 | AddressOfEntryPoint | 0x1000 | 程序入口点RVA |
| 0xAC | 0xAC | 4 | BaseOfCode | 0x1000 | .code节起始RVA |
| 0xB0 | 0xB0 | 4 | BaseOfData | 0x2000 | .data节起始RVA |
| 0xB4 | 0xB4 | 4 | ImageBase | 0x400000 | 加载起始位置 |
| 0xB8 | 0xB8 | 4 | SectionAlignment | 0x1000 | 内存对齐边界 |
| 0xBC | 0xBC | 4 | FileAlignment | 0x200 | 文件对齐边界 |
| 0xD0 | 0xD0 | 4 | SizeOfImage | 0x4000 | 内存Image大小 |
| 0xDC | 0xDC | 2 | Subsystem | 2 | Windows图形界面子系统 |
| 0x100 | 0x100 | 4 | Import Table RVA | 0x3000 | 导入表结构RVA |
| 0x104 | 0x104 | 4 | Import Table Size | 20 | 导入表结构大小 |
4. Section Table (Section Headers)
| FOA | RVA | Size | Field | Value | Description |
|---|---|---|---|---|---|
| 0x178 | 0x178 | 8 | Name | ".code" | 节名称 |
| 0x180 | 0x180 | 4 | VirtualSize | 0x1000 | 内存大小 |
| 0x184 | 0x184 | 4 | VirtualAddress | 0x1000 | 内存起始RVA |
| 0x188 | 0x188 | 4 | SizeOfRawData | 0x200 | 文件大小 |
| 0x18C | 0x18C | 4 | PointerToRawData | 0x200 | 文件起始FOA |
| 0x19C | 0x19C | 4 | Characteristics | 0x60000020 | 可执行代码节 |
五、编辑.idata节
1. 导入表结构
导入表包含三个关键数据结构:
- Directory Entry (Import Descriptor) - 描述需要导入的DLL
- Import Lookup Entry - 描述DLL中需要导入的函数
- Hint/Name Entry - 包含函数名信息
2. 手动构造导入表
| FOA | RVA | Size | Field | Value | Description |
|---|---|---|---|---|---|
| 0x600 | 0x3000 | 4 | Import Lookup Table RVA | 0x3028 | 导入查询表位置 |
| 0x60C | 0x300C | 4 | DLL Name RVA | 0x3030 | DLL名字符串位置 |
| 0x610 | 0x3010 | 4 | Import Address Table RVA | 0x3028 | 导入地址表位置 |
| 0x628 | 0x3028 | 4 | Import Lookup Table | 0x303B | 指向函数名Entry |
| 0x630 | 0x3030 | 11 | DLL Name | "user32.dll\0" | DLL名称 |
| 0x63D | 0x303D | 12 | Function Name | "MessageBoxA\0" | 函数名称 |
六、编辑.data节
| FOA | RVA | Value | Description |
|---|---|---|---|
| 0x400 | 0x2000 | "Hello, snake!\0" | 对话框标题 |
| 0x40E | 0x200E | "This is an example..." | 消息内容 |
七、编辑.code节
MessageBoxA函数调用汇编代码:
push 0x40
push 0x402000
push 0x40200E
push 0
call DWORD ptr ds:0x403028
ret
对应二进制字节流:
{ 0x6A, 0x40, 0x68, 0x00, 0x20, 0x40, 0x00, 0x68, 0x0E, 0x20, 0x40, 0x00, 0x6A, 0x00, 0xFF, 0x15, 0x28, 0x30, 0x40, 0x00, 0xC3 }
写入.code节起始位置0x200处。
八、验证
完成编辑后执行PE.exe,应能弹出指定对话框。