diff --git a/src/rz_solver.c b/src/rz_solver.c index 32025f6..707fc5d 100644 --- a/src/rz_solver.c +++ b/src/rz_solver.c @@ -40,7 +40,7 @@ static void update_rop_constraint_result(const RzRopSolverResult *result, const ut64 address) { rz_return_if_fail(result); rz_pvector_push(result->gadget_info_addr_set, (void *)address); - if (ht_pu_insert(result->constraint_result, constraint, 1)) { + if (!ht_pu_update(result->constraint_result, constraint, 1)) { rz_warn_if_reached(); } } @@ -283,26 +283,27 @@ static bool stack_constraint(const RopStackConstraintParams *params, return status; } -static bool is_direct_lookup(const RzCore *core, const RzRopGadgetInfo *gadget_info, char *dst) { - if (!gadget_info) { - return false; - } +static bool is_direct_lookup(const RzCore *core, + const RzRopGadgetInfo *gadget_info, char *dst) { + if (!gadget_info) { + return false; + } - if (rz_pvector_len(gadget_info->modified_registers) != 2) { - return false; - } + if (rz_pvector_len(gadget_info->modified_registers) != 2) { + return false; + } - RzRopRegInfo *reg_info; - RzListIter *iter; - rz_list_foreach (gadget_info->dependencies, iter, reg_info) { - if (rz_reg_is_role(core->analysis->reg, reg_info->name, RZ_REG_NAME_SP) || - rz_reg_is_role(core->analysis->reg, reg_info->name, RZ_REG_NAME_BP)) { - continue; - } - return false; + RzRopRegInfo *reg_info; + RzListIter *iter; + rz_list_foreach(gadget_info->dependencies, iter, reg_info) { + if (rz_reg_is_role(core->analysis->reg, reg_info->name, RZ_REG_NAME_SP) || + rz_reg_is_role(core->analysis->reg, reg_info->name, RZ_REG_NAME_BP)) { + continue; } + return false; + } - return true; + return true; } static void rz_solver_direct_lookup(const RzCore *core, @@ -320,7 +321,8 @@ static void rz_solver_direct_lookup(const RzCore *core, if (src_val == -1) { return; } - const bool is_dir_lookup = is_direct_lookup(core, gadget_info, rop_constraint->args[DST_REG]); + const bool is_dir_lookup = + is_direct_lookup(core, gadget_info, rop_constraint->args[DST_REG]); if (info->new_val == src_val && is_dir_lookup) { update_rop_constraint_result(result, rop_constraint, gadget_info->address); return; @@ -353,6 +355,9 @@ static void mov_const(const RzCore *core, const RzRopGadgetInfo *gadget_info, // Direct lookup case rz_solver_direct_lookup(core, gadget_info, rop_constraint, callback_params->result); + if (is_rop_solver_complete(callback_params->result)) { + return; + } const RopSolverAnalysisOpParams analysis_op_params = { .core = core, .constraint = rop_constraint, @@ -375,7 +380,7 @@ static void rop_gadget_info_constraint_find( switch (rop_constraint->type) { case MOV_CONST: return mov_const(core, gadget_info, rop_constraint, params); - case MOV_REG: + case MOV_REG: return mov_reg(core, gadget_info, rop_constraint, params); default: break; @@ -389,7 +394,7 @@ static bool rop_solver_cb(void *user, const ut64 k, const void *v) { const RzCore *core = params->core; const RzPVector *constraints = params->constraints; const RzRopGadgetInfo *gadget_info = (RzRopGadgetInfo *)v; - // If rop solver is complete bail out from here + // If rop solver is complete, bail out from here if (is_rop_solver_complete(params->result)) { return false; } @@ -420,25 +425,23 @@ setup_rop_solver_result(const RzPVector /**/ *constraints) { return result; } -RZ_API RzCmdStatus rz_rop_solver( - const RzCore *core, RzPVector /**/ *constraints) { - rz_return_val_if_fail(core && core->analysis, RZ_CMD_STATUS_ERROR); +RZ_API RzRopSolverResult * +rz_rop_solver(const RzCore *core, + RzPVector /**/ *constraints) { + rz_return_val_if_fail(core && core->analysis, NULL); if (!core->analysis->ht_rop_semantics) { RZ_LOG_ERROR("ROP analysis not performed yet. Please run /Rg"); - return RZ_CMD_STATUS_ERROR; + return NULL; } RzRopSolverResult *result = setup_rop_solver_result(constraints); if (!result) { - return RZ_CMD_STATUS_ERROR; + return NULL; } RopSolverCallbackParams params = { .core = core, .constraints = constraints, .result = result}; ht_up_foreach(core->analysis->ht_rop_semantics, rop_solver_cb, ¶ms); - rz_rop_solver_result_print(result); - - rz_rop_solver_result_free(result); - return RZ_CMD_STATUS_OK; + return result; } /** diff --git a/src/rz_solver.h b/src/rz_solver.h index 05325a0..eb2b8c4 100644 --- a/src/rz_solver.h +++ b/src/rz_solver.h @@ -17,8 +17,9 @@ typedef struct rz_rop_solver_result_t { Z3_solver solver; } RzRopSolverResult; -RZ_API RzCmdStatus rz_rop_solver( - const RzCore *core, RzPVector /**/ *constraints); +RZ_API RzRopSolverResult * +rz_rop_solver(const RzCore *core, + RzPVector /**/ *constraints); // RzRopSolverResult APIs RZ_API RzRopSolverResult *rz_rop_solver_result_new(void); diff --git a/src/rz_solver_plugin.c b/src/rz_solver_plugin.c index a1c1353..059f98c 100644 --- a/src/rz_solver_plugin.c +++ b/src/rz_solver_plugin.c @@ -47,7 +47,12 @@ RZ_IPI RzCmdStatus rz_cmd_rop_solver_handler(RzCore *core, int argc, rz_pvector_fini(constraints); return RZ_CMD_STATUS_INVALID; } - return rz_rop_solver(core, constraints); + RzRopSolverResult *result = rz_rop_solver(core, constraints); + rz_rop_solver_result_print(result); + + rz_rop_solver_result_free(result); + + return RZ_CMD_STATUS_OK; } /** diff --git a/src/rz_solver_util.c b/src/rz_solver_util.c index aecac1f..a72d4a6 100644 --- a/src/rz_solver_util.c +++ b/src/rz_solver_util.c @@ -18,10 +18,9 @@ RZ_API Z3_ast mk_var(const Z3_context ctx, const char *name, } /** - * \brie/home/z3phyr/personal/rizin-dev/rizinf Create a boolean variable with a given name. - * \param ctx Z3 context - * \param name Name of the variable - * \return Z3_ast + * \brie/home/z3phyr/personal/rizin-dev/rizinf Create a boolean variable with a + * given name. \param ctx Z3 context \param name Name of the variable \return + * Z3_ast */ RZ_API Z3_ast mk_bool_var(const Z3_context ctx, const char *name) { const Z3_sort ty = Z3_mk_bool_sort(ctx); diff --git a/tests/meson.build b/tests/meson.build index 3a1a9f0..cb9a7c0 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -1,6 +1,9 @@ rz_solver_incs = include_directories(['.', '..', '../src']) test_sources = [ + '../src'/'rz_solver.c', + '../src'/'rz_solver_util.c', + '../src'/'rz_solver_plugin.c', 'test_rop_solver.c', # Add more test files as needed ] @@ -10,5 +13,4 @@ test_exe = executable('test_solver', test_sources, include_directories: rz_solver_incs ) -# Register the test without include_directories test('rz_solver_tests', test_exe) \ No newline at end of file diff --git a/tests/minunit.h b/tests/minunit.h new file mode 100644 index 0000000..baa1b56 --- /dev/null +++ b/tests/minunit.h @@ -0,0 +1,317 @@ +// SPDX-FileCopyrightText: 2024 z3phyr +// SPDX-FileCopyrightText: 2015 Jeffrey Crowell +// SPDX-FileCopyrightText: 2018 ret2libc +// SPDX-License-Identifier: LGPL-3.0-only + +// minunit.h comes from http://www.jera.com/techinfo/jtns/jtn002.html +// +// You may use the code in this tech note for any purpose, +// with the understanding that it comes with NO WARRANTY. + +#ifndef _MINUNIT_H_ +#define _MINUNIT_H_ + +#include + +#if __WINDOWS__ +#define TRED +#define TGREEN +#define TYELLOW +#define TBLUE +#define TMAGENTA +#define TCYAN +#define TBOLD +#define TRESET +#else +#define TRED "\x1b[31m" +#define TGREEN "\x1b[32m" +#define TYELLOW "\x1b[33m" +#define TBLUE "\x1b[34m" +#define TMAGENTA "\x1b[35m" +#define TCYAN "\x1b[36m" +#define TBOLD "\x1b[1m" +#define TRESET "\x1b[0m" +#endif + +#define MU_PASSED 1 +#define MU_ERR 0 + +#define MU_TEST_UNBROKEN 0 +#define MU_TEST_BROKEN 1 + +#define MU_BUF_SIZE 8192 + +void snprint_mem(char *out, size_t out_size, const ut8 *buf, size_t len) { + size_t i; + *out = '\0'; + for (i = 0; i < len; i++) { + size_t out_len; + if (i > 0) { + out_len = strlen(out); + snprintf(out + out_len, out_size - out_len, " "); + } + out_len = strlen(out); + snprintf(out + out_len, out_size - out_len, "%02x", buf[i]); + } +} + +#define mu_assert(message, test) \ + do { \ + if (!(test)) { \ + mu_fail(message); \ + mu_test_status = MU_TEST_UNBROKEN; \ + } \ + } while (0) + +#define mu_perror(message) \ + do { \ + if (mu_test_status != MU_TEST_BROKEN) { \ + printf(TBOLD TRED "ERR\n[XX] Fail at line %d: " TRESET "%s\n\n", \ + __LINE__, message); \ + } else { \ + printf(TBOLD TYELLOW "Broken at line %d: " TRESET "%s\n\n", __LINE__, \ + message); \ + } \ + } while (0) + +#define mu_psyserror(message) \ + do { \ + perror(message); \ + mu_perror(message); \ + } while (0) + +#define mu_fail(message) \ + do { \ + mu_perror(message); \ + if (mu_test_status != MU_TEST_BROKEN) \ + return MU_ERR; \ + } while (0) + +#define mu_ignore \ + do { \ + printf(TYELLOW "IGN\n" TRESET); \ + return MU_PASSED; \ + } while (0) + +#define mu_end \ + do { \ + printf(TGREEN "OK\n" TRESET); \ + return MU_PASSED; \ + } while (0) + +#define mu_cleanup_end \ + do { \ + if (retval == MU_PASSED) { \ + mu_end; \ + } else { \ + return retval; \ + } \ + } while (0) + +#define mu_sysfail(message) \ + do { \ + perror(message); \ + mu_fail(message); \ + } while (0) + +#define mu_assert_true(actual, message) \ + do { \ + bool act__ = (actual); \ + if (!(act__)) { \ + char _meqstr[MU_BUF_SIZE]; \ + snprintf(_meqstr, MU_BUF_SIZE, "%s: expected true, got false", \ + (message)); \ + mu_assert(_meqstr, false); \ + } \ + } while (0) + +#define mu_assert_false(actual, message) \ + do { \ + bool act__ = (actual); \ + if ((act__)) { \ + char _meqstr[MU_BUF_SIZE]; \ + snprintf(_meqstr, MU_BUF_SIZE, "%s: expected false, got true", \ + (message)); \ + mu_assert(_meqstr, false); \ + } \ + } while (0) + +#define mu_assert_eq(actual, expected, message) \ + do { \ + ut64 act__ = (ut64)(actual); \ + ut64 exp__ = (ut64)(expected); \ + if ((exp__) != (act__)) { \ + char _meqstr[MU_BUF_SIZE]; \ + snprintf(_meqstr, MU_BUF_SIZE, \ + "%s: expected %" PFMT64d ", got %" PFMT64d ".", (message), \ + (ut64)(exp__), (ut64)(act__)); \ + mu_assert(_meqstr, false); \ + } \ + } while (0) + +#define mu_assert_eqf(actual, expected, message) \ + do { \ + double act__ = (double)(actual); \ + double exp__ = (double)(expected); \ + if ((exp__) != (act__)) { \ + char _meqstr[MU_BUF_SIZE]; \ + snprintf(_meqstr, MU_BUF_SIZE, "%s: expected %lf, got %lf.", (message), \ + exp__, act__); \ + mu_assert(_meqstr, false); \ + } \ + } while (0) + +#define mu_assert_neq(actual, expected, message) \ + do { \ + char _meqstr[MU_BUF_SIZE]; \ + ut64 act__ = (ut64)(actual); \ + ut64 exp__ = (ut64)(expected); \ + snprintf(_meqstr, MU_BUF_SIZE, \ + "%s: expected not %" PFMT64d ", got %" PFMT64d ".", (message), \ + (exp__), (act__)); \ + mu_assert(_meqstr, (exp__) != (act__)); \ + } while (0) + +#define mu_assert_ptreq(actual, expected, message) \ + do { \ + char _meqstr[MU_BUF_SIZE]; \ + const void *act__ = (actual); \ + const void *exp__ = (expected); \ + snprintf(_meqstr, MU_BUF_SIZE, "%s: expected %p, got %p.", (message), \ + (exp__), (act__)); \ + mu_assert(_meqstr, (exp__) == (act__)); \ + } while (0) + +#define mu_assert_ptrneq(actual, expected, message) \ + do { \ + char _meqstr[MU_BUF_SIZE]; \ + const void *act__ = (actual); \ + const void *exp__ = (expected); \ + snprintf(_meqstr, MU_BUF_SIZE, "%s: expected not %p, got %p.", (message), \ + (exp__), (act__)); \ + mu_assert(_meqstr, (exp__) != (act__)); \ + } while (0) + +#define mu_assert_null(actual, message) \ + do { \ + char _meqstr[MU_BUF_SIZE]; \ + const void *act__ = (actual); \ + snprintf(_meqstr, MU_BUF_SIZE, "%s: expected to be NULL but it wasn't.", \ + (message)); \ + mu_assert(_meqstr, (act__) == NULL); \ + } while (0) + +#define mu_assert_notnull(actual, message) \ + do { \ + char _meqstr[MU_BUF_SIZE]; \ + const void *act__ = (actual); \ + snprintf(_meqstr, MU_BUF_SIZE, "%s: expected to not be NULL but it was.", \ + (message)); \ + mu_assert(_meqstr, (act__) != NULL); \ + } while (0) + +#define mu_assert_eq_fmt(actual, expected, message, fmt) \ + do { \ + ut64 act__ = (ut64)(actual); \ + ut64 exp__ = (ut64)(expected); \ + if ((exp__) != (act__)) { \ + char _meqstr[MU_BUF_SIZE]; \ + snprintf(_meqstr, MU_BUF_SIZE, "%s: expected " fmt ", got " fmt ".", \ + (message), (exp__), (act__)); \ + mu_assert(_meqstr, false); \ + } \ + } while (0) + +#define mu_assert_streq(actual, expected, message) \ + do { \ + char _meqstr[MU_BUF_SIZE]; \ + const char *act__ = (actual); \ + act__ = act__ ? act__ : "(null)"; \ + const char *exp__ = (expected); \ + exp__ = exp__ ? exp__ : "(null)"; \ + snprintf(_meqstr, MU_BUF_SIZE, "%s: expected %s, got %s.", (message), \ + (exp__), (act__)); \ + mu_assert(_meqstr, strcmp((exp__), (act__)) == 0); \ + } while (0) + +#define mu_assert_strcontains(actual, expected, message) \ + do { \ + char _meqstr[MU_BUF_SIZE]; \ + const char *act__ = (actual); \ + act__ = act__ ? act__ : "(null)"; \ + const char *exp__ = (expected); \ + exp__ = exp__ ? exp__ : "(null)"; \ + snprintf(_meqstr, MU_BUF_SIZE, "%s: expected to find %s in %s.", \ + (message), (exp__), (act__)); \ + mu_assert(_meqstr, strstr((act__), (exp__)) != NULL); \ + } while (0) + +#define mu_assert_streq_free(actual, expected, message) \ + do { \ + char *act2__ = (actual); \ + mu_assert_streq(act2__, (expected), (message)); \ + free(act2__); \ + } while (0) + +#define mu_assert_nullable_streq(actual, expected, message) \ + do { \ + char _meqstr[MU_BUF_SIZE]; \ + const char *act__ = (actual); \ + const char *exp__ = (expected); \ + snprintf(_meqstr, MU_BUF_SIZE, "%s: expected %s, got %s.", (message), \ + (exp__ ? exp__ : "NULL"), (act__ ? act__ : "NULL")); \ + mu_assert(_meqstr, ((act__) == NULL && (exp__) == NULL) || \ + ((act__) != NULL && (exp__) != NULL && \ + strcmp((exp__), (act__)) == 0)); \ + } while (0) + +#define mu_assert_memeq(actual, expected, len, message) \ + do { \ + char _meqstr[MU_BUF_SIZE]; \ + size_t _meqstr_len; \ + const ut8 *act__ = (actual); \ + const ut8 *exp__ = (expected); \ + snprintf(_meqstr, MU_BUF_SIZE, "%s: expected ", message); \ + _meqstr_len = strlen(_meqstr); \ + snprint_mem(_meqstr + _meqstr_len, MU_BUF_SIZE - _meqstr_len, (exp__), \ + (len)); \ + _meqstr_len = strlen(_meqstr); \ + snprintf(_meqstr + _meqstr_len, MU_BUF_SIZE - _meqstr_len, ", got "); \ + _meqstr_len = strlen(_meqstr); \ + snprint_mem(_meqstr + _meqstr_len, MU_BUF_SIZE - _meqstr_len, (act__), \ + (len)); \ + mu_assert(_meqstr, memcmp((exp__), (act__), (len)) == 0); \ + } while (0) + +#define mu_run_test_named(test, name, ...) \ + do { \ + int result; \ + printf(TBOLD "%s" TRESET " ", name); \ + mu_test_status = MU_TEST_UNBROKEN; \ + result = test(__VA_ARGS__); \ + tests_run++; \ + tests_passed += result; \ + } while (0) + +#define mu_run_test(test, ...) mu_run_test_named(test, #test, __VA_ARGS__) + +#define mu_cleanup_fail(label, message) \ + do { \ + mu_perror(message); \ + retval = MU_ERR; \ + goto label; \ + } while (0) +#define mu_cleanup_sysfail(label, message) \ + do { \ + mu_psyserror(message); \ + retval = MU_ERR; \ + goto label; \ + } while (0) +int tests_run = 0; +int tests_passed = 0; +int mu_test_status = MU_TEST_UNBROKEN; + +#define mu_main(fcn) \ + int main(int argc, char **argv) { return fcn(); } + +#endif diff --git a/tests/test_rop_solver.c b/tests/test_rop_solver.c index d04d03f..b6e95c9 100644 --- a/tests/test_rop_solver.c +++ b/tests/test_rop_solver.c @@ -1,16 +1,84 @@ +#include "minunit.h" +#include "rz_solver.h" #include +#include -#define SWITCH_TO_ARCH_BITS(arch, bits) \ - rz_analysis_use(analysis, arch); \ - rz_analysis_set_bits(analysis, bits); +static RzCoreAsmHit *setup_rop_hitasm(int addr, int len) { + RzCoreAsmHit *hit = rz_core_asm_hit_new(); + if (!hit) { + return NULL; + } + hit->addr = addr; + hit->len = len; + return hit; +} +static RzList *setup_rop_hitlist(RzCore *core, ut8 *buf_str, int addr, + int len) { + RzAnalysisOp aop = {0}; + rz_analysis_op_init(&aop); + if (rz_analysis_op(core->analysis, &aop, addr + len - 1, buf_str + len - 1, 1, + RZ_ANALYSIS_OP_MASK_DISASM) < 0) { + return NULL; + } + if (aop.type != RZ_ANALYSIS_OP_TYPE_RET) { + return NULL; + } + RzList *hitlist = rz_list_newf(rz_core_asm_hit_free); + if (!hitlist) { + return NULL; + } + RzCoreAsmHit *hit = setup_rop_hitasm(addr, len - 1); + if (!hit) { + rz_list_free(hitlist); + return NULL; + } + rz_list_append(hitlist, hit); + hit = setup_rop_hitasm(addr + len - 1, 1); + if (!hit) { + rz_list_free(hitlist); + return NULL; + } + rz_list_append(hitlist, hit); + return hitlist; +} -int main() { - RzAnalysis *analysis = rz_analysis_new(); - RzAnalysisOp op; - SWITCH_TO_ARCH_BITS("x86", 64); - // mov rax, 4 - rz_analysis_op_init(&op); +bool test_rz_direct_solver() { + // mov rbx, 1; ret; + ut8 buf_str[] = "48C7C301000000C3"; - int len = rz_analysis_op(analysis, &op, 0, (const ut8 *)"\x48\xC7\xC0\x04\x00\x00\x00", 7, RZ_ANALYSIS_OP_MASK_VAL); + RzCore *core = rz_core_new(); + rz_io_open_at(core->io, "malloc://0x100", RZ_PERM_RX, 0644, 0, NULL); + rz_core_set_asm_configs(core, "x86", 64, 0); + rz_config_set_b(core->config, "asm.lines", false); + ut8 buf[128] = {0}; + int len = rz_hex_str2bin(buf_str, buf); + RzPVector *vec = rz_pvector_new((RzPVectorFree)rz_analysis_disasm_text_free); + mu_assert_notnull(vec, "rz_core_print_disasm vec not null"); + int addr = 0; + rz_io_write_at(core->io, addr, buf, len); + RzList /**/ *hitlist = + setup_rop_hitlist(core, buf, addr, len); + if (!hitlist) { + return NULL; + } + RzRopSearchContext *context = rz_core_rop_search_context_new( + core, NULL, false, RZ_ROP_GADGET_PRINT_DETAIL | RZ_ROP_GADGET_ANALYZE, + NULL); + rz_core_handle_rop_request_type(core, context, hitlist); + RzPVector *constraints = rz_core_rop_constraint_map_new(); + RzRopConstraint *rop_constraint = rop_constraint_parse_args(core, "rbx=1"); + rz_pvector_push(constraints, rop_constraint); + mu_assert_notnull(rop_constraint, "rop_constraint_parse_args failed"); + RzRopSolverResult *result = rz_rop_solver(core, constraints); + mu_assert_true(result->is_solved, "rz_rop_solver failed"); + rz_pvector_fini(constraints); + mu_end; } + +bool all_tests() { + mu_run_test(test_rz_direct_solver); + return tests_passed != tests_run; +} + +mu_main(all_tests)