// 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 #include #include #include #include #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> 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> 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); }