]>
Commit | Line | Data |
---|---|---|
2afeaf16 JJ |
1 | #!/usr/bin/env python3 |
2 | # SPDX-License-Identifier: LGPL-2.1-or-later | |
3 | ||
4 | # Convert ELF static PIE to PE/EFI image. | |
5 | ||
6 | # To do so we simply copy desired ELF sections while preserving their memory layout to ensure that | |
7 | # code still runs as expected. We then translate ELF relocations to PE relocations so that the EFI | |
8 | # loader/firmware can properly load the binary to any address at runtime. | |
9 | # | |
10 | # To make this as painless as possible we only operate on static PIEs as they should only contain | |
11 | # base relocations that are easy to handle as they have a one-to-one mapping to PE relocations. | |
12 | # | |
13 | # EDK2 does a similar process using their GenFw tool. The main difference is that they use the | |
14 | # --emit-relocs linker flag, which emits a lot of different (static) ELF relocation types that have | |
15 | # to be handled differently for each architecture and is overall more work than its worth. | |
16 | # | |
17 | # Note that on arches where binutils has PE support (x86/x86_64 mostly, aarch64 only recently) | |
18 | # objcopy can be used to convert ELF to PE. But this will still not convert ELF relocations, making | |
19 | # the resulting binary useless. gnu-efi relies on this method and contains a stub that performs the | |
20 | # ELF dynamic relocations at runtime. | |
21 | ||
79f902eb | 22 | # pylint: disable=attribute-defined-outside-init |
2afeaf16 JJ |
23 | |
24 | import argparse | |
25 | import hashlib | |
26 | import io | |
27 | import os | |
28 | import pathlib | |
29 | import time | |
ab7d54b9 | 30 | import typing |
2afeaf16 JJ |
31 | from ctypes import ( |
32 | c_char, | |
33 | c_uint8, | |
34 | c_uint16, | |
35 | c_uint32, | |
36 | c_uint64, | |
37 | LittleEndianStructure, | |
38 | sizeof, | |
39 | ) | |
40 | ||
41 | from elftools.elf.constants import SH_FLAGS | |
142f0c61 | 42 | from elftools.elf.elffile import ELFFile |
2afeaf16 JJ |
43 | from elftools.elf.enums import ( |
44 | ENUM_DT_FLAGS_1, | |
45 | ENUM_RELOC_TYPE_AARCH64, | |
46 | ENUM_RELOC_TYPE_ARM, | |
47 | ENUM_RELOC_TYPE_i386, | |
48 | ENUM_RELOC_TYPE_x64, | |
49 | ) | |
50 | from elftools.elf.relocation import ( | |
51 | Relocation as ElfRelocation, | |
52 | RelocationTable as ElfRelocationTable, | |
53 | ) | |
54 | ||
55 | ||
56 | class PeCoffHeader(LittleEndianStructure): | |
57 | _fields_ = ( | |
a0797b4a ZJS |
58 | ("Machine", c_uint16), |
59 | ("NumberOfSections", c_uint16), | |
60 | ("TimeDateStamp", c_uint32), | |
2afeaf16 | 61 | ("PointerToSymbolTable", c_uint32), |
a0797b4a | 62 | ("NumberOfSymbols", c_uint32), |
2afeaf16 | 63 | ("SizeOfOptionalHeader", c_uint16), |
a0797b4a | 64 | ("Characteristics", c_uint16), |
2afeaf16 JJ |
65 | ) |
66 | ||
67 | ||
68 | class PeDataDirectory(LittleEndianStructure): | |
69 | _fields_ = ( | |
70 | ("VirtualAddress", c_uint32), | |
a0797b4a | 71 | ("Size", c_uint32), |
2afeaf16 JJ |
72 | ) |
73 | ||
74 | ||
75 | class PeRelocationBlock(LittleEndianStructure): | |
76 | _fields_ = ( | |
a0797b4a | 77 | ("PageRVA", c_uint32), |
2afeaf16 JJ |
78 | ("BlockSize", c_uint32), |
79 | ) | |
80 | ||
81 | def __init__(self, PageRVA: int): | |
82 | super().__init__(PageRVA) | |
09444a2e | 83 | self.entries: typing.List[PeRelocationEntry] = [] |
2afeaf16 JJ |
84 | |
85 | ||
86 | class PeRelocationEntry(LittleEndianStructure): | |
87 | _fields_ = ( | |
88 | ("Offset", c_uint16, 12), | |
a0797b4a | 89 | ("Type", c_uint16, 4), |
2afeaf16 JJ |
90 | ) |
91 | ||
92 | ||
93 | class PeOptionalHeaderStart(LittleEndianStructure): | |
94 | _fields_ = ( | |
a0797b4a ZJS |
95 | ("Magic", c_uint16), |
96 | ("MajorLinkerVersion", c_uint8), | |
97 | ("MinorLinkerVersion", c_uint8), | |
98 | ("SizeOfCode", c_uint32), | |
99 | ("SizeOfInitializedData", c_uint32), | |
2afeaf16 | 100 | ("SizeOfUninitializedData", c_uint32), |
a0797b4a ZJS |
101 | ("AddressOfEntryPoint", c_uint32), |
102 | ("BaseOfCode", c_uint32), | |
2afeaf16 JJ |
103 | ) |
104 | ||
105 | ||
106 | class PeOptionalHeaderMiddle(LittleEndianStructure): | |
107 | _fields_ = ( | |
a0797b4a ZJS |
108 | ("SectionAlignment", c_uint32), |
109 | ("FileAlignment", c_uint32), | |
2afeaf16 JJ |
110 | ("MajorOperatingSystemVersion", c_uint16), |
111 | ("MinorOperatingSystemVersion", c_uint16), | |
a0797b4a ZJS |
112 | ("MajorImageVersion", c_uint16), |
113 | ("MinorImageVersion", c_uint16), | |
114 | ("MajorSubsystemVersion", c_uint16), | |
115 | ("MinorSubsystemVersion", c_uint16), | |
116 | ("Win32VersionValue", c_uint32), | |
117 | ("SizeOfImage", c_uint32), | |
118 | ("SizeOfHeaders", c_uint32), | |
119 | ("CheckSum", c_uint32), | |
120 | ("Subsystem", c_uint16), | |
121 | ("DllCharacteristics", c_uint16), | |
2afeaf16 JJ |
122 | ) |
123 | ||
124 | ||
125 | class PeOptionalHeaderEnd(LittleEndianStructure): | |
126 | _fields_ = ( | |
a0797b4a ZJS |
127 | ("LoaderFlags", c_uint32), |
128 | ("NumberOfRvaAndSizes", c_uint32), | |
129 | ("ExportTable", PeDataDirectory), | |
130 | ("ImportTable", PeDataDirectory), | |
131 | ("ResourceTable", PeDataDirectory), | |
132 | ("ExceptionTable", PeDataDirectory), | |
133 | ("CertificateTable", PeDataDirectory), | |
134 | ("BaseRelocationTable", PeDataDirectory), | |
135 | ("Debug", PeDataDirectory), | |
136 | ("Architecture", PeDataDirectory), | |
137 | ("GlobalPtr", PeDataDirectory), | |
138 | ("TLSTable", PeDataDirectory), | |
139 | ("LoadConfigTable", PeDataDirectory), | |
140 | ("BoundImport", PeDataDirectory), | |
141 | ("IAT", PeDataDirectory), | |
2afeaf16 | 142 | ("DelayImportDescriptor", PeDataDirectory), |
a0797b4a ZJS |
143 | ("CLRRuntimeHeader", PeDataDirectory), |
144 | ("Reserved", PeDataDirectory), | |
2afeaf16 JJ |
145 | ) |
146 | ||
147 | ||
148 | class PeOptionalHeader(LittleEndianStructure): | |
149 | pass | |
150 | ||
151 | ||
152 | class PeOptionalHeader32(PeOptionalHeader): | |
153 | _anonymous_ = ("Start", "Middle", "End") | |
154 | _fields_ = ( | |
a0797b4a ZJS |
155 | ("Start", PeOptionalHeaderStart), |
156 | ("BaseOfData", c_uint32), | |
157 | ("ImageBase", c_uint32), | |
158 | ("Middle", PeOptionalHeaderMiddle), | |
2afeaf16 | 159 | ("SizeOfStackReserve", c_uint32), |
a0797b4a ZJS |
160 | ("SizeOfStackCommit", c_uint32), |
161 | ("SizeOfHeapReserve", c_uint32), | |
162 | ("SizeOfHeapCommit", c_uint32), | |
163 | ("End", PeOptionalHeaderEnd), | |
2afeaf16 JJ |
164 | ) |
165 | ||
166 | ||
167 | class PeOptionalHeader32Plus(PeOptionalHeader): | |
168 | _anonymous_ = ("Start", "Middle", "End") | |
169 | _fields_ = ( | |
a0797b4a ZJS |
170 | ("Start", PeOptionalHeaderStart), |
171 | ("ImageBase", c_uint64), | |
172 | ("Middle", PeOptionalHeaderMiddle), | |
2afeaf16 | 173 | ("SizeOfStackReserve", c_uint64), |
a0797b4a ZJS |
174 | ("SizeOfStackCommit", c_uint64), |
175 | ("SizeOfHeapReserve", c_uint64), | |
176 | ("SizeOfHeapCommit", c_uint64), | |
177 | ("End", PeOptionalHeaderEnd), | |
2afeaf16 JJ |
178 | ) |
179 | ||
180 | ||
181 | class PeSection(LittleEndianStructure): | |
182 | _fields_ = ( | |
a0797b4a ZJS |
183 | ("Name", c_char * 8), |
184 | ("VirtualSize", c_uint32), | |
185 | ("VirtualAddress", c_uint32), | |
186 | ("SizeOfRawData", c_uint32), | |
187 | ("PointerToRawData", c_uint32), | |
2afeaf16 JJ |
188 | ("PointerToRelocations", c_uint32), |
189 | ("PointerToLinenumbers", c_uint32), | |
a0797b4a ZJS |
190 | ("NumberOfRelocations", c_uint16), |
191 | ("NumberOfLinenumbers", c_uint16), | |
192 | ("Characteristics", c_uint32), | |
2afeaf16 JJ |
193 | ) |
194 | ||
195 | def __init__(self): | |
196 | super().__init__() | |
197 | self.data = bytearray() | |
198 | ||
199 | ||
200 | N_DATA_DIRECTORY_ENTRIES = 16 | |
201 | ||
202 | assert sizeof(PeSection) == 40 | |
203 | assert sizeof(PeCoffHeader) == 20 | |
204 | assert sizeof(PeOptionalHeader32) == 224 | |
205 | assert sizeof(PeOptionalHeader32Plus) == 240 | |
206 | ||
142f0c61 JJ |
207 | PE_CHARACTERISTICS_RX = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE |
208 | PE_CHARACTERISTICS_RW = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE | |
a0797b4a | 209 | PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ |
142f0c61 JJ |
210 | |
211 | IGNORE_SECTIONS = [ | |
212 | ".eh_frame", | |
213 | ".eh_frame_hdr", | |
214 | ".ARM.exidx", | |
215 | ] | |
216 | ||
217 | IGNORE_SECTION_TYPES = [ | |
218 | "SHT_DYNAMIC", | |
219 | "SHT_DYNSYM", | |
220 | "SHT_GNU_ATTRIBUTES", | |
221 | "SHT_GNU_HASH", | |
222 | "SHT_HASH", | |
223 | "SHT_NOTE", | |
224 | "SHT_REL", | |
225 | "SHT_RELA", | |
226 | "SHT_RELR", | |
227 | "SHT_STRTAB", | |
228 | "SHT_SYMTAB", | |
229 | ] | |
230 | ||
2afeaf16 JJ |
231 | # EFI mandates 4KiB memory pages. |
232 | SECTION_ALIGNMENT = 4096 | |
233 | FILE_ALIGNMENT = 512 | |
234 | ||
235 | # Nobody cares about DOS headers, so put the PE header right after. | |
236 | PE_OFFSET = 64 | |
ee91e06a | 237 | PE_MAGIC = b"PE\0\0" |
2afeaf16 JJ |
238 | |
239 | ||
240 | def align_to(x: int, align: int) -> int: | |
241 | return (x + align - 1) & ~(align - 1) | |
242 | ||
243 | ||
142f0c61 JJ |
244 | def align_down(x: int, align: int) -> int: |
245 | return x & ~(align - 1) | |
2afeaf16 | 246 | |
2afeaf16 | 247 | |
7d6fd7f0 | 248 | def next_section_address(sections: typing.List[PeSection]) -> int: |
a0797b4a ZJS |
249 | return align_to(sections[-1].VirtualAddress + sections[-1].VirtualSize, |
250 | SECTION_ALIGNMENT) | |
7d6fd7f0 JJ |
251 | |
252 | ||
142f0c61 JJ |
253 | def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]: |
254 | pe_s = None | |
2afeaf16 | 255 | |
142f0c61 JJ |
256 | # This is essentially the same as copying by ELF load segments, except that we assemble them |
257 | # manually, so that we can easily strip unwanted sections. We try to only discard things we know | |
258 | # about so that there are no surprises. | |
2afeaf16 | 259 | |
823bf39a JJ |
260 | relro = None |
261 | for elf_seg in elf.iter_segments(): | |
262 | if elf_seg["p_type"] == "PT_LOAD" and elf_seg["p_align"] != SECTION_ALIGNMENT: | |
263 | raise RuntimeError("ELF segments are not properly aligned.") | |
264 | elif elf_seg["p_type"] == "PT_GNU_RELRO": | |
265 | relro = elf_seg | |
266 | ||
142f0c61 JJ |
267 | for elf_s in elf.iter_sections(): |
268 | if ( | |
269 | elf_s["sh_flags"] & SH_FLAGS.SHF_ALLOC == 0 | |
270 | or elf_s["sh_type"] in IGNORE_SECTION_TYPES | |
271 | or elf_s.name in IGNORE_SECTIONS | |
272 | ): | |
273 | continue | |
274 | if elf_s["sh_type"] not in ["SHT_PROGBITS", "SHT_NOBITS"]: | |
275 | raise RuntimeError(f"Unknown section {elf_s.name}.") | |
2afeaf16 | 276 | |
142f0c61 JJ |
277 | if elf_s["sh_flags"] & SH_FLAGS.SHF_EXECINSTR: |
278 | rwx = PE_CHARACTERISTICS_RX | |
279 | elif elf_s["sh_flags"] & SH_FLAGS.SHF_WRITE: | |
280 | rwx = PE_CHARACTERISTICS_RW | |
281 | else: | |
282 | rwx = PE_CHARACTERISTICS_R | |
2afeaf16 | 283 | |
823bf39a JJ |
284 | # PE images are always relro. |
285 | if relro and relro.section_in_segment(elf_s): | |
286 | rwx = PE_CHARACTERISTICS_R | |
287 | ||
142f0c61 JJ |
288 | if pe_s and pe_s.Characteristics != rwx: |
289 | yield pe_s | |
290 | pe_s = None | |
2afeaf16 | 291 | |
142f0c61 JJ |
292 | if pe_s: |
293 | # Insert padding to properly align the section. | |
294 | pad_len = elf_s["sh_addr"] - pe_s.VirtualAddress - len(pe_s.data) | |
295 | pe_s.data += bytearray(pad_len) + elf_s.data() | |
296 | else: | |
297 | pe_s = PeSection() | |
298 | pe_s.VirtualAddress = elf_s["sh_addr"] | |
299 | pe_s.Characteristics = rwx | |
300 | pe_s.data = elf_s.data() | |
2afeaf16 | 301 | |
142f0c61 JJ |
302 | if pe_s: |
303 | yield pe_s | |
2afeaf16 JJ |
304 | |
305 | ||
142f0c61 JJ |
306 | def convert_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSection]: |
307 | last_vma = 0 | |
2afeaf16 JJ |
308 | sections = [] |
309 | ||
142f0c61 JJ |
310 | for pe_s in iter_copy_sections(elf): |
311 | # Truncate the VMA to the nearest page and insert appropriate padding. This should not | |
312 | # cause any overlap as this is pretty much how ELF *segments* are loaded/mmapped anyways. | |
313 | # The ELF sections inside should also be properly aligned as we reuse the ELF VMA layout | |
314 | # for the PE image. | |
315 | vma = pe_s.VirtualAddress | |
316 | pe_s.VirtualAddress = align_down(vma, SECTION_ALIGNMENT) | |
317 | pe_s.data = bytearray(vma - pe_s.VirtualAddress) + pe_s.data | |
318 | ||
319 | pe_s.VirtualSize = len(pe_s.data) | |
320 | pe_s.SizeOfRawData = align_to(len(pe_s.data), FILE_ALIGNMENT) | |
321 | pe_s.Name = { | |
322 | PE_CHARACTERISTICS_RX: b".text", | |
323 | PE_CHARACTERISTICS_RW: b".data", | |
324 | PE_CHARACTERISTICS_R: b".rodata", | |
325 | }[pe_s.Characteristics] | |
326 | ||
327 | # This can happen if not building with `-z separate-code`. | |
328 | if pe_s.VirtualAddress < last_vma: | |
329 | raise RuntimeError("Overlapping PE sections.") | |
330 | last_vma = pe_s.VirtualAddress + pe_s.VirtualSize | |
2afeaf16 | 331 | |
2afeaf16 JJ |
332 | if pe_s.Name == b".text": |
333 | opt.BaseOfCode = pe_s.VirtualAddress | |
334 | opt.SizeOfCode += pe_s.VirtualSize | |
335 | else: | |
336 | opt.SizeOfInitializedData += pe_s.VirtualSize | |
337 | ||
338 | if pe_s.Name == b".data" and isinstance(opt, PeOptionalHeader32): | |
339 | opt.BaseOfData = pe_s.VirtualAddress | |
340 | ||
341 | sections.append(pe_s) | |
342 | ||
343 | return sections | |
344 | ||
345 | ||
898e9edc JJ |
346 | def copy_sections( |
347 | elf: ELFFile, | |
348 | opt: PeOptionalHeader, | |
349 | input_names: str, | |
350 | sections: typing.List[PeSection], | |
351 | ): | |
352 | for name in input_names.split(","): | |
353 | elf_s = elf.get_section_by_name(name) | |
354 | if not elf_s: | |
355 | continue | |
356 | if elf_s.data_alignment > 1 and SECTION_ALIGNMENT % elf_s.data_alignment != 0: | |
357 | raise RuntimeError(f"ELF section {name} is not aligned.") | |
358 | if elf_s["sh_flags"] & (SH_FLAGS.SHF_EXECINSTR | SH_FLAGS.SHF_WRITE) != 0: | |
359 | raise RuntimeError(f"ELF section {name} is not read-only data.") | |
360 | ||
361 | pe_s = PeSection() | |
362 | pe_s.Name = name.encode() | |
363 | pe_s.data = elf_s.data() | |
364 | pe_s.VirtualAddress = next_section_address(sections) | |
365 | pe_s.VirtualSize = len(elf_s.data()) | |
366 | pe_s.SizeOfRawData = align_to(len(elf_s.data()), FILE_ALIGNMENT) | |
367 | pe_s.Characteristics = PE_CHARACTERISTICS_R | |
368 | opt.SizeOfInitializedData += pe_s.VirtualSize | |
369 | sections.append(pe_s) | |
370 | ||
371 | ||
2afeaf16 | 372 | def apply_elf_relative_relocation( |
ee91e06a JJ |
373 | reloc: ElfRelocation, |
374 | image_base: int, | |
375 | sections: typing.List[PeSection], | |
376 | addend_size: int, | |
2afeaf16 | 377 | ): |
a0797b4a ZJS |
378 | [target] = [pe_s for pe_s in sections |
379 | if pe_s.VirtualAddress <= reloc["r_offset"] < pe_s.VirtualAddress + len(pe_s.data)] | |
2afeaf16 JJ |
380 | |
381 | addend_offset = reloc["r_offset"] - target.VirtualAddress | |
382 | ||
383 | if reloc.is_RELA(): | |
384 | addend = reloc["r_addend"] | |
385 | else: | |
386 | addend = target.data[addend_offset : addend_offset + addend_size] | |
387 | addend = int.from_bytes(addend, byteorder="little") | |
388 | ||
2afeaf16 JJ |
389 | value = (image_base + addend).to_bytes(addend_size, byteorder="little") |
390 | target.data[addend_offset : addend_offset + addend_size] = value | |
391 | ||
392 | ||
393 | def convert_elf_reloc_table( | |
394 | elf: ELFFile, | |
395 | elf_reloc_table: ElfRelocationTable, | |
142f0c61 | 396 | elf_image_base: int, |
09444a2e DDM |
397 | sections: typing.List[PeSection], |
398 | pe_reloc_blocks: typing.Dict[int, PeRelocationBlock], | |
2afeaf16 JJ |
399 | ): |
400 | NONE_RELOC = { | |
401 | "EM_386": ENUM_RELOC_TYPE_i386["R_386_NONE"], | |
402 | "EM_AARCH64": ENUM_RELOC_TYPE_AARCH64["R_AARCH64_NONE"], | |
403 | "EM_ARM": ENUM_RELOC_TYPE_ARM["R_ARM_NONE"], | |
31ffb6b1 | 404 | "EM_LOONGARCH": 0, |
2afeaf16 JJ |
405 | "EM_RISCV": 0, |
406 | "EM_X86_64": ENUM_RELOC_TYPE_x64["R_X86_64_NONE"], | |
407 | }[elf["e_machine"]] | |
408 | ||
409 | RELATIVE_RELOC = { | |
410 | "EM_386": ENUM_RELOC_TYPE_i386["R_386_RELATIVE"], | |
411 | "EM_AARCH64": ENUM_RELOC_TYPE_AARCH64["R_AARCH64_RELATIVE"], | |
412 | "EM_ARM": ENUM_RELOC_TYPE_ARM["R_ARM_RELATIVE"], | |
31ffb6b1 | 413 | "EM_LOONGARCH": 3, |
2afeaf16 JJ |
414 | "EM_RISCV": 3, |
415 | "EM_X86_64": ENUM_RELOC_TYPE_x64["R_X86_64_RELATIVE"], | |
416 | }[elf["e_machine"]] | |
417 | ||
418 | for reloc in elf_reloc_table.iter_relocations(): | |
419 | if reloc["r_info_type"] == NONE_RELOC: | |
420 | continue | |
421 | ||
422 | if reloc["r_info_type"] == RELATIVE_RELOC: | |
a0797b4a ZJS |
423 | apply_elf_relative_relocation(reloc, |
424 | elf_image_base, | |
425 | sections, | |
426 | elf.elfclass // 8) | |
2afeaf16 JJ |
427 | |
428 | # Now that the ELF relocation has been applied, we can create a PE relocation. | |
429 | block_rva = reloc["r_offset"] & ~0xFFF | |
430 | if block_rva not in pe_reloc_blocks: | |
431 | pe_reloc_blocks[block_rva] = PeRelocationBlock(block_rva) | |
432 | ||
433 | entry = PeRelocationEntry() | |
434 | entry.Offset = reloc["r_offset"] & 0xFFF | |
435 | # REL_BASED_HIGHLOW or REL_BASED_DIR64 | |
436 | entry.Type = 3 if elf.elfclass == 32 else 10 | |
437 | pe_reloc_blocks[block_rva].entries.append(entry) | |
438 | ||
439 | continue | |
440 | ||
441 | raise RuntimeError(f"Unsupported relocation {reloc}") | |
442 | ||
443 | ||
444 | def convert_elf_relocations( | |
142f0c61 JJ |
445 | elf: ELFFile, |
446 | opt: PeOptionalHeader, | |
447 | sections: typing.List[PeSection], | |
448 | minimum_sections: int, | |
ab7d54b9 | 449 | ) -> typing.Optional[PeSection]: |
2afeaf16 JJ |
450 | dynamic = elf.get_section_by_name(".dynamic") |
451 | if dynamic is None: | |
452 | raise RuntimeError("ELF .dynamic section is missing.") | |
453 | ||
454 | [flags_tag] = dynamic.iter_tags("DT_FLAGS_1") | |
455 | if not flags_tag["d_val"] & ENUM_DT_FLAGS_1["DF_1_PIE"]: | |
456 | raise RuntimeError("ELF file is not a PIE.") | |
457 | ||
5713c50d JJ |
458 | # This checks that the ELF image base is 0. |
459 | symtab = elf.get_section_by_name(".symtab") | |
460 | if symtab: | |
461 | exe_start = symtab.get_symbol_by_name("__executable_start") | |
462 | if exe_start and exe_start[0]["st_value"] != 0: | |
463 | raise RuntimeError("Unexpected ELF image base.") | |
464 | ||
a0797b4a ZJS |
465 | opt.SizeOfHeaders = align_to(PE_OFFSET |
466 | + len(PE_MAGIC) | |
467 | + sizeof(PeCoffHeader) | |
468 | + sizeof(opt) | |
469 | + sizeof(PeSection) * max(len(sections) + 1, minimum_sections), | |
470 | FILE_ALIGNMENT) | |
142f0c61 JJ |
471 | |
472 | # We use the basic VMA layout from the ELF image in the PE image. This could cause the first | |
473 | # section to overlap the PE image headers during runtime at VMA 0. We can simply apply a fixed | |
474 | # offset relative to the PE image base when applying/converting ELF relocations. Afterwards we | |
475 | # just have to apply the offset to the PE addresses so that the PE relocations work correctly on | |
476 | # the ELF portions of the image. | |
477 | segment_offset = 0 | |
478 | if sections[0].VirtualAddress < opt.SizeOfHeaders: | |
a0797b4a ZJS |
479 | segment_offset = align_to(opt.SizeOfHeaders - sections[0].VirtualAddress, |
480 | SECTION_ALIGNMENT) | |
142f0c61 JJ |
481 | |
482 | opt.AddressOfEntryPoint = elf["e_entry"] + segment_offset | |
483 | opt.BaseOfCode += segment_offset | |
484 | if isinstance(opt, PeOptionalHeader32): | |
485 | opt.BaseOfData += segment_offset | |
486 | ||
09444a2e | 487 | pe_reloc_blocks: typing.Dict[int, PeRelocationBlock] = {} |
2afeaf16 JJ |
488 | for reloc_type, reloc_table in dynamic.get_relocation_tables().items(): |
489 | if reloc_type not in ["REL", "RELA"]: | |
490 | raise RuntimeError("Unsupported relocation type {elf_reloc_type}.") | |
a0797b4a ZJS |
491 | convert_elf_reloc_table(elf, |
492 | reloc_table, | |
493 | opt.ImageBase + segment_offset, | |
494 | sections, | |
495 | pe_reloc_blocks) | |
2afeaf16 | 496 | |
142f0c61 JJ |
497 | for pe_s in sections: |
498 | pe_s.VirtualAddress += segment_offset | |
499 | ||
ab7d54b9 JJ |
500 | if len(pe_reloc_blocks) == 0: |
501 | return None | |
502 | ||
2afeaf16 JJ |
503 | data = bytearray() |
504 | for rva in sorted(pe_reloc_blocks): | |
505 | block = pe_reloc_blocks[rva] | |
506 | n_relocs = len(block.entries) | |
507 | ||
508 | # Each block must start on a 32-bit boundary. Because each entry is 16 bits | |
509 | # the len has to be even. We pad by adding a none relocation. | |
510 | if n_relocs % 2 != 0: | |
511 | n_relocs += 1 | |
512 | block.entries.append(PeRelocationEntry()) | |
513 | ||
142f0c61 | 514 | block.PageRVA += segment_offset |
a0797b4a | 515 | block.BlockSize = sizeof(PeRelocationBlock) + sizeof(PeRelocationEntry) * n_relocs |
2afeaf16 JJ |
516 | data += block |
517 | for entry in sorted(block.entries, key=lambda e: e.Offset): | |
518 | data += entry | |
519 | ||
520 | pe_reloc_s = PeSection() | |
521 | pe_reloc_s.Name = b".reloc" | |
522 | pe_reloc_s.data = data | |
7d6fd7f0 | 523 | pe_reloc_s.VirtualAddress = next_section_address(sections) |
2afeaf16 JJ |
524 | pe_reloc_s.VirtualSize = len(data) |
525 | pe_reloc_s.SizeOfRawData = align_to(len(data), FILE_ALIGNMENT) | |
2afeaf16 JJ |
526 | # CNT_INITIALIZED_DATA|MEM_READ|MEM_DISCARDABLE |
527 | pe_reloc_s.Characteristics = 0x42000040 | |
528 | ||
529 | sections.append(pe_reloc_s) | |
530 | opt.SizeOfInitializedData += pe_reloc_s.VirtualSize | |
531 | return pe_reloc_s | |
532 | ||
533 | ||
534 | def write_pe( | |
a0797b4a ZJS |
535 | file, |
536 | coff: PeCoffHeader, | |
537 | opt: PeOptionalHeader, | |
538 | sections: typing.List[PeSection], | |
2afeaf16 JJ |
539 | ): |
540 | file.write(b"MZ") | |
541 | file.seek(0x3C, io.SEEK_SET) | |
542 | file.write(PE_OFFSET.to_bytes(2, byteorder="little")) | |
543 | file.seek(PE_OFFSET, io.SEEK_SET) | |
ee91e06a | 544 | file.write(PE_MAGIC) |
2afeaf16 JJ |
545 | file.write(coff) |
546 | file.write(opt) | |
547 | ||
548 | offset = opt.SizeOfHeaders | |
549 | for pe_s in sorted(sections, key=lambda s: s.VirtualAddress): | |
550 | if pe_s.VirtualAddress < opt.SizeOfHeaders: | |
2afeaf16 JJ |
551 | raise RuntimeError(f"Section {pe_s.Name} overlapping PE headers.") |
552 | ||
553 | pe_s.PointerToRawData = offset | |
554 | file.write(pe_s) | |
555 | offset = align_to(offset + len(pe_s.data), FILE_ALIGNMENT) | |
556 | ||
ee91e06a JJ |
557 | assert file.tell() <= opt.SizeOfHeaders |
558 | ||
2afeaf16 JJ |
559 | for pe_s in sections: |
560 | file.seek(pe_s.PointerToRawData, io.SEEK_SET) | |
561 | file.write(pe_s.data) | |
562 | ||
563 | file.truncate(offset) | |
564 | ||
565 | ||
566 | def elf2efi(args: argparse.Namespace): | |
567 | elf = ELFFile(args.ELF) | |
568 | if not elf.little_endian: | |
569 | raise RuntimeError("ELF file is not little-endian.") | |
570 | if elf["e_type"] not in ["ET_DYN", "ET_EXEC"]: | |
571 | raise RuntimeError("Unsupported ELF type.") | |
572 | ||
573 | pe_arch = { | |
574 | "EM_386": 0x014C, | |
575 | "EM_AARCH64": 0xAA64, | |
576 | "EM_ARM": 0x01C2, | |
31ffb6b1 JJ |
577 | "EM_LOONGARCH": 0x6232 if elf.elfclass == 32 else 0x6264, |
578 | "EM_RISCV": 0x5032 if elf.elfclass == 32 else 0x5064, | |
2afeaf16 JJ |
579 | "EM_X86_64": 0x8664, |
580 | }.get(elf["e_machine"]) | |
581 | if pe_arch is None: | |
1365355d | 582 | raise RuntimeError(f"Unsupported ELF arch {elf['e_machine']}") |
2afeaf16 JJ |
583 | |
584 | coff = PeCoffHeader() | |
585 | opt = PeOptionalHeader32() if elf.elfclass == 32 else PeOptionalHeader32Plus() | |
586 | ||
587 | # We relocate to a unique image base to reduce the chances for runtime relocation to occur. | |
588 | base_name = pathlib.Path(args.PE.name).name.encode() | |
589 | opt.ImageBase = int(hashlib.sha1(base_name).hexdigest()[0:8], 16) | |
590 | if elf.elfclass == 32: | |
591 | opt.ImageBase = (0x400000 + opt.ImageBase) & 0xFFFF0000 | |
592 | else: | |
593 | opt.ImageBase = (0x100000000 + opt.ImageBase) & 0x1FFFF0000 | |
594 | ||
142f0c61 | 595 | sections = convert_sections(elf, opt) |
898e9edc | 596 | copy_sections(elf, opt, args.copy_sections, sections) |
142f0c61 | 597 | pe_reloc_s = convert_elf_relocations(elf, opt, sections, args.minimum_sections) |
2afeaf16 JJ |
598 | |
599 | coff.Machine = pe_arch | |
600 | coff.NumberOfSections = len(sections) | |
601 | coff.TimeDateStamp = int(os.environ.get("SOURCE_DATE_EPOCH", time.time())) | |
602 | coff.SizeOfOptionalHeader = sizeof(opt) | |
603 | # EXECUTABLE_IMAGE|LINE_NUMS_STRIPPED|LOCAL_SYMS_STRIPPED|DEBUG_STRIPPED | |
604 | # and (32BIT_MACHINE or LARGE_ADDRESS_AWARE) | |
605 | coff.Characteristics = 0x30E if elf.elfclass == 32 else 0x22E | |
606 | ||
2afeaf16 JJ |
607 | opt.SectionAlignment = SECTION_ALIGNMENT |
608 | opt.FileAlignment = FILE_ALIGNMENT | |
609 | opt.MajorImageVersion = args.version_major | |
610 | opt.MinorImageVersion = args.version_minor | |
611 | opt.MajorSubsystemVersion = args.efi_major | |
612 | opt.MinorSubsystemVersion = args.efi_minor | |
613 | opt.Subsystem = args.subsystem | |
614 | opt.Magic = 0x10B if elf.elfclass == 32 else 0x20B | |
7d6fd7f0 JJ |
615 | opt.SizeOfImage = next_section_address(sections) |
616 | ||
2afeaf16 JJ |
617 | # DYNAMIC_BASE|NX_COMPAT|HIGH_ENTROPY_VA or DYNAMIC_BASE|NX_COMPAT |
618 | opt.DllCharacteristics = 0x160 if elf.elfclass == 64 else 0x140 | |
619 | ||
620 | # These values are taken from a natively built PE binary (although, unused by EDK2/EFI). | |
621 | opt.SizeOfStackReserve = 0x100000 | |
622 | opt.SizeOfStackCommit = 0x001000 | |
623 | opt.SizeOfHeapReserve = 0x100000 | |
624 | opt.SizeOfHeapCommit = 0x001000 | |
625 | ||
626 | opt.NumberOfRvaAndSizes = N_DATA_DIRECTORY_ENTRIES | |
ab7d54b9 JJ |
627 | if pe_reloc_s: |
628 | opt.BaseRelocationTable = PeDataDirectory( | |
629 | pe_reloc_s.VirtualAddress, pe_reloc_s.VirtualSize | |
630 | ) | |
2afeaf16 JJ |
631 | |
632 | write_pe(args.PE, coff, opt, sections) | |
633 | ||
634 | ||
635 | def main(): | |
636 | parser = argparse.ArgumentParser(description="Convert ELF binaries to PE/EFI") | |
637 | parser.add_argument( | |
638 | "--version-major", | |
639 | type=int, | |
640 | default=0, | |
641 | help="Major image version of EFI image", | |
642 | ) | |
643 | parser.add_argument( | |
644 | "--version-minor", | |
645 | type=int, | |
646 | default=0, | |
647 | help="Minor image version of EFI image", | |
648 | ) | |
649 | parser.add_argument( | |
650 | "--efi-major", | |
651 | type=int, | |
652 | default=0, | |
653 | help="Minimum major EFI subsystem version", | |
654 | ) | |
655 | parser.add_argument( | |
656 | "--efi-minor", | |
657 | type=int, | |
658 | default=0, | |
659 | help="Minimum minor EFI subsystem version", | |
660 | ) | |
661 | parser.add_argument( | |
662 | "--subsystem", | |
663 | type=int, | |
664 | default=10, | |
665 | help="PE subsystem", | |
666 | ) | |
667 | parser.add_argument( | |
668 | "ELF", | |
669 | type=argparse.FileType("rb"), | |
670 | help="Input ELF file", | |
671 | ) | |
672 | parser.add_argument( | |
673 | "PE", | |
674 | type=argparse.FileType("wb"), | |
675 | help="Output PE/EFI file", | |
676 | ) | |
e18e3c43 LB |
677 | parser.add_argument( |
678 | "--minimum-sections", | |
679 | type=int, | |
680 | default=0, | |
681 | help="Minimum number of sections to leave space for", | |
682 | ) | |
898e9edc JJ |
683 | parser.add_argument( |
684 | "--copy-sections", | |
685 | type=str, | |
686 | default="", | |
687 | help="Copy these sections if found", | |
688 | ) | |
2afeaf16 JJ |
689 | |
690 | elf2efi(parser.parse_args()) | |
691 | ||
692 | ||
693 | if __name__ == "__main__": | |
694 | main() |