mirror of
https://github.com/david942j/one_gadget.git
synced 2026-10-12 02:12:49 +00:00
* fetcher: promote disasm index to Base Move disasm_lines/disasm_index (full objdump disassembly + address→index map) from the ARM fetcher into Base so any arch can walk the disassembly, and extract the terminal-call regexp into a helper. Groundwork for control-flow-aware candidate discovery; no behavior change. Co-Authored-By: Claude Opus 4.8 <[email protected]> * Add conditional-branch-aware gadget search (aarch64) Gadgets in newer glibc are often split across a conditional branch (e.g. 2.43's execvp materialises "/bin/sh" before `cmp x2, #1; b.ne`), which the straight-line-only search could not find. - fetcher: enumerate candidates by walking the CFG *backward* from each terminal call along its predecessors (fall-through + branch targets), forking at conditional edges up to MAX_FORKS(4). Gated per-arch by follow_branches? (aarch64 on; others keep slice_prefix for now). - emulators: shared Conditional module models cmp/cmn/tst + b.<cond>/cbz/ cbnz/tbz/tbnz, turning each crossed edge into a constraint via one-line- ahead resolution (full relational: ==,!=,signed/unsigned </>/<=/>=, bit tests). Sound: abort a path we can't express (e.g. no preceding compare). - aarch64: inst_stp now handles non-sp stores like inst_str (needed to reach the argv-building block); resolve argv/envp fall back when a stack register has no tracked stack (frame pointer). - gadget: score branch conditions. 2.43 now yields execve("/bin/sh", x4, x6) guarded by `x2 == 0x1`. Existing aarch64 gadgets are unchanged or added to (fixtures updated); other arches untouched. Co-Authored-By: Claude Opus 4.8 <[email protected]> * emulators: conditional-branch-aware search for arm Enable follow_branches? for arm: model cmp/cmn/tst (previously nop-aliased) plus bne/beq/... condition-suffix branches and Thumb cbz/cbnz, reusing the shared Conditional module. Add arm branch classification (path_ends? covers bx / pop{..pc} / mov pc returns). Fixes surfaced by the new paths: guard inst_ldr/inst_str write-back against a non-register base, and skip posix_spawn candidates whose pid/file_actions/attrp is a concrete integer (a fixed address we can't constrain). arm gadgets are unchanged or added to (2.27, 2.39 each gain one; fixtures updated); e.g. 2.27 0x73f2c is now found guarded by `[r1] == 0`. Co-Authored-By: Claude Opus 4.8 <[email protected]> * Extend conditional-branch-aware search to x86 (amd64/i386) Model cmp/test + the full jcc set (and jcxz) in the x86 emulator, reusing the shared Conditional module; retire amd64's jmp_case_candidates (the CFG walker subsumes it). With every arch now branch-aware, drop the dead straight-line candidates path, slice_prefix and branch?. Soundness/robustness fixes exposed by the richer x86 paths: - drop gadgets whose branch condition is unsatisfiable (X != X) and strip always-true ones (X == X); - dedup gadgets by offset (a branch can reach the call either way), keeping the easiest variant; - treat a resolve-time eval error as "not a gadget"; - Lambda.parse: model absolute [addr]; keep xmm arrays / fall back only for a call-target string base; - guard the SSE handlers, inst_sub, check_envp against non-Lambda operands. amd64/i386 now find a superset of the previous gadgets (fixtures updated for the force_file paths; prebuilt-DB assertions unchanged). Co-Authored-By: Claude Opus 4.8 <[email protected]> * docs: note branch-aware gadget search in README Co-Authored-By: Claude Opus 4.8 <[email protected]> * perf: speed up branch-aware gadget search The backward CFG walk produces many overlapping candidate paths, and find() re-emulates every start line in each; the extra work made the specs notably slower. All behaviour-preserving (gadget output is unchanged): - find(): memoize by candidate suffix so shared tails are emulated once (~57% of starts were duplicates). - Processor#parse: build the instruction set (and a mnemonic index) once instead of re-allocating it per line. - CFG walk: precompute each line's branch classification and address once (line_kinds/disasm_addrs/predecessors_map) rather than re-running the per-arch regexes on every visit; walk with a backtracking Set instead of copying visited/path each step; precompile the terminal-call regex. - ARM emulator: cache the libc bytes instead of re-reading the file for every emulator instance. ~37% faster on the integration specs. Co-Authored-By: Claude Opus 4.8 <[email protected]> * perf: cut branch-aware setup cost on large libcs The previous pass eagerly classified every disassembly line (line_kinds / predecessors_map) — costly on big libcs (aarch64 2.43 has 310k lines). Rework so the whole-binary scan is cheap and the per-line classification only runs where the walk actually goes. Behaviour-preserving. - predecessors: computed lazily and memoized (the walk only visits lines near terminal calls), instead of precomputing for all lines. - branch_pred_map: reject the ~99% non-branch lines with one precompiled per-arch regex (branch_lead_regex) before the full test, and drop the mnemonic memoization that was allocating an entry per line during this scan. - disasm_cache: cache objdump output by command so re-analysing the same file (the specs load some libcs several times; the CLI reads a file once) skips the disassembly. ~45% faster on the full suite (60.7s -> 33.6s here). Co-Authored-By: Claude Opus 4.8 <[email protected]> * perf: windowed disassembly for arm (byte-scan the calls) A full objdump of a Thumb-2 arm libc is ~4x slower than amd64/aarch64 (~1.1s vs ~0.3s), yet the branch-aware search only ever touches ~0.35% of the disassembly — everything it needs sits within ~0x1246 bytes of an exec*/posix_spawn* call (measured max: 0x98b backward span + 0x8bb branch-in across real amd64/aarch64/arm libcs). So for arm we locate the calls *without* disassembling: take the exec*/posix_spawn* addresses from the symbol table and scan .text for A32/Thumb `BL` instructions branching to them, then objdump only a 0x2000 window around each (merging overlaps). The scan is exact for the direct `bl <exec*>` calls the search cares about — validated to miss none of the objdump-grep sites on the 2.23/2.27/2.39 fixtures (a rare false positive just adds an empty window); if it finds nothing we fall back to a full disassembly. Behaviour-preserving: gadget output is identical (the arm integration specs assert exact lists). Single-libc time drops ~52-57% (2.39: 2.84s -> 1.32s; 2.23: 1.70s -> 0.73s). Only arm opts in — the fast-to-objdump arches keep the exhaustive full scan. Co-Authored-By: Claude Opus 4.8 <[email protected]> * refactor: one branch_kind per arch instead of four predicates A new architecture had to implement four overlapping methods to classify a line for the CFG walk (conditional_branch?, unconditional_branch?, path_ends?, branch_lead_regex). Collapse them into one intuitive classifier: branch_kind(line) -> :conditional | :unconditional | :terminator | nil Base derives the predicates it needs from it and builds branch_lead_regex from a tiny branch_lead_chars hint. No behavior change. Co-Authored-By: Claude Opus 4.8 <[email protected]> * refactor: share compare/operand helpers across emulators handle_compare, operand_str and mnemonic were byte-for-byte identical in the aarch64/arm/x86 emulators (x86's `test ? :test : :cmp` is just `mnem.to_sym`). Move them into the shared Conditional module so a new arch only supplies the genuinely arch-specific parts: which mnemonics are compares/branches (branch_mnem?), how to split operands (operands), and how a branch maps to a queued decision (handle_branch). No behavior change. Co-Authored-By: Claude Opus 4.8 <[email protected]> * docs: guide for adding a new architecture The arch surface is spread over a Fetcher and an Emulator subclass; collect the full contract in one place (docs/adding-an-architecture.md) and point the two base classes at it, so it's clear what a new arch must implement. Co-Authored-By: Claude Opus 4.8 <[email protected]> * docs: add ruby lang hint to code block * style: keep lib comments ASCII, enforce via rubocop * docs: add @example usage to Conditional methods * docs: prefer example/param tags over inline e.g. * refactor: name branch conditions by predicate, adapters per arch * refactor: share ARM-family emulator logic via ArmFamily module * emulators: model each compare's ALU op, not just cmp Keys compares by ALU op (:sub/:add/:and) via each arch's COMPARES, dropping the :cmp special case so a new arch just declares its compare mnemonics. Fixes the test/tst mis-rendering this exposed: test r,r; jne rendered r != r (a false contradiction that dropped valid gadgets), test r,r; jz an unsound r == r tautology, and cmn now allows ordered conditions. Recovers gadgets on amd64 2.19/2.31. * emulators: tighten Conditional per review Rename COMPARE -> COMPARE_OPS so it no longer collides with the arch COMPARES mnemonic map; co-locate each op's renderer in the table and dispatch via fetch, so an unknown op raises loudly instead of yielding a nil constraint. Document Conditional's required host interface and branch_lead_chars' regex-safety contract. * docs: add class + pipeline diagrams to the arch guide * refactor: rename queue_* branch helpers to branch_on_* queue_cond_branch -> branch_on_compare, queue_cbz -> branch_on_zero, queue_tbz -> branch_on_bit. The names now describe what the branch tests (a recorded compare / a register being zero / a single bit) instead of the ARM mnemonic, and drop the misleading 'queue' (there is only one pending branch, not a queue). * docs: keep Conditional statements arch-neutral Move arch mnemonics (cmp/cbz/b.ne/jcc/tst/...) out of the descriptive prose, param and return docs into the @example blocks; the statements now say what each method does and why in arch-independent terms, and the render_* / COMPARE_OPS comments are labelled by ALU op rather than mnemonic. --------- Co-authored-by: Claude Opus 4.8 <[email protected]>