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>
125 lines
4.1 KiB
C++
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;
|
|
}
|