Turn a detected binary stack-overflow into actionable exploit material: each buffer-overflow finding now carries candidate exploitation primitives discovered from the same binary. - rop_gadgets module (fold::binary, r2-gated): classifyGadgets() is a pure, unit-tested classifier over (addr, opcode-text) gadget pairs into reg-control / syscall / stack-pivot / control-flow classes, arch-aware (AArch64 + x86-64); discoverExploitPrimitives() enumerates via r2 /Rj, keeps top-N per class, and adds "win" targets (jumpable high-value func/stub addresses — system/execve/ puts/printf/... — resolved by name via extractCallTargetNames). - r2_backend: extractRopGadgets wraps r2 /Rj (flattened opcode text), bounded by search.maxhits to keep large static binaries fast (~0.5s on a 900KB binary). - report: ExploitContext gains a GadgetRef list; BinaryBBObserver::onBufferCopy fills input_channel (the sink) + buffer_offset (recovered capacity) + controlled_value; scanBinaryWithPcode attaches the gadget inventory once when any binary-buffer-overflow finding exists (no cost on clean binaries, graceful if r2 is absent). Serialized in JSON (write + read round-trip) and SARIF. Scope: supply classified primitives, not auto-assembled chains; attached only to buffer-overflow findings (no standalone subcommand). Opt-in security tooling for authorized CTF/pentest use. Validated: libbof_overflow BOF findings now carry exploit_context.gadgets (reg-control x0-setup sequences, stack-pivots, printf win target) + input_channel/buffer_offset. Unit: test_rop_gadgets (8 cases). meson test 75/75. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.8 KiB
C++
68 lines
2.8 KiB
C++
// Unit tests for the ROP gadget classifier (classifyGadgets). Pure — feeds
|
|
// synthetic (addr, opcode-text) gadget pairs and asserts the assigned class,
|
|
// with no r2 dependency (mirrors the scanInstructionsFor* disasm-scanner tests).
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "fold/binary/rop_gadgets.hpp"
|
|
|
|
namespace {
|
|
using fold::binary::classifyGadgets;
|
|
using fold::binary::Gadget;
|
|
using fold::binary::GadgetClass;
|
|
|
|
GadgetClass classOne(const std::string& text, const std::string& arch) {
|
|
std::vector<std::pair<std::uint64_t, std::string>> raw = {{0x1000, text}};
|
|
return classifyGadgets(raw, arch).front().cls;
|
|
}
|
|
} // namespace
|
|
|
|
TEST_CASE("rop: aarch64 epilogue ldp x29,x30 is register control", "[rop]") {
|
|
REQUIRE(classOne("ldp x29, x30, [sp], 0x10 ; ret", "arm") == GadgetClass::RegControl);
|
|
REQUIRE(classOne("mov x0, x19 ; ret", "arm") == GadgetClass::RegControl);
|
|
}
|
|
|
|
TEST_CASE("rop: aarch64 svc is a syscall gadget", "[rop]") {
|
|
REQUIRE(classOne("mov x8, 0x5d ; svc 0 ; ret", "arm") == GadgetClass::Syscall);
|
|
}
|
|
|
|
TEST_CASE("rop: explicit sp write is a stack pivot", "[rop]") {
|
|
REQUIRE(classOne("add sp, sp, 0x40 ; ret", "arm") == GadgetClass::StackPivot);
|
|
REQUIRE(classOne("mov sp, x29 ; ret", "arm") == GadgetClass::StackPivot);
|
|
}
|
|
|
|
TEST_CASE("rop: aarch64 indirect branch is control flow", "[rop]") {
|
|
REQUIRE(classOne("blr x8", "arm") == GadgetClass::ControlFlow);
|
|
REQUIRE(classOne("br x16", "arm") == GadgetClass::ControlFlow);
|
|
}
|
|
|
|
TEST_CASE("rop: x86-64 pop reg is register control, syscall is syscall", "[rop]") {
|
|
REQUIRE(classOne("pop rdi ; ret", "x86") == GadgetClass::RegControl);
|
|
REQUIRE(classOne("pop rsi ; pop r15 ; ret", "x86") == GadgetClass::RegControl);
|
|
REQUIRE(classOne("syscall", "x86") == GadgetClass::Syscall);
|
|
REQUIRE(classOne("leave ; ret", "x86") == GadgetClass::StackPivot);
|
|
}
|
|
|
|
TEST_CASE("rop: a bare ret is not a useful primitive", "[rop]") {
|
|
REQUIRE(classOne("ret", "arm") == GadgetClass::Other);
|
|
REQUIRE(classOne("nop ; ret", "arm") == GadgetClass::Other);
|
|
}
|
|
|
|
TEST_CASE("rop: syscall outranks a register load in the same gadget", "[rop]") {
|
|
// mov x8 (syscall number) then svc — the svc dominates the classification.
|
|
REQUIRE(classOne("mov x8, 0xdd ; mov x0, x19 ; svc 0", "arm") == GadgetClass::Syscall);
|
|
}
|
|
|
|
TEST_CASE("rop: classifyGadgets preserves address and text", "[rop]") {
|
|
std::vector<std::pair<std::uint64_t, std::string>> raw = {{0x401234, "pop rdi ; ret"},
|
|
{0x401240, "ret"}};
|
|
auto out = classifyGadgets(raw, "x86");
|
|
REQUIRE(out.size() == 2);
|
|
REQUIRE(out[0].addr == 0x401234);
|
|
REQUIRE(out[0].text == "pop rdi ; ret");
|
|
REQUIRE(out[0].cls == GadgetClass::RegControl);
|
|
REQUIRE(out[1].cls == GadgetClass::Other);
|
|
}
|