]>
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 | ||
22 | # pylint: disable=missing-docstring,invalid-name,attribute-defined-outside-init | |
23 | ||
24 | import argparse | |
25 | import hashlib | |
26 | import io | |
27 | import os | |
28 | import pathlib | |
29 | import time | |
30 | from ctypes import ( | |
31 | c_char, | |
32 | c_uint8, | |
33 | c_uint16, | |
34 | c_uint32, | |
35 | c_uint64, | |
36 | LittleEndianStructure, | |
37 | sizeof, | |
38 | ) | |
39 | ||
40 | from elftools.elf.constants import SH_FLAGS | |
41 | from elftools.elf.elffile import ELFFile, Section as ELFSection | |
42 | from elftools.elf.enums import ( | |
43 | ENUM_DT_FLAGS_1, | |
44 | ENUM_RELOC_TYPE_AARCH64, | |
45 | ENUM_RELOC_TYPE_ARM, | |
46 | ENUM_RELOC_TYPE_i386, | |
47 | ENUM_RELOC_TYPE_x64, | |
48 | ) | |
49 | from elftools.elf.relocation import ( | |
50 | Relocation as ElfRelocation, | |
51 | RelocationTable as ElfRelocationTable, | |
52 | ) | |
53 | ||
54 | ||
55 | class PeCoffHeader(LittleEndianStructure): | |
56 | _fields_ = ( | |
57 | ("Machine", c_uint16), | |
58 | ("NumberOfSections", c_uint16), | |
59 | ("TimeDateStamp", c_uint32), | |
60 | ("PointerToSymbolTable", c_uint32), | |
61 | ("NumberOfSymbols", c_uint32), | |
62 | ("SizeOfOptionalHeader", c_uint16), | |
63 | ("Characteristics", c_uint16), | |
64 | ) | |
65 | ||
66 | ||
67 | class PeDataDirectory(LittleEndianStructure): | |
68 | _fields_ = ( | |
69 | ("VirtualAddress", c_uint32), | |
70 | ("Size", c_uint32), | |
71 | ) | |
72 | ||
73 | ||
74 | class PeRelocationBlock(LittleEndianStructure): | |
75 | _fields_ = ( | |
76 | ("PageRVA", c_uint32), | |
77 | ("BlockSize", c_uint32), | |
78 | ) | |
79 | ||
80 | def __init__(self, PageRVA: int): | |
81 | super().__init__(PageRVA) | |
82 | self.entries: list[PeRelocationEntry] = [] | |
83 | ||
84 | ||
85 | class PeRelocationEntry(LittleEndianStructure): | |
86 | _fields_ = ( | |
87 | ("Offset", c_uint16, 12), | |
88 | ("Type", c_uint16, 4), | |
89 | ) | |
90 | ||
91 | ||
92 | class PeOptionalHeaderStart(LittleEndianStructure): | |
93 | _fields_ = ( | |
94 | ("Magic", c_uint16), | |
95 | ("MajorLinkerVersion", c_uint8), | |
96 | ("MinorLinkerVersion", c_uint8), | |
97 | ("SizeOfCode", c_uint32), | |
98 | ("SizeOfInitializedData", c_uint32), | |
99 | ("SizeOfUninitializedData", c_uint32), | |
100 | ("AddressOfEntryPoint", c_uint32), | |
101 | ("BaseOfCode", c_uint32), | |
102 | ) | |
103 | ||
104 | ||
105 | class PeOptionalHeaderMiddle(LittleEndianStructure): | |
106 | _fields_ = ( | |
107 | ("SectionAlignment", c_uint32), | |
108 | ("FileAlignment", c_uint32), | |
109 | ("MajorOperatingSystemVersion", c_uint16), | |
110 | ("MinorOperatingSystemVersion", c_uint16), | |
111 | ("MajorImageVersion", c_uint16), | |
112 | ("MinorImageVersion", c_uint16), | |
113 | ("MajorSubsystemVersion", c_uint16), | |
114 | ("MinorSubsystemVersion", c_uint16), | |
115 | ("Win32VersionValue", c_uint32), | |
116 | ("SizeOfImage", c_uint32), | |
117 | ("SizeOfHeaders", c_uint32), | |
118 | ("CheckSum", c_uint32), | |
119 | ("Subsystem", c_uint16), | |
120 | ("DllCharacteristics", c_uint16), | |
121 | ) | |
122 | ||
123 | ||
124 | class PeOptionalHeaderEnd(LittleEndianStructure): | |
125 | _fields_ = ( | |
126 | ("LoaderFlags", c_uint32), | |
127 | ("NumberOfRvaAndSizes", c_uint32), | |
128 | ("ExportTable", PeDataDirectory), | |
129 | ("ImportTable", PeDataDirectory), | |
130 | ("ResourceTable", PeDataDirectory), | |
131 | ("ExceptionTable", PeDataDirectory), | |
132 | ("CertificateTable", PeDataDirectory), | |
133 | ("BaseRelocationTable", PeDataDirectory), | |
134 | ("Debug", PeDataDirectory), | |
135 | ("Architecture", PeDataDirectory), | |
136 | ("GlobalPtr", PeDataDirectory), | |
137 | ("TLSTable", PeDataDirectory), | |
138 | ("LoadConfigTable", PeDataDirectory), | |
139 | ("BoundImport", PeDataDirectory), | |
140 | ("IAT", PeDataDirectory), | |
141 | ("DelayImportDescriptor", PeDataDirectory), | |
142 | ("CLRRuntimeHeader", PeDataDirectory), | |
143 | ("Reserved", PeDataDirectory), | |
144 | ) | |
145 | ||
146 | ||
147 | class PeOptionalHeader(LittleEndianStructure): | |
148 | pass | |
149 | ||
150 | ||
151 | class PeOptionalHeader32(PeOptionalHeader): | |
152 | _anonymous_ = ("Start", "Middle", "End") | |
153 | _fields_ = ( | |
154 | ("Start", PeOptionalHeaderStart), | |
155 | ("BaseOfData", c_uint32), | |
156 | ("ImageBase", c_uint32), | |
157 | ("Middle", PeOptionalHeaderMiddle), | |
158 | ("SizeOfStackReserve", c_uint32), | |
159 | ("SizeOfStackCommit", c_uint32), | |
160 | ("SizeOfHeapReserve", c_uint32), | |
161 | ("SizeOfHeapCommit", c_uint32), | |
162 | ("End", PeOptionalHeaderEnd), | |
163 | ) | |
164 | ||
165 | ||
166 | class PeOptionalHeader32Plus(PeOptionalHeader): | |
167 | _anonymous_ = ("Start", "Middle", "End") | |
168 | _fields_ = ( | |
169 | ("Start", PeOptionalHeaderStart), | |
170 | ("ImageBase", c_uint64), | |
171 | ("Middle", PeOptionalHeaderMiddle), | |
172 | ("SizeOfStackReserve", c_uint64), | |
173 | ("SizeOfStackCommit", c_uint64), | |
174 | ("SizeOfHeapReserve", c_uint64), | |
175 | ("SizeOfHeapCommit", c_uint64), | |
176 | ("End", PeOptionalHeaderEnd), | |
177 | ) | |
178 | ||
179 | ||
180 | class PeSection(LittleEndianStructure): | |
181 | _fields_ = ( | |
182 | ("Name", c_char * 8), | |
183 | ("VirtualSize", c_uint32), | |
184 | ("VirtualAddress", c_uint32), | |
185 | ("SizeOfRawData", c_uint32), | |
186 | ("PointerToRawData", c_uint32), | |
187 | ("PointerToRelocations", c_uint32), | |
188 | ("PointerToLinenumbers", c_uint32), | |
189 | ("NumberOfRelocations", c_uint16), | |
190 | ("NumberOfLinenumbers", c_uint16), | |
191 | ("Characteristics", c_uint32), | |
192 | ) | |
193 | ||
194 | def __init__(self): | |
195 | super().__init__() | |
196 | self.data = bytearray() | |
197 | ||
198 | ||
199 | N_DATA_DIRECTORY_ENTRIES = 16 | |
200 | ||
201 | assert sizeof(PeSection) == 40 | |
202 | assert sizeof(PeCoffHeader) == 20 | |
203 | assert sizeof(PeOptionalHeader32) == 224 | |
204 | assert sizeof(PeOptionalHeader32Plus) == 240 | |
205 | ||
206 | # EFI mandates 4KiB memory pages. | |
207 | SECTION_ALIGNMENT = 4096 | |
208 | FILE_ALIGNMENT = 512 | |
209 | ||
210 | # Nobody cares about DOS headers, so put the PE header right after. | |
211 | PE_OFFSET = 64 | |
212 | ||
213 | ||
214 | def align_to(x: int, align: int) -> int: | |
215 | return (x + align - 1) & ~(align - 1) | |
216 | ||
217 | ||
218 | def use_section(elf_s: ELFSection) -> bool: | |
219 | # These sections are either needed during conversion to PE or are otherwise not needed | |
220 | # in the final PE image. | |
221 | IGNORE_SECTIONS = [ | |
222 | ".ARM.exidx", | |
223 | ".dynamic", | |
224 | ".dynstr", | |
225 | ".dynsym", | |
226 | ".eh_frame_hdr", | |
227 | ".eh_frame", | |
228 | ".gnu.hash", | |
229 | ".hash", | |
230 | ".note.gnu.build-id", | |
231 | ".rel.dyn", | |
232 | ".rela.dyn", | |
233 | ] | |
234 | ||
235 | # Known sections we care about and want to be in the final PE. | |
236 | COPY_SECTIONS = [ | |
237 | ".data", | |
238 | ".osrel", | |
239 | ".rodata", | |
240 | ".sbat", | |
241 | ".sdmagic", | |
242 | ".text", | |
243 | ] | |
244 | ||
245 | # By only dealing with allocating sections we effectively filter out debug sections. | |
246 | if not elf_s["sh_flags"] & SH_FLAGS.SHF_ALLOC: | |
247 | return False | |
248 | ||
249 | if elf_s.name in IGNORE_SECTIONS: | |
250 | return False | |
251 | ||
252 | # For paranoia we only handle sections we know of. Any new sections that come up should | |
253 | # be added to IGNORE_SECTIONS/COPY_SECTIONS and/or the linker script. | |
254 | if elf_s.name not in COPY_SECTIONS: | |
255 | raise RuntimeError(f"Unknown section {elf_s.name}, refusing.") | |
256 | ||
257 | if elf_s["sh_addr"] % SECTION_ALIGNMENT != 0: | |
258 | raise RuntimeError(f"Section {elf_s.name} is not aligned.") | |
259 | if len(elf_s.name) > 8: | |
260 | raise RuntimeError(f"ELF section name {elf_s.name} too long.") | |
261 | ||
262 | return True | |
263 | ||
264 | ||
265 | def convert_elf_section(elf_s: ELFSection) -> PeSection: | |
266 | pe_s = PeSection() | |
267 | pe_s.Name = elf_s.name.encode() | |
268 | pe_s.VirtualSize = elf_s.data_size | |
269 | pe_s.VirtualAddress = elf_s["sh_addr"] | |
270 | pe_s.SizeOfRawData = align_to(elf_s.data_size, FILE_ALIGNMENT) | |
271 | pe_s.data = bytearray(elf_s.data()) | |
272 | ||
273 | if elf_s["sh_flags"] & SH_FLAGS.SHF_EXECINSTR: | |
274 | pe_s.Characteristics = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE | |
275 | elif elf_s["sh_flags"] & SH_FLAGS.SHF_WRITE: | |
276 | pe_s.Characteristics = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE | |
277 | else: | |
278 | pe_s.Characteristics = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ | |
279 | ||
280 | return pe_s | |
281 | ||
282 | ||
283 | def copy_sections(elf: ELFFile, opt: PeOptionalHeader) -> list[PeSection]: | |
284 | sections = [] | |
285 | ||
286 | for elf_s in elf.iter_sections(): | |
287 | if not use_section(elf_s): | |
288 | continue | |
289 | ||
290 | pe_s = convert_elf_section(elf_s) | |
291 | if pe_s.Name == b".text": | |
292 | opt.BaseOfCode = pe_s.VirtualAddress | |
293 | opt.SizeOfCode += pe_s.VirtualSize | |
294 | else: | |
295 | opt.SizeOfInitializedData += pe_s.VirtualSize | |
296 | ||
297 | if pe_s.Name == b".data" and isinstance(opt, PeOptionalHeader32): | |
298 | opt.BaseOfData = pe_s.VirtualAddress | |
299 | ||
300 | sections.append(pe_s) | |
301 | ||
302 | return sections | |
303 | ||
304 | ||
305 | def apply_elf_relative_relocation( | |
306 | reloc: ElfRelocation, image_base: int, sections: list[PeSection], addend_size: int | |
307 | ): | |
308 | # fmt: off | |
309 | [target] = [ | |
310 | pe_s for pe_s in sections | |
311 | if pe_s.VirtualAddress <= reloc["r_offset"] < pe_s.VirtualAddress + len(pe_s.data) | |
312 | ] | |
313 | # fmt: on | |
314 | ||
315 | addend_offset = reloc["r_offset"] - target.VirtualAddress | |
316 | ||
317 | if reloc.is_RELA(): | |
318 | addend = reloc["r_addend"] | |
319 | else: | |
320 | addend = target.data[addend_offset : addend_offset + addend_size] | |
321 | addend = int.from_bytes(addend, byteorder="little") | |
322 | ||
323 | # This currently assumes that the ELF file has an image base of 0. | |
324 | value = (image_base + addend).to_bytes(addend_size, byteorder="little") | |
325 | target.data[addend_offset : addend_offset + addend_size] = value | |
326 | ||
327 | ||
328 | def convert_elf_reloc_table( | |
329 | elf: ELFFile, | |
330 | elf_reloc_table: ElfRelocationTable, | |
331 | image_base: int, | |
332 | sections: list[PeSection], | |
333 | pe_reloc_blocks: dict[int, PeRelocationBlock], | |
334 | ): | |
335 | NONE_RELOC = { | |
336 | "EM_386": ENUM_RELOC_TYPE_i386["R_386_NONE"], | |
337 | "EM_AARCH64": ENUM_RELOC_TYPE_AARCH64["R_AARCH64_NONE"], | |
338 | "EM_ARM": ENUM_RELOC_TYPE_ARM["R_ARM_NONE"], | |
339 | "EM_RISCV": 0, | |
340 | "EM_X86_64": ENUM_RELOC_TYPE_x64["R_X86_64_NONE"], | |
341 | }[elf["e_machine"]] | |
342 | ||
343 | RELATIVE_RELOC = { | |
344 | "EM_386": ENUM_RELOC_TYPE_i386["R_386_RELATIVE"], | |
345 | "EM_AARCH64": ENUM_RELOC_TYPE_AARCH64["R_AARCH64_RELATIVE"], | |
346 | "EM_ARM": ENUM_RELOC_TYPE_ARM["R_ARM_RELATIVE"], | |
347 | "EM_RISCV": 3, | |
348 | "EM_X86_64": ENUM_RELOC_TYPE_x64["R_X86_64_RELATIVE"], | |
349 | }[elf["e_machine"]] | |
350 | ||
351 | for reloc in elf_reloc_table.iter_relocations(): | |
352 | if reloc["r_info_type"] == NONE_RELOC: | |
353 | continue | |
354 | ||
355 | if reloc["r_info_type"] == RELATIVE_RELOC: | |
356 | apply_elf_relative_relocation( | |
357 | reloc, image_base, sections, elf.elfclass // 8 | |
358 | ) | |
359 | ||
360 | # Now that the ELF relocation has been applied, we can create a PE relocation. | |
361 | block_rva = reloc["r_offset"] & ~0xFFF | |
362 | if block_rva not in pe_reloc_blocks: | |
363 | pe_reloc_blocks[block_rva] = PeRelocationBlock(block_rva) | |
364 | ||
365 | entry = PeRelocationEntry() | |
366 | entry.Offset = reloc["r_offset"] & 0xFFF | |
367 | # REL_BASED_HIGHLOW or REL_BASED_DIR64 | |
368 | entry.Type = 3 if elf.elfclass == 32 else 10 | |
369 | pe_reloc_blocks[block_rva].entries.append(entry) | |
370 | ||
371 | continue | |
372 | ||
373 | raise RuntimeError(f"Unsupported relocation {reloc}") | |
374 | ||
375 | ||
376 | def convert_elf_relocations( | |
377 | elf: ELFFile, opt: PeOptionalHeader, sections: list[PeSection] | |
378 | ) -> PeSection: | |
379 | dynamic = elf.get_section_by_name(".dynamic") | |
380 | if dynamic is None: | |
381 | raise RuntimeError("ELF .dynamic section is missing.") | |
382 | ||
383 | [flags_tag] = dynamic.iter_tags("DT_FLAGS_1") | |
384 | if not flags_tag["d_val"] & ENUM_DT_FLAGS_1["DF_1_PIE"]: | |
385 | raise RuntimeError("ELF file is not a PIE.") | |
386 | ||
387 | pe_reloc_blocks: dict[int, PeRelocationBlock] = {} | |
388 | for reloc_type, reloc_table in dynamic.get_relocation_tables().items(): | |
389 | if reloc_type not in ["REL", "RELA"]: | |
390 | raise RuntimeError("Unsupported relocation type {elf_reloc_type}.") | |
391 | convert_elf_reloc_table( | |
392 | elf, reloc_table, opt.ImageBase, sections, pe_reloc_blocks | |
393 | ) | |
394 | ||
395 | data = bytearray() | |
396 | for rva in sorted(pe_reloc_blocks): | |
397 | block = pe_reloc_blocks[rva] | |
398 | n_relocs = len(block.entries) | |
399 | ||
400 | # Each block must start on a 32-bit boundary. Because each entry is 16 bits | |
401 | # the len has to be even. We pad by adding a none relocation. | |
402 | if n_relocs % 2 != 0: | |
403 | n_relocs += 1 | |
404 | block.entries.append(PeRelocationEntry()) | |
405 | ||
406 | block.BlockSize = ( | |
407 | sizeof(PeRelocationBlock) + sizeof(PeRelocationEntry) * n_relocs | |
408 | ) | |
409 | data += block | |
410 | for entry in sorted(block.entries, key=lambda e: e.Offset): | |
411 | data += entry | |
412 | ||
413 | pe_reloc_s = PeSection() | |
414 | pe_reloc_s.Name = b".reloc" | |
415 | pe_reloc_s.data = data | |
416 | pe_reloc_s.VirtualSize = len(data) | |
417 | pe_reloc_s.SizeOfRawData = align_to(len(data), FILE_ALIGNMENT) | |
418 | pe_reloc_s.VirtualAddress = align_to( | |
419 | sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT | |
420 | ) | |
421 | # CNT_INITIALIZED_DATA|MEM_READ|MEM_DISCARDABLE | |
422 | pe_reloc_s.Characteristics = 0x42000040 | |
423 | ||
424 | sections.append(pe_reloc_s) | |
425 | opt.SizeOfInitializedData += pe_reloc_s.VirtualSize | |
426 | return pe_reloc_s | |
427 | ||
428 | ||
429 | def write_pe( | |
430 | file, coff: PeCoffHeader, opt: PeOptionalHeader, sections: list[PeSection] | |
431 | ): | |
432 | file.write(b"MZ") | |
433 | file.seek(0x3C, io.SEEK_SET) | |
434 | file.write(PE_OFFSET.to_bytes(2, byteorder="little")) | |
435 | file.seek(PE_OFFSET, io.SEEK_SET) | |
436 | file.write(b"PE\0\0") | |
437 | file.write(coff) | |
438 | file.write(opt) | |
439 | ||
440 | offset = opt.SizeOfHeaders | |
441 | for pe_s in sorted(sections, key=lambda s: s.VirtualAddress): | |
442 | if pe_s.VirtualAddress < opt.SizeOfHeaders: | |
443 | # Linker script should make sure this does not happen. | |
444 | raise RuntimeError(f"Section {pe_s.Name} overlapping PE headers.") | |
445 | ||
446 | pe_s.PointerToRawData = offset | |
447 | file.write(pe_s) | |
448 | offset = align_to(offset + len(pe_s.data), FILE_ALIGNMENT) | |
449 | ||
450 | for pe_s in sections: | |
451 | file.seek(pe_s.PointerToRawData, io.SEEK_SET) | |
452 | file.write(pe_s.data) | |
453 | ||
454 | file.truncate(offset) | |
455 | ||
456 | ||
457 | def elf2efi(args: argparse.Namespace): | |
458 | elf = ELFFile(args.ELF) | |
459 | if not elf.little_endian: | |
460 | raise RuntimeError("ELF file is not little-endian.") | |
461 | if elf["e_type"] not in ["ET_DYN", "ET_EXEC"]: | |
462 | raise RuntimeError("Unsupported ELF type.") | |
463 | ||
464 | pe_arch = { | |
465 | "EM_386": 0x014C, | |
466 | "EM_AARCH64": 0xAA64, | |
467 | "EM_ARM": 0x01C2, | |
468 | "EM_RISCV": 0x5064, | |
469 | "EM_X86_64": 0x8664, | |
470 | }.get(elf["e_machine"]) | |
471 | if pe_arch is None: | |
472 | raise RuntimeError(f"Unuspported ELF arch {elf['e_machine']}") | |
473 | ||
474 | coff = PeCoffHeader() | |
475 | opt = PeOptionalHeader32() if elf.elfclass == 32 else PeOptionalHeader32Plus() | |
476 | ||
477 | # We relocate to a unique image base to reduce the chances for runtime relocation to occur. | |
478 | base_name = pathlib.Path(args.PE.name).name.encode() | |
479 | opt.ImageBase = int(hashlib.sha1(base_name).hexdigest()[0:8], 16) | |
480 | if elf.elfclass == 32: | |
481 | opt.ImageBase = (0x400000 + opt.ImageBase) & 0xFFFF0000 | |
482 | else: | |
483 | opt.ImageBase = (0x100000000 + opt.ImageBase) & 0x1FFFF0000 | |
484 | ||
485 | sections = copy_sections(elf, opt) | |
486 | pe_reloc_s = convert_elf_relocations(elf, opt, sections) | |
487 | ||
488 | coff.Machine = pe_arch | |
489 | coff.NumberOfSections = len(sections) | |
490 | coff.TimeDateStamp = int(os.environ.get("SOURCE_DATE_EPOCH", time.time())) | |
491 | coff.SizeOfOptionalHeader = sizeof(opt) | |
492 | # EXECUTABLE_IMAGE|LINE_NUMS_STRIPPED|LOCAL_SYMS_STRIPPED|DEBUG_STRIPPED | |
493 | # and (32BIT_MACHINE or LARGE_ADDRESS_AWARE) | |
494 | coff.Characteristics = 0x30E if elf.elfclass == 32 else 0x22E | |
495 | ||
496 | opt.AddressOfEntryPoint = elf["e_entry"] | |
497 | opt.SectionAlignment = SECTION_ALIGNMENT | |
498 | opt.FileAlignment = FILE_ALIGNMENT | |
499 | opt.MajorImageVersion = args.version_major | |
500 | opt.MinorImageVersion = args.version_minor | |
501 | opt.MajorSubsystemVersion = args.efi_major | |
502 | opt.MinorSubsystemVersion = args.efi_minor | |
503 | opt.Subsystem = args.subsystem | |
504 | opt.Magic = 0x10B if elf.elfclass == 32 else 0x20B | |
505 | opt.SizeOfImage = align_to( | |
506 | sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT | |
507 | ) | |
508 | opt.SizeOfHeaders = align_to( | |
509 | PE_OFFSET | |
510 | + coff.SizeOfOptionalHeader | |
511 | + sizeof(PeSection) * coff.NumberOfSections, | |
512 | FILE_ALIGNMENT, | |
513 | ) | |
514 | # DYNAMIC_BASE|NX_COMPAT|HIGH_ENTROPY_VA or DYNAMIC_BASE|NX_COMPAT | |
515 | opt.DllCharacteristics = 0x160 if elf.elfclass == 64 else 0x140 | |
516 | ||
517 | # These values are taken from a natively built PE binary (although, unused by EDK2/EFI). | |
518 | opt.SizeOfStackReserve = 0x100000 | |
519 | opt.SizeOfStackCommit = 0x001000 | |
520 | opt.SizeOfHeapReserve = 0x100000 | |
521 | opt.SizeOfHeapCommit = 0x001000 | |
522 | ||
523 | opt.NumberOfRvaAndSizes = N_DATA_DIRECTORY_ENTRIES | |
524 | opt.BaseRelocationTable = PeDataDirectory( | |
525 | pe_reloc_s.VirtualAddress, pe_reloc_s.VirtualSize | |
526 | ) | |
527 | ||
528 | write_pe(args.PE, coff, opt, sections) | |
529 | ||
530 | ||
531 | def main(): | |
532 | parser = argparse.ArgumentParser(description="Convert ELF binaries to PE/EFI") | |
533 | parser.add_argument( | |
534 | "--version-major", | |
535 | type=int, | |
536 | default=0, | |
537 | help="Major image version of EFI image", | |
538 | ) | |
539 | parser.add_argument( | |
540 | "--version-minor", | |
541 | type=int, | |
542 | default=0, | |
543 | help="Minor image version of EFI image", | |
544 | ) | |
545 | parser.add_argument( | |
546 | "--efi-major", | |
547 | type=int, | |
548 | default=0, | |
549 | help="Minimum major EFI subsystem version", | |
550 | ) | |
551 | parser.add_argument( | |
552 | "--efi-minor", | |
553 | type=int, | |
554 | default=0, | |
555 | help="Minimum minor EFI subsystem version", | |
556 | ) | |
557 | parser.add_argument( | |
558 | "--subsystem", | |
559 | type=int, | |
560 | default=10, | |
561 | help="PE subsystem", | |
562 | ) | |
563 | parser.add_argument( | |
564 | "ELF", | |
565 | type=argparse.FileType("rb"), | |
566 | help="Input ELF file", | |
567 | ) | |
568 | parser.add_argument( | |
569 | "PE", | |
570 | type=argparse.FileType("wb"), | |
571 | help="Output PE/EFI file", | |
572 | ) | |
573 | ||
574 | elf2efi(parser.parse_args()) | |
575 | ||
576 | ||
577 | if __name__ == "__main__": | |
578 | main() |