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.
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
The synthesizer emits
block 0x4000e8 of function 0x4000e8 fits no executable section, skippedfor each one.Cause
PeSynthesizer._synthesizeMinimal(src/smda/synthesis/PeSynthesizer.py:430) floors the.textstart at one 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
0x1000has 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
0x94and0x158.Measured
0x1000bashlite0x4000000xe8mirai_i3860x80480000x94mirai_x640x4000000x100aarch64_static0x4000000x158Measured with the round-trip loop from #232 against
masterat5ad5504. 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.