Skip to content

PE synthesis drops functions below RVA 0x1000, losing the entry region of ELF-origin reports #236

Description

@r0ny123

PE synthesis cannot place a function that sits below RVA 0x1000, so every ELF-origin report whose code starts just past the ELF header loses its first functions. ELF and Mach-O synthesis keep all of them.

The blocks are reported and skipped rather than dropped silently, so the log does say what happened — but the synthesized PE is missing real code, which matters for the round-trip property #232 is about.

Reproducing

import lief
from pathlib import Path
from smda.Disassembler import Disassembler
from smda.SmdaConfig import SmdaConfig

raw = Path("tests/bashlite_xored").read_bytes()
data = bytes(b ^ (i % 256) for i, b in enumerate(raw))
report = Disassembler(SmdaConfig()).disassembleUnmappedBuffer(data)

parsed = lief.parse(list(bytes(report.synthesizeBinary(output_format="pe"))))
print("text starts at abs 0x%x" % (parsed.optional_header.imagebase + parsed.sections[0].virtual_address))
print("lowest function  0x%x" % min(report.xcfg))
print("functions below RVA 0x1000:", sum(1 for o in report.xcfg if o - report.base_addr < 0x1000))
text starts at abs 0x401000
lowest function  0x4000e8
functions below RVA 0x1000: 20

The synthesizer emits block 0x4000e8 of function 0x4000e8 fits no executable section, skipped for each one.

Cause

PeSynthesizer._synthesizeMinimal (src/smda/synthesis/PeSynthesizer.py:430) floors the .text start at one section alignment:

text_vaddr = max(align_down(min_rva, section_alignment), section_alignment)

which is correct as far as it goes — a PE section cannot start at RVA 0, that range belongs to the headers. But the image base is then left at the report's own base, so any function whose RVA is below 0x1000 has no section that can hold it and is skipped at planting time.

ELF binaries put their entry stub immediately after the program headers, so this is the normal layout for them rather than an edge case: all four affected fixtures are ELF-origin, with lowest RVAs between 0x94 and 0x158.

Measured

fixture base lowest RVA functions below 0x1000 PE ELF Mach-O
bashlite 0x400000 0xe8 20 7733 / 8767 8767 / 8767 8767 / 8767
mirai_i386 0x8048000 0x94 13 13365 / 14758 14758 / 14758 14758 / 14758
mirai_x64 0x400000 0x100 5 11762 / 12564 12564 / 12564 12564 / 12564
aarch64_static 0x400000 0x158 15 18963 / 19881 19881 / 19881 19881 / 19881

Measured with the round-trip loop from #232 against master at 5ad5504. PE-origin fixtures are unaffected, since a PE's own code already starts above its headers.

Suggested direction

The image base is the free variable: choosing it so that the lowest function extent lands at or above section_alignment — rather than inheriting the report's base and clamping .text — would let every function be planted at a correct RVA, at the cost of a synthetic base that differs from the report's. That is a visible trade-off, so it seems worth your call before anyone implements it; the alternative of keeping the base and accepting the loss is defensible too, in which case the skip is arguably worth surfacing more loudly than a log line.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions