Skip to content

Commit 477bca1

Browse files
committed
put in a lot of work into the PE builder.
1 parent a64d47d commit 477bca1

File tree

4 files changed

+249
-60
lines changed

4 files changed

+249
-60
lines changed

Diff for: chum/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ add_executable(chum
1111
"source/symbol.h"
1212
"source/disassembler.h"
1313
"source/disassembler.cpp"
14+
"source/pe-builder.h"
15+
"source/pe-builder.cpp"
1416
"source/util.h"
1517
"source/util.cpp"
1618
)

Diff for: chum/source/binary.cpp

+2-60
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "binary.h"
2+
#include "pe-builder.h"
23

34
#include <cassert>
45
#include <algorithm>
@@ -221,66 +222,7 @@ void binary::print(bool const verbose) {
221222

222223
// Create a new PE file from this binary.
223224
bool binary::create(char const* const path) const {
224-
std::ofstream file(path);
225-
if (!file)
226-
return false;
227-
228-
// Create a minimal MS-DOS header that just points to the PE header.
229-
IMAGE_DOS_HEADER dos_header = {};
230-
std::memset(&dos_header, 0, sizeof(dos_header));
231-
dos_header.e_magic = IMAGE_DOS_SIGNATURE;
232-
dos_header.e_lfanew = sizeof(dos_header);
233-
file.write(reinterpret_cast<char const*>(&dos_header), sizeof(dos_header));
234-
235-
IMAGE_NT_HEADERS64 nt_header = {};
236-
std::memset(&nt_header, 0, sizeof(nt_header));
237-
nt_header.Signature = IMAGE_NT_SIGNATURE;
238-
nt_header.FileHeader.Machine = IMAGE_FILE_MACHINE_AMD64;
239-
nt_header.FileHeader.NumberOfSections = 1;
240-
nt_header.FileHeader.SizeOfOptionalHeader = sizeof(nt_header.OptionalHeader);
241-
nt_header.FileHeader.Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE;
242-
nt_header.OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR64_MAGIC;
243-
nt_header.OptionalHeader.AddressOfEntryPoint = 0x1000;
244-
nt_header.OptionalHeader.ImageBase = 0x140000000;
245-
nt_header.OptionalHeader.SectionAlignment = 0x1000; // Minimum alignment of all data blocks.
246-
nt_header.OptionalHeader.FileAlignment = 0x200; // Test lower than 0x200.
247-
nt_header.OptionalHeader.MajorOperatingSystemVersion = 6;
248-
nt_header.OptionalHeader.MinorOperatingSystemVersion = 0;
249-
nt_header.OptionalHeader.MajorSubsystemVersion = 6;
250-
nt_header.OptionalHeader.MinorSubsystemVersion = 0;
251-
nt_header.OptionalHeader.SizeOfImage = 0x2000;
252-
nt_header.OptionalHeader.SizeOfHeaders = 0x200;
253-
nt_header.OptionalHeader.Subsystem = IMAGE_SUBSYSTEM_WINDOWS_CUI;
254-
nt_header.OptionalHeader.DllCharacteristics = IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLLCHARACTERISTICS_NX_COMPAT;
255-
nt_header.OptionalHeader.SizeOfStackReserve = 0x10000;
256-
nt_header.OptionalHeader.SizeOfStackCommit = 0x1000;
257-
nt_header.OptionalHeader.SizeOfHeapReserve = 0x10000;
258-
nt_header.OptionalHeader.SizeOfHeapCommit = 0x1000;
259-
nt_header.OptionalHeader.NumberOfRvaAndSizes = 16; // This can be lowered as a meme.
260-
file.write(reinterpret_cast<char const*>(&nt_header), sizeof(nt_header));
261-
262-
IMAGE_SECTION_HEADER sections[1] = {};
263-
std::memset(&sections, 0, sizeof(sections));
264-
std::memcpy(&sections[0].Name, ".text\0\0\0", 8);
265-
sections[0].Misc.VirtualSize = 1;
266-
sections[0].VirtualAddress = 0x1000;
267-
sections[0].SizeOfRawData = 0x200; // FileAlignment.
268-
sections[0].PointerToRawData = 0x200;
269-
sections[0].Characteristics = IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ;
270-
file.write(reinterpret_cast<char const*>(&sections), sizeof(sections));
271-
272-
// Fill with padding until we reach the .text section raw data.
273-
while (file.tellp() < 0x200)
274-
file.put(0);
275-
276-
// A single INT3 instruction in the .text section :)
277-
file.put(0xCC);
278-
279-
// Align.
280-
while (file.tellp() < 0x400)
281-
file.put(0);
282-
283-
return true;
225+
return pe_builder(*this).create(path);
284226
}
285227

286228
// Get the entrypoint of this binary, if it exists.

Diff for: chum/source/pe-builder.cpp

+198
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#include "pe-builder.h"
2+
#include "binary.h"
3+
4+
#include <fstream>
5+
6+
namespace chum {
7+
8+
// Try to create a PE image at the specified path.
9+
bool pe_builder::create(char const* const path) const {
10+
auto const contents = create();
11+
if (contents.empty())
12+
return false;
13+
14+
std::ofstream file(path, std::ios::binary);
15+
if (!file)
16+
return false;
17+
18+
file.write(reinterpret_cast<char const*>(contents.data()), contents.size());
19+
20+
return true;
21+
}
22+
23+
// Create and return the raw contents of a PE image. An empty vector is
24+
// returned on failure.
25+
std::vector<std::uint8_t> pe_builder::create() const {
26+
// This is the initial file size of the image, before we start adding the
27+
// raw section data. This value is aligned to the file alignment.
28+
std::size_t headers_size = 0;
29+
30+
headers_size += sizeof(IMAGE_DOS_HEADER);
31+
headers_size += sizeof(IMAGE_NT_HEADERS);
32+
33+
// Each data block has its own section, while code blocks are all stored
34+
// in a single section.
35+
headers_size += sizeof(IMAGE_SECTION_HEADER) * (bin_.data_blocks().size() + 1);
36+
37+
// Align to the file alignment.
38+
headers_size = align_integer(headers_size, file_alignment);
39+
40+
// Allocate a vector with enough space for the MS-DOS header, the PE header,
41+
// and the section headers.
42+
std::vector<std::uint8_t> contents(headers_size, 0);
43+
44+
auto const& data_blocks = bin_.data_blocks();
45+
46+
// Calculate the virtual section alignment.
47+
std::uint32_t section_alignment = 1;
48+
for (auto const& db : data_blocks)
49+
section_alignment = max(section_alignment, db->alignment);
50+
51+
std::uint32_t current_virtual_address = static_cast<std::uint32_t>(
52+
align_integer(contents.size(), section_alignment));
53+
54+
// Write the raw data of every data block to the vector, as well as
55+
// their section header info.
56+
for (std::uint32_t i = 0; i < data_blocks.size(); ++i) {
57+
auto const& db = data_blocks[i];
58+
59+
// This pointer has to be computed every time we modify the vector
60+
// since the underlying buffer could have been re-allocated.
61+
auto const section = section_header(contents, i);
62+
63+
// Fill out the section header for this data block.
64+
std::memset(section, 0, sizeof(*section));
65+
std::memcpy(section->Name, ".data\0\0\0", 8);
66+
section->Misc.VirtualSize = static_cast<std::uint32_t>(db->bytes.size());
67+
section->VirtualAddress = current_virtual_address;
68+
section->SizeOfRawData = static_cast<std::uint32_t>(
69+
align_integer(db->bytes.size(), file_alignment));
70+
section->PointerToRawData = static_cast<std::uint32_t>(contents.size());
71+
section->Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_INITIALIZED_DATA;
72+
73+
if (!db->read_only)
74+
section->Characteristics |= IMAGE_SCN_MEM_WRITE;
75+
76+
// Append the raw data of the data block to the vector.
77+
contents.insert(end(contents), begin(db->bytes), end(db->bytes));
78+
79+
// Append padding to align the vector to the file alignment value.
80+
if (contents.size() % file_alignment != 0)
81+
append_padding(contents, file_alignment - (contents.size() % file_alignment));
82+
83+
// Increment the current virtual address.
84+
current_virtual_address = static_cast<std::uint32_t>(
85+
align_integer(current_virtual_address + db->bytes.size(), section_alignment));
86+
}
87+
88+
// The current offset in the code section.
89+
std::uint32_t instr_offset = 0;
90+
91+
std::vector<std::uint32_t> sym_to_rva(bin_.symbols().size(), 0);
92+
93+
for (auto const bb : bin_.basic_blocks()) {
94+
sym_to_rva[bb->sym_id.value] = current_virtual_address + instr_offset;
95+
96+
for (auto const& instr : bb->instructions) {
97+
contents.insert(end(contents),
98+
std::begin(instr.bytes), std::begin(instr.bytes) + instr.length);
99+
instr_offset += instr.length;
100+
}
101+
102+
contents.push_back(0xCC);
103+
contents.push_back(0xCC);
104+
contents.push_back(0xCC);
105+
instr_offset += 3;
106+
}
107+
108+
// The code section is the last section (after the data blocks).
109+
auto const code_section = section_header(contents, data_blocks.size());
110+
111+
std::memset(code_section, 0, sizeof(*code_section));
112+
std::memcpy(code_section->Name, ".text\0\0\0", 8);
113+
code_section->Misc.VirtualSize = instr_offset;
114+
code_section->VirtualAddress = current_virtual_address;
115+
code_section->SizeOfRawData = static_cast<std::uint32_t>(
116+
align_integer(instr_offset, file_alignment));
117+
code_section->PointerToRawData = static_cast<std::uint32_t>(contents.size() - instr_offset);
118+
code_section->Characteristics = IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_CNT_CODE;
119+
120+
// Append padding to align the vector to the file alignment value.
121+
if (contents.size() % file_alignment != 0)
122+
append_padding(contents, file_alignment - (contents.size() % file_alignment));
123+
124+
// Align the current virtual address (which is effectively the virtual image size).
125+
current_virtual_address = static_cast<std::uint32_t>(
126+
align_integer(current_virtual_address + instr_offset, section_alignment));
127+
128+
// Write a very minimal MS-DOS header (without the DOS stub).
129+
auto const dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(contents.data());
130+
std::memset(dos_header, 0, sizeof(*dos_header));
131+
dos_header->e_magic = IMAGE_DOS_SIGNATURE;
132+
dos_header->e_lfanew = sizeof(*dos_header);
133+
134+
auto const nt_header = reinterpret_cast<PIMAGE_NT_HEADERS>(&contents[sizeof(IMAGE_DOS_HEADER)]);
135+
write_nt_header(nt_header);
136+
137+
nt_header->OptionalHeader.AddressOfEntryPoint = sym_to_rva[bin_.entrypoint()->sym_id.value];
138+
nt_header->OptionalHeader.SectionAlignment = section_alignment;
139+
nt_header->OptionalHeader.SizeOfImage = static_cast<std::uint32_t>(current_virtual_address);
140+
nt_header->OptionalHeader.SizeOfHeaders = static_cast<std::uint32_t>(headers_size);
141+
142+
return contents;
143+
}
144+
145+
// Fill out most of the stuff in the NT header.
146+
void pe_builder::write_nt_header(PIMAGE_NT_HEADERS const nt_header) const {
147+
std::memset(nt_header, 0, sizeof(*nt_header));
148+
nt_header->Signature = IMAGE_NT_SIGNATURE;
149+
nt_header->FileHeader.Machine = IMAGE_FILE_MACHINE_AMD64;
150+
nt_header->FileHeader.NumberOfSections =
151+
static_cast<std::uint16_t>(bin_.data_blocks().size() + 1);
152+
nt_header->FileHeader.SizeOfOptionalHeader = sizeof(nt_header->OptionalHeader);
153+
nt_header->FileHeader.Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE;
154+
nt_header->OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR64_MAGIC;
155+
nt_header->OptionalHeader.ImageBase = image_base;
156+
nt_header->OptionalHeader.FileAlignment = file_alignment;
157+
nt_header->OptionalHeader.MajorOperatingSystemVersion = 6;
158+
nt_header->OptionalHeader.MinorOperatingSystemVersion = 0;
159+
nt_header->OptionalHeader.MajorSubsystemVersion = 6;
160+
nt_header->OptionalHeader.MinorSubsystemVersion = 0;
161+
nt_header->OptionalHeader.Subsystem = IMAGE_SUBSYSTEM_WINDOWS_CUI;
162+
nt_header->OptionalHeader.DllCharacteristics = IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
163+
| IMAGE_DLLCHARACTERISTICS_NX_COMPAT | IMAGE_DLLCHARACTERISTICS_NO_SEH;
164+
nt_header->OptionalHeader.SizeOfStackReserve = 0x10000;
165+
nt_header->OptionalHeader.SizeOfStackCommit = 0x1000;
166+
nt_header->OptionalHeader.SizeOfHeapReserve = 0x10000;
167+
nt_header->OptionalHeader.SizeOfHeapCommit = 0x1000;
168+
nt_header->OptionalHeader.NumberOfRvaAndSizes = 16;
169+
}
170+
171+
// Get a pointer to a section header in the vector of bytes.
172+
PIMAGE_SECTION_HEADER pe_builder::section_header(
173+
std::vector<std::uint8_t>& vec, std::size_t const idx) {
174+
auto const sections = reinterpret_cast<PIMAGE_SECTION_HEADER>(
175+
&vec[sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)]);
176+
return &sections[idx];
177+
}
178+
179+
// Append padding to the vector of bytes.
180+
void pe_builder::append_padding(std::vector<std::uint8_t>& vec,
181+
std::size_t const count, std::uint8_t const value) {
182+
vec.insert(end(vec), count, value);
183+
}
184+
185+
// Align an integer up to the specified alignment.
186+
std::uint64_t pe_builder::align_integer(
187+
std::uint64_t const value, std::uint64_t const alignment) {
188+
auto const r = value % alignment;
189+
190+
// Already aligned.
191+
if (r == 0)
192+
return value;
193+
194+
return value + (alignment - r);
195+
}
196+
197+
} // namespace chum
198+

