Commit graph

3 commits

Author SHA1 Message Date
trvon
2ffa58c50f
feat: ROP chain synthesis attached to binary buffer-overflow findings (Part B)
Turn a detected overflow + gadget inventory into a candidate ROP chain
scaffold (auto target) attached to the finding.

- rop_chain (pure, fold::binary, unit-tested): synthesizeChain auto-selects a
  target — a recovered "win" function (system/execve/puts/printf), else an
  execve("/bin/sh") syscall chain assembled from syscall + register-control
  gadgets (x0/x1/x2/x8 on AArch64, rdi/rsi/rdx/rax on x86-64). Emits ordered
  pad/gadget/value/target steps + explicit caveats (PIE/ASLR base, canary, and
  that padding_len is the buffer capacity — the exact saved-return-address
  offset is target-frame-dependent and must be verified).
- ghidra_lifter: expose each pointer arg's recovered stack-frame offset
  (in_buffer_offsets) alongside its size; signed-offset helper. (Exact RA-slot
  recovery is left for future work — the saved return address for these
  overflows lives in the caller's frame, which a single-function lift can't see;
  honest caveats cover this rather than emit a wrong padding.)
- r2_backend: findStringAddress ("/bin/sh" via r2 /j) to seed execve chains.
- report: ExploitContext gains an optional RopChainInfo (padding_len, target,
  steps, caveats); serialized in JSON (write + read round-trip) and SARIF.
- binary_pcode_scan: attachExploitPrimitives now also synthesizes a chain per
  buffer-overflow finding from the gadget inventory + recovered capacity.

Validated: baby_bof's decode_base64 overflow carries a chain (target win:printf,
padding 256, steps + caveats). Unit: test_rop_chain (17 assertions). Corpus BOF
counts unchanged; meson test 77/77.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 09:34:37 -04:00
trvon
5b5fd7dbc6
feat: binary stack-buffer-overflow detection via SSA P-Code
Phase 5: detect stack buffer overflows in binaries through the P-Code
pipeline, value-aware against recovered buffer capacities.

- ir::pcode::PcodeOp gains in_buffer_sizes (parallel to in): per-pointer-arg
  recovered stack-buffer capacity, SSA-only (-1 = unknown).
- ghidra_lifter: inject locked prototypes for known copy/input sinks at their
  call-target addresses BEFORE the SSA action pipeline, so parameter recovery
  attaches the destination pointer; resolveStackBufferBytes traces the
  PTRSUB/PTRADD/INT_ADD spacebase+const def chain to a stack address and reads
  the recovered local's type size via getScopeLocal()->findOverlap().
- pcode_encoder: recognize copy/input sinks (unbounded: gets/strcpy/strcat/
  sprintf/...; bounded: fgets/read/memcpy/strncpy/snprintf/...) and fire
  onBufferCopy with the write length when the dst capacity was recovered.
- BinaryBBObserver::onBufferCopy: ugt(size, cap) bounds check -> OOBAccess
  finding (binary-buffer-overflow). Unbounded sinks -> Heuristic; bounded with
  provable len>cap -> Proved (checkAndEmit gains a confidence parameter).
- router: SSA rewrites CALL targets to synthesized addresses, so resolve callee
  names from the raw stream by target address and carry them to the SSA stream
  by call-site instruction address (annotateByTarget / annotateBySite).
- r2_backend: extractCallTargetNames maps stub/func addresses -> normalized
  callee names; scan_runner activates the lane for oob/oob-access detectors.

Validated on bench/corpora/ctf-challenges: 3 TPs in libbof_overflow
(gets->32B, strcpy->64B, sprintf->128B), genuine TPs in libcmd_injection
(sprintf->256B) and libvuln_demo (strcpy->16B), zero false positives across
the corpus; safe_version and the bounded __strncpy_chk path are not flagged.
Unit tests cover unbounded/bounded/safe/no-size/non-sink cases.
meson test: 74/74. div-by-zero and null-deref P-Code findings unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 00:09:07 -04:00
trvon
bd1f289f06
feat(binary): dual-mode Ghidra lifter (raw + SSA) -> fold::ir::pcode (Phase 2)
Phase 2 of the Ghidra P-Code binary frontend. Adds the frontend-agnostic
P-Code IR and a dual-mode lifter, validated end-to-end.

- include/fold/ir/pcode.hpp: the ~25-op core P-Code IR (Op/Space/Varnode/
  PcodeOp/Function), no Ghidra/clang types (M30 invariant). Function carries a
  Source tag (Raw|SSA) so findings record provenance.
- include/fold/binary/ghidra_lifter.hpp + src/binary/ghidra_lifter.cpp
  (Ghidra types confined): a buffer LoadImage (no BFD) + two entry points
  producing the same ir::pcode::Function:
    * liftRaw  — Sleigh::oneInstruction, per-instruction RAW P-Code;
    * liftSSA  — SleighArchitecture + Funcdata + action pipeline, SSA high
      P-Code (needs CapabilityPoint::initializeAll + whole-archive link).
  Both serialized under one mutex (decompiler global state).
- meson: libghidra_decompiler gains the arch/loader infra (sleigh_arch,
  inject_sleigh, loadimage_xml, xml_arch, raw_arch); dep switched to
  link_whole so capability self-registration constructors run; pcode_lift
  smoke driver added (built at fold's std, links the c++11 lib).

Empirical validation on arm64 sym._div_by_zero_direct confirms the dual-path
rationale: RAW preserves `(w9)=COPY const 0` + `INT_SDIV(w8,w9)` (the hazard),
while SSA folds it to `COPY const 0` (ARM64 sdiv-by-0 returns 0; Ghidra's
optimizer constant-folds x/0 -> 0). The two levels are complementary streams
into the Z3 core (next phases): raw for div/overflow/null-deref, SSA for
UAF/dataflow.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 19:39:21 -04:00