Skip to content

PE base relocations are dropped on every architecture but x86 and x86-64: the relocation tables are keyed on names archinfo does not produce #752

Description

@zardus

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Description

cle/backends/pe/relocation/__init__.py keys its relocation tables on strings
that archinfo does not produce, so every base relocation in a PE image is
dropped on every architecture except x86 and x86-64:

ALL_RELOCATIONS = {
    "AMD64": relocation_table_generic,
    "arm": relocation_table_generic | relocation_table_arm,
    "X86": relocation_table_generic,
    "mips": relocation_table_generic | relocation_table_mips,
    "RISCV": relocation_table_generic | relocation_table_riscv,
}

and the lookup is get_relocation(self.arch.name, reloc_type).
"arm", "mips" and "RISCV" are not architecture names — arch.name is
ARMEL, MIPS32, RISCV64 — and AARCH64 has no entry at all. Of the fourteen
IMAGE_FILE_MACHINE_* values the backend can resolve, only two land in the
table:

import archinfo, pefile
from cle.backends.pe.relocation import ALL_RELOCATIONS

for machine in (0x014C, 0x8664, 0xAA64, 0x01C4, 0x0169, 0x01F0, 0x5064):
    name = archinfo.arch_from_id(pefile.MACHINE_TYPE[machine]).name
    print(f"{machine:#06x} {name:8} in ALL_RELOCATIONS: {name in ALL_RELOCATIONS}")

# 0x014c X86      in ALL_RELOCATIONS: True
# 0x8664 AMD64    in ALL_RELOCATIONS: True
# 0xaa64 AARCH64  in ALL_RELOCATIONS: False
# 0x01c4 ARMEL    in ALL_RELOCATIONS: False
# 0x0169 MIPS32   in ALL_RELOCATIONS: False
# 0x01f0 PPC32    in ALL_RELOCATIONS: False
# 0x5064 RISCV64  in ALL_RELOCATIONS: False

get_relocation returns None for every type on those, __register_relocs
skips the entry, and the only thing that survives is log.warning("Unknown reloc %d on %s", ...) — once per (arch, type) pair, because complaint_log dedupes
it. IMAGE_REL_BASED_DIR64 and IMAGE_REL_BASED_HIGHLOW are in
relocation_table_generic and would be handled for any of these architectures if
the key matched; they are dropped along with the rest.

The consequence only shows once an image is rebased, which is exactly when it
matters: PE.__init__ sets mapped_base = linked_base = ImageBase, so a
single-object load happens to be correct, and a load that puts the image
anywhere else — a second DLL with the same preferred base, an explicit
base_addr — leaves every absolute pointer in it pointing at the old base with
no error.

Measurement

Counting IMAGE_BASE_RELOCATION entries out of the .reloc directory directly
and comparing with len(loader.main_object.relocs) after
cle.Loader(path, auto_load_libs=False, main_opts={"backend": "pe"}). The
remainder on the first two rows is DLL imports; not one base relocation is kept:

image arch.name base relocations in the file relocations cle built
aarch64 executable AARCH64 1,770 (all DIR64) 173
ARMNT driver ARMEL 8,777 (124 HIGHLOW, 8,653 THUMB_MOV32) 301
x86-64 executable AMD64 654 (all DIR64) 801
x86 executable X86 36,780 (all HIGHLOW) 37,021

The warning text is what makes this findable: loading an ARM64 Windows image
prints Unknown reloc 10 on AARCH64 — type 10 is IMAGE_REL_BASED_DIR64, which
relocation_table_generic implements.

Reproduction

Any Windows binary for a non-x86 target; this repository's test corpus has none,
so a recipe rather than a path:

$ clang --target=aarch64-pc-windows-msvc -o hello.exe hello.c
import cle
ld = cle.Loader("hello.exe", auto_load_libs=False, main_opts={"backend": "pe"})
print(len(ld.main_object.relocs))       # imports only; no base relocations

The snippet at the top needs no binary at all.

Options

  1. Key the table on the names archinfo produces: ARMEL, ARMHF and
    ARMCortexM for the ARM table, MIPS32 for the MIPS table, RISCV64 for the
    RISC-V table, and AARCH64 and PPC32 for the generic table. This is the
    whole fix for DIR64 and HIGHLOW, which is 1,770 of 1,770 relocations on the
    ARM64 image above.
  2. The same, plus the two ARM64-specific types the generic table does not cover,
    IMAGE_REL_BASED_ARM64_MOV32A (13) and IMAGE_REL_BASED_ARM64_MOV32T (14).
    The ARMNT image above needs the existing THUMB_MOV32 for 8,653 of its 8,777,
    so the ARM64 equivalents are likely to matter the same way on ARM64 images
    that use them.
  3. Fail loudly instead. A dropped base relocation cannot be detected downstream,
    and log.warning deduplicated per (arch, type) is one line for 8,777
    dropped fixups.

Filed as an issue rather than a pull request because which architectures should
map to which table, and whether an unhandled relocation type should be fatal, are
calls for a maintainer. Note also that angr.Project on any of these images
currently raises before this code matters (angr/angr#6794 is the open fix), so a
CFG-level before/after cannot be measured until that lands — the table above is
measured against the files' own .reloc directories instead.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions