RawrXD PE 编写器 - 完整实现
概述
这是一个完整的 PE32+ 编写器和机器码发射器,使用纯 x64 MASM 汇编实现,零依赖,不使用 C 运行时库。它从零生成可运行的 Windows 可执行文件。
架构
PE 编写器采用后端设计器方法,提供简洁的 API 用于:
- 创建 PE 可执行上下文
- 添加导入,包含正确的 IAT/INT 结构
- 发射机器码,带节管理
- 写入完整的 PE 文件,包含所有必需的头部
核心组件
1. 定义的结构
- IMAGE_DOS_HEADER:标准 DOS 头部,带 e_lfanew 指针
- IMAGE_NT_HEADERS64:包含文件头部和可选头部的 NT 头部
- IMAGE_SECTION_HEADER:.text、.rdata、.idata 节的节头部
- IMAGE_IMPORT_DESCRIPTOR:导入表描述符
- PE_CONTEXT:用于构建 PE 文件的内部上下文结构
2. 关键函数
PEWriter_CreateExecutable
- 输入:RCX = 映像基址(0 = 默认),RDX = 入口点 RVA
- 输出:RAX = PE 上下文句柄(0 = 失败)
- 用途:分配并初始化完整的 PE 上下文结构
实现细节:
- 为 DOS 头部、NT 头部、节头部分配内存
- 使用正确的签名和 e_lfanew 初始化 DOS 头部
- 设置 NT 头部,包含 AMD64 机器类型和 PE32+ 幻数
- 配置可选头部,包含映像基址、节对齐/文件对齐
- 分配代码缓冲区和导入表
- 设置默认虚拟地址和文件偏移
PEWriter_AddImport
- 输入:RCX = PE 上下文,RDX = DLL 名称,R8 = 函数名称
- 输出:RAX = 1 成功,0 失败
- 用途:构建导入表,包含正确的 IAT/INT 结构
实现细节:
- 管理多个 DLL 的导入描述符
- 创建导入查找表(INT)条目
- 设置导入地址表(IAT)条目
- 处理按名称导入的结构
- 跟踪导入计数并验证限制
PEWriter_AddCode
- 输入:RCX = PE 上下文,RDX = 代码缓冲区,R8 = 代码大小
- 输出:RAX = 代码的 RVA(0 = 失败)
- 用途:处理机器码发射,带正确的节管理
实现细节:
- 将机器码复制到内部缓冲区
- 验证代码大小是否超过最大限制
- 计算并返回代码的 RVA
- 更新内部代码大小跟踪
- 管理 .text 节内容
PEWriter_WriteFile
- 输入:RCX = PE 上下文,RDX = 文件名
- 输出:RAX = 1 成功,0 失败
- 用途:完整的文件写入,包含所有头部、节和导入表
实现细节:
- 使用正确的 Win32 API 调用创建输出文件
- 写入 DOS 头部和 DOS 存根
- 计算并更新最终的 NT 头部值
- 写入 .text、.rdata、.idata 的节头部
- 实现正确的文件填充和对齐
- 写入包含导入表的节数据
- 处理所有 RVA 计算和文件偏移
内存管理
实现使用 Windows 堆 API:
- GetProcessHeap():获取当前进程堆
- HeapAlloc():分配零初始化内存
- HeapFree():释放已分配的内存用于清理
所有内存分配都包含正确的错误处理和清理。
文件结构布局
DOS 头部 (64 字节)
DOS 存根(可变大小,填充至 0x80)
NT 头部 (PE32+ 为 248 字节)
节头部 (40 字节 × 3 个节)
填充至文件对齐 (0x400)
.text 节 (代码)
- 机器码
- 填充至文件对齐
.rdata 节 (只读数据)
- 字符串常量、资源
- 填充至文件对齐
.idata 节 (导入数据)
- 导入描述符
- 导入查找表
- 导入地址表
- 按名称导入的结构
- 填充至文件对齐
虚拟地址布局
0x1000: .text 节 (SECTION_ALIGNMENT)
0x2000: .rdata 节
0x3000: .idata 节
0x4000: 下一个可用虚拟地址
常量和默认值
- 映像基址:0x140000000(x64 的默认值)
- 节对齐:0x1000(4KB)
- 文件对齐:0x200(512 字节)
- 入口点:可配置的 RVA
- 子系统:控制台应用程序 (IMAGE_SUBSYSTEM_WINDOWS_CUI)
使用示例
; 创建 PE 上下文
mov rcx, 0 ; 默认映像基址
mov rdx, 1000h ; 入口点 RVA
call PEWriter_CreateExecutable
mov rbx, rax ; 保存上下文
; 添加 kernel32.dll 导入
mov rcx, rbx
mov rdx, offset dll_kernel32
mov r8, offset func_GetStdHandle
call PEWriter_AddImport
; 添加机器码
mov rcx, rbx
mov rdx, offset code_buffer
mov r8, code_size
call PEWriter_AddCode
; 写入可执行文件
mov rcx, rbx
mov rdx, offset filename
call PEWriter_WriteFile
构建说明
- 确保已安装 MASM64 和 Windows SDK
- 运行
build.bat 编译 PE 编写器和示例
- 执行
PE_Writer_Example.exe 生成 hello.exe
- 测试生成的可执行文件
错误处理
所有函数在失败时返回 0,成功时返回非零值。实现包括:
- 内存分配失败检查
- 文件 I/O 错误处理
- 输入验证
- 适当的资源清理
- 缓冲区的边界检查
限制
- 最多 99 个导入 (MAX_IMPORTS - 1)
- 最大 64KB 代码大小 (MAX_CODE_SIZE)
- 三个固定节 (.text、.rdata、.idata)
- 仅控制台子系统
- 不支持重定位
- 无数字签名
高级特性
该实现为以下功能提供了坚实基础:
- 自定义节创建
- 复杂导入绑定
- 资源嵌入
- 数字签名
- 重定位支持
- 异常处理表
技术说明
- 纯 x64 MASM 汇编 - 无 CRT 依赖
- 使用 Windows 堆进行内存管理
- 生成 x64 的 PE32+ 格式
- 兼容 Windows Vista 及更高版本
- 遵循 Microsoft PE 规范
- 除了 kernel32.dll 外,无外部库依赖
该实现提供了一个完整的、可用于生产的 PE32+ 编写器,适用于代码生成后端、自定义编译器和可执行文件打包器。