|
| 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 §ions[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 | + |
0 commit comments