我正在尝试为 win10 64 位编写一个自定义 PE 文件导出器,在添加对齐后我遇到了一些麻烦。我创建了一个基本测试用例:一个包含“ret”指令的 .exe 文件:
std::vector<unsigned char> text_bytecode = {
0xC3 // 'ret'
};
pe_builder builder;
builder.add_section(
".text",
text_bytecode,
windows::section_header_characteristics::mem_execute |
windows::section_header_characteristics::mem_read |
windows::section_header_characteristics::cnt_code |
windows::section_header_characteristics::align_16_bytes
);
builder.emit_to_file(path);
这是PE构建器的后端:
u64 align(u64 value, u64 alignment) {
return (value + alignment - 1) & ~(alignment - 1);
}
pe_builder::pe_builder() {
// initialize the dos header
m_dos_header.magic = 0x5A4D;
m_dos_header.cparhdr = 4;
m_dos_header.lfanew = sizeof(windows::dos_header); // place the NT headers directly behind the DOS header
// NT headers
// technically, the PE signature goes here
// initialize the file header
m_file_header.machine = windows::get_machine_type();
m_file_header.section_count = 0;
m_file_header.optional_header_size = sizeof(windows::optional_header_64_bit);
m_file_header.characteristics = windows::file_header_characteristics::executable;
// initialize the optional header
m_optional_header.magic = 0x020B; // PE32+
m_optional_header.section_alignment = 4096;
m_optional_header.file_alignment = 512;
m_optional_header.major_linker_version = 14;
m_optional_header.minor_linker_version = 0;
m_optional_header.major_os_version = 6; // win10 compatibility
m_optional_header.minor_os_version = 0;
m_optional_header.major_image_version = 1;
m_optional_header.major_subsystem_version = 6; // win10 compatibility
m_optional_header.image_base = 0x140000000;
m_optional_header.win32_version_value = 0;
m_optional_header.subsystem = 3; // windows console subsystem
m_optional_header.DLL_characteristics = 0x8160; // NX-Compatible and Terminal Server Aware
m_optional_header.stack_reserve_size = 0x100000;
m_optional_header.stack_commit_size = 0x1000;
m_optional_header.heap_reserve_size = 0x100000;
m_optional_header.heap_commit_size = 0x1000;
m_optional_header.loader_flags = 0;
m_optional_header.RVA_and_size_count = 0;
u64 header_size =
sizeof(windows::dos_header) +
sizeof(g_pe_signature) +
sizeof(windows::file_header) +
sizeof(windows::optional_header_64_bit);
m_optional_header.entry_point_address = align(header_size, m_optional_header.section_alignment);
m_optional_header.code_base = m_optional_header.entry_point_address;
}
void pe_builder::add_section(
const std::string& name,
std::vector<unsigned char> data,
windows::section_header_characteristics characteristics
) {
m_file_header.section_count++;
// If it's a code section, update the SizeOfCode in the optional header
if ((characteristics & windows::section_header_characteristics::cnt_code) != 0) {
m_optional_header.code_size += data.size();
}
windows::section_header new_section = {};
memcpy(new_section.name, name.c_str(), std::min<size_t>(name.size(), 8));
new_section.virtual_size = data.size();
new_section.raw_data_size = align(data.size(), m_optional_header.file_alignment);
if (m_section_data.empty()) {
new_section.raw_data_pointer = align(
sizeof(windows::dos_header) +
sizeof(g_pe_signature) +
sizeof(windows::file_header) +
sizeof(windows::optional_header_64_bit) +
sizeof(windows::section_header) * m_file_header.section_count,
m_optional_header.file_alignment
);
new_section.virtual_address = m_optional_header.entry_point_address;
}
else {
const auto& last_section_header = m_sections.back();
new_section.raw_data_pointer = last_section_header.raw_data_pointer +
align(m_section_data.back().size(), m_optional_header.file_alignment);
new_section.virtual_address = last_section_header.virtual_address +
align(m_section_data.back().size(), m_optional_header.section_alignment);
}
new_section.characteristics = characteristics;
m_sections.push_back(new_section);
m_section_data.push_back(data);
}
void pe_builder::emit_to_file(
const filepath& path
) {
// Open the output file in binary mode
std::ofstream file(path, std::ios::binary);
if (!file.is_open()) {
// return false; // Failed to open the file
}
file.write(reinterpret_cast<char*>(&m_dos_header), sizeof(m_dos_header));
file.write(reinterpret_cast<const char*>(&g_pe_signature), sizeof(g_pe_signature));
file.write(reinterpret_cast<char*>(&m_file_header), sizeof(m_file_header));
file.write(reinterpret_cast<char*>(&m_optional_header), sizeof(m_optional_header));
for (size_t i = 0; i < m_sections.size(); ++i) {
// Write the section header
file.write(reinterpret_cast<const char*>(&m_sections[i]), sizeof(m_sections[i]));
// Assuming m_section_data is a vector of vectors (or similar container) storing data for each section
const auto& section_data = m_section_data[i];
// Write the section data
file.write(reinterpret_cast<const char*>(section_data.data()), section_data.size());
}
// Close the file
file.close();
m_dos_header.print();
m_file_header.print();
m_optional_header.print();
for(const auto& section : m_sections) {
section.print();
}
}
对于任何想知道我已经打印了最终值的人(请注意,它们被打印为各自的 int 对应项,因此缺少十六进制格式,尽管我还应该注意,十六进制值和标志应该都是正确的,因为我创建了一个与他们一起工作的 MVP)。
dos header:
magic: 23117
cblp: 0
cp: 0
crlc: 0
cparhdr: 4
minalloc: 0
maxalloc: 0
ss: 0
sp: 0
csum: 0
ip: 0
cs: 0
lfarlc: 0
ovno: 0
res: 0 0 0 0
oemid: 0
oeminfo: 0
res2: 0 0 0 0 0 0 0 0 0 0
lfanew: 64
file header:
machine: 34404
section_count: 1
time_date_stamp: 0
pointer_to_symbol_table: 0
symbol_count: 0
optional_header_size: 240
characteristics: 2
optional header (64 bit):
magic: 523
major_linker_version: 14
minor_linker_version: 0
code_size: 1
initialized_data_size: 0
uninitialized_data_size: 0
entry_point_address: 4096
code_base: 4096
image_base: 5368709120
section_alignment: 4096
file_alignment: 512
major_os_version: 6
minor_os_version: 0
major_image_version: 1
minor_image_version: 0
major_subsystem_version: 6
minor_subsystem_version: 0
win32_version_value: 0
image_size: 0
header_size: 0
check_sum: 0
subsystem: 3
DLL_characteristics: 33120
stack_reserve_size: 1048576
stack_commit_size: 4096
heap_reserve_size: 1048576
heap_commit_size: 4096
loader_flags: 0
RVA_and_size_count: 0
data_directories:
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
virtual address: 0, size: 0
section header:
name: .text
virtual_size: 1
virtual_address: 4096
raw_data_size: 512
raw_data_pointer: 512
relocation_pointer: 0
line_number_pointer: 0
relocation_count: 0
line_number_count: 0
characteristics: 32
我还编写过一次 exe 导出器,以下是我观察到的输出中的一些异常情况:
将你的pe头特征设置为35而不是2。你只指定了IMAGE_FILE_EXECUTABLE_IMAGE,但你没有重定位表,所以你需要IMAGE_FILE_RELOCS_STRIPPED,你仍然需要IMAGE_FILE_EXECUTABLE_IMAGE,我会推荐IMAGE_FILE_LARGE_ADDRESS_AWARE用于64位应用程序。
在可选的 pe 标头中,将 SizeOfCode 设置为代码部分的对齐大小。如果代码部分只有一条指令也没关系。将 SizeOfCode 设置为节的总大小(它是SectionAlign 的倍数)。 *实际的指令数量由 .text 部分的 VirtualSize 反映,对于单个 ret 指令,VirtualSize 应保持为 1。
在可选的 pe 标头中,ImageSize 和 HeaderSize 不应为零。对于简单的测试,我会推荐静态值!对于 HeaderSize,我通常使用 1024 字节,因为所有标头都适合该空间,并且它是 FileAlign 的倍数。 ImageSize 可以是 SectionAlign * Numbers of Sections + HeaderSize,这样每个部分最多可达SectionAlign 字节 + 所有标题的一些空间。 请注意,ImageSize 的静态值仅用于测试,不应在生产环境中使用!
将 NumberOfRvaAndSizes 设置为 16,即使它们只是零,它们仍然存在。
将 .text 部分的特征设置为 1610612768 而不是 32。您需要 IMAGE_SCN_CNT_CODE、IMAGE_SCN_MEM_EXECUTE 和 IMAGE_SCN_MEM_READ。
希望您能找到问题所在!如果有其他意见,我很乐意更新我的答案。如果可能的话,您可以将导出的 exe 作为二进制文件附加,我将检查结构。