2023-02-27 18:54:48 +03:00
#!/usr/bin/env python3
# SPDX-License-Identifier: LGPL-2.1-or-later
# Convert ELF static PIE to PE/EFI image.
# To do so we simply copy desired ELF sections while preserving their memory layout to ensure that
# code still runs as expected. We then translate ELF relocations to PE relocations so that the EFI
# loader/firmware can properly load the binary to any address at runtime.
#
# To make this as painless as possible we only operate on static PIEs as they should only contain
# base relocations that are easy to handle as they have a one-to-one mapping to PE relocations.
#
# EDK2 does a similar process using their GenFw tool. The main difference is that they use the
# --emit-relocs linker flag, which emits a lot of different (static) ELF relocation types that have
# to be handled differently for each architecture and is overall more work than its worth.
#
# Note that on arches where binutils has PE support (x86/x86_64 mostly, aarch64 only recently)
# objcopy can be used to convert ELF to PE. But this will still not convert ELF relocations, making
# the resulting binary useless. gnu-efi relies on this method and contains a stub that performs the
# ELF dynamic relocations at runtime.
2023-08-09 22:57:47 +03:00
# pylint: disable=attribute-defined-outside-init
2023-02-27 18:54:48 +03:00
import argparse
import hashlib
import io
import os
import pathlib
import time
2023-05-23 20:00:52 +03:00
import typing
2023-02-27 18:54:48 +03:00
from ctypes import (
c_char ,
c_uint8 ,
c_uint16 ,
c_uint32 ,
c_uint64 ,
LittleEndianStructure ,
sizeof ,
)
from elftools . elf . constants import SH_FLAGS
2023-09-28 17:09:42 +03:00
from elftools . elf . elffile import ELFFile
2023-02-27 18:54:48 +03:00
from elftools . elf . enums import (
ENUM_DT_FLAGS_1 ,
ENUM_RELOC_TYPE_AARCH64 ,
ENUM_RELOC_TYPE_ARM ,
ENUM_RELOC_TYPE_i386 ,
ENUM_RELOC_TYPE_x64 ,
)
from elftools . elf . relocation import (
Relocation as ElfRelocation ,
RelocationTable as ElfRelocationTable ,
)
class PeCoffHeader ( LittleEndianStructure ) :
_fields_ = (
( " Machine " , c_uint16 ) ,
( " NumberOfSections " , c_uint16 ) ,
( " TimeDateStamp " , c_uint32 ) ,
( " PointerToSymbolTable " , c_uint32 ) ,
( " NumberOfSymbols " , c_uint32 ) ,
( " SizeOfOptionalHeader " , c_uint16 ) ,
( " Characteristics " , c_uint16 ) ,
)
class PeDataDirectory ( LittleEndianStructure ) :
_fields_ = (
( " VirtualAddress " , c_uint32 ) ,
( " Size " , c_uint32 ) ,
)
class PeRelocationBlock ( LittleEndianStructure ) :
_fields_ = (
( " PageRVA " , c_uint32 ) ,
( " BlockSize " , c_uint32 ) ,
)
def __init__ ( self , PageRVA : int ) :
super ( ) . __init__ ( PageRVA )
2023-07-14 15:16:44 +03:00
self . entries : typing . List [ PeRelocationEntry ] = [ ]
2023-02-27 18:54:48 +03:00
class PeRelocationEntry ( LittleEndianStructure ) :
_fields_ = (
( " Offset " , c_uint16 , 12 ) ,
( " Type " , c_uint16 , 4 ) ,
)
class PeOptionalHeaderStart ( LittleEndianStructure ) :
_fields_ = (
( " Magic " , c_uint16 ) ,
( " MajorLinkerVersion " , c_uint8 ) ,
( " MinorLinkerVersion " , c_uint8 ) ,
( " SizeOfCode " , c_uint32 ) ,
( " SizeOfInitializedData " , c_uint32 ) ,
( " SizeOfUninitializedData " , c_uint32 ) ,
( " AddressOfEntryPoint " , c_uint32 ) ,
( " BaseOfCode " , c_uint32 ) ,
)
class PeOptionalHeaderMiddle ( LittleEndianStructure ) :
_fields_ = (
( " SectionAlignment " , c_uint32 ) ,
( " FileAlignment " , c_uint32 ) ,
( " MajorOperatingSystemVersion " , c_uint16 ) ,
( " MinorOperatingSystemVersion " , c_uint16 ) ,
( " MajorImageVersion " , c_uint16 ) ,
( " MinorImageVersion " , c_uint16 ) ,
( " MajorSubsystemVersion " , c_uint16 ) ,
( " MinorSubsystemVersion " , c_uint16 ) ,
( " Win32VersionValue " , c_uint32 ) ,
( " SizeOfImage " , c_uint32 ) ,
( " SizeOfHeaders " , c_uint32 ) ,
( " CheckSum " , c_uint32 ) ,
( " Subsystem " , c_uint16 ) ,
( " DllCharacteristics " , c_uint16 ) ,
)
class PeOptionalHeaderEnd ( LittleEndianStructure ) :
_fields_ = (
( " LoaderFlags " , c_uint32 ) ,
( " NumberOfRvaAndSizes " , c_uint32 ) ,
( " ExportTable " , PeDataDirectory ) ,
( " ImportTable " , PeDataDirectory ) ,
( " ResourceTable " , PeDataDirectory ) ,
( " ExceptionTable " , PeDataDirectory ) ,
( " CertificateTable " , PeDataDirectory ) ,
( " BaseRelocationTable " , PeDataDirectory ) ,
( " Debug " , PeDataDirectory ) ,
( " Architecture " , PeDataDirectory ) ,
( " GlobalPtr " , PeDataDirectory ) ,
( " TLSTable " , PeDataDirectory ) ,
( " LoadConfigTable " , PeDataDirectory ) ,
( " BoundImport " , PeDataDirectory ) ,
( " IAT " , PeDataDirectory ) ,
( " DelayImportDescriptor " , PeDataDirectory ) ,
( " CLRRuntimeHeader " , PeDataDirectory ) ,
( " Reserved " , PeDataDirectory ) ,
)
class PeOptionalHeader ( LittleEndianStructure ) :
pass
class PeOptionalHeader32 ( PeOptionalHeader ) :
_anonymous_ = ( " Start " , " Middle " , " End " )
_fields_ = (
( " Start " , PeOptionalHeaderStart ) ,
( " BaseOfData " , c_uint32 ) ,
( " ImageBase " , c_uint32 ) ,
( " Middle " , PeOptionalHeaderMiddle ) ,
( " SizeOfStackReserve " , c_uint32 ) ,
( " SizeOfStackCommit " , c_uint32 ) ,
( " SizeOfHeapReserve " , c_uint32 ) ,
( " SizeOfHeapCommit " , c_uint32 ) ,
( " End " , PeOptionalHeaderEnd ) ,
)
class PeOptionalHeader32Plus ( PeOptionalHeader ) :
_anonymous_ = ( " Start " , " Middle " , " End " )
_fields_ = (
( " Start " , PeOptionalHeaderStart ) ,
( " ImageBase " , c_uint64 ) ,
( " Middle " , PeOptionalHeaderMiddle ) ,
( " SizeOfStackReserve " , c_uint64 ) ,
( " SizeOfStackCommit " , c_uint64 ) ,
( " SizeOfHeapReserve " , c_uint64 ) ,
( " SizeOfHeapCommit " , c_uint64 ) ,
( " End " , PeOptionalHeaderEnd ) ,
)
class PeSection ( LittleEndianStructure ) :
_fields_ = (
( " Name " , c_char * 8 ) ,
( " VirtualSize " , c_uint32 ) ,
( " VirtualAddress " , c_uint32 ) ,
( " SizeOfRawData " , c_uint32 ) ,
( " PointerToRawData " , c_uint32 ) ,
( " PointerToRelocations " , c_uint32 ) ,
( " PointerToLinenumbers " , c_uint32 ) ,
( " NumberOfRelocations " , c_uint16 ) ,
( " NumberOfLinenumbers " , c_uint16 ) ,
( " Characteristics " , c_uint32 ) ,
)
def __init__ ( self ) :
super ( ) . __init__ ( )
self . data = bytearray ( )
N_DATA_DIRECTORY_ENTRIES = 16
assert sizeof ( PeSection ) == 40
assert sizeof ( PeCoffHeader ) == 20
assert sizeof ( PeOptionalHeader32 ) == 224
assert sizeof ( PeOptionalHeader32Plus ) == 240
2023-09-28 17:09:42 +03:00
PE_CHARACTERISTICS_RX = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE
PE_CHARACTERISTICS_RW = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE
PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ
IGNORE_SECTIONS = [
" .eh_frame " ,
" .eh_frame_hdr " ,
" .ARM.exidx " ,
]
IGNORE_SECTION_TYPES = [
" SHT_DYNAMIC " ,
" SHT_DYNSYM " ,
" SHT_GNU_ATTRIBUTES " ,
" SHT_GNU_HASH " ,
" SHT_HASH " ,
" SHT_NOTE " ,
" SHT_REL " ,
" SHT_RELA " ,
" SHT_RELR " ,
" SHT_STRTAB " ,
" SHT_SYMTAB " ,
]
2023-02-27 18:54:48 +03:00
# EFI mandates 4KiB memory pages.
SECTION_ALIGNMENT = 4096
FILE_ALIGNMENT = 512
# Nobody cares about DOS headers, so put the PE header right after.
PE_OFFSET = 64
2023-07-30 21:59:04 +03:00
PE_MAGIC = b " PE \0 \0 "
2023-02-27 18:54:48 +03:00
def align_to ( x : int , align : int ) - > int :
return ( x + align - 1 ) & ~ ( align - 1 )
2023-09-28 17:09:42 +03:00
def align_down ( x : int , align : int ) - > int :
return x & ~ ( align - 1 )
2023-02-27 18:54:48 +03:00
2023-09-22 13:13:09 +03:00
def next_section_address ( sections : typing . List [ PeSection ] ) - > int :
return align_to (
sections [ - 1 ] . VirtualAddress + sections [ - 1 ] . VirtualSize , SECTION_ALIGNMENT
)
2023-09-28 17:09:42 +03:00
def iter_copy_sections ( elf : ELFFile ) - > typing . Iterator [ PeSection ] :
pe_s = None
2023-02-27 18:54:48 +03:00
2023-09-28 17:09:42 +03:00
# This is essentially the same as copying by ELF load segments, except that we assemble them
# manually, so that we can easily strip unwanted sections. We try to only discard things we know
# about so that there are no surprises.
2023-02-27 18:54:48 +03:00
2023-09-28 18:54:35 +03:00
relro = None
for elf_seg in elf . iter_segments ( ) :
if elf_seg [ " p_type " ] == " PT_LOAD " and elf_seg [ " p_align " ] != SECTION_ALIGNMENT :
raise RuntimeError ( " ELF segments are not properly aligned. " )
elif elf_seg [ " p_type " ] == " PT_GNU_RELRO " :
relro = elf_seg
2023-09-28 17:09:42 +03:00
for elf_s in elf . iter_sections ( ) :
if (
elf_s [ " sh_flags " ] & SH_FLAGS . SHF_ALLOC == 0
or elf_s [ " sh_type " ] in IGNORE_SECTION_TYPES
or elf_s . name in IGNORE_SECTIONS
) :
continue
if elf_s [ " sh_type " ] not in [ " SHT_PROGBITS " , " SHT_NOBITS " ] :
raise RuntimeError ( f " Unknown section { elf_s . name } . " )
2023-02-27 18:54:48 +03:00
2023-09-28 17:09:42 +03:00
if elf_s [ " sh_flags " ] & SH_FLAGS . SHF_EXECINSTR :
rwx = PE_CHARACTERISTICS_RX
elif elf_s [ " sh_flags " ] & SH_FLAGS . SHF_WRITE :
rwx = PE_CHARACTERISTICS_RW
else :
rwx = PE_CHARACTERISTICS_R
2023-02-27 18:54:48 +03:00
2023-09-28 18:54:35 +03:00
# PE images are always relro.
if relro and relro . section_in_segment ( elf_s ) :
rwx = PE_CHARACTERISTICS_R
2023-09-28 17:09:42 +03:00
if pe_s and pe_s . Characteristics != rwx :
yield pe_s
pe_s = None
2023-02-27 18:54:48 +03:00
2023-09-28 17:09:42 +03:00
if pe_s :
# Insert padding to properly align the section.
pad_len = elf_s [ " sh_addr " ] - pe_s . VirtualAddress - len ( pe_s . data )
pe_s . data + = bytearray ( pad_len ) + elf_s . data ( )
else :
pe_s = PeSection ( )
pe_s . VirtualAddress = elf_s [ " sh_addr " ]
pe_s . Characteristics = rwx
pe_s . data = elf_s . data ( )
2023-02-27 18:54:48 +03:00
2023-09-28 17:09:42 +03:00
if pe_s :
yield pe_s
2023-02-27 18:54:48 +03:00
2023-09-28 17:09:42 +03:00
def convert_sections ( elf : ELFFile , opt : PeOptionalHeader ) - > typing . List [ PeSection ] :
last_vma = 0
2023-02-27 18:54:48 +03:00
sections = [ ]
2023-09-28 17:09:42 +03:00
for pe_s in iter_copy_sections ( elf ) :
# Truncate the VMA to the nearest page and insert appropriate padding. This should not
# cause any overlap as this is pretty much how ELF *segments* are loaded/mmapped anyways.
# The ELF sections inside should also be properly aligned as we reuse the ELF VMA layout
# for the PE image.
vma = pe_s . VirtualAddress
pe_s . VirtualAddress = align_down ( vma , SECTION_ALIGNMENT )
pe_s . data = bytearray ( vma - pe_s . VirtualAddress ) + pe_s . data
pe_s . VirtualSize = len ( pe_s . data )
pe_s . SizeOfRawData = align_to ( len ( pe_s . data ) , FILE_ALIGNMENT )
pe_s . Name = {
PE_CHARACTERISTICS_RX : b " .text " ,
PE_CHARACTERISTICS_RW : b " .data " ,
PE_CHARACTERISTICS_R : b " .rodata " ,
} [ pe_s . Characteristics ]
# This can happen if not building with `-z separate-code`.
if pe_s . VirtualAddress < last_vma :
raise RuntimeError ( " Overlapping PE sections. " )
last_vma = pe_s . VirtualAddress + pe_s . VirtualSize
2023-02-27 18:54:48 +03:00
if pe_s . Name == b " .text " :
opt . BaseOfCode = pe_s . VirtualAddress
opt . SizeOfCode + = pe_s . VirtualSize
else :
opt . SizeOfInitializedData + = pe_s . VirtualSize
if pe_s . Name == b " .data " and isinstance ( opt , PeOptionalHeader32 ) :
opt . BaseOfData = pe_s . VirtualAddress
sections . append ( pe_s )
return sections
2023-09-22 13:15:55 +03:00
def copy_sections (
elf : ELFFile ,
opt : PeOptionalHeader ,
input_names : str ,
sections : typing . List [ PeSection ] ,
) :
for name in input_names . split ( " , " ) :
elf_s = elf . get_section_by_name ( name )
if not elf_s :
continue
if elf_s . data_alignment > 1 and SECTION_ALIGNMENT % elf_s . data_alignment != 0 :
raise RuntimeError ( f " ELF section { name } is not aligned. " )
if elf_s [ " sh_flags " ] & ( SH_FLAGS . SHF_EXECINSTR | SH_FLAGS . SHF_WRITE ) != 0 :
raise RuntimeError ( f " ELF section { name } is not read-only data. " )
pe_s = PeSection ( )
pe_s . Name = name . encode ( )
pe_s . data = elf_s . data ( )
pe_s . VirtualAddress = next_section_address ( sections )
pe_s . VirtualSize = len ( elf_s . data ( ) )
pe_s . SizeOfRawData = align_to ( len ( elf_s . data ( ) ) , FILE_ALIGNMENT )
pe_s . Characteristics = PE_CHARACTERISTICS_R
opt . SizeOfInitializedData + = pe_s . VirtualSize
sections . append ( pe_s )
2023-02-27 18:54:48 +03:00
def apply_elf_relative_relocation (
2023-07-30 21:59:04 +03:00
reloc : ElfRelocation ,
image_base : int ,
sections : typing . List [ PeSection ] ,
addend_size : int ,
2023-02-27 18:54:48 +03:00
) :
# fmt: off
[ target ] = [
pe_s for pe_s in sections
if pe_s . VirtualAddress < = reloc [ " r_offset " ] < pe_s . VirtualAddress + len ( pe_s . data )
]
# fmt: on
addend_offset = reloc [ " r_offset " ] - target . VirtualAddress
if reloc . is_RELA ( ) :
addend = reloc [ " r_addend " ]
else :
addend = target . data [ addend_offset : addend_offset + addend_size ]
addend = int . from_bytes ( addend , byteorder = " little " )
value = ( image_base + addend ) . to_bytes ( addend_size , byteorder = " little " )
target . data [ addend_offset : addend_offset + addend_size ] = value
def convert_elf_reloc_table (
elf : ELFFile ,
elf_reloc_table : ElfRelocationTable ,
2023-09-28 17:09:42 +03:00
elf_image_base : int ,
2023-07-14 15:16:44 +03:00
sections : typing . List [ PeSection ] ,
pe_reloc_blocks : typing . Dict [ int , PeRelocationBlock ] ,
2023-02-27 18:54:48 +03:00
) :
NONE_RELOC = {
" EM_386 " : ENUM_RELOC_TYPE_i386 [ " R_386_NONE " ] ,
" EM_AARCH64 " : ENUM_RELOC_TYPE_AARCH64 [ " R_AARCH64_NONE " ] ,
" EM_ARM " : ENUM_RELOC_TYPE_ARM [ " R_ARM_NONE " ] ,
2023-02-28 20:05:18 +03:00
" EM_LOONGARCH " : 0 ,
2023-02-27 18:54:48 +03:00
" EM_RISCV " : 0 ,
" EM_X86_64 " : ENUM_RELOC_TYPE_x64 [ " R_X86_64_NONE " ] ,
} [ elf [ " e_machine " ] ]
RELATIVE_RELOC = {
" EM_386 " : ENUM_RELOC_TYPE_i386 [ " R_386_RELATIVE " ] ,
" EM_AARCH64 " : ENUM_RELOC_TYPE_AARCH64 [ " R_AARCH64_RELATIVE " ] ,
" EM_ARM " : ENUM_RELOC_TYPE_ARM [ " R_ARM_RELATIVE " ] ,
2023-02-28 20:05:18 +03:00
" EM_LOONGARCH " : 3 ,
2023-02-27 18:54:48 +03:00
" EM_RISCV " : 3 ,
" EM_X86_64 " : ENUM_RELOC_TYPE_x64 [ " R_X86_64_RELATIVE " ] ,
} [ elf [ " e_machine " ] ]
for reloc in elf_reloc_table . iter_relocations ( ) :
if reloc [ " r_info_type " ] == NONE_RELOC :
continue
if reloc [ " r_info_type " ] == RELATIVE_RELOC :
apply_elf_relative_relocation (
2023-09-28 17:09:42 +03:00
reloc , elf_image_base , sections , elf . elfclass / / 8
2023-02-27 18:54:48 +03:00
)
# Now that the ELF relocation has been applied, we can create a PE relocation.
block_rva = reloc [ " r_offset " ] & ~ 0xFFF
if block_rva not in pe_reloc_blocks :
pe_reloc_blocks [ block_rva ] = PeRelocationBlock ( block_rva )
entry = PeRelocationEntry ( )
entry . Offset = reloc [ " r_offset " ] & 0xFFF
# REL_BASED_HIGHLOW or REL_BASED_DIR64
entry . Type = 3 if elf . elfclass == 32 else 10
pe_reloc_blocks [ block_rva ] . entries . append ( entry )
continue
raise RuntimeError ( f " Unsupported relocation { reloc } " )
def convert_elf_relocations (
2023-09-28 17:09:42 +03:00
elf : ELFFile ,
opt : PeOptionalHeader ,
sections : typing . List [ PeSection ] ,
minimum_sections : int ,
2023-05-23 20:00:52 +03:00
) - > typing . Optional [ PeSection ] :
2023-02-27 18:54:48 +03:00
dynamic = elf . get_section_by_name ( " .dynamic " )
if dynamic is None :
raise RuntimeError ( " ELF .dynamic section is missing. " )
[ flags_tag ] = dynamic . iter_tags ( " DT_FLAGS_1 " )
if not flags_tag [ " d_val " ] & ENUM_DT_FLAGS_1 [ " DF_1_PIE " ] :
raise RuntimeError ( " ELF file is not a PIE. " )
2023-09-28 17:22:13 +03:00
# This checks that the ELF image base is 0.
symtab = elf . get_section_by_name ( " .symtab " )
if symtab :
exe_start = symtab . get_symbol_by_name ( " __executable_start " )
if exe_start and exe_start [ 0 ] [ " st_value " ] != 0 :
raise RuntimeError ( " Unexpected ELF image base. " )
2023-09-28 17:09:42 +03:00
opt . SizeOfHeaders = align_to (
PE_OFFSET
+ len ( PE_MAGIC )
+ sizeof ( PeCoffHeader )
+ sizeof ( opt )
+ sizeof ( PeSection ) * max ( len ( sections ) + 1 , minimum_sections ) ,
FILE_ALIGNMENT ,
)
# We use the basic VMA layout from the ELF image in the PE image. This could cause the first
# section to overlap the PE image headers during runtime at VMA 0. We can simply apply a fixed
# offset relative to the PE image base when applying/converting ELF relocations. Afterwards we
# just have to apply the offset to the PE addresses so that the PE relocations work correctly on
# the ELF portions of the image.
segment_offset = 0
if sections [ 0 ] . VirtualAddress < opt . SizeOfHeaders :
segment_offset = align_to (
opt . SizeOfHeaders - sections [ 0 ] . VirtualAddress , SECTION_ALIGNMENT
)
opt . AddressOfEntryPoint = elf [ " e_entry " ] + segment_offset
opt . BaseOfCode + = segment_offset
if isinstance ( opt , PeOptionalHeader32 ) :
opt . BaseOfData + = segment_offset
2023-07-14 15:16:44 +03:00
pe_reloc_blocks : typing . Dict [ int , PeRelocationBlock ] = { }
2023-02-27 18:54:48 +03:00
for reloc_type , reloc_table in dynamic . get_relocation_tables ( ) . items ( ) :
if reloc_type not in [ " REL " , " RELA " ] :
raise RuntimeError ( " Unsupported relocation type {elf_reloc_type} . " )
convert_elf_reloc_table (
2023-09-28 17:09:42 +03:00
elf , reloc_table , opt . ImageBase + segment_offset , sections , pe_reloc_blocks
2023-02-27 18:54:48 +03:00
)
2023-09-28 17:09:42 +03:00
for pe_s in sections :
pe_s . VirtualAddress + = segment_offset
2023-05-23 20:00:52 +03:00
if len ( pe_reloc_blocks ) == 0 :
return None
2023-02-27 18:54:48 +03:00
data = bytearray ( )
for rva in sorted ( pe_reloc_blocks ) :
block = pe_reloc_blocks [ rva ]
n_relocs = len ( block . entries )
# Each block must start on a 32-bit boundary. Because each entry is 16 bits
# the len has to be even. We pad by adding a none relocation.
if n_relocs % 2 != 0 :
n_relocs + = 1
block . entries . append ( PeRelocationEntry ( ) )
2023-09-28 17:09:42 +03:00
block . PageRVA + = segment_offset
2023-02-27 18:54:48 +03:00
block . BlockSize = (
sizeof ( PeRelocationBlock ) + sizeof ( PeRelocationEntry ) * n_relocs
)
data + = block
for entry in sorted ( block . entries , key = lambda e : e . Offset ) :
data + = entry
pe_reloc_s = PeSection ( )
pe_reloc_s . Name = b " .reloc "
pe_reloc_s . data = data
2023-09-22 13:13:09 +03:00
pe_reloc_s . VirtualAddress = next_section_address ( sections )
2023-02-27 18:54:48 +03:00
pe_reloc_s . VirtualSize = len ( data )
pe_reloc_s . SizeOfRawData = align_to ( len ( data ) , FILE_ALIGNMENT )
# CNT_INITIALIZED_DATA|MEM_READ|MEM_DISCARDABLE
pe_reloc_s . Characteristics = 0x42000040
sections . append ( pe_reloc_s )
opt . SizeOfInitializedData + = pe_reloc_s . VirtualSize
return pe_reloc_s
def write_pe (
2023-07-14 15:16:44 +03:00
file , coff : PeCoffHeader , opt : PeOptionalHeader , sections : typing . List [ PeSection ]
2023-02-27 18:54:48 +03:00
) :
file . write ( b " MZ " )
file . seek ( 0x3C , io . SEEK_SET )
file . write ( PE_OFFSET . to_bytes ( 2 , byteorder = " little " ) )
file . seek ( PE_OFFSET , io . SEEK_SET )
2023-07-30 21:59:04 +03:00
file . write ( PE_MAGIC )
2023-02-27 18:54:48 +03:00
file . write ( coff )
file . write ( opt )
offset = opt . SizeOfHeaders
for pe_s in sorted ( sections , key = lambda s : s . VirtualAddress ) :
if pe_s . VirtualAddress < opt . SizeOfHeaders :
# Linker script should make sure this does not happen.
raise RuntimeError ( f " Section { pe_s . Name } overlapping PE headers. " )
pe_s . PointerToRawData = offset
file . write ( pe_s )
offset = align_to ( offset + len ( pe_s . data ) , FILE_ALIGNMENT )
2023-07-30 21:59:04 +03:00
assert file . tell ( ) < = opt . SizeOfHeaders
2023-02-27 18:54:48 +03:00
for pe_s in sections :
file . seek ( pe_s . PointerToRawData , io . SEEK_SET )
file . write ( pe_s . data )
file . truncate ( offset )
def elf2efi ( args : argparse . Namespace ) :
elf = ELFFile ( args . ELF )
if not elf . little_endian :
raise RuntimeError ( " ELF file is not little-endian. " )
if elf [ " e_type " ] not in [ " ET_DYN " , " ET_EXEC " ] :
raise RuntimeError ( " Unsupported ELF type. " )
pe_arch = {
" EM_386 " : 0x014C ,
" EM_AARCH64 " : 0xAA64 ,
" EM_ARM " : 0x01C2 ,
2023-02-28 20:05:18 +03:00
" EM_LOONGARCH " : 0x6232 if elf . elfclass == 32 else 0x6264 ,
" EM_RISCV " : 0x5032 if elf . elfclass == 32 else 0x5064 ,
2023-02-27 18:54:48 +03:00
" EM_X86_64 " : 0x8664 ,
} . get ( elf [ " e_machine " ] )
if pe_arch is None :
2023-09-21 20:06:55 +03:00
raise RuntimeError ( f " Unsupported ELF arch { elf [ ' e_machine ' ] } " )
2023-02-27 18:54:48 +03:00
coff = PeCoffHeader ( )
opt = PeOptionalHeader32 ( ) if elf . elfclass == 32 else PeOptionalHeader32Plus ( )
# We relocate to a unique image base to reduce the chances for runtime relocation to occur.
base_name = pathlib . Path ( args . PE . name ) . name . encode ( )
opt . ImageBase = int ( hashlib . sha1 ( base_name ) . hexdigest ( ) [ 0 : 8 ] , 16 )
if elf . elfclass == 32 :
opt . ImageBase = ( 0x400000 + opt . ImageBase ) & 0xFFFF0000
else :
opt . ImageBase = ( 0x100000000 + opt . ImageBase ) & 0x1FFFF0000
2023-09-28 17:09:42 +03:00
sections = convert_sections ( elf , opt )
2023-09-22 13:15:55 +03:00
copy_sections ( elf , opt , args . copy_sections , sections )
2023-09-28 17:09:42 +03:00
pe_reloc_s = convert_elf_relocations ( elf , opt , sections , args . minimum_sections )
2023-02-27 18:54:48 +03:00
coff . Machine = pe_arch
coff . NumberOfSections = len ( sections )
coff . TimeDateStamp = int ( os . environ . get ( " SOURCE_DATE_EPOCH " , time . time ( ) ) )
coff . SizeOfOptionalHeader = sizeof ( opt )
# EXECUTABLE_IMAGE|LINE_NUMS_STRIPPED|LOCAL_SYMS_STRIPPED|DEBUG_STRIPPED
# and (32BIT_MACHINE or LARGE_ADDRESS_AWARE)
coff . Characteristics = 0x30E if elf . elfclass == 32 else 0x22E
opt . SectionAlignment = SECTION_ALIGNMENT
opt . FileAlignment = FILE_ALIGNMENT
opt . MajorImageVersion = args . version_major
opt . MinorImageVersion = args . version_minor
opt . MajorSubsystemVersion = args . efi_major
opt . MinorSubsystemVersion = args . efi_minor
opt . Subsystem = args . subsystem
opt . Magic = 0x10B if elf . elfclass == 32 else 0x20B
2023-09-22 13:13:09 +03:00
opt . SizeOfImage = next_section_address ( sections )
2023-02-27 18:54:48 +03:00
# DYNAMIC_BASE|NX_COMPAT|HIGH_ENTROPY_VA or DYNAMIC_BASE|NX_COMPAT
opt . DllCharacteristics = 0x160 if elf . elfclass == 64 else 0x140
# These values are taken from a natively built PE binary (although, unused by EDK2/EFI).
opt . SizeOfStackReserve = 0x100000
opt . SizeOfStackCommit = 0x001000
opt . SizeOfHeapReserve = 0x100000
opt . SizeOfHeapCommit = 0x001000
opt . NumberOfRvaAndSizes = N_DATA_DIRECTORY_ENTRIES
2023-05-23 20:00:52 +03:00
if pe_reloc_s :
opt . BaseRelocationTable = PeDataDirectory (
pe_reloc_s . VirtualAddress , pe_reloc_s . VirtualSize
)
2023-02-27 18:54:48 +03:00
write_pe ( args . PE , coff , opt , sections )
def main ( ) :
parser = argparse . ArgumentParser ( description = " Convert ELF binaries to PE/EFI " )
parser . add_argument (
" --version-major " ,
type = int ,
default = 0 ,
help = " Major image version of EFI image " ,
)
parser . add_argument (
" --version-minor " ,
type = int ,
default = 0 ,
help = " Minor image version of EFI image " ,
)
parser . add_argument (
" --efi-major " ,
type = int ,
default = 0 ,
help = " Minimum major EFI subsystem version " ,
)
parser . add_argument (
" --efi-minor " ,
type = int ,
default = 0 ,
help = " Minimum minor EFI subsystem version " ,
)
parser . add_argument (
" --subsystem " ,
type = int ,
default = 10 ,
help = " PE subsystem " ,
)
parser . add_argument (
" ELF " ,
type = argparse . FileType ( " rb " ) ,
help = " Input ELF file " ,
)
parser . add_argument (
" PE " ,
type = argparse . FileType ( " wb " ) ,
help = " Output PE/EFI file " ,
)
2023-05-23 03:43:59 +03:00
parser . add_argument (
" --minimum-sections " ,
type = int ,
default = 0 ,
help = " Minimum number of sections to leave space for " ,
)
2023-09-22 13:15:55 +03:00
parser . add_argument (
" --copy-sections " ,
type = str ,
default = " " ,
help = " Copy these sections if found " ,
)
2023-02-27 18:54:48 +03:00
elf2efi ( parser . parse_args ( ) )
if __name__ == " __main__ " :
main ( )