Files
rea/docs/binary-diagnostics.md
Jason Cat 951c2aae3b fix(native): raise default PE resource entry budget to 16384 (#1703)
inspect-pe-resources rejected stock Windows resource images because their
tables exceed the 4096-entry default. Windows 11 25H2 SystemResources
measures imageres.dll.mun at 6476 examined entries, shell32.dll.mun at 5903
and wmploc.DLL.mun at 5440, so the default budget now admits each of them
with headroom while the maximum stays 65536. The independent 8 MiB metadata
budget still bounds allocation, and exceeding any budget still fails without
partial results.

The stable filesystem boundary test now emits a 6476-entry table through a
larger fixture section and asserts that the default budgets report complete
coverage, covering stock-image scale without a real Windows file.
2026-10-11 19:01:33 +08:00

11 KiB

Offline binary diagnostics

inspect_binary_layout / inspect-binary-layout answers what an explicit file contains: section/segment layout, original symbols/relocations, static linkage names and mitigation indicators. It works without an active Hopper, Ghidra or IDA target. CTF is one possible use; crash triage, compatibility and ordinary binary inspection use the same modular capability.

Bring your own engine

Initial real verification covers Linux x64, ELF64 x86-64 little-endian EXEC/DYN/REL. Provide an absolute Python executable whose environment already contains unchanged pwntools 4.15.0, pyelftools 0.33 and Unicorn 2.1.2:

REA_PWNTOOLS_PYTHON=/absolute/isolated-env/bin/python \
  rea inspect-binary-layout ./selected.elf --json
{
  "name": "inspect_binary_layout",
  "arguments": { "path": "/artifacts/selected.elf" }
}

REA never installs Python, packages or GDB, changes a user init file, launches the selected object as a host process, or requests runtime library resolution. The Python process uses isolated mode and an owned cache. Exact upstream profiles are recorded in upstream provenance.

Interpreting results

  • Artifact path, SHA-256 and size identify original selected bytes. All parsing reads an owned stable snapshot; the original is unchanged.
  • Addresses, offsets, lengths and flags are hexadecimal strings. A linked address is not a runtime address. Runtime load base remains null. A zero EXEC/DYN entry means absent; a relocatable entry is not applicable.
  • Each symbol retains its table and entry index. Its reported value can mean undefined, alignment, absolute value, no address, unknown section index, section offset, TLS offset or linked virtual address. Duplicate names remain separate entries. SHN_XINDEX remains unresolved in the selected upstream representation; its external index table is not silently treated as resolved.
  • Relocatable objects have section-relative relocations and no executable entry claim. Signed REL/RELA relocation addends are decimal strings. Packed RELR tables retain full original bytes and upstream-decoded offsets as derived evidence, with per-offset packed-word locations and implicit addends unknown. Overall relocation inventory completeness remains unknown. A relocatable SHN_UNDEF target or inactive SHT_NULL header remains an explicit unknown with the original reported section index and offset.
  • REL/RELA symbol references retain their original table and entry indices. Positive indices resolve through a validated symbol table; symbol index zero means a zero symbol value without a table lookup, including when no table is linked. Malformed references fail with the affected section and symbol index.
  • Section-name and symbol tables require declared SHT_STRTAB links. SHN_UNDEF explicitly means no section names: display is empty, raw name and location unknown, with the original name offsets retained. Extended section-name indices resolve through section zero and undergo the same validation.
  • Symbol tables require a declared SHT_STRTAB link. Name offsets and terminators must stay inside the declared string table; malformed references fail before unrelated bytes can become names. Dynamic dependencies use DT_STRTAB/DT_STRSZ with a unique file-backed mapping; ambiguous or unbacked mappings are explicitly unsupported.
  • Name display strings use UTF-8 replacement for opaque bytes, including sectionless dependency names and interpreter paths. Raw name bytes and string-table ranges retain observed identity where resolvable; ranges include the terminating NUL and base64 bytes exclude it.
  • NOBITS and NULL sections provide no file bytes. Reported file-backed ranges are checked against snapshot size again at the public boundary. PT_NULL payload fields are unused: reported numbers remain, file backing is none and interpreted permissions are null. Segments with zero file size also have no file bytes.
  • Sectionless dynamic images still report dependency names with raw bytes and file locations where uniquely mapped. Missing symbol/relocation section tables do not establish that those runtime tables are absent. Upstream RELRO/canary heuristics rely on sections and can be incomplete in this case; their reported candidates retain an explicit coverage limitation.
  • DT_NEEDED/PT_INTERP are reported names, not resolved runtime paths. GOT/PLT maps are derived convenience views and may collapse aliases. PLT inference can emulate selected instructions in Unicorn; its outputs are static candidates. Exec syscall tracing observes host process launches. Map completeness remains unknown and original upstream warnings are returned inline.
  • Canary, PIE, NX, executable-stack and RELRO are static inferences. NX and executable-stack are separate upstream indicators: on x86-64 an executable stack can accompany an unknown (null) NX indicator. ET_DYN can be a shared library, and an absent canary symbol does not prove every function unprotected.

When complete layout Evidence is already retained, inspect_analysis_view / inspect-analysis-view projects a summary, the mitigations or linkage facet, one section or symbol, or a stable page without re-running the decoder. CLI one-shot invocations pass portable inline Evidence JSON; an MCP session uses the retained evidence_id. Page limit is required and bounded by the measured MCP stdio budget documented on the tool contract.

Over MCP, inspect_binary_layout with "detail": "summary" retains the complete layout Evidence in the session and returns only its summary view. Its normalized_result.parent_evidence_id names the retained layout for later section, symbol, mitigation or linkage views. The default complete detail and CLI output are unchanged.

Complete results have a 32 MiB input, 64 MiB reply, 1 MiB combined diagnostics and 30-second owned command deadline. The reply budget applies to the decoder record; the CLI/MCP Evidence envelope adds copies and encoding overhead. The pinned MCP SDK defaults to a 10 MiB receive buffer. Large complete results need a caller-configured StdioClientTransport({ maxBufferSize: ... }); the large extended-index verification uses 256 MiB and an explicit five-minute request timeout in its separate opt-in MCP lane. REA does not silently truncate the result. Python lowers resource soft limits to at most 3 GiB virtual address space, 30 CPU seconds and 64 MiB file output. Inherited tighter soft/hard limits are retained, and the effective values are returned in the result limitations. Virtual address space is not RSS; Unicorn needs a large virtual map. Limits fail with no partial success, and owned process/root cleanup completes independently of cancellation.

Core files, GDB session/state/control, optional pwndbg enrichment and instruction inspection are separate increments tracked by #969. This increment makes no real support claim for those features or other operating systems/architectures.

Typed decoder failures retain bounded captured_output (stdout, stderr and an explicit truncation flag) alongside the original failure category and reason. Successful diagnostics also exposes the supervisor's truncated flag. Output beyond the complete diagnostic budget is rejected, including when the decoder writes a valid reply; cleanup and late cancellation reuse the observed flag. Memory allocation failures use resource_constraint, with effective resource limits (or an explicit unknown) and memory-specific recovery guidance. A MemoryError alone does not establish the exact failed allocation or that a particular budget was exhausted. Invalid undersized symbol entries are rejected before REA reports source ranges for them. The bridge reserves 1 MiB to report allocation failures. If even error reporting or serialization fails with MemoryError, its reserved exit status preserves the resource classification with unknown effective limits. Reserved exits require a matching private marker written by the bridge's actual failure branch. Bare launcher exits or missing/unwritable markers retain process diagnostics with the resource cause unverified. Signal termination alone is never classified as observed memory exhaustion. The small bootstrap also covers catchable MemoryError during adapter imports, compilation and initialization. Python interpreter startup failures before the bootstrap runs retain their observed process diagnostics without guessed causes. SIGXCPU retains a CPU resource diagnostic and CPU-specific recovery advice. Observed EFBIG writes or SIGXFSZ termination retain a file-size resource diagnostic and file-size-specific recovery advice. If a tight file limit also prevents writing the limit record, effective values remain unknown. The bridge records actual limits in owned storage before analysis; missing or malformed limit reports remain unknown with their read failure preserved. A received signal alone does not establish its exact cause. Dynamic tags require a complete DT_NULL inside PT_DYNAMIC; interpreter names and ranges end at the first NUL, with any padding still represented by the original segment range.

Portable PE resources

inspect_pe_resources / inspect-pe-resources inspects an explicit PE32 or PE32+ file without an active target, native engine, Python, or Windows SDK:

rea inspect-pe-resources ./selected.exe --json
{
  "name": "inspect_pe_resources",
  "arguments": { "path": "/artifacts/selected.exe" }
}

The complete Evidence identifies the stable artifact snapshot by path, byte count and SHA-256. Resources retain separate type, name and language identities: numeric IDs are distinct from UTF-16 names, whose original bytes are also reported. Directory headers, entries, data entries and payloads retain original file offsets and lengths. Each opaque payload has its own SHA-256; code page and reserved data-entry fields are preserved.

RT_GROUP_ICON records expose original image descriptors and RT_ICON candidates. A reference resolves only when an icon with the same numeric ID and language exists. Missing references, other-language candidates and declared-size mismatch remain explicit. Encoded dimensions are preserved (zero denotes 256 pixels). This is static reference inspection; no image decoding, language fallback, resource loading or selected-file execution occurs.

An absent resource data directory is a complete empty inventory. Malformed headers, truncated tables, cycles, duplicate sibling identities, unbacked or ambiguous RVA mappings, nonidentical overlapping ranges and unsupported tree shapes fail explicitly, rather than returning partial inventories. Exact shared payload ranges are supported. The supported tree has three levels: type, name, and language. Other resource payload formats remain opaque.

max_file_bytes defaults to 64 MiB (maximum 512 MiB); max_entries defaults to 16384, the largest stock Windows resource image measured with headroom (maximum 65536). CLI options are --max-file-bytes and --max-entries. Metadata is bounded to 8 MiB; exceeding any budget fails without truncation. Large Evidence envelopes may require a caller-configured MCP transport receive buffer because envelope copies add overhead. Requests honor cancellation during snapshot reading, directory traversal and payload hashing. The operation reads regular files only and rejects symbolic links.