From c8d05054c1c122831ef49bd7a8e7ac0ebcf72a90 Mon Sep 17 00:00:00 2001 From: z3phyr Date: Tue, 9 Jul 2024 23:41:47 -0500 Subject: [PATCH] Ropchain constraint syntax parser (#4552) Co-authored-by: Giridhar Prasath R --- .gitignore | 1 + librz/arch/analysis.c | 28 + librz/core/cconfig.c | 3 +- librz/core/cmd/cmd_search.c | 861 +--------------- librz/core/cmd/cmd_search_rop.c | 1028 +++++++------------ librz/core/cmd_descs/cmd_descs.c | 25 +- librz/core/cmd_descs/cmd_descs.h | 2 + librz/core/cmd_descs/cmd_search.yaml | 21 +- librz/core/meson.build | 1 + librz/core/project_migrate.c | 24 +- librz/core/rop.c | 1238 +++++++++++++++++++++++ librz/include/meson.build | 1 + librz/include/rz_analysis.h | 3 + librz/include/rz_project.h | 3 +- librz/include/rz_rop.h | 143 +++ test/db/analysis/avr | 25 +- test/db/cmd/cmd_rop | 272 ++++- test/db/cmd/cmd_search | 51 + test/db/cmd/project | 1 + test/integration/test_project_migrate.c | 27 + test/prj/v17-rop-config.rzdb | 82 ++ test/unit/meson.build | 1 + test/unit/test_rop_constraint.c | 142 +++ 23 files changed, 2472 insertions(+), 1511 deletions(-) create mode 100644 librz/core/rop.c create mode 100644 librz/include/rz_rop.h create mode 100644 test/prj/v17-rop-config.rzdb create mode 100644 test/unit/test_rop_constraint.c diff --git a/.gitignore b/.gitignore index 2447d8df650..1f44b3410a4 100644 --- a/.gitignore +++ b/.gitignore @@ -116,6 +116,7 @@ peda-session-* /.vs .cache/ test/.tmp/* +test/.sync_disk_db subprojects/capstone-*/ subprojects/pcre2*/ subprojects/libzip-*/ diff --git a/librz/arch/analysis.c b/librz/arch/analysis.c index ecb90e58e53..8414332e76a 100644 --- a/librz/arch/analysis.c +++ b/librz/arch/analysis.c @@ -8,6 +8,7 @@ #include #include #include +#include /** * \brief Returns the default size byte width of memory access operations. @@ -129,6 +130,7 @@ RZ_API RzAnalysis *rz_analysis_new(void) { } } analysis->ht_global_var = ht_sp_new(HT_STR_DUP, NULL, (HtSPFreeValue)rz_analysis_var_global_free); + analysis->ht_rop = NULL; analysis->global_var_tree = NULL; analysis->il_vm = NULL; analysis->hash = rz_hash_new(); @@ -185,6 +187,7 @@ RZ_API RzAnalysis *rz_analysis_free(RzAnalysis *a) { rz_list_free(a->imports); rz_str_constpool_fini(&a->constpool); ht_sp_free(a->ht_global_var); + ht_up_free(a->ht_rop); rz_list_free(a->plugins); rz_analysis_debug_info_free(a->debug_info); free(a); @@ -240,6 +243,31 @@ RZ_API char *rz_analysis_get_reg_profile(RzAnalysis *analysis) { : NULL; } +/** + * \brief Check if a register is in the analysis profile. + * \param analysis Pointer to the RzAnalysis object. + * \param name The register name to check. + * \return true if the register name is found, false otherwise. + * + * This function checks if the given register name is present + * in the register profile of the given RzAnalysis. + */ +RZ_API bool rz_analysis_is_reg_in_profile(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL const char *name) { + rz_return_val_if_fail(analysis && name, false); + + char *reg_prof = rz_analysis_get_reg_profile(analysis); + if (!reg_prof) { + return false; + } + + if (strstr(reg_prof, name)) { + free(reg_prof); + return true; + } + free(reg_prof); + return false; +} + RZ_API bool rz_analysis_set_reg_profile(RzAnalysis *analysis) { bool ret = false; char *p = rz_analysis_get_reg_profile(analysis); diff --git a/librz/core/cconfig.c b/librz/core/cconfig.c index 772c8a60d3b..cdb7d893fe4 100644 --- a/librz/core/cconfig.c +++ b/librz/core/cconfig.c @@ -3735,8 +3735,7 @@ RZ_API int rz_core_config_init(RzCore *core) { /* rop */ SETI("rop.len", 5, "Maximum ROP gadget length"); - SETBPREF("rop.sdb", "false", "Cache results in sdb (experimental)"); - SETBPREF("rop.db", "true", "Categorize rop gadgets in sdb"); + SETBPREF("rop.cache", "false", "Cache rop gadget results(experimental)"); SETBPREF("rop.subchains", "false", "Display every length gadget from rop.len=X to 2 in /Rl"); SETBPREF("rop.conditional", "false", "Include conditional jump, calls and returns in ropsearch"); SETBPREF("rop.comments", "false", "Display comments in rop search output"); diff --git a/librz/core/cmd/cmd_search.c b/librz/core/cmd/cmd_search.c index f2aa8a31cc2..e0c0ab9f0de 100644 --- a/librz/core/cmd/cmd_search.c +++ b/librz/core/cmd/cmd_search.c @@ -1,12 +1,10 @@ // SPDX-FileCopyrightText: 2010-2021 pancake // SPDX-License-Identifier: LGPL-3.0-only -#include #include #include #include #include -#include #include #include "../core_private.h" @@ -17,10 +15,6 @@ #define AES_SEARCH_LENGTH 40 #define PRIVATE_KEY_SEARCH_LENGTH 11 -static int rz_core_search_rop(RzCore *core, const char *greparg, int regexp, RzCmdStateOutput *state); -static void rop_kuery(void *data, const char *input, RzCmdStateOutput *state); -static void print_rop(RzCore *core, RzList /**/ *hitlist, RzCmdStateOutput *state); - static const char *help_msg_search_esil[] = { "/E", " [esil-expr]", "search offsets matching a specific esil expression", "/Ej", " [esil-expr]", "same as above but using the given magic file", @@ -153,11 +147,6 @@ struct search_parameters { bool privkey_search; }; -struct endlist_pair { - int instr_offset; - int delay_size; -}; - static int search_hash(RzCore *core, const char *hashname, const char *hashstr, ut32 minlen, ut32 maxlen, struct search_parameters *param) { RzIOMap *map; ut8 *buf; @@ -236,51 +225,24 @@ RZ_IPI RzCmdStatus rz_cmd_info_gadget_handler(RzCore *core, int argc, const char return RZ_CMD_STATUS_ERROR; } - Sdb *gadgetSdb = sdb_ns(core->sdb, "gadget_sdb", false); + RzRopSearchContext *context = rz_core_rop_search_context_new(core, argv[1], false, RZ_ROP_GADGET_PRINT, state); + return rz_core_rop_gadget_info(core, context); +} - if (!gadgetSdb) { - rz_core_search_rop(core, argv[1], 0, state); - return RZ_CMD_STATUS_OK; +RZ_IPI RzCmdStatus rz_cmd_query_gadget_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) { + RzList /**/ *constraints = rop_constraint_list_parse(core, argc, argv); + if (!constraints) { + return RZ_CMD_STATUS_ERROR; } - void **iter; - RzPVector *items = sdb_get_items(gadgetSdb, true); - - rz_cmd_state_output_array_start(state); - rz_pvector_foreach (items, iter) { - SdbKv *kv = *iter; - RzList *hitlist = rz_core_asm_hit_list_new(); - if (!hitlist) { - break; - } - - const char *s = sdbkv_value(kv); - ut64 addr; - int opsz; - - do { - RzCoreAsmHit *hit = rz_core_asm_hit_new(); - if (!hit) { - rz_list_free(hitlist); - break; - } - sscanf(s, "%" PFMT64x "(%" PFMT32d ")", &addr, &opsz); - hit->addr = addr; - hit->len = opsz; - rz_list_append(hitlist, hit); - } while (*(s = strchr(s, ')') + 1) != '\0'); - - print_rop(core, hitlist, state); - rz_list_free(hitlist); + if (rz_list_empty(constraints)) { + rz_list_free(constraints); + return RZ_CMD_STATUS_INVALID; } - rz_pvector_free(items); - rz_cmd_state_output_array_end(state); - return RZ_CMD_STATUS_OK; -} -RZ_IPI RzCmdStatus rz_cmd_query_gadget_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) { - const char *input = argc > 1 ? argv[1] : ""; - rop_kuery(core, input, state); - return RZ_CMD_STATUS_OK; + RzRopSearchContext *context = rz_core_rop_search_context_new(core, argv[1], false, RZ_ROP_GADGET_PRINT, state); + const RzCmdStatus cmd_status = rz_core_rop_search(core, context); + rz_list_free(constraints); + return cmd_status; } RZ_IPI RzCmdStatus rz_cmd_search_gadget_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) { @@ -288,8 +250,16 @@ RZ_IPI RzCmdStatus rz_cmd_search_gadget_handler(RzCore *core, int argc, const ch if (!input) { return RZ_CMD_STATUS_ERROR; } - rz_core_search_rop(core, argv[1], 1, state); - return RZ_CMD_STATUS_OK; + RzRopSearchContext *context = rz_core_rop_search_context_new(core, input, true, RZ_ROP_GADGET_PRINT, state); + return rz_core_rop_search(core, context); +} + +RZ_IPI RzCmdStatus rz_cmd_detail_gadget_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state) { + const char *input = argc > 1 ? argv[1] : ""; + + RzRopSearchContext *context = rz_core_rop_search_context_new(core, input, true, RZ_ROP_GADGET_PRINT_DETAIL | RZ_ROP_GADGET_ANALYZE, state); + return rz_core_rop_search(core, context); + ; } static void cmd_search_bin(RzCore *core, RzInterval itv) { @@ -1011,689 +981,6 @@ RZ_API RZ_OWN RzList /**/ *rz_core_get_boundaries_prot(RzCore *core, return list; } -static bool is_end_gadget(const RzAnalysisOp *aop, const ut8 crop) { - if (aop->family == RZ_ANALYSIS_OP_FAMILY_SECURITY) { - return false; - } - switch (aop->type) { - case RZ_ANALYSIS_OP_TYPE_TRAP: - case RZ_ANALYSIS_OP_TYPE_RET: - case RZ_ANALYSIS_OP_TYPE_UCALL: - case RZ_ANALYSIS_OP_TYPE_RCALL: - case RZ_ANALYSIS_OP_TYPE_ICALL: - case RZ_ANALYSIS_OP_TYPE_IRCALL: - case RZ_ANALYSIS_OP_TYPE_UJMP: - case RZ_ANALYSIS_OP_TYPE_RJMP: - case RZ_ANALYSIS_OP_TYPE_IJMP: - case RZ_ANALYSIS_OP_TYPE_IRJMP: - case RZ_ANALYSIS_OP_TYPE_JMP: - case RZ_ANALYSIS_OP_TYPE_CALL: - return true; - } - if (crop) { // if conditional jumps, calls and returns should be used for the gadget-search too - switch (aop->type) { - case RZ_ANALYSIS_OP_TYPE_CJMP: - case RZ_ANALYSIS_OP_TYPE_UCJMP: - case RZ_ANALYSIS_OP_TYPE_CCALL: - case RZ_ANALYSIS_OP_TYPE_UCCALL: - case RZ_ANALYSIS_OP_TYPE_CRET: - return true; - } - } - return false; -} - -static bool insert_into(void *user, const ut64 k, const ut64 v) { - HtUU *ht = (HtUU *)user; - ht_uu_insert(ht, k, v); - return true; -} - -// TODO: follow unconditional jumps -static RzList /**/ *construct_rop_gadget(RzCore *core, ut64 addr, ut8 *buf, int buflen, int idx, const char *grep, int regex, RzList /**/ *rx_list, struct endlist_pair *end_gadget, HtUU *badstart) { - int endaddr = end_gadget->instr_offset; - int branch_delay = end_gadget->delay_size; - RzAnalysisOp aop = { 0 }; - const char *start = NULL, *end = NULL; - char *grep_str = NULL; - RzCoreAsmHit *hit = NULL; - RzList *hitlist = rz_core_asm_hit_list_new(); - ut8 nb_instr = 0; - const ut8 max_instr = rz_config_get_i(core->config, "rop.len"); - bool valid = false; - int grep_find; - int search_hit; - char *rx = NULL; - HtUUOptions opt = { 0 }; - HtUU *localbadstart = ht_uu_new_opt(&opt); - int count = 0; - - if (grep) { - start = grep; - end = strchr(grep, ';'); - if (!end) { // We filter on a single opcode, so no ";" - end = start + strlen(grep); - } - grep_str = calloc(1, end - start + 1); - strncpy(grep_str, start, end - start); - if (regex) { - // get the first regexp. - if (rz_list_length(rx_list) > 0) { - rx = rz_list_get_n(rx_list, count++); - } - } - } - - bool found; - ht_uu_find(badstart, idx, &found); - if (found) { - valid = false; - goto ret; - } - while (nb_instr < max_instr) { - ht_uu_insert(localbadstart, idx, 1); - rz_analysis_op_init(&aop); - int error = rz_analysis_op(core->analysis, &aop, addr, buf + idx, buflen - idx, RZ_ANALYSIS_OP_MASK_DISASM); - if (error < 0 || (nb_instr == 0 && (is_end_gadget(&aop, 0) || aop.type == RZ_ANALYSIS_OP_TYPE_NOP))) { - valid = false; - goto ret; - } - - const int opsz = aop.size; - // opsz = rz_strbuf_length (asmop.buf); - char *opst = aop.mnemonic; - if (!opst) { - RZ_LOG_WARN("Analysis plugin %s did not return disassembly\n", core->analysis->cur->name); - RzAsmOp asmop; - rz_asm_set_pc(core->rasm, addr); - if (rz_asm_disassemble(core->rasm, &asmop, buf + idx, buflen - idx) < 0) { - valid = false; - goto ret; - } - opst = strdup(rz_asm_op_get_asm(&asmop)); - rz_asm_op_fini(&asmop); - } - if (!rz_str_ncasecmp(opst, "invalid", strlen("invalid")) || - !rz_str_ncasecmp(opst, ".byte", strlen(".byte"))) { - valid = false; - goto ret; - } - - hit = rz_core_asm_hit_new(); - if (hit) { - hit->addr = addr; - hit->len = opsz; - rz_list_append(hitlist, hit); - } - - // Move on to the next instruction - idx += opsz; - addr += opsz; - if (rx) { - grep_find = rz_regex_contains(rx, opst, RZ_REGEX_ZERO_TERMINATED, RZ_REGEX_EXTENDED, RZ_REGEX_DEFAULT); - search_hit = (end && grep && grep_find); - } else { - search_hit = (end && grep && strstr(opst, grep_str)); - } - - // Handle (possible) grep - if (search_hit) { - if (end[0] == ';') { // fields are semicolon-separated - start = end + 1; // skip the ; - end = strchr(start, ';'); - end = end ? end : start + strlen(start); // latest field? - free(grep_str); - grep_str = calloc(1, end - start + 1); - if (grep_str) { - strncpy(grep_str, start, end - start); - } - } else { - end = NULL; - } - if (regex) { - rx = rz_list_get_n(rx_list, count++); - } - } - if (endaddr <= (idx - opsz)) { - valid = (endaddr == idx - opsz); - goto ret; - } - rz_analysis_op_fini(&aop); - nb_instr++; - } -ret: - rz_analysis_op_fini(&aop); - free(grep_str); - if (regex && rx) { - rz_list_free(hitlist); - ht_uu_free(localbadstart); - return NULL; - } - if (!valid || (grep && end)) { - rz_list_free(hitlist); - ht_uu_free(localbadstart); - return NULL; - } - ht_uu_foreach(localbadstart, insert_into, badstart); - ht_uu_free(localbadstart); - // If our arch has bds then we better be including them - if (branch_delay && rz_list_length(hitlist) < (1 + branch_delay)) { - rz_list_free(hitlist); - return NULL; - } - return hitlist; -} - -static void print_rop(RzCore *core, RzList /**/ *hitlist, RzCmdStateOutput *state) { - RzCoreAsmHit *hit = NULL; - RzListIter *iter; - RzList *ropList = NULL; - unsigned int size = 0; - char *asmop_str = NULL, *asmop_hex_str = NULL; - RzAnalysisOp aop = RZ_EMPTY; - const char *comment = NULL; - Sdb *db = NULL; - const bool colorize = rz_config_get_i(core->config, "scr.color"); - const bool rop_comments = rz_config_get_i(core->config, "rop.comments"); - const bool esil = rz_config_get_i(core->config, "asm.esil"); - const bool rop_db = rz_config_get_i(core->config, "rop.db"); - char tmpbuf[16]; - ut8 *buf = NULL; - RzStrBuf *colored_asm = NULL, *bw_str = NULL; - if (rop_db) { - db = sdb_ns(core->sdb, "rop", true); - ropList = rz_list_newf(free); - if (!db) { - RZ_LOG_ERROR("core: Could not create SDB 'rop' namespace\n"); - rz_list_free(ropList); - return; - } - } - - rz_cmd_state_output_set_columnsf(state, "XXs", "addr", "bytes", "disasm"); - if (state->mode == RZ_OUTPUT_MODE_JSON) { - pj_o(state->d.pj); - pj_ka(state->d.pj, "opcodes"); - } else if (state->mode == RZ_OUTPUT_MODE_QUIET) { - rz_cons_printf("0x%08" PFMT64x ":", ((RzCoreAsmHit *)rz_list_first(hitlist))->addr); - } - const ut64 addr = ((RzCoreAsmHit *)rz_list_first(hitlist))->addr; - - rz_list_foreach (hitlist, iter, hit) { - RzAsmOp *asmop = rz_asm_op_new(); - switch (state->mode) { - case RZ_OUTPUT_MODE_JSON: - buf = malloc(hit->len); - if (!buf) { - goto cleanup; - } - rz_io_read_at(core->io, hit->addr, buf, hit->len); - rz_asm_set_pc(core->rasm, hit->addr); - rz_asm_disassemble(core->rasm, asmop, buf, hit->len); - rz_analysis_op_init(&aop); - rz_analysis_op(core->analysis, &aop, hit->addr, buf, hit->len, RZ_ANALYSIS_OP_MASK_ESIL); - size += hit->len; - if (aop.type != RZ_ANALYSIS_OP_TYPE_RET) { - char *opstr_n = rz_str_newf(" %s", RZ_STRBUF_SAFEGET(&aop.esil)); - rz_list_append(ropList, opstr_n); - } - pj_o(state->d.pj); - pj_kn(state->d.pj, "offset", hit->addr); - pj_ki(state->d.pj, "size", hit->len); - pj_ks(state->d.pj, "opcode", rz_asm_op_get_asm(asmop)); - pj_ks(state->d.pj, "type", rz_analysis_optype_to_string(aop.type)); - pj_end(state->d.pj); - free(buf); - rz_analysis_op_fini(&aop); - break; - case RZ_OUTPUT_MODE_QUIET: - // Print gadgets in a 'linear manner', each sequence on one line. - buf = malloc(hit->len); - if (!buf) { - goto cleanup; - } - rz_io_read_at(core->io, hit->addr, buf, hit->len); - rz_asm_set_pc(core->rasm, hit->addr); - rz_asm_disassemble(core->rasm, asmop, buf, hit->len); - rz_analysis_op_init(&aop); - rz_analysis_op(core->analysis, &aop, hit->addr, buf, hit->len, RZ_ANALYSIS_OP_MASK_BASIC); - size += hit->len; - const char *opstr = RZ_STRBUF_SAFEGET(&aop.esil); - if (aop.type != RZ_ANALYSIS_OP_TYPE_RET) { - rz_list_append(ropList, rz_str_newf(" %s", opstr)); - } - if (esil) { - rz_cons_printf("%s\n", opstr); - } else if (colorize) { - bw_str = rz_strbuf_new(rz_asm_op_get_asm(asmop)); - RzAsmParseParam *param = rz_asm_get_parse_param(core->analysis->reg, aop.type); - colored_asm = rz_asm_colorize_asm_str(bw_str, core->print, param, asmop->asm_toks); - rz_asm_parse_param_free(param); - rz_cons_printf(" %s%s;", colored_asm ? rz_strbuf_get(colored_asm) : "", Color_RESET); - rz_strbuf_free(colored_asm); - rz_strbuf_free(bw_str); - } else { - rz_cons_printf(" %s;", rz_asm_op_get_asm(asmop)); - } - free(buf); - rz_analysis_op_fini(&aop); - break; - case RZ_OUTPUT_MODE_STANDARD: - // Print gadgets with new instruction on a new line. - comment = rop_comments ? rz_meta_get_string(core->analysis, RZ_META_TYPE_COMMENT, hit->addr) : NULL; - if (hit->len < 0) { - RZ_LOG_ERROR("core: Invalid hit length here\n"); - continue; - } - buf = malloc(1 + hit->len); - if (!buf) { - break; - } - buf[hit->len] = 0; - rz_io_read_at(core->io, hit->addr, buf, hit->len); - rz_asm_set_pc(core->rasm, hit->addr); - rz_asm_disassemble(core->rasm, asmop, buf, hit->len); - rz_analysis_op_init(&aop); - rz_analysis_op(core->analysis, &aop, hit->addr, buf, hit->len, RZ_ANALYSIS_OP_MASK_ESIL); - size += hit->len; - if (aop.type != RZ_ANALYSIS_OP_TYPE_RET) { - char *opstr_n = rz_str_newf(" %s", RZ_STRBUF_SAFEGET(&aop.esil)); - rz_list_append(ropList, opstr_n); - } - char *asm_op_hex = rz_asm_op_get_hex(asmop); - if (colorize) { - bw_str = rz_strbuf_new(rz_asm_op_get_asm(asmop)); - RzAsmParseParam *param = rz_asm_get_parse_param(core->analysis->reg, aop.type); - colored_asm = rz_asm_colorize_asm_str(bw_str, core->print, param, asmop->asm_toks); - rz_asm_parse_param_free(param); - if (comment) { - rz_cons_printf(" 0x%08" PFMT64x " %18s %s%s ; %s\n", - hit->addr, asm_op_hex, colored_asm ? rz_strbuf_get(colored_asm) : "", Color_RESET, comment); - } else { - rz_cons_printf(" 0x%08" PFMT64x " %18s %s%s\n", - hit->addr, asm_op_hex, colored_asm ? rz_strbuf_get(colored_asm) : "", Color_RESET); - } - rz_strbuf_free(colored_asm); - rz_strbuf_free(bw_str); - } else { - if (comment) { - rz_cons_printf(" 0x%08" PFMT64x " %18s %s ; %s\n", - hit->addr, asm_op_hex, rz_asm_op_get_asm(asmop), comment); - } else { - rz_cons_printf(" 0x%08" PFMT64x " %18s %s\n", - hit->addr, asm_op_hex, rz_asm_op_get_asm(asmop)); - } - } - free(asm_op_hex); - free(buf); - rz_analysis_op_fini(&aop); - comment = NULL; - break; - case RZ_OUTPUT_MODE_TABLE: - buf = malloc(hit->len); - if (!buf) { - goto cleanup; - } - rz_io_read_at(core->io, hit->addr, buf, hit->len); - rz_asm_set_pc(core->rasm, hit->addr); - rz_asm_disassemble(core->rasm, asmop, buf, hit->len); - rz_analysis_op_init(&aop); - rz_analysis_op(core->analysis, &aop, hit->addr, buf, hit->len, RZ_ANALYSIS_OP_MASK_BASIC); - size += hit->len; - if (asmop_str) { - asmop_str = rz_str_append(asmop_str, rz_asm_op_get_asm(asmop)); - const ut64 addr_last = ((RzCoreAsmHit *)rz_list_last(hitlist))->addr; - if (addr_last != hit->addr) { - asmop_str = rz_str_append(asmop_str, "; "); - } - } else { - asmop_str = rz_str_newf("%s; ", rz_asm_op_get_asm(asmop)); - } - char *asmop_hex_str_dup = NULL; - if (asmop_hex_str) { - asmop_hex_str_dup = rz_asm_op_get_hex(asmop); - asmop_hex_str = rz_str_append(asmop_hex_str, asmop_hex_str_dup); - } else { - asmop_hex_str_dup = rz_asm_op_get_hex(asmop); - asmop_hex_str = rz_str_newf("%s", asmop_hex_str_dup); - } - free(asmop_hex_str_dup); - free(buf); - rz_analysis_op_fini(&aop); - break; - default: - rz_warn_if_reached(); - break; - } - rz_asm_op_free(asmop); - } - switch (state->mode) { - case RZ_OUTPUT_MODE_JSON: - pj_end(state->d.pj); - if (db && hit) { - const char *key = rz_strf(tmpbuf, "0x%08" PFMT64x, addr); - rop_classify(core, db, ropList, key, size); - } - if (hit) { - pj_kn(state->d.pj, "retaddr", hit->addr); - pj_ki(state->d.pj, "size", size); - } - pj_end(state->d.pj); - break; - case RZ_OUTPUT_MODE_QUIET: - rz_cons_newline(); - break; - // fallthrough - case RZ_OUTPUT_MODE_STANDARD: - if (db && hit) { - rz_cons_printf("Gadget size: %d\n", (int)size); - const char *key = rz_strf(tmpbuf, "0x%08" PFMT64x, addr); - rop_classify(core, db, ropList, key, size); - } - rz_cons_newline(); - break; - case RZ_OUTPUT_MODE_TABLE: - rz_table_add_rowf(state->d.t, "Xss", addr, asmop_hex_str, asmop_str); - free(asmop_str); - free(asmop_hex_str); - break; - default: - rz_warn_if_reached(); - } -cleanup: - rz_list_free(ropList); -} - -static int rz_core_search_rop(RzCore *core, const char *greparg, int regexp, RzCmdStateOutput *state) { - const ut8 crop = rz_config_get_i(core->config, "rop.conditional"); // decide if cjmp, cret, and ccall should be used too for the gadget-search - const ut8 subchain = rz_config_get_i(core->config, "rop.subchains"); - const ut8 max_instr = rz_config_get_i(core->config, "rop.len"); - const char *arch = rz_config_get(core->config, "asm.arch"); - int max_count = rz_config_get_i(core->config, "search.maxhits"); - int i = 0, end = 0, increment = 1, ret, result = true; - RzList /**/ *end_list = rz_list_newf(free); - RzList /**/ *rx_list = NULL; - int align = core->search->align; - RzListIter *itermap = NULL; - char *grep_arg = NULL; - char *tok, *gregexp = NULL; - char *rx = NULL; - RzAsmOp *asmop = NULL; - RzList *boundaries = NULL; - int delta = 0; - ut8 *buf; - RzIOMap *map; - - const ut64 search_from = rz_config_get_i(core->config, "search.from"), - search_to = rz_config_get_i(core->config, "search.to"); - if (search_from > search_to && search_to) { - RZ_LOG_ERROR("core: search.from > search.to is not supported\n"); - ret = false; - goto bad; - } - // {.addr = UT64_MAX, .size = 0} means search range is unspecified - RzInterval search_itv = { search_from, search_to - search_from }; - bool empty_search_itv = search_from == search_to && search_from != UT64_MAX; - if (empty_search_itv) { - RZ_LOG_ERROR("core: `from` address is equal `to`\n"); - ret = false; - goto bad; - } - // TODO full address cannot be represented, shrink 1 byte to [0, UT64_MAX) - if (search_from == UT64_MAX && search_to == UT64_MAX) { - search_itv.addr = 0; - search_itv.size = UT64_MAX; - } - - Sdb *gadgetSdb = NULL; - if (rz_config_get_i(core->config, "rop.sdb")) { - if (!(gadgetSdb = sdb_ns(core->sdb, "gadget_sdb", false))) { - gadgetSdb = sdb_ns(core->sdb, "gadget_sdb", true); - } - } - if (max_count == 0) { - max_count = -1; - } - if (max_instr <= 1) { - rz_list_free(end_list); - RZ_LOG_ERROR("core: ROP length (rop.len) must be greater than 1.\n"); - if (max_instr == 1) { - RZ_LOG_ERROR("core: For rop.len = 1, use /c to search for single " - "instructions. See /c? for help.\n"); - } - return false; - } - - if (!strcmp(arch, "mips")) { // MIPS has no jump-in-the-middle - increment = 4; - } else if (!strcmp(arch, "arm")) { // ARM has no jump-in-the-middle - increment = rz_config_get_i(core->config, "asm.bits") == 16 ? 2 : 4; - } else if (!strcmp(arch, "avr")) { // AVR is halfword aligned. - increment = 2; - } - - if (greparg) { - grep_arg = strdup(greparg); - grep_arg = rz_str_replace(grep_arg, ",,", ";", true); - } - - // Deal with the grep guy. - if (grep_arg && regexp) { - if (!rx_list) { - rx_list = rz_list_newf(free); - } - gregexp = strdup(grep_arg); - tok = strtok(gregexp, ";"); - while (tok) { - rx = strdup(tok); - rz_list_append(rx_list, rx); - tok = strtok(NULL, ";"); - } - } - rz_cmd_state_output_array_start(state); - rz_cons_break_push(NULL, NULL); - const char *mode_str = rz_config_get(core->config, "search.in"); - boundaries = rz_core_get_boundaries_prot(core, -1, mode_str, "search"); - if (!boundaries) { - rz_cmd_state_output_array_end(state); - } - rz_list_foreach (boundaries, itermap, map) { - HtUUOptions opt = { 0 }; - HtUU *badstart = ht_uu_new_opt(&opt); - if (!rz_itv_overlap(search_itv, map->itv)) { - continue; - } - RzInterval itv = rz_itv_intersect(search_itv, map->itv); - ut64 from = itv.addr, to = rz_itv_end(itv); - if (rz_cons_is_breaked()) { - break; - } - delta = to - from; - buf = calloc(1, delta); - if (!buf) { - result = false; - goto bad; - } - (void)rz_io_read_at(core->io, from, buf, delta); - - // Find the end gadgets. - for (i = 0; i + 32 < delta; i += increment) { - RzAnalysisOp end_gadget = RZ_EMPTY; - // Disassemble one. - rz_analysis_op_init(&end_gadget); - if (rz_analysis_op(core->analysis, &end_gadget, from + i, buf + i, - delta - i, RZ_ANALYSIS_OP_MASK_BASIC) < 1) { - rz_analysis_op_fini(&end_gadget); - continue; - } - if (is_end_gadget(&end_gadget, crop)) { -#if 0 - if (search->maxhits && rz_list_length (end_list) >= search->maxhits) { - // limit number of high level rop gadget results - rz_analysis_op_fini (&end_gadget); - break; - } -#endif - struct endlist_pair *epair = RZ_NEW0(struct endlist_pair); - if (epair) { - // If this arch has branch delay slots, add the next instr as well - if (end_gadget.delay) { - epair->instr_offset = i + increment; - epair->delay_size = end_gadget.delay; - } else { - epair->instr_offset = (intptr_t)i; - epair->delay_size = end_gadget.delay; - } - rz_list_append(end_list, (void *)(intptr_t)epair); - } - } - rz_analysis_op_fini(&end_gadget); - if (rz_cons_is_breaked()) { - break; - } - // Right now we have a list of all of the end/stop gadgets. - // We can just construct gadgets from a little bit before them. - } - rz_list_reverse(end_list); - // If we have no end gadgets, just skip all of this search nonsense. - if (!rz_list_empty(end_list)) { - int prev, next, ropdepth; - const int max_inst_size_x86 = 15; - // Get the depth of rop search, should just be max_instr - // instructions, x86 and friends are weird length instructions, so - // we'll just assume 15 byte instructions. - ropdepth = increment == 1 ? max_instr * max_inst_size_x86 /* wow, x86 is long */ : max_instr * increment; - if (rz_cons_is_breaked()) { - break; - } - struct endlist_pair *end_gadget = (struct endlist_pair *)rz_list_pop(end_list); - next = end_gadget->instr_offset; - prev = 0; - // Start at just before the first end gadget. - for (i = next - ropdepth; i < (delta - max_inst_size_x86) && max_count; i += increment) { - if (increment == 1) { - // give in-boundary instructions a shot - if (i < prev - max_inst_size_x86) { - i = prev - max_inst_size_x86; - } - } else { - if (i < prev) { - i = prev; - } - } - if (i < 0) { - i = 0; - } - if (rz_cons_is_breaked()) { - break; - } - if (i >= next) { - // We've exhausted the first end-gadget section, - // move to the next one. - free(end_gadget); - if (rz_list_get_n(end_list, 0)) { - prev = i; - end_gadget = (struct endlist_pair *)rz_list_pop(end_list); - next = end_gadget->instr_offset; - i = next - ropdepth; - if (i < 0) { - i = 0; - } - } else { - break; - } - } - if (i >= end) { // read by chunk of 4k - rz_io_read_at(core->io, from + i, buf + i, - RZ_MIN((delta - i), 4096)); - end = i + 2048; - } - asmop = rz_asm_op_new(); - ret = rz_asm_disassemble(core->rasm, asmop, buf + i, delta - i); - if (ret) { - rz_asm_set_pc(core->rasm, from + i); - RzList *hitlist = construct_rop_gadget(core, - from + i, buf, delta, i, greparg, regexp, - rx_list, end_gadget, badstart); - if (!hitlist) { - rz_asm_op_free(asmop); - asmop = NULL; - continue; - } - if (align && 0 != (from + i) % align) { - rz_asm_op_free(asmop); - asmop = NULL; - continue; - } - if (gadgetSdb) { - RzListIter *iter; - - RzCoreAsmHit *hit = rz_list_first(hitlist); - char *headAddr = rz_str_newf("%" PFMT64x, hit->addr); - if (!headAddr) { - result = false; - free(buf); - ht_uu_free(badstart); - goto bad; - } - - rz_list_foreach (hitlist, iter, hit) { - char *addr = rz_str_newf("%" PFMT64x "(%" PFMT32d ")", hit->addr, hit->len); - if (!addr) { - free(headAddr); - result = false; - free(buf); - ht_uu_free(badstart); - goto bad; - } - sdb_concat(gadgetSdb, headAddr, addr); - free(addr); - } - free(headAddr); - } - - if (subchain) { - do { - print_rop(core, hitlist, state); - hitlist->head = hitlist->head->next; - } while (hitlist->head->next); - } else { - print_rop(core, hitlist, state); - } - rz_list_free(hitlist); - if (max_count > 0) { - max_count--; - if (max_count < 1) { - break; - } - } - } - if (increment != 1) { - i = next; - } - rz_asm_op_free(asmop); - asmop = NULL; - } - } - free(buf); - ht_uu_free(badstart); - } - if (rz_cons_is_breaked()) { - eprintf("\n"); - } - -bad: - rz_cmd_state_output_array_end(state); - rz_cons_break_pop(); - rz_asm_op_free(asmop); - rz_list_free(rx_list); - rz_list_free(end_list); - rz_list_free(boundaries); - free(grep_arg); - free(gregexp); - return result; -} - static bool esil_addrinfo(RzAnalysisEsil *esil) { RzCore *core = (RzCore *)esil->cb.user; ut64 num = 0; @@ -2565,106 +1852,6 @@ static void do_string_search(RzCore *core, RzInterval search_itv, struct search_ } } -static void rop_kuery(void *data, const char *input, RzCmdStateOutput *state) { - RzCore *core = data; - Sdb *db_rop = sdb_ns(core->sdb, "rop", false); - RzListIter *it; - void **items_iter; - SdbNs *ns; - char *out; - - if (!db_rop) { - RZ_LOG_ERROR("core: could not find SDB 'rop' namespace\n"); - return; - } - - switch (state->mode) { - case RZ_OUTPUT_MODE_QUIET: - rz_list_foreach (db_rop->ns, it, ns) { - RzPVector *items = sdb_get_items(ns->sdb, false); - rz_pvector_foreach (items, items_iter) { - SdbKv *kv = *items_iter; - rz_cons_printf("%s ", sdbkv_key(kv)); - } - rz_pvector_free(items); - } - break; - case RZ_OUTPUT_MODE_JSON: - pj_o(state->d.pj); - pj_ka(state->d.pj, "gadgets"); - rz_list_foreach (db_rop->ns, it, ns) { - RzPVector *items = sdb_get_items(ns->sdb, false); - rz_pvector_foreach (items, items_iter) { - SdbKv *kv = *items_iter; - char *dup = sdbkv_dup_value(kv); - bool flag = false; // to free tok when doing strdup - char *size = strtok(dup, " "); - char *tok = strtok(NULL, "{}"); - if (!tok) { - tok = strdup("NOP"); - flag = true; - } - pj_o(state->d.pj); - pj_ks(state->d.pj, "address", sdbkv_key(kv)); - pj_ks(state->d.pj, "size", size); - pj_ks(state->d.pj, "type", ns->name); - pj_ks(state->d.pj, "effect", tok); - pj_end(state->d.pj); - free(dup); - if (flag) { - free(tok); - } - } - rz_pvector_free(items); - } - pj_end(state->d.pj); - pj_end(state->d.pj); - break; - case ' ': - if (!strcmp(input + 1, "nop")) { - out = sdb_querys(core->sdb, NULL, 0, "rop/nop/*"); - if (out) { - rz_cons_println(out); - free(out); - } - } else if (!strcmp(input + 1, "mov")) { - out = sdb_querys(core->sdb, NULL, 0, "rop/mov/*"); - if (out) { - rz_cons_println(out); - free(out); - } - } else if (!strcmp(input + 1, "const")) { - out = sdb_querys(core->sdb, NULL, 0, "rop/const/*"); - if (out) { - rz_cons_println(out); - free(out); - } - } else if (!strcmp(input + 1, "arithm")) { - out = sdb_querys(core->sdb, NULL, 0, "rop/arithm/*"); - if (out) { - rz_cons_println(out); - free(out); - } - } else if (!strcmp(input + 1, "arithm_ct")) { - out = sdb_querys(core->sdb, NULL, 0, "rop/arithm_ct/*"); - if (out) { - rz_cons_println(out); - free(out); - } - } else { - RZ_LOG_ERROR("core: Invalid ROP class\n"); - } - break; - default: - out = sdb_querys(core->sdb, NULL, 0, "rop/***"); - if (out) { - rz_cons_println(out); - free(out); - } - break; - } -} - static int memcmpdiff(const ut8 *a, const ut8 *b, int len) { int i, diff = 0; for (i = 0; i < len; i++) { diff --git a/librz/core/cmd/cmd_search_rop.c b/librz/core/cmd/cmd_search_rop.c index 89d64e135e4..7b88c5f114b 100644 --- a/librz/core/cmd/cmd_search_rop.c +++ b/librz/core/cmd/cmd_search_rop.c @@ -1,729 +1,459 @@ +// SPDX-FileCopyrightText: 2024 z3phyr // SPDX-FileCopyrightText: 2009-2016 Alexandru Caciulescu // SPDX-License-Identifier: LGPL-3.0-only #include #include "rz_core.h" -#include "rz_io.h" #include "rz_list.h" #include "rz_types_base.h" +#include "rz_rop.h" -static RzList /**/ *parse_list(const char *str) { - char *line, *data, *str_n; - - if (!str) { - return NULL; +static void skip_whitespace(const char *str, ut64 *idx) { + while (IS_WHITECHAR(str[*idx])) { + (*idx)++; } - str_n = strdup(str); - line = strtok(str_n, "\n"); - data = strchr(line, '='); +} - RzList *list = rz_str_split_duplist(data + 1, ",", false); +static bool parse_eof(const char *str, ut64 idx) { + skip_whitespace(str, &idx); + return str[idx] == '\0'; +} - free(str_n); - return list; +static bool parse_il_equal(const char *str, ut64 *idx) { + skip_whitespace(str, idx); + if (*idx >= strlen(str)) { + return false; + } + if (str[*idx] == '=') { + (*idx)++; + return true; + } + return false; } -static RzList /**/ *get_constants(const char *str) { - RzList *list; - char *p, *data; - if (!str) { +static char *parse_register(const RzCore *core, const char *str, ut64 *idx) { + char reg[256] = { 0 }; + ut64 reg_idx = 0; + + skip_whitespace(str, idx); + + while (isalnum(str[*idx]) || str[*idx] == '_') { + reg[reg_idx++] = str[*idx]; + (*idx)++; + } + + if (reg_idx == 0) { return NULL; } - data = strdup(str); - list = rz_list_newf(free); - p = strtok(data, ","); - while (p) { - if (strtol(p, NULL, 0)) { - rz_list_append(list, (void *)strdup(p)); - } - p = strtok(NULL, ","); + // Check if the register is correct for the given architecture. + if (rz_analysis_is_reg_in_profile(core->analysis, reg)) { + return strdup(reg); } - free(data); - return list; + + return NULL; } -static bool isFlag(RzRegItem *reg) { - const char *type = rz_reg_get_type(reg->type); +static bool parse_constant(const char *str, RZ_NONNULL ut64 *idx, unsigned long long *value) { + rz_return_val_if_fail(idx, false); + int neg = 0; + int len = strlen(str); - if (!strcmp(type, "flg")) - return true; - return false; -} + skip_whitespace(str, idx); -// binary op -static bool simulate_op(const char *op, ut64 src1, ut64 src2, ut64 old_src1, ut64 old_src2, ut64 *result, int size) { - ut64 limit; - if (size == 64) { - limit = UT64_MAX; - } else { - limit = 1ULL << size; + if (*idx < len && str[*idx] == '-') { + neg = 1; + (*idx)++; } - if (!strcmp(op, "^")) { - *result = src1 ^ src2; - return true; + skip_whitespace(str, idx); + + int base = 10; + if (*idx + 1 < len && str[*idx] == '0' && (str[*idx + 1] == 'x' || str[*idx + 1] == 'X')) { + base = 16; + *idx += 2; } - if (!strcmp(op, "+")) { - *result = src1 + src2; - return true; + + int num_idx = 0; + char num_str[256] = { 0 }; + while (isdigit(str[*idx]) || (base == 16 && isxdigit(str[*idx]))) { + num_str[num_idx++] = str[*idx]; + (*idx)++; } - if (!strcmp(op, "-")) { - if (src2 > src1) { - *result = limit + (src1 - src2); - } else { - *result = src1 - src2; - } - return true; + + if (num_idx == 0) { + return false; } - if (!strcmp(op, "*")) { - *result = src1 * src2; - return true; + + *value = strtoull(num_str, NULL, base); + if (neg) { + *value = -*value; } - if (!strcmp(op, "|")) { - *result = src1 | src2; - return true; + + return true; +} + +static bool parse_reg_to_const(const RzCore *core, const char *str, RzRopConstraint *rop_constraint) { + ut64 idx = 0; + char *dst_reg = parse_register(core, str, &idx); + if (!dst_reg) { + return false; } - if (!strcmp(op, "/")) { - *result = src1 / src2; - return true; + + if (!parse_il_equal(str, &idx)) { + free(dst_reg); + return false; } - if (!strcmp(op, "%")) { - *result = src1 % src2; - return true; + + ut64 const_value; + if (!parse_constant(str, &idx, &const_value)) { + free(dst_reg); + return false; } - if (!strcmp(op, "<<")) { - *result = src1 << src2; - return true; + + if (!parse_eof(str, idx)) { + free(dst_reg); + return false; } - if (!strcmp(op, ">>")) { - *result = src1 >> src2; - return true; + + rop_constraint->type = MOV_CONST; + rop_constraint->args[DST_REG] = dst_reg; + rop_constraint->args[SRC_REG] = NULL; + rop_constraint->args[SRC_CONST] = rz_str_newf("%" PFMT64u, const_value); + return true; +} + +static bool parse_reg_to_reg(const RzCore *core, const char *str, RzRopConstraint *rop_constraint) { + ut64 idx = 0; + char *dst_reg = parse_register(core, str, &idx); + if (!dst_reg) { + return false; } - if (!strcmp(op, "&")) { - *result = src1 & src2; - return true; + + if (!parse_il_equal(str, &idx)) { + free(dst_reg); + return false; } - if (!strcmp(op, "+=")) { - *result = old_src1 + src2; - return true; + + char *src_reg = parse_register(core, str, &idx); + if (!src_reg) { + free(dst_reg); + return false; + } + + if (!parse_eof(str, idx)) { + free(dst_reg); + return false; } - if (!strcmp(op, "-=")) { - if (src2 > old_src1) { - *result = limit + (old_src1 - src2); + + rop_constraint->type = MOV_REG; + rop_constraint->args[DST_REG] = dst_reg; + rop_constraint->args[SRC_REG] = src_reg; + return true; +} + +static bool parse_il_op(RzList /**/ *args, const char *str, ut64 *idx) { + RzILOpPureCode res = RZ_IL_OP_VAR; + + skip_whitespace(str, idx); + if (*idx >= strlen(str)) { + return false; + } + + switch (str[*idx]) { + case '+': + (*idx)++; + res = RZ_IL_OP_ADD; + break; + case '/': + (*idx)++; + res = RZ_IL_OP_DIV; + break; + case '*': + (*idx)++; + res = RZ_IL_OP_MUL; + break; + case '^': + (*idx)++; + res = RZ_IL_OP_XOR; + break; + case '&': + (*idx)++; + res = RZ_IL_OP_AND; + break; + case '|': + (*idx)++; + res = RZ_IL_OP_OR; + break; + case '%': + (*idx)++; + res = RZ_IL_OP_MOD; + break; + case '-': + (*idx)++; + res = RZ_IL_OP_SUB; + default: break; + } + if (res == RZ_IL_OP_VAR) { + if (strncmp(&str[*idx], "<<", 2) == 0) { + *idx += 2; + res = RZ_IL_OP_SHIFTL; + } else if (strncmp(&str[*idx], ">>", 2) == 0) { + *idx += 2; + res = RZ_IL_OP_SHIFTR; } else { - *result = old_src1 - src2; + return false; } - return true; - } - if (!strcmp(op, "*=")) { - *result = old_src1 * src2; - return true; } - if (!strcmp(op, "/=")) { - *result = old_src1 / src2; - return true; - } - if (!strcmp(op, "%=")) { - *result = old_src1 % src2; - return true; - } - if (!strcmp(op, "<<")) { - *result = src1 << src2; - return true; - } - if (!strcmp(op, ">>")) { - *result = src1 >> src2; - return true; - } - if (!strcmp(op, "&=")) { - *result = src1 & src2; - return true; - } - if (!strcmp(op, "^=")) { - *result = src1 ^ src2; - return true; - } - if (!strcmp(op, "|=")) { - *result = src1 | src2; - return true; + + RzILOpPureCode *op_ptr = RZ_NEW0(RzILOpPureCode); + if (!op_ptr) { + return false; } - return false; + *op_ptr = res; + rz_list_append(args, op_ptr); + + return true; } -// fill REGs with known values -static void fillRegisterValues(RzCore *core) { - RzListIter *iter_reg; - RzRegItem *reg_item; - int nr = 10; +static bool parse_reg_op_const(const RzCore *core, const char *str, RzRopConstraint *rop_constraint) { + ut64 idx = 0; + char *dst_reg = parse_register(core, str, &idx); + if (!dst_reg) { + return false; + } - const RzList *regs = rz_reg_get_list(core->analysis->reg, RZ_REG_TYPE_GPR); - if (!regs) { - return; + if (!parse_il_equal(str, &idx)) { + free(dst_reg); + return false; } - rz_list_foreach (regs, iter_reg, reg_item) { - rz_reg_arena_pop(core->analysis->reg); - rz_reg_set_value(core->analysis->reg, reg_item, nr); - rz_reg_arena_push(core->analysis->reg); - nr += 3; + + char *src_reg = parse_register(core, str, &idx); + if (!src_reg) { + free(dst_reg); + return false; + } + RzList *args = rz_list_new(); + if (!parse_il_op(args, str, &idx)) { + free(dst_reg); + free(src_reg); + rz_list_free(args); + return false; } -} -// split esil string in flags part and main instruction -// hacky, only tested for x86, TODO: portable version -// NOTE: esil_main and esil_flg are heap allocated and must be freed by the caller -static void esil_split_flg(char *esil_str, char **esil_main, char **esil_flg) { - char *split = strstr(esil_str, "f,="); - const int kCommaHits = 2; - int hits = 0; - - if (split) { - while (hits != kCommaHits) { - --split; - if (*split == ',') { - hits++; - } - } - *esil_flg = strdup(++split); - *esil_main = rz_str_ndup(esil_str, strlen(esil_str) - strlen(*esil_flg) - 1); + ut64 const_value; + if (!parse_constant(str, &idx, &const_value)) { + free(dst_reg); + free(src_reg); + return false; } -} -#define FREE_ROP \ - { \ - RZ_FREE(out); \ - RZ_FREE(esil_flg); \ - RZ_FREE(esil_main); \ - rz_list_free(ops_list); \ - ops_list = NULL; \ - rz_list_free(flg_read); \ - flg_read = NULL; \ - rz_list_free(flg_write); \ - flg_write = NULL; \ - rz_list_free(reg_read); \ - reg_read = NULL; \ - rz_list_free(reg_write); \ - reg_write = NULL; \ - rz_list_free(mem_read); \ - mem_read = NULL; \ - rz_list_free(mem_write); \ - mem_write = NULL; \ - } - -static char *rop_classify_constant(RzCore *core, RzList /**/ *ropList) { - char *esil_str, *constant; - char *ct = NULL, *esil_main = NULL, *esil_flg = NULL, *out = NULL; - RzListIter *iter_r, *iter_dst, *iter_const; - RzRegItem *item_dst; - const RzList *head; - RzList *constants; - RzList *ops_list = NULL, *flg_read = NULL, *flg_write = NULL, - *reg_read = NULL, *reg_write = NULL, *mem_read = NULL, - *mem_write = NULL; - const bool romem = rz_config_get_i(core->config, "esil.romem"); - const bool stats = rz_config_get_i(core->config, "esil.stats"); - - if (!romem || !stats) { - // eprintf ("Error: esil.romem and esil.stats must be set TRUE"); - return NULL; + if (!parse_eof(str, idx)) { + free(dst_reg); + free(src_reg); + rz_list_free(args); + return false; } - rz_list_foreach (ropList, iter_r, esil_str) { - constants = get_constants(esil_str); - // if there are no constants in the instruction continue - if (rz_list_empty(constants)) { - continue; - } - // init regs with known values - fillRegisterValues(core); - head = rz_reg_get_list(core->analysis->reg, RZ_REG_TYPE_GPR); - if (!head) { - ct = NULL; - goto continue_error; - } - esil_split_flg(esil_str, &esil_main, &esil_flg); - cmd_analysis_esil(core, esil_main ? esil_main : esil_str); - out = sdb_querys(core->analysis->esil->stats, NULL, 0, "*"); - if (!out) { - goto continue_error; - } - ops_list = parse_list(strstr(out, "ops.list")); - flg_read = parse_list(strstr(out, "flg.read")); - flg_write = parse_list(strstr(out, "flg.write")); - reg_read = parse_list(strstr(out, "reg.read")); - reg_write = parse_list(strstr(out, "reg.write")); - mem_read = parse_list(strstr(out, "mem.read")); - mem_write = parse_list(strstr(out, "mem.write")); - if (!rz_list_find(ops_list, "=", (RzListComparator)strcmp, NULL)) { - goto continue_error; - } - head = rz_reg_get_list(core->analysis->reg, RZ_REG_TYPE_GPR); - if (!head) { - goto out_error; - } - rz_list_foreach (head, iter_dst, item_dst) { - ut64 diff_dst, value_dst; - if (!rz_list_find(reg_write, item_dst->name, - (RzListComparator)strcmp, NULL)) { - continue; - } + rop_constraint->type = MOV_OP_CONST; + rop_constraint->args[DST_REG] = dst_reg; + rop_constraint->args[SRC_REG] = src_reg; + RzILOpPureCode *op = rz_list_get_n(args, 0); + if (!op) { + free(dst_reg); + free(src_reg); + rz_list_free(args); + return false; + } - value_dst = rz_reg_get_value(core->analysis->reg, item_dst); - rz_reg_arena_swap(core->analysis->reg, false); - diff_dst = rz_reg_get_value(core->analysis->reg, item_dst); - rz_reg_arena_swap(core->analysis->reg, false); - // restore initial value - rz_reg_set_value(core->analysis->reg, item_dst, diff_dst); - - if (value_dst != diff_dst) { - rz_list_foreach (constants, iter_const, constant) { - if (value_dst == rz_num_get(NULL, constant)) { - ct = rz_str_appendf(ct, "%s <-- 0x%" PFMT64x ";", item_dst->name, value_dst); - } - } - } - } - continue_error: - // coverity may complain here but as long as the pointer is set back to - // NULL is safe that is why is used RZ_FREE - FREE_ROP; - rz_list_free(constants); - } - return ct; -out_error: - FREE_ROP; - rz_list_free(constants); - return NULL; + char op_str[16]; + rz_strf(op_str, "%" PFMT64u, const_value); + rop_constraint->args[SRC_CONST] = strdup(op_str); + const char *value_str = rz_il_op_pure_code_stringify(*op); + rop_constraint->args[OP] = rz_str_dup(value_str); + return true; } -static char *rop_classify_mov(RzCore *core, RzList /**/ *ropList) { - char *esil_str; - char *mov = NULL, *esil_main = NULL, *esil_flg = NULL, *out = NULL; - RzListIter *iter_src, *iter_r, *iter_dst; - RzRegItem *item_src, *item_dst; - const RzList *head; - RzList *ops_list = NULL, *flg_read = NULL, *flg_write = NULL, - *reg_read = NULL, *reg_write = NULL, *mem_read = NULL, - *mem_write = NULL; - const bool romem = rz_config_get_i(core->config, "esil.romem"); - const bool stats = rz_config_get_i(core->config, "esil.stats"); - - if (!romem || !stats) { - // eprintf ("Error: esil.romem and esil.stats must be set TRUE"); +/** + * \brief Create a new RzRopSearchContext object. + * \param core RZ_NONNULL Pointer to the RzCore structure containing configuration settings. + * \param greparg RZ_NULLABLE Pointer to a string containing the grep argument. + * \param regexp Flag specifying whether regular expressions should be used. + * \param mask ROP request mask specifying the ROP request parameters. + * \param state RZ_BORROW Pointer to the command state output structure. + * \return RZ_OUT A pointer to the newly created RzRopSearchContext object, or NULL if memory allocation fails. + * + * This function allocates and initializes a new RzRopSearchContext object. + */ +RZ_OWN RZ_API RzRopSearchContext *rz_core_rop_search_context_new(RZ_NONNULL const RzCore *core, RZ_NULLABLE const char *greparg, const bool regexp, + const RzRopRequestMask mask, RZ_BORROW RzCmdStateOutput *state) { + + rz_return_val_if_fail(core, NULL); + rz_return_val_if_fail(state, NULL); + + RzRopSearchContext *context = RZ_NEW0(RzRopSearchContext); + if (!context) { return NULL; } - rz_list_foreach (ropList, iter_r, esil_str) { - // init regs with known values - fillRegisterValues(core); - head = rz_reg_get_list(core->analysis->reg, RZ_REG_TYPE_GPR); - if (!head) { - goto out_error; - } - esil_split_flg(esil_str, &esil_main, &esil_flg); - cmd_analysis_esil(core, esil_main ? esil_main : esil_str); - out = sdb_querys(core->analysis->esil->stats, NULL, 0, "*"); - if (out) { - ops_list = parse_list(strstr(out, "ops.list")); - flg_read = parse_list(strstr(out, "flg.read")); - flg_write = parse_list(strstr(out, "flg.write")); - reg_read = parse_list(strstr(out, "reg.read")); - reg_write = parse_list(strstr(out, "reg.write")); - mem_read = parse_list(strstr(out, "mem.read")); - mem_write = parse_list(strstr(out, "mem.write")); - } else { - goto continue_error; - } + context->greparg = greparg ? strdup(greparg) : NULL; + context->regexp = regexp; + context->mask = mask; + context->state = state; + context->max_instr = rz_config_get_i(core->config, "rop.len"); + context->max_count = rz_config_get_i(core->config, "search.maxhits"); + context->increment = 1; + context->from = 0; + context->to = 0; + context->end_list = NULL; + context->unique_hitlists = NULL; + context->crop = rz_config_get_i(core->config, "rop.conditional"); + context->subchain = rz_config_get_i(core->config, "rop.subchain"); + + return context; +} - if (!rz_list_find(ops_list, "=", (RzListComparator)strcmp, NULL)) { - goto continue_error; - } +/** + * \brief Free an RzRopSearchContext object. + * \param context RZ_NULLABLE Pointer to the RzRopSearchContext object to free. + * + * Frees the memory allocated for an RzRopSearchContext object. + * Note: Other elements must be freed by the caller/callee. + */ +RZ_API void rz_core_rop_search_context_free(RZ_NULLABLE RzRopSearchContext *context) { + if (!context) { + return; + } - head = rz_reg_get_list(core->analysis->reg, RZ_REG_TYPE_GPR); - if (!head) { - goto out_error; - } - rz_list_foreach (head, iter_dst, item_dst) { - ut64 diff_dst, value_dst; - if (!rz_list_find(reg_write, item_dst->name, - (RzListComparator)strcmp, NULL)) { - continue; - } + free(context->greparg); + free(context); +} - // you never mov into flags - if (isFlag(item_dst)) { - continue; - } +static bool parse_reg_op_reg(const RzCore *core, const char *str, RzRopConstraint *rop_constraint) { + ut64 idx = 0; + char *dst_reg = parse_register(core, str, &idx); + if (!dst_reg) { + return false; + } - value_dst = rz_reg_get_value(core->analysis->reg, item_dst); - rz_reg_arena_swap(core->analysis->reg, false); - diff_dst = rz_reg_get_value(core->analysis->reg, item_dst); - rz_reg_arena_swap(core->analysis->reg, false); - rz_list_foreach (head, iter_src, item_src) { - ut64 diff_src, value_src; - if (!rz_list_find(reg_read, item_src->name, - (RzListComparator)strcmp, NULL)) { - continue; - } - // you never mov from flags - if (item_src == item_dst || isFlag(item_src)) { - continue; - } - value_src = rz_reg_get_value(core->analysis->reg, item_src); - rz_reg_arena_swap(core->analysis->reg, false); - diff_src = rz_reg_get_value(core->analysis->reg, item_src); - rz_reg_arena_swap(core->analysis->reg, false); - // restore initial value - rz_reg_set_value(core->analysis->reg, item_src, diff_src); - if (value_dst == value_src && value_dst != diff_dst) { - mov = rz_str_appendf(mov, "%s <-- %s;", - item_dst->name, item_src->name); - } - } - } - continue_error: - FREE_ROP; + if (!parse_il_equal(str, &idx)) { + free(dst_reg); + return false; } - return mov; -out_error: - FREE_ROP; - return NULL; -} -static char *rop_classify_arithmetic(RzCore *core, RzList /**/ *ropList) { - char *esil_str, *op; - char *arithmetic = NULL, *esil_flg = NULL, *esil_main = NULL, - *out = NULL; - RzListIter *iter_src1, *iter_src2, *iter_r, *iter_dst, *iter_ops; - RzRegItem *item_src1, *item_src2, *item_dst; - const RzList *head; - RzList *ops_list = NULL, *flg_read = NULL, *flg_write = NULL, - *reg_read = NULL, *reg_write = NULL, *mem_read = NULL, - *mem_write = NULL; - const bool romem = rz_config_get_i(core->config, "esil.romem"); - const bool stats = rz_config_get_i(core->config, "esil.stats"); - ut64 *op_result = RZ_NEW0(ut64); - ut64 *op_result_r = RZ_NEW0(ut64); - - if (!romem || !stats) { - // eprintf ("Error: esil.romem and esil.stats must be set TRUE"); - free(op_result); - free(op_result_r); - return NULL; + char *src_reg1 = parse_register(core, str, &idx); + if (!src_reg1) { + free(dst_reg); + return false; } - rz_list_foreach (ropList, iter_r, esil_str) { - // init regs with known values - fillRegisterValues(core); - head = rz_reg_get_list(core->analysis->reg, RZ_REG_TYPE_GPR); - if (!head) { - goto out_error; - } - esil_split_flg(esil_str, &esil_main, &esil_flg); - if (esil_main) { - cmd_analysis_esil(core, esil_main); - } else { - cmd_analysis_esil(core, esil_str); - } - out = sdb_querys(core->analysis->esil->stats, NULL, 0, "*"); - // rz_cons_println (out); - if (!out) { - goto continue_error; - } - ops_list = parse_list(strstr(out, "ops.list")); - flg_read = parse_list(strstr(out, "flg.read")); - flg_write = parse_list(strstr(out, "flg.write")); - reg_read = parse_list(strstr(out, "reg.read")); - reg_write = parse_list(strstr(out, "reg.write")); - mem_read = parse_list(strstr(out, "mem.read")); - mem_write = parse_list(strstr(out, "mem.write")); - - rz_list_foreach (ops_list, iter_ops, op) { - rz_list_foreach (head, iter_src1, item_src1) { - ut64 value_src1, diff_src1; - - value_src1 = rz_reg_get_value(core->analysis->reg, item_src1); - rz_reg_arena_swap(core->analysis->reg, false); - diff_src1 = rz_reg_get_value(core->analysis->reg, item_src1); - rz_reg_arena_swap(core->analysis->reg, false); - if (!rz_list_find(reg_read, item_src1->name, - (RzListComparator)strcmp, NULL)) { - continue; - } - - rz_list_foreach (head, iter_src2, item_src2) { - ut64 value_src2, diff_src2; - value_src2 = rz_reg_get_value(core->analysis->reg, item_src2); - rz_reg_arena_swap(core->analysis->reg, false); - diff_src2 = rz_reg_get_value(core->analysis->reg, item_src2); - - if (!rz_list_find(reg_read, item_src2->name, - (RzListComparator)strcmp, NULL)) { - continue; - } - // TODO check condition - if (iter_src1 == iter_src2) { - continue; - } - - rz_list_foreach (head, iter_dst, item_dst) { - ut64 value_dst; - bool redundant = false, simulate, simulate_r; - - value_dst = rz_reg_get_value(core->analysis->reg, item_dst); - rz_reg_arena_swap(core->analysis->reg, false); - if (!rz_list_find(reg_write, item_dst->name, - (RzListComparator)strcmp, NULL)) { - continue; - } - // don't check flags for arithmetic - if (isFlag(item_dst)) { - continue; - } - simulate = simulate_op(op, value_src1, value_src2, diff_src1, diff_src2, op_result, item_dst->size); - simulate_r = simulate_op(op, value_src2, value_src1, diff_src2, diff_src1, op_result_r, item_dst->size); - if (/*value_src1 != 0 && value_src2 != 0 && */ simulate && value_dst == *op_result) { - // rz_cons_println ("Debug: FOUND ONE !"); - char *tmp = rz_str_newf("%s <-- %s %s %s;", item_dst->name, item_src1->name, op, item_src2->name); - if (arithmetic && !strstr(arithmetic, tmp)) { - arithmetic = rz_str_append(arithmetic, tmp); - } else if (!arithmetic) { - arithmetic = rz_str_append(arithmetic, tmp); - } - free(tmp); - } else if (!redundant /*&& value_src1 != 0 && value_src2 != 0*/ && simulate_r && value_dst == *op_result_r) { - // rz_cons_println ("Debug: FOUND ONE reversed!"); - char *tmp = rz_str_newf("%s <-- %s %s %s;", item_dst->name, item_src2->name, op, item_src1->name); - if (arithmetic && !strstr(arithmetic, tmp)) { - arithmetic = rz_str_append(arithmetic, tmp); - } else if (!arithmetic) { - arithmetic = rz_str_append(arithmetic, tmp); - } - free(tmp); - } - } - } - } - } - continue_error: - FREE_ROP; - } - free(op_result); - free(op_result_r); - return arithmetic; -out_error: - FREE_ROP; - free(op_result); - free(op_result_r); - return NULL; -} + RzList *args = rz_list_new(); + if (!args || !parse_il_op(args, str, &idx)) { + free(dst_reg); + free(src_reg1); + rz_list_free(args); + return false; + } -static char *rop_classify_arithmetic_const(RzCore *core, RzList /**/ *ropList) { - char *esil_str, *op, *constant; - char *arithmetic = NULL, *esil_flg = NULL, *esil_main = NULL; - RzListIter *iter_src1, *iter_r, *iter_dst, *iter_ops, *iter_const; - RzRegItem *item_src1, *item_dst; - const RzList *head; - RzList *constants; - RzList *ops_list = NULL, *flg_read = NULL, *flg_write = NULL, *reg_read = NULL, - *reg_write = NULL, *mem_read = NULL, *mem_write = NULL; - const bool romem = rz_config_get_i(core->config, "esil.romem"); - const bool stats = rz_config_get_i(core->config, "esil.stats"); - ut64 *op_result = RZ_NEW0(ut64); - ut64 *op_result_r = RZ_NEW0(ut64); - - if (!romem || !stats) { - // eprintf ("Error: esil.romem and esil.stats must be set TRUE"); - RZ_FREE(op_result); - RZ_FREE(op_result_r); - return NULL; + char *dst_reg2 = parse_register(core, str, &idx); + if (!dst_reg2) { + free(dst_reg); + free(src_reg1); + return false; } - rz_list_foreach (ropList, iter_r, esil_str) { - constants = get_constants(esil_str); - // if there are no constants in the instruction continue - if (rz_list_empty(constants)) { - continue; - } - // init regs with known values - fillRegisterValues(core); - head = rz_reg_get_list(core->analysis->reg, RZ_REG_TYPE_GPR); - if (!head) { - arithmetic = NULL; - continue; - } - esil_split_flg(esil_str, &esil_main, &esil_flg); - if (esil_main) { - cmd_analysis_esil(core, esil_main); - } else { - cmd_analysis_esil(core, esil_str); - } - char *out = sdb_querys(core->analysis->esil->stats, NULL, 0, "*"); - // rz_cons_println (out); - if (out) { - ops_list = parse_list(strstr(out, "ops.list")); - flg_read = parse_list(strstr(out, "flg.read")); - flg_write = parse_list(strstr(out, "flg.write")); - reg_read = parse_list(strstr(out, "reg.read")); - reg_write = parse_list(strstr(out, "reg.write")); - mem_read = parse_list(strstr(out, "mem.read")); - mem_write = parse_list(strstr(out, "mem.write")); - } else { - RZ_FREE(op_result); - RZ_FREE(op_result_r); - goto continue_error; - } + if (!parse_eof(str, idx)) { + free(dst_reg); + free(src_reg1); + free(dst_reg2); + rz_list_free(args); + return false; + } - rz_list_foreach (ops_list, iter_ops, op) { - rz_list_foreach (head, iter_src1, item_src1) { - ut64 value_src1, diff_src1; - value_src1 = rz_reg_get_value(core->analysis->reg, item_src1); - rz_reg_arena_swap(core->analysis->reg, false); - diff_src1 = rz_reg_get_value(core->analysis->reg, item_src1); - rz_reg_arena_swap(core->analysis->reg, false); - - if (!rz_list_find(reg_read, item_src1->name, - (RzListComparator)strcmp, NULL)) { - continue; - } - rz_list_foreach (head, iter_dst, item_dst) { - ut64 value_dst, diff_dst; - bool redundant = false, simulate, simulate_r; - value_dst = rz_reg_get_value(core->analysis->reg, item_dst); - rz_reg_arena_swap(core->analysis->reg, false); - diff_dst = rz_reg_get_value(core->analysis->reg, item_dst); - rz_reg_arena_swap(core->analysis->reg, false); - if (!rz_list_find(reg_write, item_dst->name, - (RzListComparator)strcmp, NULL)) { - continue; - } - // don't check flags for arithmetic - if (isFlag(item_dst)) { - continue; - } - if (value_dst != diff_dst) { - rz_list_foreach (constants, iter_const, constant) { - ut64 value_ct = rz_num_get(NULL, constant); - simulate = simulate_op(op, value_src1, value_ct, - diff_src1, value_ct, op_result, - item_dst->size); - simulate_r = simulate_op(op, value_ct, value_src1, - value_ct, diff_src1, op_result_r, - item_dst->size); - if (simulate && op_result && value_dst == *op_result) { - char *tmp = rz_str_newf("%s <-- %s %s %s;", item_dst->name, item_src1->name, op, constant); - if (arithmetic && !strstr(arithmetic, tmp)) { - arithmetic = rz_str_append(arithmetic, tmp); - } else if (!arithmetic) { - arithmetic = rz_str_append(arithmetic, tmp); - } - free(tmp); - redundant = true; - } else if (!redundant && simulate_r && value_dst == *op_result_r) { - char *tmp = rz_str_newf("%s <-- %s %s %s;", item_dst->name, constant, op, item_src1->name); - if (arithmetic && !strstr(arithmetic, tmp)) { - arithmetic = rz_str_append(arithmetic, tmp); - } else if (!arithmetic) { - arithmetic = rz_str_append(arithmetic, tmp); - } - free(tmp); - } - } - } - } - } - } - continue_error: - FREE_ROP; - rz_list_free(constants); + rop_constraint->type = MOV_OP_REG; + rop_constraint->args[DST_REG] = dst_reg; + rop_constraint->args[SRC_REG] = src_reg1; + RzILOpPureCode *op = rz_list_get_n(args, 0); + if (!op) { + free(dst_reg); + free(src_reg1); + free(dst_reg2); + rz_list_free(args); + return false; } - free(op_result); - free(op_result_r); - return arithmetic; + + const char *op_str = rz_il_op_pure_code_stringify(*op); + rop_constraint->args[OP] = rz_str_dup(op_str); + rop_constraint->args[SRC_CONST] = dst_reg2; + return true; } -static int rop_classify_nops(RzCore *core, RzList /**/ *ropList) { - char *esil_str; - int changes = 1; - RzListIter *iter_r; - const bool romem = rz_config_get_i(core->config, "esil.romem"); - const bool stats = rz_config_get_i(core->config, "esil.stats"); +/** + * \brief Analyze and parse a constraint string. + * \param core Pointer to the RzCore object. + * \param str The constraint string to analyze. + * \param rop_constraint Pointer to the RzRopConstraint object to store the parsed result. + * \return true if the constraint string is successfully parsed, false otherwise. + * + * This function analyzes a given constraint string and attempts to parse it into + * the provided RzRopConstraint. It tries four different parsing methods: + * + * The function returns true if any of these parsing methods succeed. + */ +RZ_API bool rz_core_rop_analyze_constraint(RzCore *core, const char *str, RzRopConstraint *rop_constraint) { + rz_return_val_if_fail(core, false); + return parse_reg_to_const(core, str, rop_constraint) || + parse_reg_to_reg(core, str, rop_constraint) || + parse_reg_op_const(core, str, rop_constraint) || + parse_reg_op_reg(core, str, rop_constraint); +} - if (!romem || !stats) { - // eprintf ("Error: esil.romem and esil.stats must be set TRUE\n"); - return -2; +static RzRopConstraint *rop_constraint_parse_args(RzCore *core, char *token) { + RzRopConstraint *rop_constraint = RZ_NEW0(RzRopConstraint); + RzList *l = rz_str_split_duplist_n(token, "=", 1, false); + char *key = rz_list_get_n(l, 0); + char *value = rz_list_get_n(l, 1); + if (RZ_STR_ISEMPTY(key) || RZ_STR_ISEMPTY(value)) { + RZ_LOG_ERROR("core: Make sure to use the format = without spaces.\n"); + rz_list_free(l); + return NULL; } - - rz_list_foreach (ropList, iter_r, esil_str) { - fillRegisterValues(core); - - // rz_cons_printf ("Emulating nop:%s\n", esil_str); - cmd_analysis_esil(core, esil_str); - char *out = sdb_querys(core->analysis->esil->stats, NULL, 0, "*"); - // rz_cons_println (out); - if (out) { - free(out); - return 0; - } - // directly say NOP - continue; + if (!rop_constraint) { + rz_list_free(l); + return NULL; + } + if (!rz_core_rop_analyze_constraint(core, token, rop_constraint)) { + free(rop_constraint); + rz_list_free(l); + return NULL; } - return changes; + rz_list_free(l); + return rop_constraint; } -static void rop_classify(RzCore *core, Sdb *db, RzList /**/ *ropList, const char *key, unsigned int size) { - int nop = 0; - rop_classify_nops(core, ropList); - char *mov, *ct, *arithm, *arithm_ct, *str; - Sdb *db_nop = sdb_ns(db, "nop", true); - Sdb *db_mov = sdb_ns(db, "mov", true); - Sdb *db_ct = sdb_ns(db, "const", true); - Sdb *db_aritm = sdb_ns(db, "arithm", true); - Sdb *db_aritm_ct = sdb_ns(db, "arithm_ct", true); - - if (!db_nop || !db_mov || !db_ct || !db_aritm || !db_aritm_ct) { - RZ_LOG_ERROR("core: could not create SDB 'rop' sub-namespaces\n"); - return; - } - nop = rop_classify_nops(core, ropList); - mov = rop_classify_mov(core, ropList); - ct = rop_classify_constant(core, ropList); - arithm = rop_classify_arithmetic(core, ropList); - arithm_ct = rop_classify_arithmetic_const(core, ropList); - str = rz_str_newf("0x%u", size); - - if (nop == 1) { - char *str_nop = rz_str_newf("%s NOP", str); - sdb_set(db_nop, key, str_nop); - free(str_nop); - } else { - if (mov) { - char *str_mov = rz_str_newf("%s MOV { %s }", str, mov); - sdb_set(db_mov, key, str_mov); - free(str_mov); - free(mov); - } - if (ct) { - char *str_ct = rz_str_newf("%s LOAD_CONST { %s }", str, ct); - sdb_set(db_ct, key, str_ct); - free(str_ct); - free(ct); +static RzList /**/ *rop_constraint_list_parse(RzCore *core, const int argc, const char **argv) { + RzList *constr_list = rz_rop_constraint_list_new(); + for (int i = 1; i < argc; i++) { + RzList *l = rz_str_split_duplist_n(argv[i], ",", 1, false); + if (!l) { + return constr_list; } - if (arithm) { - char *str_arithm = rz_str_newf("%s ARITHMETIC { %s }", str, arithm); - sdb_set(db_aritm, key, str_arithm); - free(str_arithm); - free(arithm); + size_t llen = rz_list_length(l); + if (!llen) { + return constr_list; } - if (arithm_ct) { - char *str_arithm_ct = rz_str_newf("%s ARITHMETIC_CONST { %s }", str, arithm_ct); - sdb_set(db_aritm_ct, key, str_arithm_ct); - free(str_arithm_ct); - free(arithm_ct); + RzListIter *it; + char *token; + rz_list_foreach (l, it, token) { + RzRopConstraint *rop_constraint = rop_constraint_parse_args(core, token); + if (!rop_constraint) { + continue; + } + rz_list_append(constr_list, rop_constraint); } + rz_list_free(l); } - - free(str); -} + return constr_list; +} \ No newline at end of file diff --git a/librz/core/cmd_descs/cmd_descs.c b/librz/core/cmd_descs/cmd_descs.c index 3020a2a8189..38454b5d69a 100644 --- a/librz/core/cmd_descs/cmd_descs.c +++ b/librz/core/cmd_descs/cmd_descs.c @@ -104,6 +104,7 @@ static const RzCmdDescArg interpret_macro_multiple_args[4]; static const RzCmdDescArg cmd_info_gadget_args[2]; static const RzCmdDescArg cmd_search_gadget_args[2]; static const RzCmdDescArg cmd_query_gadget_args[2]; +static const RzCmdDescArg cmd_detail_gadget_args[2]; static const RzCmdDescArg remote_args[3]; static const RzCmdDescArg remote_send_args[3]; static const RzCmdDescArg remote_add_args[2]; @@ -1388,7 +1389,7 @@ static const RzCmdDescHelp cmd_search_gadget_help = { static const RzCmdDescArg cmd_query_gadget_args[] = { { - .name = "nop|mov|arithm", + .name = "key=value", .type = RZ_CMD_ARG_TYPE_STRING, .flags = RZ_CMD_ARG_FLAG_LAST, .optional = false, @@ -1397,10 +1398,26 @@ static const RzCmdDescArg cmd_query_gadget_args[] = { { 0 }, }; static const RzCmdDescHelp cmd_query_gadget_help = { - .summary = "Query ROP Gadgets", + .summary = "Query ROP Gadgets by providing constraints", + .args_str = " [=] [[=] ...]]", .args = cmd_query_gadget_args, }; +static const RzCmdDescArg cmd_detail_gadget_args[] = { + { + .name = "Gadget address", + .type = RZ_CMD_ARG_TYPE_STRING, + .flags = RZ_CMD_ARG_FLAG_LAST, + .optional = true, + + }, + { 0 }, +}; +static const RzCmdDescHelp cmd_detail_gadget_help = { + .summary = "Gadget detail info", + .args = cmd_detail_gadget_args, +}; + static const RzCmdDescHelp R_help = { .summary = "Connect with other instances of rizin", }; @@ -19230,6 +19247,10 @@ RZ_IPI void rzshell_cmddescs_init(RzCore *core) { rz_warn_if_fail(cmd_query_gadget_cd); rz_cmd_desc_set_default_mode(cmd_query_gadget_cd, RZ_OUTPUT_MODE_STANDARD); + RzCmdDesc *cmd_detail_gadget_cd = rz_cmd_desc_argv_state_new(core->rcmd, slash_R_cd, "/Rg", RZ_OUTPUT_MODE_STANDARD | RZ_OUTPUT_MODE_JSON | RZ_OUTPUT_MODE_QUIET | RZ_OUTPUT_MODE_TABLE, rz_cmd_detail_gadget_handler, &cmd_detail_gadget_help); + rz_warn_if_fail(cmd_detail_gadget_cd); + rz_cmd_desc_set_default_mode(cmd_detail_gadget_cd, RZ_OUTPUT_MODE_STANDARD); + RzCmdDesc *R_cd = rz_cmd_desc_group_new(core->rcmd, root_cd, "R", rz_remote_handler, &remote_help, &R_help); rz_warn_if_fail(R_cd); RzCmdDesc *remote_send_cd = rz_cmd_desc_argv_new(core->rcmd, R_cd, "R<", rz_remote_send_handler, &remote_send_help); diff --git a/librz/core/cmd_descs/cmd_descs.h b/librz/core/cmd_descs/cmd_descs.h index a5458c81481..8dcaad5f3b4 100644 --- a/librz/core/cmd_descs/cmd_descs.h +++ b/librz/core/cmd_descs/cmd_descs.h @@ -67,6 +67,8 @@ RZ_IPI RzCmdStatus rz_cmd_info_gadget_handler(RzCore *core, int argc, const char RZ_IPI RzCmdStatus rz_cmd_search_gadget_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state); // "/Rk" RZ_IPI RzCmdStatus rz_cmd_query_gadget_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state); +// "/Rg" +RZ_IPI RzCmdStatus rz_cmd_detail_gadget_handler(RzCore *core, int argc, const char **argv, RzCmdStateOutput *state); // "/" RZ_IPI int rz_cmd_search(void *data, const char *input); // "R" diff --git a/librz/core/cmd_descs/cmd_search.yaml b/librz/core/cmd_descs/cmd_search.yaml index 291967326de..9d964e866ef 100644 --- a/librz/core/cmd_descs/cmd_search.yaml +++ b/librz/core/cmd_descs/cmd_search.yaml @@ -36,7 +36,7 @@ commands: optional: true - name: "/Rk" cname: cmd_query_gadget - summary: Query ROP Gadgets + summary: Query ROP Gadgets by providing constraints type: RZ_CMD_DESC_TYPE_ARGV_STATE default_mode: RZ_OUTPUT_MODE_STANDARD modes: @@ -44,7 +44,22 @@ commands: - RZ_OUTPUT_MODE_JSON - RZ_OUTPUT_MODE_QUIET - RZ_OUTPUT_MODE_TABLE + args_str: " [=] [[=] ...]]" args: - - name: nop|mov|arithm + - name: key=value type: RZ_CMD_ARG_TYPE_STRING - optional: false \ No newline at end of file + optional: false + - name: "/Rg" + cname: cmd_detail_gadget + summary: Gadget detail info + type: RZ_CMD_DESC_TYPE_ARGV_STATE + default_mode: RZ_OUTPUT_MODE_STANDARD + modes: + - RZ_OUTPUT_MODE_STANDARD + - RZ_OUTPUT_MODE_JSON + - RZ_OUTPUT_MODE_QUIET + - RZ_OUTPUT_MODE_TABLE + args: + - name: Gadget address + type: RZ_CMD_ARG_TYPE_STRING + optional: true \ No newline at end of file diff --git a/librz/core/meson.build b/librz/core/meson.build index 9ab6628ec36..bb6b7f6e107 100644 --- a/librz/core/meson.build +++ b/librz/core/meson.build @@ -54,6 +54,7 @@ rz_core_sources = [ 'libs.c', 'linux_heap_glibc.c', 'linux_heap_glibc64.c', + 'rop.c', 'project.c', 'project_migrate.c', 'rtr.c', diff --git a/librz/core/project_migrate.c b/librz/core/project_migrate.c index 4517ceb4c81..9cbf4fe3ccc 100644 --- a/librz/core/project_migrate.c +++ b/librz/core/project_migrate.c @@ -656,6 +656,27 @@ RZ_API bool rz_project_migrate_v16_v17(RzProject *prj, RzSerializeResultInfo *re return true; } +// -- +// Migration 17 -> 18 +// +// Changes from : +// Removed: +// - "rop.sdb" +// - "rop.db" +// Set: +// - "rop.cache" + +RZ_API bool rz_project_migrate_v17_v18(RzProject *prj, RzSerializeResultInfo *res) { + Sdb *core_db; + RZ_SERIALIZE_SUB(prj, core_db, res, "core", return false;); + Sdb *config_db; + RZ_SERIALIZE_SUB(core_db, config_db, res, "config", return false;); + sdb_unset(config_db, "rop.sdb"); + sdb_unset(config_db, "rop.db"); + sdb_set(config_db, "rop.cache", "false"); + return true; +} + static bool (*const migrations[])(RzProject *prj, RzSerializeResultInfo *res) = { rz_project_migrate_v1_v2, rz_project_migrate_v2_v3, @@ -672,7 +693,8 @@ static bool (*const migrations[])(RzProject *prj, RzSerializeResultInfo *res) = rz_project_migrate_v13_v14, rz_project_migrate_v14_v15, rz_project_migrate_v15_v16, - rz_project_migrate_v16_v17 + rz_project_migrate_v16_v17, + rz_project_migrate_v17_v18, }; /// Migrate the given project to the current version in-place diff --git a/librz/core/rop.c b/librz/core/rop.c new file mode 100644 index 00000000000..78bb850e4a2 --- /dev/null +++ b/librz/core/rop.c @@ -0,0 +1,1238 @@ +// SPDX-FileCopyrightText: 2024 z3phyr +// SPDX-License-Identifier: LGPL-3.0-only + +#include +#include +#include +#include +#include + +static bool is_end_gadget(const RzAnalysisOp *aop, const ut8 crop) { + if (aop->family == RZ_ANALYSIS_OP_FAMILY_SECURITY) { + return false; + } + switch (aop->type) { + case RZ_ANALYSIS_OP_TYPE_TRAP: + case RZ_ANALYSIS_OP_TYPE_RET: + case RZ_ANALYSIS_OP_TYPE_UCALL: + case RZ_ANALYSIS_OP_TYPE_RCALL: + case RZ_ANALYSIS_OP_TYPE_ICALL: + case RZ_ANALYSIS_OP_TYPE_IRCALL: + case RZ_ANALYSIS_OP_TYPE_UJMP: + case RZ_ANALYSIS_OP_TYPE_RJMP: + case RZ_ANALYSIS_OP_TYPE_IJMP: + case RZ_ANALYSIS_OP_TYPE_IRJMP: + case RZ_ANALYSIS_OP_TYPE_JMP: + case RZ_ANALYSIS_OP_TYPE_CALL: + return true; + default: + return false; + } + + if (!crop) { + return false; + } + switch (aop->type) { + case RZ_ANALYSIS_OP_TYPE_CJMP: + case RZ_ANALYSIS_OP_TYPE_UCJMP: + case RZ_ANALYSIS_OP_TYPE_CCALL: + case RZ_ANALYSIS_OP_TYPE_UCCALL: + case RZ_ANALYSIS_OP_TYPE_CRET: + return true; + default: + return false; + } +} + +static bool rz_rop_process_asm_op(const RzCore *core, const RzCoreAsmHit *hit, RzAsmOp *asmop, RzAnalysisOp *aop, unsigned int *size, char **asmop_str, char **asmop_hex_str) { + ut8 *buf = malloc(hit->len); + if (!buf) { + return false; + } + if (rz_io_nread_at(core->io, hit->addr, buf, hit->len) < 0) { + free(buf); + return false; + } + rz_asm_set_pc(core->rasm, hit->addr); + if (rz_asm_disassemble(core->rasm, asmop, buf, hit->len) < 0) { + free(buf); + return false; + } + rz_analysis_op_init(aop); + rz_analysis_op(core->analysis, aop, hit->addr, buf, hit->len, RZ_ANALYSIS_OP_MASK_DISASM); + *size += hit->len; + + // Append assembly operation string + if (asmop_str) { + *asmop_str = rz_str_append(*asmop_str, rz_asm_op_get_asm(asmop)); + *asmop_str = rz_str_append(*asmop_str, "; "); + } + + // Append hex string of assembly operation + if (asmop_hex_str) { + char *asmop_hex = rz_asm_op_get_hex(asmop); + *asmop_hex_str = rz_str_append(*asmop_hex_str, asmop_hex); + free(asmop_hex); + } + + free(buf); + return true; +} + +static bool rz_rop_print_table_mode(const RzCore *core, const RzCoreAsmHit *hit, const RzList /**/ *hitlist, + unsigned int *size, char **asmop_str, char **asmop_hex_str) { + RzAnalysisOp aop = RZ_EMPTY; + RzAsmOp *asmop = rz_asm_op_new(); + if (!asmop) { + return false; + } + + if (!rz_rop_process_asm_op(core, hit, asmop, &aop, size, asmop_str, asmop_hex_str)) { + rz_asm_op_free(asmop); + return false; + } + const ut64 addr_last = ((RzCoreAsmHit *)rz_list_last(hitlist))->addr; + if (addr_last != hit->addr) { + *asmop_str = rz_str_append(*asmop_str, "; "); + } + rz_asm_op_free(asmop); + rz_analysis_op_fini(&aop); + return true; +} + +static bool rz_rop_print_quiet_mode(const RzCore *core, const RzCoreAsmHit *hit, unsigned int *size, const bool colorize) { + RzAnalysisOp aop = RZ_EMPTY; + RzAsmOp *asmop = rz_asm_op_new(); + if (!asmop) { + return false; + } + + if (!rz_rop_process_asm_op(core, hit, asmop, &aop, size, NULL, NULL)) { + rz_asm_op_free(asmop); + return false; + } + if (colorize) { + RzStrBuf *bw_str = rz_strbuf_new(rz_asm_op_get_asm(asmop)); + RzAsmParseParam *param = rz_asm_get_parse_param(core->analysis->reg, aop.type); + RzStrBuf *colored_asm = rz_asm_colorize_asm_str(bw_str, core->print, param, asmop->asm_toks); + rz_asm_parse_param_free(param); + rz_cons_printf(" %s%s;", colored_asm ? rz_strbuf_get(colored_asm) : "", Color_RESET); + rz_strbuf_free(colored_asm); + rz_strbuf_free(bw_str); + } else { + rz_cons_printf(" %s;", rz_asm_op_get_asm(asmop)); + } + rz_asm_op_free(asmop); + rz_analysis_op_fini(&aop); + return true; +} + +static bool rz_rop_print_standard_mode(const RzCore *core, const RzCoreAsmHit *hit, + unsigned int *size, const bool rop_comments, const bool colorize) { + RzAnalysisOp aop = RZ_EMPTY; + RzAsmOp *asmop = rz_asm_op_new(); + if (!asmop) { + return false; + } + if (!rz_rop_process_asm_op(core, hit, asmop, &aop, size, NULL, NULL)) { + rz_asm_op_free(asmop); + return false; + } + const char *comment = rop_comments ? rz_meta_get_string(core->analysis, RZ_META_TYPE_COMMENT, hit->addr) : NULL; + char *asm_op_hex = rz_asm_op_get_hex(asmop); + if (colorize) { + RzStrBuf *bw_str = rz_strbuf_new(rz_asm_op_get_asm(asmop)); + RzAsmParseParam *param = rz_asm_get_parse_param(core->analysis->reg, aop.type); + RzStrBuf *colored_asm = rz_asm_colorize_asm_str(bw_str, core->print, param, asmop->asm_toks); + rz_asm_parse_param_free(param); + if (comment) { + rz_cons_printf(" 0x%08" PFMT64x " %18s %s%s ; %s\n", + hit->addr, asm_op_hex, colored_asm ? rz_strbuf_get(colored_asm) : "", Color_RESET, comment); + } else { + rz_cons_printf(" 0x%08" PFMT64x " %18s %s%s\n", + hit->addr, asm_op_hex, colored_asm ? rz_strbuf_get(colored_asm) : "", Color_RESET); + } + rz_strbuf_free(colored_asm); + rz_strbuf_free(bw_str); + } else { + if (comment) { + rz_cons_printf(" 0x%08" PFMT64x " %18s %s ; %s\n", + hit->addr, asm_op_hex, rz_asm_op_get_asm(asmop), comment); + } else { + rz_cons_printf(" 0x%08" PFMT64x " %18s %s\n", + hit->addr, asm_op_hex, rz_asm_op_get_asm(asmop)); + } + } + free(asm_op_hex); + rz_analysis_op_fini(&aop); + rz_asm_op_free(asmop); + return true; +} + +static bool rz_rop_print_json_mode(const RzCore *core, const RzCoreAsmHit *hit, unsigned int *size, PJ *pj) { + RzAnalysisOp aop = RZ_EMPTY; + RzAsmOp *asmop = rz_asm_op_new(); + if (!asmop) { + return false; + } + if (!rz_rop_process_asm_op(core, hit, asmop, &aop, size, NULL, NULL)) { + rz_asm_op_free(asmop); + return false; + } + + pj_o(pj); + pj_kn(pj, "offset", hit->addr); + pj_ki(pj, "size", hit->len); + pj_ks(pj, "opcode", rz_asm_op_get_asm(asmop)); + pj_ks(pj, "type", rz_analysis_optype_to_string(aop.type)); + pj_end(pj); + + rz_analysis_op_fini(&aop); + rz_asm_op_free(asmop); + return true; +} + +RZ_IPI void rz_core_rop_reg_info_free(RzRopRegInfo *reg_info) { + if (!reg_info) { + return; + } + free(reg_info->name); + free(reg_info); +} + +RZ_IPI RzRopRegInfo *rz_core_rop_reg_info_new(const RzCore *core, const RzILEvent *evt, const ut64 init_val, const ut64 new_val) { + RzRopRegInfo *reg_info = RZ_NEW0(RzRopRegInfo); + const char *name = NULL; + if (evt->type == RZ_IL_EVENT_VAR_READ) { + reg_info->is_var_read = true; + name = evt->data.var_read.variable; + } else if (evt->type == RZ_IL_EVENT_VAR_WRITE) { + reg_info->is_var_write = true; + name = evt->data.var_write.variable; + } + if (!reg_info) { + return NULL; + } + const RzList *head = rz_reg_get_list(core->analysis->reg, RZ_REG_TYPE_GPR); + if (!head) { + free(reg_info); + return NULL; + } + RzListIter *iter_dst; + RzRegItem *item_dst; + rz_list_foreach (head, iter_dst, item_dst) { + if (RZ_STR_EQ(name, item_dst->name) && item_dst->type == RZ_REG_TYPE_GPR) { + reg_info->name = strdup(name); + break; + } + } + + if (!reg_info->name) { + free(reg_info); + return NULL; + } + reg_info->init_val = init_val; + reg_info->new_val = new_val; + return reg_info; +} + +/** + * \brief Create a new RzRopGadgetInfo object. + * \param address The address of the ROP gadget. + * \return RZ_OUT A pointer to the newly created RzRopGadgetInfo object, or NULL if memory allocation fails. + * + * This function allocates and initializes a new RzRopGadgetInfo object with the given address. + */ +RZ_API RZ_OWN RzRopGadgetInfo *rz_core_rop_gadget_info_new(const ut64 address) { + RzRopGadgetInfo *gadget_info = RZ_NEW0(RzRopGadgetInfo); + if (!gadget_info) { + return NULL; + } + + gadget_info->address = address; + gadget_info->stack_change = 0LL; + gadget_info->curr_pc_val = address; + gadget_info->is_pc_write = false; + gadget_info->is_syscall = false; + gadget_info->modified_registers = rz_pvector_new((RzPVectorFree)rz_core_rop_reg_info_free); + gadget_info->dependencies = rz_list_newf((RzListFree)rz_core_rop_reg_info_free); + + return gadget_info; +} + +/** + * \brief Free an RzRopGadgetInfo object. + * \param gadget_info RZ_NULLABLE Pointer to the RzRopGadgetInfo object to free. + * + * Frees the memory allocated for an RzRopGadgetInfo object, including its modified registers and dependencies. + */ +RZ_API void rz_core_rop_gadget_info_free(RZ_NULLABLE RzRopGadgetInfo *gadget_info) { + rz_return_if_fail(gadget_info); + + rz_pvector_free(gadget_info->modified_registers); + rz_list_free(gadget_info->dependencies); + free(gadget_info); +} + +/** + * \brief Add a register info to an RzRopGadgetInfo object. + * \param gadget_info RZ_NONNULL Pointer to the RzRopGadgetInfo object. + * \param reg_info RZ_NONNULL Pointer to the RzRopRegInfo object. + * \param is_dependency Boolean indicating whether the register is a dependency. + * + * Adds the given register info to the modified registers of the RzRopGadgetInfo object if it is not a dependency. + */ +RZ_API void rz_core_rop_gadget_info_add_register(const RZ_NONNULL RZ_OUT RzRopGadgetInfo *gadget_info, + RZ_NONNULL RzRopRegInfo *reg_info, const bool is_dependency) { + rz_return_if_fail(gadget_info && reg_info); + + if (!is_dependency) { + rz_pvector_push(gadget_info->modified_registers, reg_info); + } +} + +/** + * \brief Get the modified register info by name. + * \param gadget_info RZ_NONNULL Pointer to the RzRopGadgetInfo object. + * \param name RZ_NONNULL Pointer to the name of the register. + * \return RZ_OUT A pointer to the RzRopRegInfo object if found, or NULL if not found or if gadget_info is NULL. + * + * Searches the modified registers in the RzRopGadgetInfo object for the register with the given name and returns its info. + */ +RZ_BORROW RZ_API RzRopRegInfo *rz_core_rop_gadget_info_get_modified_register(const RZ_NONNULL RzRopGadgetInfo *gadget_info, RZ_NONNULL const char *name) { + rz_return_val_if_fail(gadget_info && name, NULL); + void **it; + rz_pvector_foreach (gadget_info->modified_registers, it) { + RzRopRegInfo *reg_info = *it; + if (RZ_STR_EQ(reg_info->name, name)) { + return reg_info; + } + } + return NULL; +} + +/** + * \brief Update a register info in the RzRopGadgetInfo object. + * \param gadget_info RZ_INOUT Pointer to the RzRopGadgetInfo object. + * \param new_reg_info RZ_NONNULL Pointer to the new RzRopRegInfo object. + * \return 0 on success, -1 on failure. + * + * Updates the register info in the RzRopGadgetInfo object with the values from the new register info. + * If the register is not already in the modified registers list, it is added. + */ +RZ_API int rz_core_rop_gadget_info_update_register(RZ_INOUT RzRopGadgetInfo *gadget_info, RZ_NONNULL RzRopRegInfo *new_reg_info) { + rz_return_val_if_fail(gadget_info, -1); + rz_return_val_if_fail(new_reg_info, -1); + + RzRopRegInfo *existing_reg_info = rz_core_rop_gadget_info_get_modified_register(gadget_info, new_reg_info->name); + if (existing_reg_info) { + existing_reg_info->init_val = new_reg_info->init_val; + existing_reg_info->new_val = new_reg_info->new_val; + existing_reg_info->is_mem_read = new_reg_info->is_mem_read; + existing_reg_info->is_pc_write = new_reg_info->is_pc_write; + existing_reg_info->is_mem_write = new_reg_info->is_mem_write; + existing_reg_info->is_var_read = new_reg_info->is_var_read; + } else { + rz_pvector_push(gadget_info->modified_registers, new_reg_info); + } + return 0; +} + +RZ_IPI RzRopRegInfo *rz_core_rop_reg_info_dup(RzRopRegInfo *src) { + rz_return_val_if_fail(src, NULL); + + RzRopRegInfo *dup = RZ_NEW0(RzRopRegInfo); + if (!dup) { + return NULL; + } + + dup->name = strdup(src->name); + dup->is_mem_read = src->is_mem_read; + dup->is_pc_write = src->is_pc_write; + dup->is_mem_write = src->is_mem_write; + dup->is_var_read = src->is_var_read; + dup->is_var_write = src->is_var_write; + dup->init_val = src->init_val; + dup->new_val = src->new_val; + + return dup; +} + +static bool is_stack_pointer(const RzCore *core, const char *name) { + RzRegItem *reg_item = rz_reg_get(core->analysis->reg, name, RZ_REG_TYPE_GPR); + if (!reg_item) { + return false; + } + if (core->analysis->bits == 32 && RZ_STR_EQ(core->analysis->cpu, "x86")) { + return !strcmp(reg_item->name, "esp"); + } + if (core->analysis->bits == 64) { + return !strcmp(reg_item->name, "rsp") || !strcmp(reg_item->name, "esp"); + } + if (core->analysis->bits == 64 && !strcmp(core->analysis->cpu, "arm")) { + return !strcmp(reg_item->name, "r13"); + } + return reg_item->name; +} + +static bool is_base_pointer(const RzCore *core, const char *name) { + RzRegItem *reg_item = rz_reg_get(core->analysis->reg, name, RZ_REG_TYPE_GPR); + if (!reg_item) { + return false; + } + if (core->analysis->bits == 32 && RZ_STR_EQ(core->analysis->cpu, "x86")) { + return RZ_STR_EQ(reg_item->name, "ebp"); + } + if (core->analysis->bits == 64) { + return RZ_STR_EQ(reg_item->name, "rbp") || RZ_STR_EQ(reg_item->name, "ebp"); + } + if (core->analysis->bits == 64 && !strcmp(core->analysis->cpu, "arm")) { + return RZ_STR_EQ(reg_item->name, "r11"); + } + return reg_item->name; +} + +static void rz_rop_gadget_info_add_dependency(const RzCore *core, RzRopGadgetInfo *gadget_info, const RzILEvent *evt, RzRopRegInfo *reg_info) { + + RzRopRegInfo *reg_info_dup = rz_core_rop_reg_info_dup(reg_info); + if (!reg_info_dup) { + return; + } + switch (evt->type) { + case RZ_IL_EVENT_MEM_READ: { + const RzILEventMemRead *mem_read = &evt->data.mem_read; + reg_info->is_mem_read = true; + reg_info->is_mem_write = false; + reg_info->is_var_write = false; + reg_info_dup->new_val = rz_bv_to_ut64(mem_read->address); + break; + } + case RZ_IL_EVENT_MEM_WRITE: { + reg_info->is_mem_write = true; + reg_info->is_mem_read = false; + reg_info->is_var_write = false; + const RzILEventMemWrite *mem_write = &evt->data.mem_write; + reg_info_dup->init_val = rz_bv_to_ut64(mem_write->old_value); + reg_info_dup->new_val = rz_bv_to_ut64(mem_write->new_value); + break; + } + case RZ_IL_EVENT_VAR_WRITE: { + reg_info->is_var_write = true; + reg_info->is_mem_read = false; + reg_info->is_mem_write = false; + const RzILEventVarWrite *var_write = &evt->data.var_write; + RzBitVector *init_val = rz_il_value_to_bv(var_write->old_value); + RzBitVector *new_val = rz_il_value_to_bv(var_write->new_value); + if (!init_val || !new_val) { + rz_bv_free(init_val); + rz_bv_free(new_val); + break; + } + reg_info_dup->new_val = rz_bv_to_ut64(new_val); + if (is_stack_pointer(core, reg_info->name)) { + gadget_info->stack_change += rz_bv_to_ut64(new_val) - reg_info->new_val; + } + rz_bv_free(init_val); + rz_bv_free(new_val); + + break; + } + default: + break; + } + rz_list_append(gadget_info->dependencies, reg_info_dup); +} + +static int fill_rop_gadget_info_from_events(RzCore *core, RzRopGadgetInfo *gadget_info, const RzILEvent *curr_event, + RzILEvent *event, RzPVector /**/ *vec, const bool is_dependency) { + if (!gadget_info) { + return -1; + } + switch (event->type) { + case RZ_IL_EVENT_VAR_READ: { + const RzILEventVarRead *var_read = &event->data.var_read; + RzRopRegInfo *reg_info = rz_core_rop_gadget_info_get_modified_register(gadget_info, var_read->variable); + if (reg_info && !is_dependency) { + RzRopRegInfo *new_reg_info = rz_core_rop_reg_info_dup(reg_info); + if (!new_reg_info) { + break; + } + RzBitVector *val = rz_il_value_to_bv(var_read->value); + if (!val) { + break; + } + new_reg_info->new_val = rz_bv_to_ut64(val); + if (rz_core_rop_gadget_info_update_register(gadget_info, new_reg_info) < 0) { + break; + } + rz_core_rop_reg_info_free(new_reg_info); + rz_pvector_push(vec, event); + rz_bv_free(val); + break; + } + if (is_dependency && curr_event) { + // shouldn't take this path if it is a dependency + if (curr_event->type == RZ_IL_EVENT_VAR_READ) { + break; + } + rz_rop_gadget_info_add_dependency(core, gadget_info, curr_event, reg_info); + break; + } + if (reg_info) { + break; + } + RzBitVector *val = rz_il_value_to_bv(var_read->value); + if (!val) { + break; + } + reg_info = rz_core_rop_reg_info_new(core, event, rz_bv_to_ut64(val), rz_bv_to_ut64(val)); + rz_core_rop_gadget_info_add_register(gadget_info, reg_info, is_dependency); + if (!is_dependency) { + rz_pvector_push(vec, event); + } + rz_bv_free(val); + } break; + case RZ_IL_EVENT_VAR_WRITE: { + if (is_dependency) { + break; + } + while (!rz_pvector_empty(vec)) { + RzILEvent *evt = rz_pvector_pop(vec); + fill_rop_gadget_info_from_events(core, gadget_info, event, evt, vec, true); + } + const RzILEventVarWrite *var_write = &event->data.var_write; + RzRopRegInfo *reg_info = rz_core_rop_gadget_info_get_modified_register(gadget_info, var_write->variable); + if (reg_info && !is_dependency) { + RzRopRegInfo *new_reg_info = rz_core_rop_reg_info_dup(reg_info); + if (!new_reg_info) { + break; + } + RzBitVector *old_val = rz_il_value_to_bv(var_write->old_value); + RzBitVector *new_val = rz_il_value_to_bv(var_write->old_value); + if (old_val && new_val) { + new_reg_info->new_val = rz_bv_to_ut64(new_val); + new_reg_info->is_mem_write = true; + rz_core_rop_gadget_info_update_register(gadget_info, new_reg_info); + } + rz_bv_free(old_val); + rz_bv_free(new_val); + break; + } + + if (!reg_info) { + RzBitVector *old_val = rz_il_value_to_bv(var_write->old_value); + RzBitVector *new_val = rz_il_value_to_bv(var_write->new_value); + if (!old_val || !new_val) { + rz_bv_free(old_val); + rz_bv_free(new_val); + break; + } + reg_info = rz_core_rop_reg_info_new(core, event, rz_bv_to_ut64(old_val), + rz_bv_to_ut64(new_val)); + rz_core_rop_gadget_info_add_register(gadget_info, reg_info, is_dependency); + rz_bv_free(old_val); + rz_bv_free(new_val); + } + } break; + case RZ_IL_EVENT_MEM_READ: { + while (!rz_pvector_empty(vec)) { + RzILEvent *evt = rz_pvector_pop(vec); + fill_rop_gadget_info_from_events(core, gadget_info, event, evt, vec, true); + } + } break; + case RZ_IL_EVENT_MEM_WRITE: { + while (!rz_pvector_empty(vec)) { + RzILEvent *evt = rz_pvector_pop(vec); + fill_rop_gadget_info_from_events(core, gadget_info, event, evt, vec, true); + } + } break; + case RZ_IL_EVENT_PC_WRITE: { + if (!gadget_info->is_pc_write) { + gadget_info->is_pc_write = true; + } + } break; + default: + break; + } + return 0; +} + +static int analyze_gadget(RzCore *core, const RzCoreAsmHit *hit, RzRopGadgetInfo *rop_gadget_info) { + rz_return_val_if_fail(core && core->analysis, -1); + int ret = 0; + + ut64 old_addr = core->offset; + rz_core_seek(core, hit->addr, true); + rz_core_analysis_il_reinit(core); + rz_config_set(core->config, "io.cache", "true"); + rz_core_il_step(core, 1); + + if (!core->analysis->il_vm) { + ret = -1; + goto cleanup; + } + RzILVM *vm = core->analysis->il_vm->vm; + if (!vm) { + ret = -1; + goto cleanup; + } + void **it; + + RzPVector vec; + rz_pvector_init(&vec, (RzPVectorFree)rz_il_event_free); + rz_pvector_foreach (vm->events, it) { + RzILEvent *evt = *it; + ret = fill_rop_gadget_info_from_events(core, rop_gadget_info, NULL, evt, &vec, false); + if (ret < 0) { + break; + } + } + +cleanup: + rz_pvector_flush(&vec); + rz_core_seek(core, old_addr, true); + return ret; +} + +static void print_rop_gadget_info(const RzCore *core, const RzRopGadgetInfo *gadget_info) { + rz_cons_printf("\nGadget 0x%" PFMT64x "\n", gadget_info->address); + rz_cons_printf("Stack change: 0x%" PFMT64x "\n", gadget_info->stack_change); + + rz_cons_printf("Changed registers: "); + RzRopRegInfo *reg_info; + void **it; + rz_pvector_foreach (gadget_info->modified_registers, it) { + reg_info = *it; + if (reg_info->is_var_write) { + rz_cons_printf("%s ", reg_info->name); + } + } + + rz_cons_printf("\nRegister dependencies:\n"); + RzListIter *iter; + rz_list_foreach (gadget_info->dependencies, iter, reg_info) { + if (is_stack_pointer(core, reg_info->name) || is_base_pointer(core, reg_info->name)) { + continue; + } + if (reg_info->is_var_write) { + rz_cons_printf("Var write: %s %llu %llu\n", reg_info->name, reg_info->init_val, reg_info->new_val); + } else if (reg_info->is_mem_read) { + rz_cons_printf("Memory Read: %s %llu\n", reg_info->name, reg_info->new_val); + } else if (reg_info->is_mem_write) { + rz_cons_printf("Memory Write: %s %llu %llu\n", reg_info->name, reg_info->init_val, reg_info->new_val); + } + } +} + +static int print_rop(const RzCore *core, const RzList /**/ *hitlist, RzCmdStateOutput *state) { + const RzCoreAsmHit *hit = NULL; + RzListIter *iter; + unsigned int size = 0; + char *asmop_str = NULL, *asmop_hex_str = NULL; + const bool colorize = rz_config_get_i(core->config, "scr.color"); + const bool rop_comments = rz_config_get_i(core->config, "rop.comments"); + + rz_cmd_state_output_set_columnsf(state, "XXs", "addr", "bytes", "disasm"); + if (state->mode == RZ_OUTPUT_MODE_JSON) { + pj_o(state->d.pj); + pj_ka(state->d.pj, "opcodes"); + } else if (state->mode == RZ_OUTPUT_MODE_QUIET) { + rz_cons_printf("0x%08" PFMT64x ":", ((RzCoreAsmHit *)rz_list_first(hitlist))->addr); + } + const ut64 addr = ((RzCoreAsmHit *)rz_list_first(hitlist))->addr; + + bool result = 0; + rz_list_foreach (hitlist, iter, hit) { + switch (state->mode) { + case RZ_OUTPUT_MODE_JSON: + result = rz_rop_print_json_mode(core, hit, &size, state->d.pj) != 0; + break; + case RZ_OUTPUT_MODE_QUIET: + result = rz_rop_print_quiet_mode(core, hit, &size, colorize) != 0; + break; + case RZ_OUTPUT_MODE_STANDARD: + result = rz_rop_print_standard_mode(core, hit, &size, rop_comments, colorize) != 0; + break; + case RZ_OUTPUT_MODE_TABLE: + result = rz_rop_print_table_mode(core, hit, hitlist, &size, &asmop_str, &asmop_hex_str) != 0; + break; + default: + rz_warn_if_reached(); + break; + } + if (!result) { + return result; + } + } + switch (state->mode) { + case RZ_OUTPUT_MODE_JSON: + pj_end(state->d.pj); + if (hit) { + pj_kn(state->d.pj, "retaddr", hit->addr); + pj_ki(state->d.pj, "size", size); + } + pj_end(state->d.pj); + break; + case RZ_OUTPUT_MODE_QUIET: + rz_cons_newline(); + break; + // fallthrough + case RZ_OUTPUT_MODE_STANDARD: + if (hit) { + rz_cons_printf("Gadget size: %d\n", (int)size); + } + rz_cons_newline(); + break; + case RZ_OUTPUT_MODE_TABLE: + rz_table_add_rowf(state->d.t, "Xss", addr, asmop_hex_str, asmop_str); + free(asmop_str); + free(asmop_hex_str); + break; + default: + rz_warn_if_reached(); + } + return 0; +} + +static int handle_rop_list(RzStrBuf *sb, const RzRopSearchContext *context, + const RzRopEndListPair *end_gadget, RZ_OWN RzList /**/ *hitlist) { + rz_return_val_if_fail(sb && context && context->unique_hitlists, -1); + if (end_gadget->delay_size && rz_list_length(hitlist) < 1 + end_gadget->delay_size) { + rz_list_free(hitlist); + return -1; + } + + bool is_found = true; + const char *asm_op_hex = NULL; + if (sb->len) { + asm_op_hex = rz_strbuf_get(sb); + ht_su_find(context->unique_hitlists, asm_op_hex, &is_found); + } + if (!is_found && asm_op_hex) { + ht_su_insert(context->unique_hitlists, asm_op_hex, 1); + } else { + rz_list_free(hitlist); + return -1; + } + return 0; +} + +static void init_grep_context(const RzRopSearchContext *context, char **grep_str, + const char **start, const char **end, const RzList /**/ *rx_list, char **rx, int *count) { + if (context->greparg) { + *start = context->greparg; + *end = strchr(context->greparg, ';'); + if (!*end) { + *end = *start + strlen(context->greparg); + } + *grep_str = calloc(1, *end - *start + 1); + strncpy(*grep_str, *start, *end - *start); + if (context->regexp && rz_list_length(rx_list) > 0) { + *rx = rz_list_get_n(rx_list, (*count)++); + } + } +} + +static bool process_instruction(const RzCore *core, RzAnalysisOp *aop, const int addr, const ut8 *buf, const int buf_len, ut32 *end_gadget_cnt) { + const int error = rz_analysis_op(core->analysis, aop, addr, buf, buf_len, RZ_ANALYSIS_OP_MASK_DISASM); + if (error < 0 || (aop->type == RZ_ANALYSIS_OP_TYPE_NOP && aop->size == 0)) { + return false; + } + if (is_end_gadget(aop, 0)) { + (*end_gadget_cnt)++; + } + return true; +} + +static bool is_invalid_instruction(const char *opst, const int end_gadget_cnt) { + return !rz_str_ncasecmp(opst, "invalid", strlen("invalid")) || + !rz_str_ncasecmp(opst, ".byte", strlen(".byte")) || + end_gadget_cnt > 1; +} + +static void update_search_context(const RzRopSearchContext *context, const char **start, const char **end, + char **grep_str, const RzList /**/ *rx_list, char **rx, int *count) { + if (*end && (*end)[0] == ';') { // fields are semicolon-separated + *start = *end + 1; // skip the ; + *end = strchr(*start, ';'); + if (!*end) { + *end = *start + strlen(*start); // latest field? + } + free(*grep_str); + *grep_str = calloc(1, *end - *start + 1); + if (*grep_str) { + strncpy(*grep_str, *start, *end - *start); + } + } else { + *end = NULL; + } + + if (context->regexp) { + *rx = rz_list_get_n(rx_list, (*count)++); + } +} + +static RzList /**/ *construct_rop_gadget(RzCore *core, ut8 *buf, int idx, RzRopSearchContext *context, + RzList /**/ *rx_list, RzRopEndListPair *end_gadget) { + int endaddr = end_gadget->instr_offset; + const char *start = NULL, *end = NULL; + char *grep_str = NULL; + RzCoreAsmHit *hit = NULL; + bool valid = false; + char *rx = NULL; + int count = 0; + + RzStrBuf *sb = rz_strbuf_new(""); + init_grep_context(context, &grep_str, &start, &end, rx_list, &rx, &count); + + RzList *hitlist = rz_core_asm_hit_list_new(); + if (!hitlist) { + goto cleanup; + } + + ut8 nb_instr = 0; + int addr = context->from + idx; + int delta = context->to - context->from; + ut32 end_gadget_cnt = 0; + + RzAnalysisOp aop = { 0 }; + while (nb_instr < context->max_instr) { + rz_analysis_op_init(&aop); + if (idx >= delta || !process_instruction(core, &aop, addr, buf + idx, delta - idx, &end_gadget_cnt)) { + valid = false; + goto cleanup; + } + + char *opst = aop.mnemonic; + RzAsmOp asmop = RZ_EMPTY; + int ret = rz_asm_disassemble(core->rasm, &asmop, buf + idx, delta - idx) < 0; + if (ret < 0) { + valid = false; + goto cleanup; + } + + if (is_invalid_instruction(opst, end_gadget_cnt)) { + valid = false; + goto cleanup; + } + + hit = rz_core_asm_hit_new(); + if (!hit) { + valid = false; + goto cleanup; + } + + hit->addr = addr; + hit->len = aop.size; + char *asm_op_hex = rz_asm_op_get_hex(&asmop); + rz_strbuf_append(sb, asm_op_hex); + free(asm_op_hex); + rz_list_append(hitlist, hit); + if (ret >= 0) { + rz_asm_op_fini(&asmop); + } + idx += aop.size; + addr += aop.size; + + bool search_hit = false; + if (rx) { + int grep_find = rz_regex_contains(rx, opst, RZ_REGEX_ZERO_TERMINATED, RZ_REGEX_EXTENDED, RZ_REGEX_DEFAULT); + search_hit = end && context->greparg && grep_find; + } else { + search_hit = end && context->greparg && strstr(opst, grep_str); + } + + if (search_hit) { + update_search_context(context, &start, &end, &grep_str, rx_list, &rx, &count); + } + if (endaddr <= idx - aop.size) { + valid = endaddr == idx - aop.size; + goto cleanup; + } + rz_analysis_op_fini(&aop); + nb_instr++; + } + +cleanup: + rz_analysis_op_fini(&aop); + free(grep_str); + if ((context->regexp && rx) || (!valid || (context->greparg && end))) { + rz_list_free(hitlist); + rz_strbuf_free(sb); + return NULL; + } + if (handle_rop_list(sb, context, end_gadget, hitlist) < 0) { + rz_strbuf_free(sb); + return NULL; + } + rz_strbuf_free(sb); + return hitlist; +} + +static int handle_rop_request_type(RzCore *core, RzRopSearchContext *context, RzList /**/ *hitlist) { + rz_return_val_if_fail(core && core->analysis, -1); + if (context->mask & RZ_ROP_GADGET_PRINT) { + if (context->subchain) { + do { + print_rop(core, hitlist, context->state); + hitlist->head = hitlist->head->next; + } while (hitlist->head->next); + } else { + print_rop(core, hitlist, context->state); + } + } + if (rz_config_get_i(core->config, "rop.cache") && !core->analysis->ht_rop) { + core->analysis->ht_rop = ht_up_new(NULL, (HtUPFreeValue)rz_core_rop_gadget_info_free); + } + RzRopGadgetInfo *rop_gadget_info = NULL; + + if (context->mask & RZ_ROP_GADGET_ANALYZE) { + RzListIter *iter; + RzCoreAsmHit *hit; + if (!core->analysis->ht_rop) { + core->analysis->ht_rop = ht_up_new(NULL, (HtUPFreeValue)rz_core_rop_gadget_info_free); + } + const ut64 addr_start = ((RzCoreAsmHit *)rz_list_first(hitlist))->addr; + rop_gadget_info = rz_core_rop_gadget_info_new(addr_start); + rz_list_foreach (hitlist, iter, hit) { + if (analyze_gadget(core, hit, rop_gadget_info) < 0) { + RZ_LOG_WARN("Failed to analyze gadget at 0x%" PFMT64x "\n", hit->addr); + } + } + ht_up_insert(core->analysis->ht_rop, addr_start, rop_gadget_info); + } + if (context->mask & RZ_ROP_GADGET_PRINT_DETAIL) { + print_rop_gadget_info(core, rop_gadget_info); + } + return 0; +} + +static int fetch_search_itv(const RzCore *core, RzInterval *search_itv) { + rz_return_val_if_fail(core && core->config, -1); + const ut64 search_from = rz_config_get_i(core->config, "search.from"), + search_to = rz_config_get_i(core->config, "search.to"); + if (search_from > search_to && search_to) { + RZ_LOG_ERROR("core: search.from > search.to is not supported\n"); + return -1; + } + search_itv->addr = search_from; + search_itv->size = search_to - search_from; + + const bool empty_search_itv = search_from == search_to && search_from != UT64_MAX; + if (empty_search_itv) { + RZ_LOG_ERROR("core: `from` address is equal `to`\n"); + return -1; + } + // TODO full address cannot be represented, shrink 1 byte to [0, UT64_MAX) + if (search_from == UT64_MAX && search_to == UT64_MAX) { + search_itv->addr = 0; + search_itv->size = UT64_MAX; + } + return 0; +} + +static RzList /**/ *compute_end_gadget_list(const RzCore *core, const ut8 *buf, const RzRopSearchContext *context) { + RzList /**/ *end_list = rz_list_newf(free); + const int delta = context->to - context->from; + + for (int i = 0; i < delta; i += context->increment) { + RzAnalysisOp end_gadget = RZ_EMPTY; + // Disassemble one. + rz_analysis_op_init(&end_gadget); + if (rz_analysis_op(core->analysis, &end_gadget, context->from + i, buf + i, + delta - i, RZ_ANALYSIS_OP_MASK_BASIC) < 1) { + rz_analysis_op_fini(&end_gadget); + continue; + } + + if (is_end_gadget(&end_gadget, context->crop)) { + RzRopEndListPair *epair = RZ_NEW0(RzRopEndListPair); + if (epair) { + epair->instr_offset = i + (end_gadget.delay ? context->increment : 0); + epair->delay_size = end_gadget.delay; + rz_list_append(end_list, epair); + } + } + rz_analysis_op_fini(&end_gadget); + if (rz_cons_is_breaked()) { + break; + } + } + return end_list; +} + +static void set_increment_based_on_arch(const RzCore *core, const char *arch, int *increment) { + if (RZ_STR_EQ(arch, "mips")) { // MIPS has no jump-in-the-middle + *increment = 4; + } else if (RZ_STR_EQ(arch, "arm")) { // ARM has no jump-in-the-middle + *increment = rz_config_get_i(core->config, "asm.bits") == 16 ? 2 : 4; + } else if (RZ_STR_EQ(arch, "avr")) { // AVR is halfword aligned. + *increment = 2; + } +} + +static RzList /**/ *handle_grep_args(const char *greparg, const bool regexp) { + if (!greparg && !regexp) { + return NULL; + } + + char *grep_arg = strdup(greparg); + if (!grep_arg) { + return NULL; + } + char *gregexp = rz_str_replace(grep_arg, ",,", ";", true); + if (!gregexp) { + return NULL; + } + + RzList *rx_list = rz_list_newf(free); + if (!rx_list) { + free(gregexp); + return NULL; + } + + const char *tok = strtok(gregexp, ";"); + while (tok) { + char *rx = strdup(tok); + if (!rx) { + break; + } + rz_list_append(rx_list, rx); + tok = strtok(NULL, ";"); + } + + free(gregexp); + return rx_list; +} + +static bool process_disassembly(RzCore *core, ut8 *buf, const int idx, RzRopSearchContext *context, + RzList /**/ *rx_list, RzRopEndListPair *end_gadget) { + RzAsmOp *asmop = rz_asm_op_new(); + bool status = false; + const int ret = rz_asm_disassemble(core->rasm, asmop, buf + idx, context->to - context->from - idx); + if (!ret) { + goto fini; + } + + rz_asm_set_pc(core->rasm, context->from + idx); + RzList *hitlist = construct_rop_gadget(core, buf, idx, context, rx_list, end_gadget); + + if (!hitlist) { + goto fini; + } + + if (core->search->align && (context->from + idx) % core->search->align != 0) { + rz_list_free(hitlist); + goto fini; + } + + if (handle_rop_request_type(core, context, hitlist) < 0) { + rz_list_free(hitlist); + goto fini; + } + rz_list_free(hitlist); + + if (context->max_count > 0) { + context->max_count--; + if (context->max_count < 1) { + status = true; + } + } + +fini: + rz_asm_op_free(asmop); + return status; +} + +RZ_BORROW static int update_end_gadget(int *i, const int ropdepth, RzRopEndListPair **end_gadget, const RzRopSearchContext *context) { + if (*i > (*end_gadget)->instr_offset) { + // We've exhausted the first end-gadget section, + // move to the next one. + free(*end_gadget); + + if (rz_list_get_n(context->end_list, 0)) { + *end_gadget = (RzRopEndListPair *)rz_list_pop(context->end_list); + *i = (*end_gadget)->instr_offset - ropdepth; + if (*i < 0) { + *i = 0; + } + } else { + *end_gadget = NULL; + return -1; + } + } + return 0; +} + +/** + * \brief Search for ROP gadgets. + * \param core Pointer to the RzCore object. + * \param context Pointer to the RzRopSearchContext object. + * \return true if the search is successful, false otherwise. + * + * Searches for ROP gadgets within the address range specified by configuration. + * Disassembles instructions, identifies end gadgets, constructs ROP gadgets, and + * filters results based on the grep argument and request mask. Outputs results to + * the provided state object. + */ +RZ_API RzCmdStatus rz_core_rop_search(RzCore *core, RZ_OWN RzRopSearchContext *context) { + rz_return_val_if_fail(core && core->search, RZ_CMD_STATUS_ERROR); + int result = -1; + + RzInterval search_itv; + result = fetch_search_itv(core, &search_itv); + if (result != 0) { + return -1; + } + + if (context->max_instr <= 1) { + RZ_LOG_ERROR("core: ROP length (rop.len) must be greater than 1.\n"); + if (context->max_instr == 1) { + RZ_LOG_ERROR("core: For rop.len = 1, use /c to search for single " + "instructions. See /c? for help.\n"); + } + return -1; + } + const char *arch = rz_config_get(core->config, "asm.arch"); + set_increment_based_on_arch(core, arch, &context->increment); + RzList /**/ *rx_list = handle_grep_args(context->greparg, context->regexp); + rz_cmd_state_output_array_start(context->state); + rz_cons_break_push(NULL, NULL); + const char *mode_str = rz_config_get(core->config, "search.in"); + RzList *boundaries = rz_core_get_boundaries_prot(core, -1, mode_str, "search"); + if (!boundaries) { + rz_cmd_state_output_array_end(context->state); + } + context->max_count = rz_config_get_i(core->config, "search.maxhits"); + if (context->max_count == 0) { + context->max_count = -1; + } + context->unique_hitlists = ht_su_new(HT_STR_DUP); + RzListIter *itermap; + context->subchain = rz_config_get_i(core->config, "rop.subchains"); + RzIOMap *map; + rz_list_foreach (boundaries, itermap, map) { + if (!rz_itv_overlap(search_itv, map->itv)) { + continue; + } + const RzInterval itv = rz_itv_intersect(search_itv, map->itv); + context->from = itv.addr; + context->to = rz_itv_end(itv); + if (rz_cons_is_breaked()) { + break; + } + const ut64 delta = context->to - context->from; + ut8 *buf = calloc(1, delta); + if (!buf) { + result = false; + continue; + } + if (rz_io_nread_at(core->io, context->from, buf, delta) < 0) { + free(buf); + continue; + } + + context->end_list = compute_end_gadget_list(core, buf, context); + // If we have no end gadgets, just skip all of this search nonsense. + if (rz_list_empty(context->end_list)) { + free(buf); + rz_list_free(context->end_list); + continue; + } + rz_list_reverse(context->end_list); + const int max_inst_size_x86 = 15; + // Get the depth of rop search, should just be max_instr + // instructions, x86 and friends are weird length instructions, so + // we'll just assume 15 byte instructions. + const int ropdepth = context->increment == 1 ? context->max_instr * max_inst_size_x86 /* wow, x86 is long */ : context->max_instr * context->increment; + if (rz_cons_is_breaked()) { + break; + } + RzRopEndListPair *end_gadget = rz_list_pop(context->end_list); + // Start at just before the first end gadget. + const int next = end_gadget->instr_offset; + for (int i = 0; i < delta && context->max_count; i += context->increment) { + // TODO: Test this and check if this line is needed in x86 + const int prev = 0; + if (context->increment == 1 && i < prev - max_inst_size_x86) { + i = prev - max_inst_size_x86; + } else if (context->increment != 1 && i < prev) { + i = prev; + } + if (rz_cons_is_breaked()) { + break; + } + if (i > next && update_end_gadget(&i, ropdepth, &end_gadget, context) < 0) { + break; + } + if (process_disassembly(core, buf, i, context, rx_list, end_gadget)) { + break; + } + } + free(end_gadget); + free(buf); + rz_list_free(context->end_list); + } + ht_su_free(context->unique_hitlists); + if (rz_cons_is_breaked()) { + eprintf("\n"); + } + + rz_cmd_state_output_array_end(context->state); + rz_cons_break_pop(); + rz_list_free(rx_list); + rz_core_rop_search_context_free(context); + rz_list_free(boundaries); + return result; +} + +/** + * \brief Display ROP gadget information. + * \param core Pointer to the RzCore object. + * \param context Pointer to the RzRopSearchContext object. + * \return RZ_CMD_STATUS_OK on success. + * + * Displays ROP gadgets from the gadgetSdb. + * If unavailable, performs a ROP search with the input. + */ +RZ_API RzCmdStatus rz_core_rop_gadget_info(RzCore *core, RZ_OWN RzRopSearchContext *context) { + rz_return_val_if_fail(core && core->analysis, RZ_CMD_STATUS_ERROR); + + if (!core->analysis->ht_rop) { + // TODO: resolve this logic later. + } + return rz_core_rop_search(core, context); +} + +/** + * \brief Free an RzRopConstraint object. + * \param data Pointer to the RzRopConstraint object to free. + * + * Frees the memory allocated for an RzRopConstraint object. + */ +RZ_API void rz_core_rop_constraint_free(RZ_NULLABLE void *data) { + RzRopConstraint *constraint = data; + if (!constraint) { + return; + } + for (int i = 0; i < NUM_ARGS; i++) { + if (constraint->args[i]) { + free(constraint->args[i]); + } + } + free(constraint); +} + +/** + * \brief Create a new list of RzRopConstraint objects. + * \return Pointer to the newly created list. + * + * Creates a new RzList for RzRopConstraint object. + */ +RZ_OWN RZ_API RzList /**/ *rz_rop_constraint_list_new(void) { + RzList *list = rz_list_new(); + if (list) { + list->free = &rz_core_rop_constraint_free; + } + return list; +} \ No newline at end of file diff --git a/librz/include/meson.build b/librz/include/meson.build index 686641c8e2c..ee9930380e9 100644 --- a/librz/include/meson.build +++ b/librz/include/meson.build @@ -54,6 +54,7 @@ include_files = [ 'rz_vector.h', 'rz_windows.h', 'rz_windows_heap.h', + 'rz_rop.h', ] install_headers(include_files, install_dir: rizin_incdir) diff --git a/librz/include/rz_analysis.h b/librz/include/rz_analysis.h index 006dc555e99..ffa34e46eb5 100644 --- a/librz/include/rz_analysis.h +++ b/librz/include/rz_analysis.h @@ -528,6 +528,7 @@ typedef struct rz_analysis_t { RzPlatformTarget *arch_target; RzPlatformTargetIndex *platform_target; HtSP *ht_global_var; // global variables + HtUP *ht_rop; ///< store rop gadget address. RBTree global_var_tree; // global variables by address. must not overlap RzHash *hash; RzAnalysisDebugInfo *debug_info; ///< store all debug info parsed from DWARF, etc.. @@ -1573,6 +1574,7 @@ RZ_API int rz_analysis_archinfo(RzAnalysis *analysis, RzAnalysisInfoType query); RZ_API bool rz_analysis_use(RzAnalysis *analysis, const char *name); RZ_API bool rz_analysis_set_reg_profile(RzAnalysis *analysis); RZ_API char *rz_analysis_get_reg_profile(RzAnalysis *analysis); +RZ_API bool rz_analysis_is_reg_in_profile(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL const char *name); RZ_API bool rz_analysis_set_bits(RzAnalysis *analysis, int bits); RZ_API bool rz_analysis_set_os(RzAnalysis *analysis, const char *os); RZ_API void rz_analysis_set_cpu(RzAnalysis *analysis, const char *cpu); @@ -1886,6 +1888,7 @@ RZ_API RZ_OWN RzAnalysisVarGlobal *rz_analysis_var_global_new(RZ_NONNULL const c RZ_API bool rz_analysis_var_global_add(RzAnalysis *analysis, RZ_NONNULL RzAnalysisVarGlobal *global_var); RZ_API bool rz_analysis_var_global_create(RzAnalysis *analysis, RZ_NONNULL const char *name, RZ_NONNULL RZ_BORROW RzType *type, ut64 addr); RZ_API void rz_analysis_var_global_free(RzAnalysisVarGlobal *glob); + RZ_API RZ_NULLABLE RzFlagItem *rz_analysis_var_global_get_flag_item(RzAnalysisVarGlobal *glob); RZ_API bool rz_analysis_var_global_delete(RZ_NONNULL RzAnalysis *analysis, RZ_NONNULL RzAnalysisVarGlobal *glob); RZ_API bool rz_analysis_var_global_delete_byname(RzAnalysis *analysis, RZ_NONNULL const char *name); diff --git a/librz/include/rz_project.h b/librz/include/rz_project.h index b5d02cd6167..5fc61ae7b95 100644 --- a/librz/include/rz_project.h +++ b/librz/include/rz_project.h @@ -12,7 +12,7 @@ extern "C" { #endif -#define RZ_PROJECT_VERSION 17 +#define RZ_PROJECT_VERSION 18 typedef Sdb RzProject; @@ -62,6 +62,7 @@ RZ_API bool rz_project_migrate_v13_v14(RzProject *prj, RzSerializeResultInfo *re RZ_API bool rz_project_migrate_v14_v15(RzProject *prj, RzSerializeResultInfo *res); RZ_API bool rz_project_migrate_v15_v16(RzProject *prj, RzSerializeResultInfo *res); RZ_API bool rz_project_migrate_v16_v17(RzProject *prj, RzSerializeResultInfo *res); +RZ_API bool rz_project_migrate_v17_v18(RzProject *prj, RzSerializeResultInfo *res); RZ_API bool rz_project_migrate(RzProject *prj, unsigned long version, RzSerializeResultInfo *res); #ifdef __cplusplus diff --git a/librz/include/rz_rop.h b/librz/include/rz_rop.h new file mode 100644 index 00000000000..4d1f7c61ee5 --- /dev/null +++ b/librz/include/rz_rop.h @@ -0,0 +1,143 @@ +// SPDX-FileCopyrightText: 2024 z3phyr +// SPDX-License-Identifier: LGPL-3.0-only + +#ifndef RZ_ROP_H +#define RZ_ROP_H + +/** + * \file rz_rop.h + * \brief Return-Oriented Programming (ROP) related APIs and structures.. + * + * This file contains definitions, structures, and function prototypes for handling ROP gadgets and constraints. + */ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \brief Information about a register. + */ +typedef struct rz_rop_reg_info_t { + char *name; + bool is_mem_read; ///< Register involved in Memory read. + bool is_pc_write; ///< PC write flag. + bool is_var_read; ///< Register involved in Variable read. + bool is_var_write; ///< Register involved in Variable write. + bool is_mem_write; ///< Register involved in Memory write. + ut64 init_val; + ut64 new_val; +} RzRopRegInfo; + +/** + * \brief Information about a ROP gadget. + */ +typedef struct rz_rop_gadget_info_t { + ut64 address; ///< Gadget address. + ut64 stack_change; ///< Stack change. + ut64 curr_pc_val; ///< Current PC value. + bool is_pc_write; ///< PC write flag. + bool is_syscall; ///< Syscall flag. + RzPVector /**/ *modified_registers; ///< Modified registers. + RzList /**/ *dependencies; ///< Dependencies. +} RzRopGadgetInfo; + +/** + * \brief Types of IL instructions for ROP constraints. + */ +typedef enum rz_rop_il_instr_type { + MOV_CONST, ///< reg <- const + MOV_REG, ///< reg <- reg + MOV_OP_CONST, ///< reg <- reg OP const + MOV_OP_REG, ///< reg <- reg OP reg + SYSCALL, ///< syscall +} RzRopILInstructionType; + +/** + * \brief Argument types for ROP constraints. + */ +typedef enum { + SRC_REG, + DST_REG, + SRC_CONST, + DST_REG_SECOND, + OP, + NUM_ARGS +} RzRopArgType; + +/** + * \brief ROP request mask for filtering gadgets. + */ +typedef enum { + RZ_ROP_GADGET_PRINT = 1 << 0, ///< Print ROP gadgets. + RZ_ROP_GADGET_PRINT_DETAIL = 1 << 1, ///< Detailed ROP gadgets. + RZ_ROP_GADGET_ANALYZE = 1 << 2, ///< Detailed ROP gadgets. + RZ_ROP_GADGET_ALL = RZ_ROP_GADGET_PRINT | RZ_ROP_GADGET_PRINT_DETAIL | RZ_ROP_GADGET_ANALYZE ///< All ROP gadgets requests. +} RzRopRequestMask; + +/** + * \brief Pair representing an end gadget with instruction offset and delay size. + */ +typedef struct rz_rop_endlist_pair_t { + int instr_offset; ///< Instruction offset. + int delay_size; ///< Delay size. +} RzRopEndListPair; + +/** + * \brief Structure representing a ROP constraint. + */ +typedef struct rz_rop_constraint_t { + RzRopILInstructionType type; ///< IL instruction type. + char *args[NUM_ARGS]; ///< Arguments. +} RzRopConstraint; + +/** + * \brief Structure representing a ROP search context. + */ +typedef struct rz_rop_search_context_t { + ut8 max_instr; + ut8 subchain; + ut8 crop; + char *greparg; + bool regexp; + int max_count; + int increment; + RzRopRequestMask mask; + RzCmdStateOutput *state; + ut64 from; + ut64 to; + RzList /**/ *end_list; + HtSU *unique_hitlists; +} RzRopSearchContext; + +// Command APIs +RZ_API RzCmdStatus rz_core_rop_search(RzCore *core, RZ_OWN RzRopSearchContext *context); +RZ_API RzCmdStatus rz_core_rop_gadget_info(RzCore *core, RZ_OWN RzRopSearchContext *context); +RZ_API bool rz_core_rop_analyze_constraint(RzCore *core, const char *str, RzRopConstraint *rop_constraint); + +// ROP Search Context APIs +RZ_OWN RZ_API RzRopSearchContext *rz_core_rop_search_context_new(RZ_NONNULL const RzCore *core, RZ_NULLABLE const char *greparg, + bool regexp, RzRopRequestMask mask, RZ_BORROW RzCmdStateOutput *state); +RZ_API void rz_core_rop_search_context_free(RZ_NULLABLE RzRopSearchContext *context); + +// ROP Constraint APIs +RZ_API void rz_core_rop_constraint_free(RZ_NULLABLE void *data); +RZ_OWN RZ_API RzList /**/ *rz_rop_constraint_list_new(void); + +// ROP Gadget Info APIs +RZ_API void rz_core_rop_gadget_info_free(RZ_NULLABLE RzRopGadgetInfo *gadget_info); +RZ_API void rz_core_rop_gadget_info_add_register(const RZ_NONNULL RZ_OUT RzRopGadgetInfo *gadget_info, + RZ_NONNULL RzRopRegInfo *reg_info, bool is_dependency); +RZ_API int rz_core_rop_gadget_info_update_register(RZ_INOUT RzRopGadgetInfo *gadget_info, RZ_NONNULL RzRopRegInfo *new_reg_info); +RZ_API RZ_OWN RzRopGadgetInfo *rz_core_rop_gadget_info_new(ut64 address); +RZ_IPI RzRopRegInfo *rz_core_rop_reg_info_dup(RzRopRegInfo *src); +RZ_IPI void rz_core_rop_reg_info_free(RzRopRegInfo *reg_info); +RZ_IPI RzRopRegInfo *rz_core_rop_reg_info_new(const RzCore *core, const RzILEvent *evt, ut64 init_val, ut64 new_val); +RZ_BORROW RZ_API RzRopRegInfo *rz_core_rop_gadget_info_get_modified_register(const RZ_NONNULL RzRopGadgetInfo *gadget_info, RZ_NONNULL const char *name); + +#ifdef __cplusplus +} +#endif +#endif // RZ_ROP_H diff --git a/test/db/analysis/avr b/test/db/analysis/avr index c9655920498..d782a3808d3 100644 --- a/test/db/analysis/avr +++ b/test/db/analysis/avr @@ -542,7 +542,6 @@ NAME=Search rop gadgets for in command FILE=bins/firmware/arduino_avr.bin ARGS=-a avr CMDS=<config, "rop.cache"), false, "rop.cache"); + rz_core_free(core); + mu_end; +} + int all_tests() { mu_run_test(test_migrate_v1_v2_noreturn); mu_run_test(test_migrate_v1_v2_noreturn_empty); @@ -998,6 +1023,7 @@ int all_tests() { mu_run_test(test_migrate_v14_v15); mu_run_test(test_migrate_v15_v16_str_config); mu_run_test(test_migrate_v16_v17_flags_base); + mu_run_test(test_migrate_v17_v18_rop_config); mu_run_test(test_load_v1_noreturn); mu_run_test(test_load_v1_noreturn_empty); mu_run_test(test_load_v1_unknown_type); @@ -1020,6 +1046,7 @@ int all_tests() { mu_run_test(test_load_v15_seek_history); mu_run_test(test_load_v15_str_config); mu_run_test(test_load_v16); + mu_run_test(test_load_v17); return tests_passed != tests_run; } diff --git a/test/prj/v17-rop-config.rzdb b/test/prj/v17-rop-config.rzdb new file mode 100644 index 00000000000..d855d3bca08 --- /dev/null +++ b/test/prj/v17-rop-config.rzdb @@ -0,0 +1,82 @@ +/ +type=rizin rz-db project +version=17 + +/core +blocksize=0x100 +offset=0x5ae0 + +/core/analysis + +/core/analysis/blocks + +/core/analysis/callables + +/core/analysis/cc + +/core/analysis/classes + +/core/analysis/classes/attrs + +/core/analysis/functions + +/core/analysis/hints + +/core/analysis/imports + +/core/analysis/meta + +/core/analysis/meta/spaces +name=CS +spacestack=["*"] + +/core/analysis/meta/spaces/spaces +bin=s + +/core/analysis/noreturn + +/core/analysis/types + +/core/analysis/vars + +/core/analysis/xrefs + +/core/config +rop.sdb=false +rop.db=false +rop.comments=false +rop.conditional=false +rop.len=5 +rop.subchains=false + +/core/debug + +/core/debug/breakpoints + +/core/file +relative=../bins/elf/crackme0x05 + +/core/flags +base=0 +realnames=1 + +/core/flags/flags + +/core/flags/spaces +name=fs +spacestack=["*"] + +/core/flags/spaces/spaces +classes=s +relocs=s +sections=s +segments=s +strings=s +symbols=s + +/core/flags/tags + +/core/flags/zones + +/core/seek +0={"offset":23264,"cursor":0,"current":true} diff --git a/test/unit/meson.build b/test/unit/meson.build index c5ca0883b74..99d44094b20 100644 --- a/test/unit/meson.build +++ b/test/unit/meson.build @@ -76,6 +76,7 @@ if get_option('enable_tests') 'rbtree', 'reg', 'regex', + 'rop_constraint', 'run', 'rz_test', 'sdb_array', diff --git a/test/unit/test_rop_constraint.c b/test/unit/test_rop_constraint.c new file mode 100644 index 00000000000..b01462816a1 --- /dev/null +++ b/test/unit/test_rop_constraint.c @@ -0,0 +1,142 @@ +// SPDX-FileCopyrightText: 2024 z3phyr +// SPDX-License-Identifier: LGPL-3.0-only + +#include +#include "minunit.h" +#include + +// Define the register profile string for your architecture +#define REGISTER_PROFILE_STRING \ + "=PC trip\n" \ + "=SP rsp\n" \ + "=BP rbp\n" \ + "=A0 rdi\n" \ + "=A1 rsi\n" \ + "=A2 rdx\n" \ + "=A3 rcx\n" \ + "=A4 r8\n" \ + "=A5 r9\n" \ + "=A6 r10\n" \ + "=A7 r11\n" \ + "=SN rax\n" \ + "gpr rax .64 80 0\n" \ + "gpr eax .32 80 0\n" \ + "gpr ax .16 80 0\n" \ + "gpr al .8 80 0\n" \ + "gpr ah .8 81 0\n" \ + "gpr rbx .64 40 0\n" \ + "gpr ebx .32 40 0\n" \ + "gpr bx .16 40 0\n" \ + "gpr bl .8 40 0\n" \ + "gpr bh .8 41 0\n" + +void setup_rzcore(RzCore *core) { + rz_reg_set_profile_string(core->analysis->reg, REGISTER_PROFILE_STRING); +} + +bool test_parse_reg_to_const(void) { + RzCore *core = rz_core_new(); + mu_assert_notnull(core, "new RzCore instance"); + setup_rzcore(core); + RzRopConstraint rop_constraint = { 0 }; + + // Test case 1: Valid register to constant + char str1[] = " eax = 123 "; + mu_assert("parse_reg_to_const failed on valid input", rz_core_rop_analyze_constraint(core, str1, &rop_constraint)); + mu_assert_eq(strcmp(rop_constraint.args[DST_REG], "eax"), 0, "Invalid destination register"); + mu_assert("Source register should be NULL", rop_constraint.args[SRC_REG] == NULL); + mu_assert_eq(strcmp(rop_constraint.args[SRC_CONST], "123"), 0, "Invalid constant value"); + + free(rop_constraint.args[DST_REG]); + free(rop_constraint.args[SRC_CONST]); + + // Test case 2: Invalid format + char str2[] = "eax ="; + mu_assert("parse_reg_to_const should fail on invalid input", !rz_core_rop_analyze_constraint(core, str2, &rop_constraint)); + + mu_end; +} + +bool test_parse_reg_to_reg(void) { + RzCore *core = rz_core_new(); + mu_assert_notnull(core, "new RzCore instance"); + setup_rzcore(core); + RzRopConstraint rop_constraint = { 0 }; + + // Test case 1: Valid register to register + char str1[] = "eax = ebx "; + mu_assert("parse_reg_to_reg failed on valid input", rz_core_rop_analyze_constraint(core, str1, &rop_constraint)); + mu_assert_eq(strcmp(rop_constraint.args[DST_REG], "eax"), 0, "Invalid destination register"); + mu_assert_eq(strcmp(rop_constraint.args[SRC_REG], "ebx"), 0, "Invalid source register"); + + free(rop_constraint.args[DST_REG]); + free(rop_constraint.args[SRC_REG]); + + // Test case 2: Invalid format + char str2[] = "eax ="; + mu_assert("parse_reg_to_reg should fail on invalid input", !rz_core_rop_analyze_constraint(core, str2, &rop_constraint)); + + mu_end; +} + +bool test_parse_reg_op_const(void) { + RzCore *core = rz_core_new(); + mu_assert_notnull(core, "new RzCore instance"); + setup_rzcore(core); + RzRopConstraint rop_constraint = { 0 }; + + // Test case 1: Valid register operation with constant + char str1[] = "eax=eax+3"; + mu_assert("parse_reg_op_const failed on valid input", rz_core_rop_analyze_constraint(core, str1, &rop_constraint)); + mu_assert_eq(strcmp(rop_constraint.args[DST_REG], "eax"), 0, "Invalid destination register"); + mu_assert_eq(strcmp(rop_constraint.args[SRC_REG], "eax"), 0, "Invalid source register"); + mu_assert_eq(strcmp(rop_constraint.args[OP], "add"), 0, "Invalid operator"); + mu_assert_eq(strcmp(rop_constraint.args[SRC_CONST], "3"), 0, "Invalid constant value"); + + free(rop_constraint.args[DST_REG]); + free(rop_constraint.args[SRC_REG]); + free(rop_constraint.args[OP]); + free(rop_constraint.args[SRC_CONST]); + + // Test case 2: Invalid format + char str2[] = "eax=eax+"; + mu_assert("parse_reg_op_const should fail on invalid input", !rz_core_rop_analyze_constraint(core, str2, &rop_constraint)); + + mu_end; +} + +bool test_parse_reg_op_reg(void) { + RzCore *core = rz_core_new(); + mu_assert_notnull(core, "new RzCore instance"); + setup_rzcore(core); + RzRopConstraint rop_constraint = { 0 }; + + // Test case 1: Valid register operation with register + char str1[] = "eax=eax-ebx"; + mu_assert("parse_reg_op_reg failed on valid input", rz_core_rop_analyze_constraint(core, str1, &rop_constraint)); + mu_assert_eq(strcmp(rop_constraint.args[DST_REG], "eax"), 0, "Invalid destination register"); + mu_assert_eq(strcmp(rop_constraint.args[SRC_REG], "eax"), 0, "Invalid source register"); + mu_assert_eq(strcmp(rop_constraint.args[OP], "sub"), 0, "Invalid operator"); + mu_assert_eq(strcmp(rop_constraint.args[SRC_CONST], "ebx"), 0, "Invalid destination constant register"); + + free(rop_constraint.args[DST_REG]); + free(rop_constraint.args[SRC_REG]); + free(rop_constraint.args[OP]); + free(rop_constraint.args[SRC_CONST]); + + // Test case 2: Invalid format + char str2[] = "eax = eax+ "; + mu_assert("parse_reg_op_reg should fail on invalid input", !rz_core_rop_analyze_constraint(core, str2, &rop_constraint)); + + mu_end; +} + +bool all_tests(void) { + mu_run_test(test_parse_reg_to_const); + mu_run_test(test_parse_reg_to_reg); + mu_run_test(test_parse_reg_op_const); + mu_run_test(test_parse_reg_op_reg); + return tests_passed != tests_run; +} + +mu_main(all_tests)