elf2efi: import whole module, not individual symbols

When reading the code, it was hard to figure out if the given name was
imported or a local class. And the renaming of imports also made it
harder to look things up online. Arguably, the deeply nested import
structure and inconsistent naming in elftools is partially to blame:
there is just no good way to make this look nice. But anyway, let's use
the usual style of importing the module and using names prefixed with
the module path so that the origin of imported names is clear.

elfutils.elf.elffile is importered separately, because a) it needs to be
imported separately anyway bxecause the module does lazy imports
internally, a) the name already indicates the origin, c) is used in
quite a few places so the shorter name is nice.
This commit is contained in:
Zbigniew Jędrzejewski-Szmek
2026-02-19 15:34:10 +01:00
parent 469879aa44
commit 528a939c89
+55 -63
View File
@@ -39,19 +39,8 @@ from ctypes import (
sizeof,
)
from elftools.elf.constants import SH_FLAGS
from elftools.elf.elffile import ELFFile
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,
)
from elftools import elf
from elftools.elf import elffile
class PeCoffHeader(LittleEndianStructure):
@@ -257,7 +246,7 @@ class BadSectionError(ValueError):
"One of the sections is in a bad state"
def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]:
def iter_copy_sections(file: elffile.ELFFile) -> typing.Iterator[PeSection]:
pe_s = None
# This is essentially the same as copying by ELF load segments, except that we assemble them
@@ -265,16 +254,16 @@ def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]:
# about so that there are no surprises.
relro = None
for elf_seg in elf.iter_segments():
for elf_seg in file.iter_segments():
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"
f" ({elf_seg['p_align']} != {SECTION_ALIGNMENT})")
if elf_seg["p_type"] == "PT_GNU_RELRO":
relro = elf_seg
for elf_s in elf.iter_sections():
for elf_s in file.iter_sections():
if (
elf_s["sh_flags"] & 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.name in IGNORE_SECTIONS
or elf_s["sh_size"] == 0
@@ -286,9 +275,9 @@ def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]:
# FIXME: figure out why those sections are inserted
print("WARNING: Non-empty .got section", file=sys.stderr)
if elf_s["sh_flags"] & SH_FLAGS.SHF_EXECINSTR:
if elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_EXECINSTR:
rwx = PE_CHARACTERISTICS_RX
elif elf_s["sh_flags"] & SH_FLAGS.SHF_WRITE:
elif elf_s["sh_flags"] & elf.constants.SH_FLAGS.SHF_WRITE:
rwx = PE_CHARACTERISTICS_RW
else:
rwx = PE_CHARACTERISTICS_R
@@ -315,11 +304,14 @@ def iter_copy_sections(elf: ELFFile) -> typing.Iterator[PeSection]:
yield pe_s
def convert_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSection]:
def convert_sections(
file: elffile.ELFFile,
opt: PeOptionalHeader,
) -> typing.List[PeSection]:
last_vma = (0, 0)
sections = []
for pe_s in iter_copy_sections(elf):
for pe_s in iter_copy_sections(file):
# 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
@@ -357,18 +349,18 @@ def convert_sections(elf: ELFFile, opt: PeOptionalHeader) -> typing.List[PeSecti
def copy_sections(
elf: ELFFile,
file: elffile.ELFFile,
opt: PeOptionalHeader,
input_names: str,
sections: typing.List[PeSection],
):
for name in input_names.split(","):
elf_s = elf.get_section_by_name(name)
elf_s = file.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 BadSectionError(f"ELF section {name} is not aligned")
if elf_s["sh_flags"] & (SH_FLAGS.SHF_EXECINSTR | SH_FLAGS.SHF_WRITE) != 0:
if elf_s["sh_flags"] & (elf.constants.SH_FLAGS.SHF_EXECINSTR | elf.constants.SH_FLAGS.SHF_WRITE) != 0:
raise BadSectionError(f"ELF section {name} is not read-only data")
pe_s = PeSection()
@@ -383,7 +375,7 @@ def copy_sections(
def apply_elf_relative_relocation(
reloc: ElfRelocation,
reloc: elf.relocation.Relocation,
image_base: int,
sections: typing.List[PeSection],
addend_size: int,
@@ -404,29 +396,29 @@ def apply_elf_relative_relocation(
def convert_elf_reloc_table(
elf: ELFFile,
elf_reloc_table: ElfRelocationTable,
file: elffile.ELFFile,
elf_reloc_table: elf.relocation.RelocationTable,
elf_image_base: int,
sections: typing.List[PeSection],
pe_reloc_blocks: typing.Dict[int, PeRelocationBlock],
):
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"],
"EM_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_NONE"],
"EM_AARCH64": elf.enums.ENUM_RELOC_TYPE_AARCH64["R_AARCH64_NONE"],
"EM_ARM": elf.enums.ENUM_RELOC_TYPE_ARM["R_ARM_NONE"],
"EM_LOONGARCH": 0,
"EM_RISCV": 0,
"EM_X86_64": ENUM_RELOC_TYPE_x64["R_X86_64_NONE"],
}[elf["e_machine"]]
"EM_RISCV": 0,
"EM_X86_64": elf.enums.ENUM_RELOC_TYPE_x64["R_X86_64_NONE"],
}[file["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"],
"EM_386": elf.enums.ENUM_RELOC_TYPE_i386["R_386_RELATIVE"],
"EM_AARCH64": elf.enums.ENUM_RELOC_TYPE_AARCH64["R_AARCH64_RELATIVE"],
"EM_ARM": elf.enums.ENUM_RELOC_TYPE_ARM["R_ARM_RELATIVE"],
"EM_LOONGARCH": 3,
"EM_RISCV": 3,
"EM_X86_64": ENUM_RELOC_TYPE_x64["R_X86_64_RELATIVE"],
}[elf["e_machine"]]
"EM_RISCV": 3,
"EM_X86_64": elf.enums.ENUM_RELOC_TYPE_x64["R_X86_64_RELATIVE"],
}[file["e_machine"]]
for reloc in elf_reloc_table.iter_relocations():
if reloc["r_info_type"] == NONE_RELOC:
@@ -436,7 +428,7 @@ def convert_elf_reloc_table(
apply_elf_relative_relocation(reloc,
elf_image_base,
sections,
elf.elfclass // 8)
file.elfclass // 8)
# Now that the ELF relocation has been applied, we can create a PE relocation.
block_rva = reloc["r_offset"] & ~0xFFF
@@ -446,7 +438,7 @@ def convert_elf_reloc_table(
entry = PeRelocationEntry()
entry.Offset = reloc["r_offset"] & 0xFFF
# REL_BASED_HIGHLOW or REL_BASED_DIR64
entry.Type = 3 if elf.elfclass == 32 else 10
entry.Type = 3 if file.elfclass == 32 else 10
pe_reloc_blocks[block_rva].entries.append(entry)
continue
@@ -455,21 +447,21 @@ def convert_elf_reloc_table(
def convert_elf_relocations(
elf: ELFFile,
file: elffile.ELFFile,
opt: PeOptionalHeader,
sections: typing.List[PeSection],
minimum_sections: int,
) -> typing.Optional[PeSection]:
dynamic = elf.get_section_by_name(".dynamic")
dynamic = file.get_section_by_name(".dynamic")
if dynamic is None:
raise BadSectionError("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"]:
if not flags_tag["d_val"] & elf.enums.ENUM_DT_FLAGS_1["DF_1_PIE"]:
raise ValueError("ELF file is not a PIE")
# This checks that the ELF image base is 0.
symtab = elf.get_section_by_name(".symtab")
symtab = file.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:
@@ -492,7 +484,7 @@ def convert_elf_relocations(
segment_offset = align_to(opt.SizeOfHeaders - sections[0].VirtualAddress,
SECTION_ALIGNMENT)
opt.AddressOfEntryPoint = elf["e_entry"] + segment_offset
opt.AddressOfEntryPoint = file["e_entry"] + segment_offset
opt.BaseOfCode += segment_offset
if isinstance(opt, PeOptionalHeader32):
opt.BaseOfData += segment_offset
@@ -501,7 +493,7 @@ def convert_elf_relocations(
for reloc_type, reloc_table in dynamic.get_relocation_tables().items():
if reloc_type not in ["REL", "RELA"]:
raise BadSectionError(f"Unsupported relocation type {reloc_type}")
convert_elf_reloc_table(elf,
convert_elf_reloc_table(file,
reloc_table,
opt.ImageBase + segment_offset,
sections,
@@ -578,37 +570,37 @@ def write_pe(
def elf2efi(args: argparse.Namespace):
elf = ELFFile(args.ELF)
if not elf.little_endian:
file = elffile.ELFFile(args.ELF)
if not file.little_endian:
raise ValueError("ELF file is not little-endian")
if elf["e_type"] not in ["ET_DYN", "ET_EXEC"]:
raise ValueError(f"Unsupported ELF type {elf['e_type']}")
if file["e_type"] not in ["ET_DYN", "ET_EXEC"]:
raise ValueError(f"Unsupported ELF type {file['e_type']}")
pe_arch = {
"EM_386": 0x014C,
"EM_AARCH64": 0xAA64,
"EM_ARM": 0x01C2,
"EM_LOONGARCH": 0x6232 if elf.elfclass == 32 else 0x6264,
"EM_RISCV": 0x5032 if elf.elfclass == 32 else 0x5064,
"EM_LOONGARCH": 0x6232 if file.elfclass == 32 else 0x6264,
"EM_RISCV": 0x5032 if file.elfclass == 32 else 0x5064,
"EM_X86_64": 0x8664,
}.get(elf["e_machine"])
}.get(file["e_machine"])
if pe_arch is None:
raise ValueError(f"Unsupported ELF architecture {elf['e_machine']}")
raise ValueError(f"Unsupported ELF architecture {file['e_machine']}")
coff = PeCoffHeader()
opt = PeOptionalHeader32() if elf.elfclass == 32 else PeOptionalHeader32Plus()
opt = PeOptionalHeader32() if file.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:
if file.elfclass == 32:
opt.ImageBase = (0x400000 + opt.ImageBase) & 0xFFFF0000
else:
opt.ImageBase = (0x100000000 + opt.ImageBase) & 0x1FFFF0000
sections = convert_sections(elf, opt)
copy_sections(elf, opt, args.copy_sections, sections)
pe_reloc_s = convert_elf_relocations(elf, opt, sections, args.minimum_sections)
sections = convert_sections(file, opt)
copy_sections(file, opt, args.copy_sections, sections)
pe_reloc_s = convert_elf_relocations(file, opt, sections, args.minimum_sections)
coff.Machine = pe_arch
coff.NumberOfSections = len(sections)
@@ -616,7 +608,7 @@ def elf2efi(args: argparse.Namespace):
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
coff.Characteristics = 0x30E if file.elfclass == 32 else 0x22E
opt.SectionAlignment = SECTION_ALIGNMENT
opt.FileAlignment = FILE_ALIGNMENT
@@ -625,11 +617,11 @@ def elf2efi(args: argparse.Namespace):
opt.MajorSubsystemVersion = args.efi_major
opt.MinorSubsystemVersion = args.efi_minor
opt.Subsystem = args.subsystem
opt.Magic = 0x10B if elf.elfclass == 32 else 0x20B
opt.Magic = 0x10B if file.elfclass == 32 else 0x20B
opt.SizeOfImage = next_section_address(sections)
# DYNAMIC_BASE|NX_COMPAT|HIGH_ENTROPY_VA or DYNAMIC_BASE|NX_COMPAT
opt.DllCharacteristics = 0x160 if elf.elfclass == 64 else 0x140
opt.DllCharacteristics = 0x160 if file.elfclass == 64 else 0x140
# These values are taken from a natively built PE binary (although, unused by EDK2/EFI).
opt.SizeOfStackReserve = 0x100000