Repository navigation
Conversation
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS Validation record for head
Reproducer on a public fixture: Corpus A/B over 402 COFF objects, sampled 25 per (collection, architecture) from a private corpus of 25,051, one object per forked child and one side per child, each side run twice:
Caveats: corpus objects are referred to by architecture, container and digest because the dataset is not public; the reproducer above uses a fixture already in Re-keyed 2026-08-28. The figures above were measured at |
|
Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_761 |
461bb73 to
22f6feb
Compare
A COFF object names what it needs from elsewhere by giving the symbol section number zero. The loader already allocates each of those an extern address and relocates against it, but the symbols never say they are imports, so Relocation.__init__ does not record them and self.imports stays empty. angr's Project._register_object iterates obj.imports and nothing else, so a COFF object gets nothing hooked: a call to an undefined external lands in the extern object's zero fill and is decoded as instructions, and under symbolic execution it executes that zero fill instead of a stub. Say so on the symbol; the generic relocation machinery does the rest, __imp_* entries included, which are data and so are listed without being hooked. A COFF symbol's Type field also says 0x20 for a function and 0 for nothing at all, which is what every GCC-family toolchain emits, and reading 0 as data meant those imports were listed and then skipped rather than hooked. TYPE_NONE is how CLE spells "the file did not say", and it is already what a relocatable ELF's STT_NOTYPE symbols become. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
22f6feb to
7acefe5
Compare
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS The imports cle reports for the x86-64 COFF fixture, with the symbol type and the extern address each resolves to. the reproducerimport logging, os
logging.getLogger("cle").setLevel(logging.CRITICAL)
import cle
BIN = os.environ["BINARIES"] # a checkout of angr/binaries
T = lambda *p: os.path.join(BIN, "tests", *p)
path = T("x86_64", "fauxware.obj")
ld = cle.Loader(path, auto_load_libs=False)
obj = ld.main_object
print(f"obj.imports: {len(obj.imports)} entries")
for name in sorted(obj.imports):
reloc = obj.imports[name]
sym = reloc.resolvedby
where = ld.find_object_containing(sym.rebased_addr) if sym is not None else None
kind = reloc.symbol.type.name if reloc.symbol is not None else "?"
print(f" {name:<52} {kind:<14} -> {sym.rebased_addr:#x} in {type(where).__name__}"
if sym is not None else f" {name:<52} {kind:<14} unresolved")Before — the object's undefined externals never reach self.imports, so angr hooks none of them: cle master at
|
|
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS What this changes on a real corpus, measured. It replaces a Sample. 12,000 objects drawn uniformly at random, from a seeded permutation, Method. All 468 are loaded with the catalogue's declared recipe against What changes. On What that does to analysis. Recovered functions go from 15,457 to 15,479 Overlap with the other open COFF changes. #724, #764 and #775 each touch The corpus is not redistributable, so its objects are described by architecture, session: sharpen |
THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS
Problem
A COFF object never says what it needs from elsewhere. On
binaries/tests/x86_64/fauxware.obj:Project._register_objectreadsself.importsand nothing else to decide whatto hook, so a call to
strcmporexitin this object lands in the externobject's zero fill and is decoded as instructions. The object's ten undefined
externals are all reachable from its code.
Root cause
A COFF object names an undefined external by giving the symbol section number
zero. cle already allocates each of those an extern address and relocates against
it -- the addresses exist -- but nothing on the symbol says it is an import, so
self.importsis never populated. The second half isType: a COFF symbol says0x20for a function and0for "no type information", which is what everyGCC-family toolchain emits, and cle read
0as data. An import listed as data islisted and then skipped rather than hooked.
Fix
Say so on the symbol -- the generic relocation machinery already does the rest --
and map an untyped COFF symbol to
TYPE_NONE, as a relocatable ELF'sSTT_NOTYPEsymbols already are, rather than to a data symbol.Testing
tests/test_coff.py::TestCoff::test_undefined_externals_are_importsasserts thatthe fixture reports
strcmpand_RTC_CheckStackVarsamong its imports, thatmainis not among them, and that every import carries anis_importsymbolresolved into the extern object.
obj.importsis empty on the merge base, so itfails on the first assertion.
Fixes #746. Validation: #761 (comment)
session: sharpen