Skip to content

CGC: Repair only the header bytes that are loaded - #726

Open
zardus wants to merge 1 commit into
masterfrom
feature/fix-cle-cgc-offset
Open

CGC: Repair only the header bytes that are loaded#726
zardus wants to merge 1 commit into
masterfrom
feature/fix-cle-cgc-offset

Conversation

@zardus

@zardus zardus commented Aug 10, 2026

Copy link
Copy Markdown
Member

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

The CGC backend substitutes an ELF magic into the stream so pyelftools will parse the file, then stores the real CGC header back into memory. That store assumed the header has a virtual address, but nothing requires it to sit inside a PT_LOAD. Real challenge binaries with no PT_PHDR and a first PT_LOAD past the header make offset_to_addr return None, and AT.from_raw then raises TypeError from inside address arithmetic, so the load fails outright.

The backend that makes the substitution is what has to undo it, so it now stores back only the bytes that were loaded. A binary mapping none of the header loads unchanged, and one whose first PT_LOAD begins partway into the header keeps its own bytes rather than the substituted magic.

The regression moves the first PT_LOAD of tests/cgc/CADET_00002 in a temporary copy, so no new fixture is needed.

Validation: #726 (comment)

The CGC backend substitutes an ELF magic into the stream so pyelftools will
parse the file, then writes the real CGC header back into memory afterwards.
That store assumed the whole 16-byte header has a virtual address, but nothing
requires the header to be inside a PT_LOAD. When file offset 0 is not loaded,
offset_to_addr returns None and AT.from_raw raises TypeError from inside
address arithmetic, so the load fails outright. Real CGC challenge binaries are
laid out this way, with no PT_PHDR and a first PT_LOAD starting well past the
header.

Store back only the bytes that the substitution actually reached. A binary that
maps none of the header now loads unchanged, and one whose first PT_LOAD starts
partway into the header keeps the bytes it really contains instead of the
substituted magic.
@zardus

zardus commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Validation record for head aa99be0055126c0c1bfa7f05a8eb5e94a240b7f1 against baseline b58ea02a446106647cdaae32bdf91b7062404cc1.

  • Regression: pytest tests/test_cgc.py::test_cgc_unmapped_header — on baseline fails with TypeError: unsupported operand type(s) for -: 'NoneType' and 'int' at cle/address_translator.py:63, passes on head
  • Regression: pytest tests/test_cgc.py::test_cgc_partially_mapped_header — fails against a variant that only checks file offset 0, which loads eight zero bytes where the file has 01 4d 65 72 69 6e 6f 00; passes on head
  • Focused: pytest tests/test_cgc.py — 3 passed
  • Full suite: pytest tests/ — 205 passed, 9 skipped on head; 202 passed, 9 skipped on baseline
  • Lint/type: pre-commit run --all-files — 24 hooks pass, no file rewritten; pylint 10.00 -> 10.00 and pyright badness 0.0 -> 0.0 on both changed files against the baseline
  • Workspace gate: cle — pass (Python 3.12.13, pyright 1.1.411)

Sweep over a copy of tests/cgc/CADET_00002 whose first PT_LOAD is moved to a range of file offsets, comparing loaded memory against the file bytes each segment maps:

First PT_LOAD Baseline Head
0x0, as shipped matches file matches file
0x1, 0x4, 0x8, 0xf TypeError matches file
0x10, 0x40, 0xa0 TypeError matches file
0x0, mapping 8 bytes KeyError: 8 matches file

The last row is a separate pre-existing crash from the same assumption, fixed by the same change. No substituted ELF magic survives in the loaded image in any row.

Caveats: the unmapped-header layout comes from real CGC challenge binaries seen in a corpus sweep, none of which can be redistributed here; every CGC fixture in angr/binaries maps its header at file offset 0, which is why CI never reached this. A first PT_LOAD that begins inside the header is not a layout that sweep saw, and it is covered because the fix would otherwise have left substituted bytes in memory there. Load time for tests/cgc/CADET_00002 is unchanged, 1.2-1.7 ms per load over three runs of 20 on both revisions.

@angr-bot

Copy link
Copy Markdown
Member

Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_726

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants