elf2efi: apply "ruff format" and "ruff check --fix"

This commit is contained in:
Yu Watanabe
2026-05-18 02:44:47 +09:00
parent 7e62c9be6f
commit 0df17e665b
+235 -236
View File
@@ -31,12 +31,12 @@ import sys
import time import time
import typing import typing
from ctypes import ( from ctypes import (
LittleEndianStructure,
c_char, c_char,
c_uint8, c_uint8,
c_uint16, c_uint16,
c_uint32, c_uint32,
c_uint64, c_uint64,
LittleEndianStructure,
sizeof, sizeof,
) )
@@ -46,28 +46,28 @@ from elftools.elf import elffile
class PeCoffHeader(LittleEndianStructure): class PeCoffHeader(LittleEndianStructure):
_fields_ = ( _fields_ = (
("Machine", c_uint16), ('Machine', c_uint16),
("NumberOfSections", c_uint16), ('NumberOfSections', c_uint16),
("TimeDateStamp", c_uint32), ('TimeDateStamp', c_uint32),
("PointerToSymbolTable", c_uint32), ('PointerToSymbolTable', c_uint32),
("NumberOfSymbols", c_uint32), ('NumberOfSymbols', c_uint32),
("SizeOfOptionalHeader", c_uint16), ('SizeOfOptionalHeader', c_uint16),
("Characteristics", c_uint16), ('Characteristics', c_uint16),
) ) # fmt: skip
class PeDataDirectory(LittleEndianStructure): class PeDataDirectory(LittleEndianStructure):
_fields_ = ( _fields_ = (
("VirtualAddress", c_uint32), ('VirtualAddress', c_uint32),
("Size", c_uint32), ('Size', c_uint32),
) ) # fmt: skip
class PeRelocationBlock(LittleEndianStructure): class PeRelocationBlock(LittleEndianStructure):
_fields_ = ( _fields_ = (
("PageRVA", c_uint32), ('PageRVA', c_uint32),
("BlockSize", c_uint32), ('BlockSize', c_uint32),
) ) # fmt: skip
def __init__(self, PageRVA: int): def __init__(self, PageRVA: int):
super().__init__(PageRVA) super().__init__(PageRVA)
@@ -76,64 +76,64 @@ class PeRelocationBlock(LittleEndianStructure):
class PeRelocationEntry(LittleEndianStructure): class PeRelocationEntry(LittleEndianStructure):
_fields_ = ( _fields_ = (
("Offset", c_uint16, 12), ('Offset', c_uint16, 12),
("Type", c_uint16, 4), ('Type', c_uint16, 4),
) ) # fmt: skip
class PeOptionalHeaderStart(LittleEndianStructure): class PeOptionalHeaderStart(LittleEndianStructure):
_fields_ = ( _fields_ = (
("Magic", c_uint16), ('Magic', c_uint16),
("MajorLinkerVersion", c_uint8), ('MajorLinkerVersion', c_uint8),
("MinorLinkerVersion", c_uint8), ('MinorLinkerVersion', c_uint8),
("SizeOfCode", c_uint32), ('SizeOfCode', c_uint32),
("SizeOfInitializedData", c_uint32), ('SizeOfInitializedData', c_uint32),
("SizeOfUninitializedData", c_uint32), ('SizeOfUninitializedData', c_uint32),
("AddressOfEntryPoint", c_uint32), ('AddressOfEntryPoint', c_uint32),
("BaseOfCode", c_uint32), ('BaseOfCode', c_uint32),
) ) # fmt: skip
class PeOptionalHeaderMiddle(LittleEndianStructure): class PeOptionalHeaderMiddle(LittleEndianStructure):
_fields_ = ( _fields_ = (
("SectionAlignment", c_uint32), ('SectionAlignment', c_uint32),
("FileAlignment", c_uint32), ('FileAlignment', c_uint32),
("MajorOperatingSystemVersion", c_uint16), ('MajorOperatingSystemVersion', c_uint16),
("MinorOperatingSystemVersion", c_uint16), ('MinorOperatingSystemVersion', c_uint16),
("MajorImageVersion", c_uint16), ('MajorImageVersion', c_uint16),
("MinorImageVersion", c_uint16), ('MinorImageVersion', c_uint16),
("MajorSubsystemVersion", c_uint16), ('MajorSubsystemVersion', c_uint16),
("MinorSubsystemVersion", c_uint16), ('MinorSubsystemVersion', c_uint16),
("Win32VersionValue", c_uint32), ('Win32VersionValue', c_uint32),
("SizeOfImage", c_uint32), ('SizeOfImage', c_uint32),
("SizeOfHeaders", c_uint32), ('SizeOfHeaders', c_uint32),
("CheckSum", c_uint32), ('CheckSum', c_uint32),
("Subsystem", c_uint16), ('Subsystem', c_uint16),
("DllCharacteristics", c_uint16), ('DllCharacteristics', c_uint16),
) ) # fmt: skip
class PeOptionalHeaderEnd(LittleEndianStructure): class PeOptionalHeaderEnd(LittleEndianStructure):
_fields_ = ( _fields_ = (
("LoaderFlags", c_uint32), ('LoaderFlags', c_uint32),
("NumberOfRvaAndSizes", c_uint32), ('NumberOfRvaAndSizes', c_uint32),
("ExportTable", PeDataDirectory), ('ExportTable', PeDataDirectory),
("ImportTable", PeDataDirectory), ('ImportTable', PeDataDirectory),
("ResourceTable", PeDataDirectory), ('ResourceTable', PeDataDirectory),
("ExceptionTable", PeDataDirectory), ('ExceptionTable', PeDataDirectory),
("CertificateTable", PeDataDirectory), ('CertificateTable', PeDataDirectory),
("BaseRelocationTable", PeDataDirectory), ('BaseRelocationTable', PeDataDirectory),
("Debug", PeDataDirectory), ('Debug', PeDataDirectory),
("Architecture", PeDataDirectory), ('Architecture', PeDataDirectory),
("GlobalPtr", PeDataDirectory), ('GlobalPtr', PeDataDirectory),
("TLSTable", PeDataDirectory), ('TLSTable', PeDataDirectory),
("LoadConfigTable", PeDataDirectory), ('LoadConfigTable', PeDataDirectory),
("BoundImport", PeDataDirectory), ('BoundImport', PeDataDirectory),
("IAT", PeDataDirectory), ('IAT', PeDataDirectory),
("DelayImportDescriptor", PeDataDirectory), ('DelayImportDescriptor', PeDataDirectory),
("CLRRuntimeHeader", PeDataDirectory), ('CLRRuntimeHeader', PeDataDirectory),
("Reserved", PeDataDirectory), ('Reserved', PeDataDirectory),
) ) # fmt: skip
class PeOptionalHeader(LittleEndianStructure): class PeOptionalHeader(LittleEndianStructure):
@@ -141,47 +141,47 @@ class PeOptionalHeader(LittleEndianStructure):
class PeOptionalHeader32(PeOptionalHeader): class PeOptionalHeader32(PeOptionalHeader):
_anonymous_ = ("Start", "Middle", "End") _anonymous_ = ('Start', 'Middle', 'End')
_fields_ = ( _fields_ = (
("Start", PeOptionalHeaderStart), ('Start', PeOptionalHeaderStart),
("BaseOfData", c_uint32), ('BaseOfData', c_uint32),
("ImageBase", c_uint32), ('ImageBase', c_uint32),
("Middle", PeOptionalHeaderMiddle), ('Middle', PeOptionalHeaderMiddle),
("SizeOfStackReserve", c_uint32), ('SizeOfStackReserve', c_uint32),
("SizeOfStackCommit", c_uint32), ('SizeOfStackCommit', c_uint32),
("SizeOfHeapReserve", c_uint32), ('SizeOfHeapReserve', c_uint32),
("SizeOfHeapCommit", c_uint32), ('SizeOfHeapCommit', c_uint32),
("End", PeOptionalHeaderEnd), ('End', PeOptionalHeaderEnd),
) ) # fmt: skip
class PeOptionalHeader32Plus(PeOptionalHeader): class PeOptionalHeader32Plus(PeOptionalHeader):
_anonymous_ = ("Start", "Middle", "End") _anonymous_ = ('Start', 'Middle', 'End')
_fields_ = ( _fields_ = (
("Start", PeOptionalHeaderStart), ('Start', PeOptionalHeaderStart),
("ImageBase", c_uint64), ('ImageBase', c_uint64),
("Middle", PeOptionalHeaderMiddle), ('Middle', PeOptionalHeaderMiddle),
("SizeOfStackReserve", c_uint64), ('SizeOfStackReserve', c_uint64),
("SizeOfStackCommit", c_uint64), ('SizeOfStackCommit', c_uint64),
("SizeOfHeapReserve", c_uint64), ('SizeOfHeapReserve', c_uint64),
("SizeOfHeapCommit", c_uint64), ('SizeOfHeapCommit', c_uint64),
("End", PeOptionalHeaderEnd), ('End', PeOptionalHeaderEnd),
) ) # fmt: skip
class PeSection(LittleEndianStructure): class PeSection(LittleEndianStructure):
_fields_ = ( _fields_ = (
("Name", c_char * 8), ('Name', c_char * 8),
("VirtualSize", c_uint32), ('VirtualSize', c_uint32),
("VirtualAddress", c_uint32), ('VirtualAddress', c_uint32),
("SizeOfRawData", c_uint32), ('SizeOfRawData', c_uint32),
("PointerToRawData", c_uint32), ('PointerToRawData', c_uint32),
("PointerToRelocations", c_uint32), ('PointerToRelocations', c_uint32),
("PointerToLinenumbers", c_uint32), ('PointerToLinenumbers', c_uint32),
("NumberOfRelocations", c_uint16), ('NumberOfRelocations', c_uint16),
("NumberOfLinenumbers", c_uint16), ('NumberOfLinenumbers', c_uint16),
("Characteristics", c_uint32), ('Characteristics', c_uint32),
) ) # fmt: skip
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
@@ -195,30 +195,32 @@ assert sizeof(PeCoffHeader) == 20
assert sizeof(PeOptionalHeader32) == 224 assert sizeof(PeOptionalHeader32) == 224
assert sizeof(PeOptionalHeader32Plus) == 240 assert sizeof(PeOptionalHeader32Plus) == 240
# fmt: off
PE_CHARACTERISTICS_RX = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE PE_CHARACTERISTICS_RX = 0x60000020 # CNT_CODE|MEM_READ|MEM_EXECUTE
PE_CHARACTERISTICS_RW = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE PE_CHARACTERISTICS_RW = 0xC0000040 # CNT_INITIALIZED_DATA|MEM_READ|MEM_WRITE
PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ PE_CHARACTERISTICS_R = 0x40000040 # CNT_INITIALIZED_DATA|MEM_READ
# fmt: on
IGNORE_SECTIONS = [ IGNORE_SECTIONS = [
".eh_frame", '.eh_frame',
".eh_frame_hdr", '.eh_frame_hdr',
".ARM.exidx", '.ARM.exidx',
".relro_padding", '.relro_padding',
".sframe", '.sframe',
] ]
IGNORE_SECTION_TYPES = [ IGNORE_SECTION_TYPES = [
"SHT_DYNAMIC", 'SHT_DYNAMIC',
"SHT_DYNSYM", 'SHT_DYNSYM',
"SHT_GNU_ATTRIBUTES", 'SHT_GNU_ATTRIBUTES',
"SHT_GNU_HASH", 'SHT_GNU_HASH',
"SHT_HASH", 'SHT_HASH',
"SHT_NOTE", 'SHT_NOTE',
"SHT_REL", 'SHT_REL',
"SHT_RELA", 'SHT_RELA',
"SHT_RELR", 'SHT_RELR',
"SHT_STRTAB", 'SHT_STRTAB',
"SHT_SYMTAB", 'SHT_SYMTAB',
] ]
# EFI mandates 4KiB memory pages. # EFI mandates 4KiB memory pages.
@@ -227,7 +229,7 @@ FILE_ALIGNMENT = 512
# Nobody cares about DOS headers, so put the PE header right after. # Nobody cares about DOS headers, so put the PE header right after.
PE_OFFSET = 64 PE_OFFSET = 64
PE_MAGIC = b"PE\0\0" PE_MAGIC = b'PE\0\0'
def align_to(x: int, align: int) -> int: def align_to(x: int, align: int) -> int:
@@ -239,8 +241,7 @@ def align_down(x: int, align: int) -> int:
def next_section_address(sections: list[PeSection]) -> int: def next_section_address(sections: list[PeSection]) -> int:
return align_to(sections[-1].VirtualAddress + sections[-1].VirtualSize, return align_to(sections[-1].VirtualAddress + sections[-1].VirtualSize, SECTION_ALIGNMENT)
SECTION_ALIGNMENT)
class BadSectionError(ValueError): class BadSectionError(ValueError):
@@ -256,29 +257,30 @@ def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]:
relro = None relro = None
for elf_seg in file.iter_segments(): for elf_seg in file.iter_segments():
if elf_seg["p_type"] == "PT_LOAD" and elf_seg["p_align"] != SECTION_ALIGNMENT: if elf_seg['p_type'] == 'PT_LOAD' and elf_seg['p_align'] != SECTION_ALIGNMENT:
raise BadSectionError(f"ELF segment {elf_seg['p_type']} is not properly aligned" raise BadSectionError(
f" ({elf_seg['p_align']} != {SECTION_ALIGNMENT})") f'ELF segment {elf_seg["p_type"]} is not properly aligned ({elf_seg["p_align"]} != {SECTION_ALIGNMENT})'
if elf_seg["p_type"] == "PT_GNU_RELRO": )
if elf_seg['p_type'] == 'PT_GNU_RELRO':
relro = elf_seg relro = elf_seg
for elf_s in file.iter_sections(): for elf_s in file.iter_sections():
if ( if (
elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_ALLOC == 0 elf_s['sh_flags'] & elf.constants.SH_FLAGS.SHF_ALLOC == 0
or elf_s["sh_type"] in IGNORE_SECTION_TYPES or elf_s['sh_type'] in IGNORE_SECTION_TYPES
or elf_s.name in IGNORE_SECTIONS or elf_s.name in IGNORE_SECTIONS
or elf_s["sh_size"] == 0 or elf_s['sh_size'] == 0
): ):
continue continue
if elf_s["sh_type"] not in ["SHT_PROGBITS", "SHT_NOBITS"]: if elf_s['sh_type'] not in ['SHT_PROGBITS', 'SHT_NOBITS']:
raise BadSectionError(f"Unknown section {elf_s.name} with type {elf_s['sh_type']}") raise BadSectionError(f'Unknown section {elf_s.name} with type {elf_s["sh_type"]}')
if elf_s.name == '.got': if elf_s.name == '.got':
# FIXME: figure out why those sections are inserted # FIXME: figure out why those sections are inserted
print("WARNING: Non-empty .got section", file=sys.stderr) print('WARNING: Non-empty .got section', file=sys.stderr)
if elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_EXECINSTR: if elf_s['sh_flags'] & elf.constants.SH_FLAGS.SHF_EXECINSTR:
rwx = PE_CHARACTERISTICS_RX rwx = PE_CHARACTERISTICS_RX
elif elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_WRITE: elif elf_s['sh_flags'] & elf.constants.SH_FLAGS.SHF_WRITE:
rwx = PE_CHARACTERISTICS_RW rwx = PE_CHARACTERISTICS_RW
else: else:
rwx = PE_CHARACTERISTICS_R rwx = PE_CHARACTERISTICS_R
@@ -293,11 +295,11 @@ def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]:
if pe_s: if pe_s:
# Insert padding to properly align the section. # Insert padding to properly align the section.
pad_len = elf_s["sh_addr"] - pe_s.VirtualAddress - len(pe_s.data) pad_len = elf_s['sh_addr'] - pe_s.VirtualAddress - len(pe_s.data)
pe_s.data += bytearray(pad_len) + elf_s.data() pe_s.data += bytearray(pad_len) + elf_s.data()
else: else:
pe_s = PeSection() pe_s = PeSection()
pe_s.VirtualAddress = elf_s["sh_addr"] pe_s.VirtualAddress = elf_s['sh_addr']
pe_s.Characteristics = rwx pe_s.Characteristics = rwx
pe_s.data = elf_s.data() pe_s.data = elf_s.data()
@@ -306,8 +308,8 @@ def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]:
def convert_sections( def convert_sections(
file: elffile.ELFFile, file: elffile.ELFFile,
opt: PeOptionalHeader, opt: PeOptionalHeader,
) -> list[PeSection]: ) -> list[PeSection]:
last_vma = (0, 0) last_vma = (0, 0)
sections = [] sections = []
@@ -324,24 +326,25 @@ def convert_sections(
pe_s.VirtualSize = len(pe_s.data) pe_s.VirtualSize = len(pe_s.data)
pe_s.SizeOfRawData = align_to(len(pe_s.data), FILE_ALIGNMENT) pe_s.SizeOfRawData = align_to(len(pe_s.data), FILE_ALIGNMENT)
pe_s.Name = { pe_s.Name = {
PE_CHARACTERISTICS_RX: b".text", PE_CHARACTERISTICS_RX: b'.text',
PE_CHARACTERISTICS_RW: b".data", PE_CHARACTERISTICS_RW: b'.data',
PE_CHARACTERISTICS_R: b".rodata", PE_CHARACTERISTICS_R: b'.rodata',
}[pe_s.Characteristics] }[pe_s.Characteristics]
# This can happen if not building with '-z separate-code'. # This can happen if not building with '-z separate-code'.
if pe_s.VirtualAddress < sum(last_vma): if pe_s.VirtualAddress < sum(last_vma):
raise BadSectionError(f"Section {pe_s.Name.decode()!r} @0x{pe_s.VirtualAddress:x} overlaps" raise BadSectionError(
f" previous section @0x{last_vma[0]:x}+0x{last_vma[1]:x}=@0x{sum(last_vma):x}") f'Section {pe_s.Name.decode()!r} @{pe_s.VirtualAddress:#x} overlaps previous section @{last_vma[0]:#x}+{last_vma[1]:#x}=@{sum(last_vma):#x}'
)
last_vma = (pe_s.VirtualAddress, pe_s.VirtualSize) last_vma = (pe_s.VirtualAddress, pe_s.VirtualSize)
if pe_s.Name == b".text": if pe_s.Name == b'.text':
opt.BaseOfCode = pe_s.VirtualAddress opt.BaseOfCode = pe_s.VirtualAddress
opt.SizeOfCode += pe_s.VirtualSize opt.SizeOfCode += pe_s.VirtualSize
else: else:
opt.SizeOfInitializedData += pe_s.VirtualSize opt.SizeOfInitializedData += pe_s.VirtualSize
if pe_s.Name == b".data" and isinstance(opt, PeOptionalHeader32): if pe_s.Name == b'.data' and isinstance(opt, PeOptionalHeader32):
opt.BaseOfData = pe_s.VirtualAddress opt.BaseOfData = pe_s.VirtualAddress
sections.append(pe_s) sections.append(pe_s)
@@ -355,14 +358,14 @@ def copy_sections(
input_names: str, input_names: str,
sections: list[PeSection], sections: list[PeSection],
) -> None: ) -> None:
for name in input_names.split(","): for name in input_names.split(','):
elf_s = file.get_section_by_name(name) elf_s = file.get_section_by_name(name)
if not elf_s: if not elf_s:
continue continue
if elf_s.data_alignment > 1 and SECTION_ALIGNMENT % elf_s.data_alignment != 0: if elf_s.data_alignment > 1 and SECTION_ALIGNMENT % elf_s.data_alignment != 0:
raise BadSectionError(f"ELF section {name} is not aligned") raise BadSectionError(f'ELF section {name} is not aligned')
if elf_s["sh_flags"] & (elf.constants.SH_FLAGS.SHF_EXECINSTR | elf.constants.SH_FLAGS.SHF_WRITE) != 0: if elf_s['sh_flags'] & (elf.constants.SH_FLAGS.SHF_EXECINSTR | elf.constants.SH_FLAGS.SHF_WRITE) != 0: # fmt: skip
raise BadSectionError(f"ELF section {name} is not read-only data") raise BadSectionError(f'ELF section {name} is not read-only data')
pe_s = PeSection() pe_s = PeSection()
pe_s.Name = name.encode() pe_s.Name = name.encode()
@@ -381,18 +384,21 @@ def apply_elf_relative_relocation(
sections: list[PeSection], sections: list[PeSection],
addend_size: int, addend_size: int,
) -> None: ) -> None:
[target] = [pe_s for pe_s in sections [target] = [
if pe_s.VirtualAddress <= reloc["r_offset"] < pe_s.VirtualAddress + len(pe_s.data)] pe_s
for pe_s in sections
if pe_s.VirtualAddress <= reloc['r_offset'] < pe_s.VirtualAddress + len(pe_s.data)
]
addend_offset = reloc["r_offset"] - target.VirtualAddress addend_offset = reloc['r_offset'] - target.VirtualAddress
if reloc.is_RELA(): if reloc.is_RELA():
addend = reloc["r_addend"] addend = reloc['r_addend']
else: else:
addend = target.data[addend_offset : addend_offset + addend_size] addend = target.data[addend_offset : addend_offset + addend_size]
addend = int.from_bytes(addend, byteorder="little") addend = int.from_bytes(addend, byteorder='little')
value = (image_base + addend).to_bytes(addend_size, byteorder="little") value = (image_base + addend).to_bytes(addend_size, byteorder='little')
target.data[addend_offset : addend_offset + addend_size] = value target.data[addend_offset : addend_offset + addend_size] = value
@@ -404,47 +410,44 @@ def convert_elf_reloc_table(
pe_reloc_blocks: dict[int, PeRelocationBlock], pe_reloc_blocks: dict[int, PeRelocationBlock],
) -> None: ) -> None:
NONE_RELOC = { NONE_RELOC = {
"EM_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_NONE"], 'EM_386': elf.enums.ENUM_RELOC_TYPE_i386['R_386_NONE'],
"EM_AARCH64": elf.enums.ENUM_RELOC_TYPE_AARCH64["R_AARCH64_NONE"], 'EM_AARCH64': elf.enums.ENUM_RELOC_TYPE_AARCH64['R_AARCH64_NONE'],
"EM_ARM": elf.enums.ENUM_RELOC_TYPE_ARM["R_ARM_NONE"], 'EM_ARM': elf.enums.ENUM_RELOC_TYPE_ARM['R_ARM_NONE'],
"EM_LOONGARCH": 0, 'EM_LOONGARCH': 0,
"EM_RISCV": 0, 'EM_RISCV': 0,
"EM_X86_64": elf.enums.ENUM_RELOC_TYPE_x64["R_X86_64_NONE"], 'EM_X86_64': elf.enums.ENUM_RELOC_TYPE_x64['R_X86_64_NONE'],
}[file["e_machine"]] }[file['e_machine']] # fmt: skip
RELATIVE_RELOC = { RELATIVE_RELOC = {
"EM_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_RELATIVE"], 'EM_386': elf.enums.ENUM_RELOC_TYPE_i386['R_386_RELATIVE'],
"EM_AARCH64": elf.enums.ENUM_RELOC_TYPE_AARCH64["R_AARCH64_RELATIVE"], 'EM_AARCH64': elf.enums.ENUM_RELOC_TYPE_AARCH64['R_AARCH64_RELATIVE'],
"EM_ARM": elf.enums.ENUM_RELOC_TYPE_ARM["R_ARM_RELATIVE"], 'EM_ARM': elf.enums.ENUM_RELOC_TYPE_ARM['R_ARM_RELATIVE'],
"EM_LOONGARCH": 3, 'EM_LOONGARCH': 3,
"EM_RISCV": 3, 'EM_RISCV': 3,
"EM_X86_64": elf.enums.ENUM_RELOC_TYPE_x64["R_X86_64_RELATIVE"], 'EM_X86_64': elf.enums.ENUM_RELOC_TYPE_x64['R_X86_64_RELATIVE'],
}[file["e_machine"]] }[file['e_machine']] # fmt: skip
for reloc in elf_reloc_table.iter_relocations(): for reloc in elf_reloc_table.iter_relocations():
if reloc["r_info_type"] == NONE_RELOC: if reloc['r_info_type'] == NONE_RELOC:
continue continue
if reloc["r_info_type"] == RELATIVE_RELOC: if reloc['r_info_type'] == RELATIVE_RELOC:
apply_elf_relative_relocation(reloc, apply_elf_relative_relocation(reloc, elf_image_base, sections, file.elfclass // 8)
elf_image_base,
sections,
file.elfclass // 8)
# Now that the ELF relocation has been applied, we can create a PE relocation. # Now that the ELF relocation has been applied, we can create a PE relocation.
block_rva = reloc["r_offset"] & ~0xFFF block_rva = reloc['r_offset'] & ~0xFFF
if block_rva not in pe_reloc_blocks: if block_rva not in pe_reloc_blocks:
pe_reloc_blocks[block_rva] = PeRelocationBlock(block_rva) pe_reloc_blocks[block_rva] = PeRelocationBlock(block_rva)
entry = PeRelocationEntry() entry = PeRelocationEntry()
entry.Offset = reloc["r_offset"] & 0xFFF entry.Offset = reloc['r_offset'] & 0xFFF
# REL_BASED_HIGHLOW or REL_BASED_DIR64 # REL_BASED_HIGHLOW or REL_BASED_DIR64
entry.Type = 3 if file.elfclass == 32 else 10 entry.Type = 3 if file.elfclass == 32 else 10
pe_reloc_blocks[block_rva].entries.append(entry) pe_reloc_blocks[block_rva].entries.append(entry)
continue continue
raise BadSectionError(f"Unsupported relocation {reloc}") raise BadSectionError(f'Unsupported relocation {reloc}')
def convert_elf_relocations( def convert_elf_relocations(
@@ -453,27 +456,29 @@ def convert_elf_relocations(
sections: list[PeSection], sections: list[PeSection],
minimum_sections: int, minimum_sections: int,
) -> typing.Optional[PeSection]: ) -> typing.Optional[PeSection]:
dynamic = file.get_section_by_name(".dynamic") dynamic = file.get_section_by_name('.dynamic')
if dynamic is None: if dynamic is None:
raise BadSectionError("ELF .dynamic section is missing") raise BadSectionError('ELF .dynamic section is missing')
[flags_tag] = dynamic.iter_tags("DT_FLAGS_1") [flags_tag] = dynamic.iter_tags('DT_FLAGS_1')
if not flags_tag["d_val"] & elf.enums.ENUM_DT_FLAGS_1["DF_1_PIE"]: if not flags_tag['d_val'] & elf.enums.ENUM_DT_FLAGS_1['DF_1_PIE']:
raise ValueError("ELF file is not a PIE") raise ValueError('ELF file is not a PIE')
# This checks that the ELF image base is 0. # This checks that the ELF image base is 0.
symtab = file.get_section_by_name(".symtab") symtab = file.get_section_by_name('.symtab')
if symtab: if symtab:
exe_start = symtab.get_symbol_by_name("__executable_start") exe_start = symtab.get_symbol_by_name('__executable_start')
if exe_start and exe_start[0]["st_value"] != 0: if exe_start and exe_start[0]['st_value'] != 0:
raise ValueError("Unexpected ELF image base") raise ValueError('Unexpected ELF image base')
opt.SizeOfHeaders = align_to(PE_OFFSET opt.SizeOfHeaders = align_to(
+ len(PE_MAGIC) PE_OFFSET
+ sizeof(PeCoffHeader) + len(PE_MAGIC)
+ sizeof(opt) + sizeof(PeCoffHeader)
+ sizeof(PeSection) * max(len(sections) + 1, minimum_sections), + sizeof(opt)
FILE_ALIGNMENT) + 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 # 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 # section to overlap the PE image headers during runtime at VMA 0. We can simply apply a fixed
@@ -482,23 +487,18 @@ def convert_elf_relocations(
# the ELF portions of the image. # the ELF portions of the image.
segment_offset = 0 segment_offset = 0
if sections[0].VirtualAddress < opt.SizeOfHeaders: if sections[0].VirtualAddress < opt.SizeOfHeaders:
segment_offset = align_to(opt.SizeOfHeaders - sections[0].VirtualAddress, segment_offset = align_to(opt.SizeOfHeaders - sections[0].VirtualAddress, SECTION_ALIGNMENT)
SECTION_ALIGNMENT)
opt.AddressOfEntryPoint = file["e_entry"] + segment_offset opt.AddressOfEntryPoint = file['e_entry'] + segment_offset
opt.BaseOfCode += segment_offset opt.BaseOfCode += segment_offset
if isinstance(opt, PeOptionalHeader32): if isinstance(opt, PeOptionalHeader32):
opt.BaseOfData += segment_offset opt.BaseOfData += segment_offset
pe_reloc_blocks: dict[int, PeRelocationBlock] = {} pe_reloc_blocks: dict[int, PeRelocationBlock] = {}
for reloc_type, reloc_table in dynamic.get_relocation_tables().items(): for reloc_type, reloc_table in dynamic.get_relocation_tables().items():
if reloc_type not in ["REL", "RELA"]: if reloc_type not in ['REL', 'RELA']:
raise BadSectionError(f"Unsupported relocation type {reloc_type}") raise BadSectionError(f'Unsupported relocation type {reloc_type}')
convert_elf_reloc_table(file, convert_elf_reloc_table(file, reloc_table, opt.ImageBase + segment_offset, sections, pe_reloc_blocks)
reloc_table,
opt.ImageBase + segment_offset,
sections,
pe_reloc_blocks)
for pe_s in sections: for pe_s in sections:
pe_s.VirtualAddress += segment_offset pe_s.VirtualAddress += segment_offset
@@ -524,7 +524,7 @@ def convert_elf_relocations(
data += entry data += entry
pe_reloc_s = PeSection() pe_reloc_s = PeSection()
pe_reloc_s.Name = b".reloc" pe_reloc_s.Name = b'.reloc'
pe_reloc_s.data = data pe_reloc_s.data = data
pe_reloc_s.VirtualAddress = next_section_address(sections) pe_reloc_s.VirtualAddress = next_section_address(sections)
pe_reloc_s.VirtualSize = len(data) pe_reloc_s.VirtualSize = len(data)
@@ -543,9 +543,9 @@ def write_pe(
opt: PeOptionalHeader, opt: PeOptionalHeader,
sections: list[PeSection], sections: list[PeSection],
) -> None: ) -> None:
file.write(b"MZ") file.write(b'MZ')
file.seek(0x3C, io.SEEK_SET) file.seek(0x3C, io.SEEK_SET)
file.write(PE_OFFSET.to_bytes(2, byteorder="little")) file.write(PE_OFFSET.to_bytes(2, byteorder='little'))
file.seek(PE_OFFSET, io.SEEK_SET) file.seek(PE_OFFSET, io.SEEK_SET)
file.write(PE_MAGIC) file.write(PE_MAGIC)
file.write(coff) file.write(coff)
@@ -554,8 +554,9 @@ def write_pe(
offset = opt.SizeOfHeaders offset = opt.SizeOfHeaders
for pe_s in sorted(sections, key=lambda s: s.VirtualAddress): for pe_s in sorted(sections, key=lambda s: s.VirtualAddress):
if pe_s.VirtualAddress < opt.SizeOfHeaders: if pe_s.VirtualAddress < opt.SizeOfHeaders:
raise BadSectionError(f"Section {pe_s.Name} @0x{pe_s.VirtualAddress:x} overlaps" raise BadSectionError(
" PE headers ending at 0x{opt.SizeOfHeaders:x}") f'Section {pe_s.Name} @{pe_s.VirtualAddress:#x} overlaps PE headers ending at {opt.SizeOfHeaders:#x}'
)
pe_s.PointerToRawData = offset pe_s.PointerToRawData = offset
file.write(pe_s) file.write(pe_s)
@@ -573,20 +574,20 @@ def write_pe(
def elf2efi(args: argparse.Namespace) -> None: def elf2efi(args: argparse.Namespace) -> None:
file = elffile.ELFFile(args.ELF) file = elffile.ELFFile(args.ELF)
if not file.little_endian: if not file.little_endian:
raise ValueError("ELF file is not little-endian") raise ValueError('ELF file is not little-endian')
if file["e_type"] not in ["ET_DYN", "ET_EXEC"]: if file['e_type'] not in ['ET_DYN', 'ET_EXEC']:
raise ValueError(f"Unsupported ELF type {file['e_type']}") raise ValueError(f'Unsupported ELF type {file["e_type"]}')
pe_arch = { pe_arch = {
"EM_386": 0x014C, 'EM_386': 0x014C,
"EM_AARCH64": 0xAA64, 'EM_AARCH64': 0xAA64,
"EM_ARM": 0x01C2, 'EM_ARM': 0x01C2,
"EM_LOONGARCH": 0x6232 if file.elfclass == 32 else 0x6264, 'EM_LOONGARCH': 0x6232 if file.elfclass == 32 else 0x6264,
"EM_RISCV": 0x5032 if file.elfclass == 32 else 0x5064, 'EM_RISCV': 0x5032 if file.elfclass == 32 else 0x5064,
"EM_X86_64": 0x8664, 'EM_X86_64': 0x8664,
}.get(file["e_machine"]) }.get(file['e_machine']) # fmt: skip
if pe_arch is None: if pe_arch is None:
raise ValueError(f"Unsupported ELF architecture {file['e_machine']}") raise ValueError(f'Unsupported ELF architecture {file["e_machine"]}')
coff = PeCoffHeader() coff = PeCoffHeader()
opt = PeOptionalHeader32() if file.elfclass == 32 else PeOptionalHeader32Plus() opt = PeOptionalHeader32() if file.elfclass == 32 else PeOptionalHeader32Plus()
@@ -605,7 +606,7 @@ def elf2efi(args: argparse.Namespace) -> None:
coff.Machine = pe_arch coff.Machine = pe_arch
coff.NumberOfSections = len(sections) coff.NumberOfSections = len(sections)
coff.TimeDateStamp = int(os.environ.get("SOURCE_DATE_EPOCH") or time.time()) coff.TimeDateStamp = int(os.environ.get('SOURCE_DATE_EPOCH') or time.time())
coff.SizeOfOptionalHeader = sizeof(opt) coff.SizeOfOptionalHeader = sizeof(opt)
# EXECUTABLE_IMAGE|LINE_NUMS_STRIPPED|LOCAL_SYMS_STRIPPED|DEBUG_STRIPPED # EXECUTABLE_IMAGE|LINE_NUMS_STRIPPED|LOCAL_SYMS_STRIPPED|DEBUG_STRIPPED
# and (32BIT_MACHINE or LARGE_ADDRESS_AWARE) # and (32BIT_MACHINE or LARGE_ADDRESS_AWARE)
@@ -632,66 +633,64 @@ def elf2efi(args: argparse.Namespace) -> None:
opt.NumberOfRvaAndSizes = N_DATA_DIRECTORY_ENTRIES opt.NumberOfRvaAndSizes = N_DATA_DIRECTORY_ENTRIES
if pe_reloc_s: if pe_reloc_s:
opt.BaseRelocationTable = PeDataDirectory( opt.BaseRelocationTable = PeDataDirectory(pe_reloc_s.VirtualAddress, pe_reloc_s.VirtualSize)
pe_reloc_s.VirtualAddress, pe_reloc_s.VirtualSize
)
write_pe(args.PE, coff, opt, sections) write_pe(args.PE, coff, opt, sections)
def create_parser() -> argparse.ArgumentParser: def create_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Convert ELF binaries to PE/EFI") parser = argparse.ArgumentParser(description='Convert ELF binaries to PE/EFI')
parser.add_argument( parser.add_argument(
"--version-major", '--version-major',
type=int, type=int,
default=0, default=0,
help="Major image version of EFI image", help='Major image version of EFI image',
) )
parser.add_argument( parser.add_argument(
"--version-minor", '--version-minor',
type=int, type=int,
default=0, default=0,
help="Minor image version of EFI image", help='Minor image version of EFI image',
) )
parser.add_argument( parser.add_argument(
"--efi-major", '--efi-major',
type=int, type=int,
default=0, default=0,
help="Minimum major EFI subsystem version", help='Minimum major EFI subsystem version',
) )
parser.add_argument( parser.add_argument(
"--efi-minor", '--efi-minor',
type=int, type=int,
default=0, default=0,
help="Minimum minor EFI subsystem version", help='Minimum minor EFI subsystem version',
) )
parser.add_argument( parser.add_argument(
"--subsystem", '--subsystem',
type=int, type=int,
default=10, default=10,
help="PE subsystem", help='PE subsystem',
) )
parser.add_argument( parser.add_argument(
"ELF", 'ELF',
type=argparse.FileType("rb"), type=argparse.FileType('rb'),
help="Input ELF file", help='Input ELF file',
) )
parser.add_argument( parser.add_argument(
"PE", 'PE',
type=argparse.FileType("wb"), type=argparse.FileType('wb'),
help="Output PE/EFI file", help='Output PE/EFI file',
) )
parser.add_argument( parser.add_argument(
"--minimum-sections", '--minimum-sections',
type=int, type=int,
default=0, default=0,
help="Minimum number of sections to leave space for", help='Minimum number of sections to leave space for',
) )
parser.add_argument( parser.add_argument(
"--copy-sections", '--copy-sections',
type=str, type=str,
default="", default='',
help="Copy these sections if found", help='Copy these sections if found',
) )
return parser return parser
@@ -701,5 +700,5 @@ def main() -> None:
elf2efi(parser.parse_args()) elf2efi(parser.parse_args())
if __name__ == "__main__": if __name__ == '__main__':
main() main()