手写 PE 文件
此内容是逆向工程实验内容,对应完整 PE 文件结构更加复杂
此处是借助 C 语言完成的,因为真正手写没有意义
- 真正手写查错困难
- 核心目的是了解 PE 文件的大概结构
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <winnt.h>
/**
* @brief 生成一个简单 32 位 Windows 的 PE 文件 ||
* 功能: 创建一个 pe.exe 文件,其功能是弹出提示对话框 ||
* 主要使用 user32.dll 的 MessageBoxA 函数和 kernel32.dll 的 ExitProcess 函数 ||
*
* @return int
*/
int main(void)
{
/* 1. 生成 DOS 头 */
IMAGE_DOS_HEADER dosHeader = {
.e_magic = IMAGE_DOS_SIGNATURE,
.e_cblp = 0,
.e_cp = 0,
.e_crlc = 0,
.e_cparhdr = 0,
.e_minalloc = 0,
.e_maxalloc = 0,
.e_ss = 0,
.e_sp = 0,
.e_csum = 0,
.e_ip = 0,
.e_cs = 0,
.e_lfarlc = 0,
.e_ovno = 0,
.e_res = {0, 0, 0, 0},
.e_oemid = 0,
.e_oeminfo = 0,
.e_res2 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
.e_lfanew = 0x00000040, // PE 头的偏移
};
/* 2. 生成 PE 头 */
IMAGE_NT_HEADERS32 peHeader = {
// PE 标识符 0x00004550
.Signature = IMAGE_NT_SIGNATURE,
// 生成文件头 IMAGE_FILE_HEADER
.FileHeader = {
.Machine = IMAGE_FILE_MACHINE_I386, // 可执行文件的目标 CPU 类型,此处为 32 位
.NumberOfSections = 0x0003, // 包含的节表的数量,此处为 3 个
.TimeDateStamp = 0x00000000, // 文件创建时间【值以格林威治时间(GMT)1970 年 1 月 1 日午夜开始所经过的秒数表示】
.PointerToSymbolTable = 0x00000000, // 符号表的偏移,此处为 0
.NumberOfSymbols = 0x00000000, // 符号表的数量,此处为 0
.SizeOfOptionalHeader = 0x00E0, // 可选头的大小,此处为 0xE0
.Characteristics = 0x0103, // 文件类型,此处为 可执行文件且不存在重定向信息
},
// 生成可选头 IMAGE_OPTIONAL_HEADER
.OptionalHeader = {
.Magic = IMAGE_NT_OPTIONAL_HDR32_MAGIC, // 可选头的标识符,此处为 32 位
.MajorLinkerVersion = 0x00, // 链接器的主版本号
.MinorLinkerVersion = 0x00, // 链接器的次版本号
.SizeOfCode = 0x00001000, // 代码区的大小,此处为 0x1000
.SizeOfInitializedData = 0x00000000, // 初始化数据区的大小
.SizeOfUninitializedData = 0x00000000, // 未初始化数据区的大小
.AddressOfEntryPoint = 0x00001000, // 入口点的地址,此处为 0x1000
.BaseOfCode = 0x00001000, // 代码区的基地址,此处为 0x1000
.BaseOfData = 0x00002000, // 数据区的基地址,此处为 0x2000
.ImageBase = 0x00400000, // PE 文件的基地址
.SectionAlignment = 0x00001000, // 节表对齐【节表的大小必须是此值的整数倍,Win32 通过为 0x1000 表示 4KB】,此处为 0x1000
.FileAlignment = 0x00001000, // 文件对齐【文件的大小必须是此值的整数倍,Win32 通过为 0x1000 或 0x200 表示 4KB 或 512B】,此处为 0x200
.MajorOperatingSystemVersion = 0x0004, // 操作系统的主版本号
.MinorOperatingSystemVersion = 0x0000, // 操作系统的次版本号
.MajorImageVersion = 0x0000, // PE 文件的主版本号
.MinorImageVersion = 0x0000, // PE 文件的次版本号
.MajorSubsystemVersion = 0x0004, // 子系统的主版本号
.MinorSubsystemVersion = 0x0000, // 子系统的次版本号
.Win32VersionValue = 0x00000000, // 保留
.SizeOfImage = 0x00004000, // PE 文件的大小,此处为 0x4000
.SizeOfHeaders = 0x00001000, // 整个 PE 文件头的大小【包括 DOS 头、PE 头、节表】,此处为 0x1000
.CheckSum = 0x00000000, // 校验和【通常为 0】
.Subsystem = 0x0002, // 子系统类型,此处为 Windows 图形界面子系统
.DllCharacteristics = 0x0000, // 指定 DLL 的特性
.SizeOfStackReserve = 0x00100000, // 保留的栈空间大小
.SizeOfStackCommit = 0x00001000, // 提交的栈空间大小
.SizeOfHeapReserve = 0x00100000, // 保留的堆空间大小
.SizeOfHeapCommit = 0x00001000, // 提交的堆空间大小
.LoaderFlags = 0x00000000, // 保留
.NumberOfRvaAndSizes = 0x00000010, // 数据目录的数量,此处为 16 个
.DataDirectory = {
{0x00000000, 0x00000000}, // 0. 导出表
{0x00003010, 0x00000028}, // 1. 导入表,此处为 0x3010,大小为 0x28
{0x00000000, 0x00000000}, // 2. 资源表
{0x00000000, 0x00000000}, // 3. 异常表
{0x00000000, 0x00000000}, // 4. 安全表
{0x00000000, 0x00000000}, // 5. 重定位表
{0x00000000, 0x00000000}, // 6. 调试表
{0x00000000, 0x00000000}, // 7. 特征表
{0x00000000, 0x00000000}, // 8. 全局指针
{0x00000000, 0x00000000}, // 9. TLS 表
{0x00000000, 0x00000000}, // 10. 加载配置表
{0x00000000, 0x00000000}, // 11. 绑定表
{0x00003000, 0x00000010}, // 12. 导入地址表,此处为 0x3000,大小为 0x10
{0x00000000, 0x00000000}, // 13. 延迟导入表
{0x00000000, 0x00000000}, // 14. COM+ 运行时头
{0x00000000, 0x00000000}, // 15. 保留
},
},
};
/* 3. 节表 */
// 生成节表 IMAGE_SECTION_HEADER,此处为 3 个节表:.text, .data, .idata
IMAGE_SECTION_HEADER sectionHeader[3] = {
{
.Name = ".text", // 节表的名称
.Misc = {0x00001000}, // 保留
.VirtualAddress = 0x00001000, // 节表的虚拟地址,此处为 0x1000
.SizeOfRawData = 0x00001000, // 节表的大小,此处为 0x1000
.PointerToRawData = 0x00001000, // 节表在文件中的偏移,此处为 0x1000
.PointerToRelocations = 0x00000000, // 重定位表的偏移,此处为 0
.PointerToLinenumbers = 0x00000000, // 行号表的偏移,此处为 0
.NumberOfRelocations = 0x0000, // 重定位表的数量,此处为 0
.NumberOfLinenumbers = 0x0000, // 行号表的数量,此处为 0
.Characteristics = 0x60000020, // 节表的特性,此处为 可执行代码且可读
},
{
.Name = ".data", // 节表的名称
.Misc = {0x00001000}, // 保留
.VirtualAddress = 0x00002000, // 节表的虚拟地址,此处为 0x2000
.SizeOfRawData = 0x00001000, // 节表的大小,此处为 0x1000
.PointerToRawData = 0x00002000, // 节表在文件中的偏移,此处为 0x2000
.PointerToRelocations = 0x00000000, // 重定位表的偏移,此处为 0
.PointerToLinenumbers = 0x00000000, // 行号表的偏移,此处为 0
.NumberOfRelocations = 0x0000, // 重定位表的数量,此处为 0
.NumberOfLinenumbers = 0x0000, // 行号表的数量,此处为 0
.Characteristics = 0xC0000040, // 节表的特性,此处为 可读写数据且可读
},
{
.Name = ".idata", // 节表的名称
.Misc = {0x00001000}, // 保留
.VirtualAddress = 0x00003000, // 节表的虚拟地址,此处为 0x3000
.SizeOfRawData = 0x00001000, // 节表的大小,此处为 0x1000
.PointerToRawData = 0x00003000, // 节表在文件中的偏移,此处为 0x3000
.PointerToRelocations = 0x00000000, // 重定位表的偏移,此处为 0
.PointerToLinenumbers = 0x00000000, // 行号表的偏移,此处为 0
.NumberOfRelocations = 0x0000, // 重定位表的数量,此处为 0
.NumberOfLinenumbers = 0x0000, // 行号表的数量,此处为 0
.Characteristics = 0xC0000040, // 节表的特性,此处为 可读写数据且可读
}
};
/* 4. 填充整个 PE 文件头【DOS 头 + NT 头 + 节表】的大小为 0x1000 剩余的空间 */
char fillPeHeader[0x1000 - sizeof(IMAGE_DOS_HEADER) - sizeof(IMAGE_NT_HEADERS32) - sizeof(IMAGE_SECTION_HEADER) * 3] = {0};
/* 节表信息 */
// .text 节表区域
char TextSection[0x1000] = {0};
// .text 汇编代码
char textCode[] = {
0x6A, 0x00, // PUSH 0x00000000
0x68, 0x20, 0x20, 0x40, 0x00, // PUSH 0x00402020 ; "Binary Diy"
0x68, 0x00, 0x20, 0x40, 0x00, // PUSH 0x00402000 ; "Hello, Pe Binary Diy!!"
0x6A, 0x00, // PUSH 0x00000000
0xE8, 0x07, 0x00, 0x00, 0x00, // CALL 0x0040101A ; JMP. &user32.MessageBoxA
0x6A, 0x00, // PUSH 0x00000000
0xE8, 0x06, 0x00, 0x00, 0x00, // CALL 0x00401020 ; JMP. &kernel32.ExitProcess
0xFF, 0x25, 0x00, 0x30, 0x40, 0x00, // JMP 0x00403000 ; user32.MessageBoxA
0xFF, 0x25, 0x08, 0x30, 0x40, 0x00 // JMP 0x00403008 ; kernel32.ExitProcess
};
// 写入 .text 汇编代码
memcpy(TextSection, textCode, sizeof(textCode));
// .data 节表区域
char DataSection[0x1000] = {0};
// .data 数据
char data1[] = "Hello, Pe Binary Diy!!";
char data2[] = "Binary Diy";
// 写入 .data 数据
memcpy(DataSection, data1, sizeof(data1));
memcpy(DataSection + 0x20, data2, sizeof(data2));
// .idata 节表区域
char RDataSection[0x1000] = {0};
// .idata 数据
IMAGE_THUNK_DATA32 importAddressTable[4] = {
{
.u1.AddressOfData = 0x00003070, // 导入函数名称表
},
{
.u1.AddressOfData = 0x00000000, // 导入函数名称表
},
{
.u1.AddressOfData = 0x000030A0, // 导入函数名称表
},
{
.u1.AddressOfData = 0x00000000, // 导入函数名称表
}
};
IMAGE_IMPORT_DESCRIPTOR importDescriptor[3] = {
{
.OriginalFirstThunk = 0x00003060, // 原始导入地址表
.TimeDateStamp = 0x00000000, // 时间戳
.ForwarderChain = 0x00000000, // 转发链
.Name = 0x00003050, // DLL 名称
.FirstThunk = 0x00003000, // 导入地址表
},
{
.OriginalFirstThunk = 0x00003090, // 原始导入地址表
.TimeDateStamp = 0x00000000, // 时间戳
.ForwarderChain = 0x00000000, // 转发链
.Name = 0x00003080, // DLL 名称
.FirstThunk = 0x00003008, // 导入地址表
},
{
.OriginalFirstThunk = 0x00000000, // 原始导入地址表
.TimeDateStamp = 0x00000000, // 时间戳
.ForwarderChain = 0x00000000, // 转发链
.Name = 0x00000000, // DLL 名称
.FirstThunk = 0x00000000 // 导入地址表
}
};
char user32[] = "user32.dll";
char messageboxa[] = "MessageBoxA";
char kernel32[] = "kernel32.dll";
char exitprocess[] = "ExitProcess";
// 写入 .idata 数据
memcpy(RDataSection, importAddressTable, sizeof(importAddressTable));
memcpy(RDataSection + 0x10, importDescriptor, sizeof(importDescriptor));
memcpy(RDataSection + importDescriptor[0].Name - 0x00003000, user32, sizeof(user32));
memcpy(
RDataSection + importDescriptor[0].OriginalFirstThunk - 0x00003000,
RDataSection + importDescriptor[0].FirstThunk - 0x00003000,
sizeof(importAddressTable[0].u1.AddressOfData)
);
memcpy(
RDataSection + importAddressTable[0].u1.AddressOfData - 0x00003000 + 0x2,
messageboxa,
sizeof(messageboxa)
);
memcpy(RDataSection + importDescriptor[1].Name - 0x00003000, kernel32, sizeof(kernel32));
memcpy(
RDataSection + importDescriptor[1].OriginalFirstThunk - 0x00003000,
RDataSection + importDescriptor[1].FirstThunk - 0x00003000,
sizeof(importAddressTable[2].u1.AddressOfData)
);
memcpy(
RDataSection + importAddressTable[2].u1.AddressOfData - 0x00003000 + 0x2,
exitprocess,
sizeof(exitprocess)
);
/* 上面 PE 数据写入文件 pe.exe,注意文件操纵时的异常处理 */
FILE *fp = fopen("pe.exe", "wb");
if (fp == NULL) {
printf("open file error!\n");
return -1;
}
// 写入 DOS 头、PE 头、节表、PE 头填充数据
fwrite(&dosHeader, sizeof(dosHeader), 1, fp);
fwrite(&peHeader, sizeof(peHeader), 1, fp);
fwrite(§ionHeader, sizeof(sectionHeader), 1, fp);
fwrite(fillPeHeader, sizeof(fillPeHeader), 1, fp);
// 写入 .text .data .idata 节表区域
fwrite(TextSection, sizeof(TextSection), 1, fp);
fwrite(DataSection, sizeof(DataSection), 1, fp);
fwrite(RDataSection, sizeof(RDataSection), 1, fp);
/* 输出上述结构体的大小 */
printf("SIZE: %X, TO: %X\n", sizeof(IMAGE_DOS_HEADER), sizeof(IMAGE_DOS_HEADER));
printf("SIZE: %X, TO: %X\n", sizeof(IMAGE_NT_HEADERS32), sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS32));
printf(
"SIZE: %X, TO: %X\n", sizeof(IMAGE_SECTION_HEADER) * 3,
sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS32) + sizeof(IMAGE_SECTION_HEADER) * 3
);
printf(
"SIZE: %X, TO: %X\n", sizeof(fillPeHeader),
sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS32) + sizeof(IMAGE_SECTION_HEADER) * 3 + sizeof(fillPeHeader)
);
return 0;
}
标签:文件,0x00000000,此处,节表,PE,手写,sizeof,IMAGE
From: https://www.cnblogs.com/shadow-/p/17342945.html