CGC: Repair only the header bytes that are loaded - #726
Conversation
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.
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Validation record for head
Sweep over a copy of
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 |
|
Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_726 |
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_addrreturn None, andAT.from_rawthen raisesTypeErrorfrom 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_00002in a temporary copy, so no new fixture is needed.Validation: #726 (comment)