Diff for: chum/source/pe-builder.h

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#pragma once
2+
3+
#include <vector>
4+
5+
#include <Windows.h>
6+
7+
namespace chum {
8+
9+
class binary;
10+
11+
class pe_builder {
12+
public:
13+
pe_builder(binary const& bin)
14+
: bin_(bin) {}
15+
16+
// Try to create a PE image at the specified path.
17+
bool create(char const* path) const;
18+
19+
// Create and return the raw contents of a PE image. An empty vector is
20+
// returned on failure.
21+
std::vector<std::uint8_t> create() const;
22+
23+
private:
24+
// Fill out most of the stuff in the NT header.
25+
void write_nt_header(PIMAGE_NT_HEADERS nt_header) const;
26+
27+
// Get a pointer to a section header in the vector of bytes.
28+
static PIMAGE_SECTION_HEADER section_header(
29+
std::vector<std::uint8_t>& vec, std::size_t idx);
30+
31+
// Append padding to the vector of bytes.
32+
static void append_padding(std::vector<std::uint8_t>& vec,
33+
std::size_t count, std::uint8_t value = 0);
34+
35+
// Align an integer up to the specified alignment.
36+
static std::uint64_t align_integer(std::uint64_t value, std::uint64_t alignment);
37+
38+
private:
39+
binary const& bin_;
40+
41+
// TODO: Pass options like this to the PE builder.
42+
static constexpr std::uint32_t file_alignment = 0x200;
43+
static constexpr std::uint64_t image_base = 0x140000000;
44+
};
45+
46+
} // namespace chum
47+

0 commit comments

Comments
 (0)