fold/tools/pcode_lift.cpp
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

125 lines
4.1 KiB
C++

// Phase-2 smoke driver for the Ghidra SSA lifter (fold::binary::liftFunctionToPcode).
// Reads a raw function-bytes file and prints the lifted fold::ir::pcode ops.
//
// pcode_lift <specs_dir> <language_id> <base_vaddr_hex> <raw_bytes_file>
//
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
#include <vector>
#include "fold/binary/ghidra_lifter.hpp"
namespace {
const char* spaceName(fold::ir::pcode::Space s) {
using S = fold::ir::pcode::Space;
switch (s) {
case S::Constant:
return "const";
case S::Register:
return "reg";
case S::Memory:
return "ram";
case S::Unique:
return "uniq";
case S::StackBase:
return "stack";
case S::Other:
return "other";
}
return "?";
}
void printVn(const fold::ir::pcode::Varnode& v) {
std::cout << '(' << spaceName(v.space) << ",0x" << std::hex << v.offset << std::dec << ','
<< v.size << ')';
}
} // namespace
void dump(const fold::binary::LiftRequest& req, const fold::ir::pcode::Function& fn,
const char* level) {
std::cout << "=== " << level << ": '" << fn.name << "' @ 0x" << std::hex << fn.entry << std::dec
<< " — " << fn.ops.size() << " ops ===\n";
for (const auto& op : fn.ops) {
std::cout << " 0x" << std::hex << op.address << std::dec << " ";
if (op.out) {
printVn(*op.out);
std::cout << " = ";
}
std::cout << fold::ir::pcode::opName(op.op);
for (const auto& in : op.in) {
std::cout << ' ';
printVn(in);
}
if (!op.in_buffer_sizes.empty()) {
std::cout << " bufsizes[";
for (std::size_t i = 0; i < op.in_buffer_sizes.size(); ++i)
std::cout << (i ? "," : "") << op.in_buffer_sizes[i];
std::cout << ']';
}
if (!op.in_buffer_offsets.empty()) {
std::cout << " bufoffs[";
for (std::size_t i = 0; i < op.in_buffer_offsets.size(); ++i)
std::cout << (i ? "," : "") << op.in_buffer_offsets[i];
std::cout << ']';
}
std::cout << '\n';
}
}
int main(int argc, char** argv) {
if (argc != 6) {
std::cerr << "usage: " << argv[0]
<< " <specs_dir> <sla_path> <language_id> <base_vaddr_hex> <raw_bytes_file>\n";
return 2;
}
const std::string specs_dir = argv[1];
const std::string sla_path = argv[2];
fold::binary::LiftRequest req;
req.language_id = argv[3];
req.base = std::strtoull(argv[4], nullptr, 16);
req.name = "smoke";
std::ifstream bf(argv[5], std::ios::binary);
if (!bf) {
std::cerr << "cannot open bytes file: " << argv[5] << '\n';
return 2;
}
const std::vector<char> raw((std::istreambuf_iterator<char>(bf)),
std::istreambuf_iterator<char>());
req.bytes.assign(raw.begin(), raw.end());
// Optional sink prototypes for SSA arg recovery, e.g.
// FOLD_SINK_TARGETS="0x5fc=gets,0x620=strcpy"
if (const char* env = std::getenv("FOLD_SINK_TARGETS")) {
std::string s(env);
std::size_t pos = 0;
while (pos < s.size()) {
const std::size_t comma = s.find(',', pos);
const std::string tok = s.substr(pos, comma == std::string::npos ? comma : comma - pos);
const std::size_t eq = tok.find('=');
if (eq != std::string::npos)
req.sink_targets.emplace_back(std::strtoull(tok.c_str(), nullptr, 16),
tok.substr(eq + 1));
if (comma == std::string::npos)
break;
pos = comma + 1;
}
}
std::string err_raw;
if (auto fn = fold::binary::liftRaw(req, sla_path, &err_raw))
dump(req, *fn, "RAW");
else
std::cerr << "raw lift failed: " << err_raw << '\n';
std::string err_ssa;
if (auto fn = fold::binary::liftSSA(req, specs_dir, &err_ssa))
dump(req, *fn, "SSA");
else
std::cerr << "ssa lift failed: " << err_ssa << '\n';
return 0;
}