2393 lines
72 KiB
C
2393 lines
72 KiB
C
// SPDX-FileCopyrightText: 2026 MrQuantum1915 <darshanpatelgdh@gmail.com>
|
||
// SPDX-FileCopyrightText: 2024 z3phyr <giridh1337@gmail.com>
|
||
// SPDX-License-Identifier: LGPL-3.0-only
|
||
|
||
#include <rz_core.h>
|
||
#include <rz_asm.h>
|
||
#include <rz_util/rz_log.h>
|
||
#include <rz_util/rz_regex.h>
|
||
#include <rz_gadget.h>
|
||
|
||
static bool rz_gadget_rop_is_valid_terminator(const RzAnalysisOp *aop, const bool allow_conditional) {
|
||
|
||
switch (aop->type & RZ_ANALYSIS_OP_TYPE_MASK) {
|
||
case RZ_ANALYSIS_OP_TYPE_RET:
|
||
return true;
|
||
case RZ_ANALYSIS_OP_TYPE_CRET:
|
||
return allow_conditional;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static bool rz_gadget_jop_is_valid_terminator(const RzAnalysisOp *aop, const bool allow_conditional) {
|
||
switch (aop->type & RZ_ANALYSIS_OP_TYPE_MASK) {
|
||
// direct jumps not useful for JOP
|
||
case RZ_ANALYSIS_OP_TYPE_UJMP:
|
||
case RZ_ANALYSIS_OP_TYPE_RJMP:
|
||
case RZ_ANALYSIS_OP_TYPE_IJMP:
|
||
case RZ_ANALYSIS_OP_TYPE_IRJMP:
|
||
return true;
|
||
case RZ_ANALYSIS_OP_TYPE_UCJMP:
|
||
return allow_conditional;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static bool rz_gadget_cop_is_valid_terminator(const RzAnalysisOp *aop, const bool allow_conditional) {
|
||
switch (aop->type & RZ_ANALYSIS_OP_TYPE_MASK) {
|
||
// direct calls not useful for COP
|
||
case RZ_ANALYSIS_OP_TYPE_UCALL:
|
||
case RZ_ANALYSIS_OP_TYPE_RCALL:
|
||
case RZ_ANALYSIS_OP_TYPE_ICALL:
|
||
case RZ_ANALYSIS_OP_TYPE_IRCALL:
|
||
return true;
|
||
case RZ_ANALYSIS_OP_TYPE_UCCALL:
|
||
return allow_conditional;
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static bool gadget_is_valid_terminator(const RzGadgetType gadget_type, const RzCore *core, const RzCoreAsmHit *hit, const bool allow_conditional) {
|
||
rz_return_val_if_fail(core && core->analysis && hit, false);
|
||
bool status = false;
|
||
RzAnalysisOp aop = { 0 };
|
||
ut8 *buf = malloc(hit->len);
|
||
if (rz_io_nread_at(core->io, hit->addr, buf, hit->len) < 0) {
|
||
free(buf);
|
||
return status;
|
||
}
|
||
rz_analysis_op_init(&aop);
|
||
if (rz_analysis_op(core->analysis, &aop, hit->addr, buf, hit->len, RZ_ANALYSIS_OP_MASK_DISASM) < 0) {
|
||
free(buf);
|
||
return status;
|
||
}
|
||
|
||
switch (gadget_type) {
|
||
case RZ_GADGET_TYPE_ROP:
|
||
status = rz_gadget_rop_is_valid_terminator(&aop, allow_conditional);
|
||
break;
|
||
case RZ_GADGET_TYPE_COP:
|
||
status = rz_gadget_cop_is_valid_terminator(&aop, allow_conditional);
|
||
break;
|
||
case RZ_GADGET_TYPE_JOP:
|
||
status = rz_gadget_jop_is_valid_terminator(&aop, allow_conditional);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
|
||
rz_analysis_op_fini(&aop);
|
||
free(buf);
|
||
return status;
|
||
}
|
||
|
||
static RzCoreAsmHit *find_gadget_terminator(const RzPVector /*<RzCoreAsmHit *>*/ *hitlist, int delay_size) {
|
||
if (!hitlist || delay_size < 0) {
|
||
return NULL;
|
||
}
|
||
const size_t len = rz_pvector_len(hitlist);
|
||
if (((size_t)delay_size + 1) > len) {
|
||
return NULL;
|
||
}
|
||
return rz_pvector_at(hitlist, len - 1 - (size_t)delay_size);
|
||
}
|
||
|
||
static bool gadget_is_valid_end_gadget(const RzGadgetType gadget_type, const RzAnalysisOp *aop, const bool allow_conditional) {
|
||
if (aop->family == RZ_ANALYSIS_OP_FAMILY_SECURITY) {
|
||
return false;
|
||
}
|
||
|
||
switch (gadget_type) {
|
||
case RZ_GADGET_TYPE_ROP:
|
||
return rz_gadget_rop_is_valid_terminator(aop, allow_conditional);
|
||
case RZ_GADGET_TYPE_COP:
|
||
return rz_gadget_cop_is_valid_terminator(aop, allow_conditional);
|
||
case RZ_GADGET_TYPE_JOP:
|
||
return rz_gadget_jop_is_valid_terminator(aop, allow_conditional);
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static bool gadget_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 gadget_hitlist_print_table_mode(const RzCore *core, const RzCoreAsmHit *hit, const RzPVector /*<RzCoreAsmHit *>*/ *hitlist,
|
||
ut32 *size, char **asmop_str, char **asmop_hex_str, RzGadgetSearchContext *context) {
|
||
rz_return_val_if_fail(core && hitlist, false);
|
||
RzAnalysisOp aop = RZ_EMPTY;
|
||
RzAsmOp *asmop = rz_asm_op_new();
|
||
if (!asmop) {
|
||
return false;
|
||
}
|
||
|
||
if (!gadget_process_asm_op(core, hit, asmop, &aop, size, asmop_str, asmop_hex_str)) {
|
||
rz_asm_op_free(asmop);
|
||
return false;
|
||
}
|
||
size_t len = rz_pvector_len(hitlist);
|
||
if (len > 0) {
|
||
const RzCoreAsmHit *last_hit = rz_pvector_at(hitlist, len - 1);
|
||
if (last_hit->addr != hit->addr) {
|
||
*asmop_str = rz_str_append(*asmop_str, "; ");
|
||
}
|
||
}
|
||
rz_asm_op_free(asmop);
|
||
rz_analysis_op_fini(&aop);
|
||
return true;
|
||
}
|
||
|
||
static bool gadget_prepare_asm_op(const RzCore *core, const RzCoreAsmHit *hit, RzAsmOp **asmop_out, RzAnalysisOp *aop, ut32 *size) {
|
||
RzAsmOp *asmop = rz_asm_op_new();
|
||
if (!asmop) {
|
||
return false;
|
||
}
|
||
if (!gadget_process_asm_op(core, hit, asmop, aop, size, NULL, NULL)) {
|
||
rz_asm_op_free(asmop);
|
||
return false;
|
||
}
|
||
*asmop_out = asmop;
|
||
return true;
|
||
}
|
||
|
||
static RzStrBuf *get_colored_asm_str(const RzCore *core, RzAsmOp *asmop, RzAnalysisOp *aop) {
|
||
RzStrBuf *bw_str = rz_strbuf_new(rz_asm_op_get_asm(asmop));
|
||
RzReg *rreg = rz_analysis_get_reg(core->analysis);
|
||
RzAsmParseParam *param = rz_asm_get_parse_param(rreg, 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_strbuf_free(bw_str);
|
||
return colored_asm;
|
||
}
|
||
|
||
static RZ_OWN char *get_colored_asm(const RzCore *core, RzAsmOp *asmop, RzAnalysisOp *aop) {
|
||
const char *plain = rz_asm_op_get_asm(asmop);
|
||
if (!rz_config_get_i(core->config, "scr.color")) {
|
||
return rz_str_dup(plain);
|
||
}
|
||
RzStrBuf *colored_asm = get_colored_asm_str(core, asmop, aop);
|
||
if (!colored_asm) {
|
||
return rz_str_dup(plain);
|
||
}
|
||
char *ret_str_asm = rz_str_dup(rz_strbuf_get(colored_asm));
|
||
rz_strbuf_free(colored_asm);
|
||
return ret_str_asm;
|
||
}
|
||
|
||
static bool gadget_hitlist_print_quiet_mode(const RzCore *core, const RzCoreAsmHit *hit, ut32 *size, RzGadgetSearchContext *context) {
|
||
if (!core || !context) {
|
||
return false;
|
||
}
|
||
|
||
RzAnalysisOp aop = RZ_EMPTY;
|
||
RzAsmOp *asmop = NULL;
|
||
if (!gadget_prepare_asm_op(core, hit, &asmop, &aop, size)) {
|
||
return false;
|
||
}
|
||
|
||
const bool colorize = rz_config_get_i(core->config, "scr.color");
|
||
|
||
char *asm_str = get_colored_asm(core, asmop, &aop);
|
||
const char *reset_color = colorize ? Color_RESET : "";
|
||
const char *format = " %s%s;";
|
||
char *output_str = asm_str;
|
||
if (context->ret_val) {
|
||
rz_strbuf_appendf(context->buf, format, output_str, reset_color);
|
||
} else {
|
||
rz_cons_printf(format, output_str, reset_color);
|
||
}
|
||
free(output_str);
|
||
rz_asm_op_free(asmop);
|
||
rz_analysis_op_fini(&aop);
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool gadget_hitlist_print_standard_mode(const RzCore *core, const RzCoreAsmHit *hit, ut32 *size, RzGadgetSearchContext *context, bool is_conditional) {
|
||
rz_return_val_if_fail(core && context, false);
|
||
RzAnalysisOp aop = RZ_EMPTY;
|
||
RzAsmOp *asmop = NULL;
|
||
if (!gadget_prepare_asm_op(core, hit, &asmop, &aop, size)) {
|
||
return false;
|
||
}
|
||
|
||
const bool colorize = rz_config_get_i(core->config, "scr.color");
|
||
bool ret_val = context->ret_val;
|
||
|
||
const char *comment = NULL;
|
||
if (context->comments) {
|
||
comment = rz_meta_get_string(core->analysis, RZ_META_TYPE_COMMENT, hit->addr);
|
||
}
|
||
|
||
char *asm_op_hex = rz_asm_op_get_hex(asmop);
|
||
const char *asm_str = rz_asm_op_get_asm(asmop);
|
||
RzStrBuf *colored_asm = NULL;
|
||
if (colorize) {
|
||
colored_asm = get_colored_asm_str(core, asmop, &aop);
|
||
asm_str = colored_asm ? rz_strbuf_get(colored_asm) : "";
|
||
}
|
||
|
||
const char *reset_color = colorize ? Color_RESET : "";
|
||
const char *format_with_comment = " 0x%08" PFMT64x " %18s %s%s ; %s\n";
|
||
const char *format_without_comment = " 0x%08" PFMT64x " %18s %s%s\n";
|
||
const char *format = comment ? format_with_comment : format_without_comment;
|
||
|
||
if (ret_val) {
|
||
if (comment) {
|
||
rz_strbuf_appendf(context->buf, format, hit->addr, asm_op_hex, asm_str, reset_color, comment);
|
||
} else {
|
||
rz_strbuf_appendf(context->buf, format, hit->addr, asm_op_hex, asm_str, reset_color);
|
||
}
|
||
} else {
|
||
if (comment) {
|
||
rz_cons_printf(format, hit->addr, asm_op_hex, asm_str, reset_color, comment);
|
||
} else {
|
||
rz_cons_printf(format, hit->addr, asm_op_hex, asm_str, reset_color);
|
||
}
|
||
}
|
||
|
||
free(asm_op_hex);
|
||
if (colored_asm) {
|
||
rz_strbuf_free(colored_asm);
|
||
}
|
||
rz_asm_op_free(asmop);
|
||
rz_analysis_op_fini(&aop);
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool gadget_hitlist_print_json_mode(const RzCore *core, const RzCoreAsmHit *hit, unsigned int *size, PJ *pj) {
|
||
rz_return_val_if_fail(core && hit && pj, false);
|
||
RzAnalysisOp aop = RZ_EMPTY;
|
||
RzAsmOp *asmop = rz_asm_op_new();
|
||
if (!asmop) {
|
||
return false;
|
||
}
|
||
if (!gadget_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_API void rz_core_gadget_reg_info_free(RZ_NULLABLE RzGadgetRegInfo *reg_info) {
|
||
if (!reg_info) {
|
||
return;
|
||
}
|
||
free(reg_info->name);
|
||
free(reg_info->value_transformations);
|
||
free(reg_info);
|
||
}
|
||
|
||
RZ_API RZ_OWN RzGadgetRegInfo *rz_core_gadget_reg_info_new(RZ_NONNULL const RzCore *core, RZ_NONNULL const RzILEvent *evt,
|
||
const ut64 init_val, const ut64 new_val) {
|
||
rz_return_val_if_fail(core && evt, NULL);
|
||
RzGadgetRegInfo *reg_info = RZ_NEW0(RzGadgetRegInfo);
|
||
if (!reg_info) {
|
||
return NULL;
|
||
}
|
||
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;
|
||
}
|
||
RzReg *rreg = rz_analysis_get_reg(core->analysis);
|
||
const RzList *head = rz_reg_get_list(rreg, 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 = rz_str_dup(name);
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!reg_info->name) {
|
||
free(reg_info);
|
||
return NULL;
|
||
}
|
||
reg_info->init_val = init_val;
|
||
reg_info->new_val = new_val;
|
||
reg_info->bits = rz_asm_get_bits(core->rasm);
|
||
reg_info->value_transformations = NULL; // Fill this as you need
|
||
return reg_info;
|
||
}
|
||
|
||
/**
|
||
* \brief Create a new RzGadgetInfo object.
|
||
* \param address The address of the gadget.
|
||
* \return A pointer to the newly created RzGadgetInfo object, or NULL if memory allocation fails.
|
||
*
|
||
* This function allocates and initializes a new RzGadgetInfo object with the given address.
|
||
*/
|
||
RZ_API RZ_OWN RzGadgetInfo *rz_core_gadget_info_new(const ut64 address) {
|
||
RzGadgetInfo *gadget_info = RZ_NEW0(RzGadgetInfo);
|
||
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_gadget_reg_info_free);
|
||
gadget_info->dependencies = rz_list_newf((RzListFree)rz_core_gadget_reg_info_free);
|
||
gadget_info->analysis_cache = NULL;
|
||
gadget_info->size = 0;
|
||
return gadget_info;
|
||
}
|
||
|
||
/**
|
||
* \brief Free an RzGadgetInfo object.
|
||
* \param gadget_info Pointer to the RzGadgetInfo object to free.
|
||
*
|
||
* Frees the memory allocated for an RzGadgetInfo object, including its modified registers and dependencies.
|
||
*/
|
||
RZ_API void rz_core_gadget_info_free(RZ_NULLABLE RzGadgetInfo *gadget_info) {
|
||
if (!gadget_info) {
|
||
return;
|
||
}
|
||
rz_pvector_free(gadget_info->modified_registers);
|
||
rz_list_free(gadget_info->dependencies);
|
||
rz_iterator_free(gadget_info->analysis_cache);
|
||
free(gadget_info);
|
||
}
|
||
|
||
/**
|
||
* \brief Add a register info to an RzGadgetInfo object.
|
||
* \param gadget_info Pointer to the RzGadgetInfo object.
|
||
* \param reg_info Pointer to the RzGadgetRegInfo object.
|
||
* \param is_dependency Boolean indicating whether the register is a dependency.
|
||
*
|
||
* Adds the given register info to the modified registers of the RzGadgetInfo object if it is not a dependency.
|
||
*/
|
||
RZ_API void rz_core_gadget_info_add_register(const RZ_NONNULL RZ_OUT RzGadgetInfo *gadget_info,
|
||
RZ_NONNULL RzGadgetRegInfo *reg_info, const bool is_dependency) {
|
||
rz_return_if_fail(gadget_info);
|
||
|
||
if (!is_dependency && reg_info) {
|
||
rz_pvector_push(gadget_info->modified_registers, reg_info);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* \brief Get the modified register info by name.
|
||
* \param gadget_info Pointer to the RzGadgetInfo object.
|
||
* \param name Pointer to the name of the register.
|
||
* \return A pointer to the RzGadgetRegInfo object if found, or NULL if not found or if gadget_info is NULL.
|
||
*
|
||
* Searches the modified registers in the RzGadgetInfo object for the register with the given name and returns its info.
|
||
*/
|
||
RZ_API RZ_BORROW RzGadgetRegInfo *rz_core_gadget_info_get_modified_register(const RZ_NONNULL RzGadgetInfo *gadget_info,
|
||
const RZ_NONNULL char *name) {
|
||
rz_return_val_if_fail(gadget_info && name, NULL);
|
||
void **it;
|
||
rz_pvector_foreach (gadget_info->modified_registers, it) {
|
||
RzGadgetRegInfo *reg_info = *it;
|
||
if (RZ_STR_EQ(reg_info->name, name)) {
|
||
return reg_info;
|
||
}
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/**
|
||
* \brief Update a register info in the RzGadgetInfo object.
|
||
* \param gadget_info Pointer to the RzGadgetInfo object.
|
||
* \param new_reg_info Pointer to the new RzGadgetRegInfo object.
|
||
* \return void
|
||
*
|
||
* Updates the register info in the RzGadgetInfo 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 void rz_core_gadget_info_update_register(const RZ_INOUT RzGadgetInfo *gadget_info, RZ_INOUT RZ_NONNULL RzGadgetRegInfo *new_reg_info) {
|
||
rz_return_if_fail(gadget_info && new_reg_info);
|
||
|
||
RzGadgetRegInfo *existing_reg_info = rz_core_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);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* \brief Duplicate the RzGadgetRegInfo
|
||
* \param src Pointer to RzGadgetRegInfo
|
||
* \return RzGadgetRegInfo* on success, NULL on failure
|
||
*/
|
||
RZ_API RZ_OWN RzGadgetRegInfo *rz_core_gadget_reg_info_dup(RZ_BORROW RZ_NONNULL RzGadgetRegInfo *src) {
|
||
rz_return_val_if_fail(src, NULL);
|
||
RzGadgetRegInfo *dup = RZ_NEW0(RzGadgetRegInfo);
|
||
if (!dup) {
|
||
return NULL;
|
||
}
|
||
|
||
dup->name = rz_str_dup(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;
|
||
}
|
||
|
||
/**
|
||
* \brief Find the Gadget Register information for the given register
|
||
* \param gadget_info Pointer to the RzGadgetInfo object.
|
||
* \param name Pointer to the name of the register to filter dependencies.
|
||
* \return A pointer to an RzPVector of RzGadgetRegInfo objects matching the given name.
|
||
*/
|
||
RZ_API RZ_OWN RzPVector /*<RzGadgetRegInfo *>*/ *rz_core_gadget_reg_info_find(const RZ_NONNULL RzGadgetInfo *gadget_info, const RZ_NONNULL char *name) {
|
||
rz_return_val_if_fail(gadget_info && name, NULL);
|
||
RzPVector * /*<RzGadgetRegInfo *>*/ reg_info_v = rz_pvector_new((RzPVectorFree)rz_core_gadget_reg_info_free);
|
||
if (!reg_info_v) {
|
||
return NULL;
|
||
}
|
||
RzListIter *iter;
|
||
RzGadgetRegInfo *reg_info;
|
||
rz_list_foreach (gadget_info->dependencies, iter, reg_info) {
|
||
if (RZ_STR_EQ(reg_info->name, name)) {
|
||
rz_pvector_push(reg_info_v, reg_info);
|
||
}
|
||
}
|
||
return reg_info_v;
|
||
}
|
||
|
||
/**
|
||
* \brief Check if a register with a specific name exists in the modified registers of a RzGadgetInfo object.
|
||
* \param gadget_info Pointer to the RzGadgetInfo object.
|
||
* \param name Pointer to the name of the register.
|
||
* \return true if a register with the given name exists, false otherwise.
|
||
*
|
||
* Checks the modified registers in the RzGadgetInfo object to see if a register with the given name exists.
|
||
*/
|
||
RZ_API bool rz_core_gadget_info_has_register(const RZ_NONNULL RzGadgetInfo *gadget_info, const RZ_NONNULL char *name) {
|
||
rz_return_val_if_fail(gadget_info && name, false);
|
||
void **it;
|
||
rz_pvector_foreach (gadget_info->modified_registers, it) {
|
||
const RzGadgetRegInfo *reg_info = *it;
|
||
if (RZ_STR_EQ(reg_info->name, name)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static inline bool is_var_read_event(const RzGadgetRegInfo *reg_info) {
|
||
return reg_info->is_var_read && !reg_info->is_mem_read && !reg_info->is_var_write && !reg_info->is_mem_write;
|
||
}
|
||
|
||
static inline bool is_var_write_event(const RzGadgetRegInfo *reg_info) {
|
||
return reg_info->is_var_write;
|
||
}
|
||
|
||
static inline bool is_mem_read_event(const RzGadgetRegInfo *reg_info) {
|
||
return reg_info->is_mem_read;
|
||
}
|
||
|
||
static inline bool is_mem_write_event(const RzGadgetRegInfo *reg_info) {
|
||
return reg_info->is_mem_write;
|
||
}
|
||
|
||
static inline bool is_pc_write_event(const RzGadgetRegInfo *reg_info) {
|
||
return reg_info->is_pc_write;
|
||
}
|
||
|
||
rz_gadget_event_check_fn rz_gadget_event_functions[RZ_GADGET_EVENT_COUNT] = {
|
||
is_var_read_event,
|
||
is_var_write_event,
|
||
is_mem_read_event,
|
||
is_mem_write_event,
|
||
is_pc_write_event,
|
||
};
|
||
|
||
/**
|
||
* \brief Check if a given event dependency is present for a register
|
||
* \param gadget_info Pointer to the RzGadgetInfo object.
|
||
* \param event The RzGadgetEvent to check.
|
||
* \param reg_name Name of the register
|
||
* \return True if there is an \p event which uses \p reg_name. False otherwise.
|
||
*/
|
||
RZ_API bool rz_core_gadget_reg_info_has_event(const RZ_NONNULL RzGadgetInfo *gadget_info,
|
||
const RzGadgetEvent event, const RZ_NULLABLE char *reg_name) {
|
||
rz_return_val_if_fail(gadget_info, false);
|
||
if (event >= RZ_GADGET_EVENT_COUNT) {
|
||
return false;
|
||
}
|
||
RzListIter *iter;
|
||
RzGadgetRegInfo *reg_info;
|
||
rz_list_foreach (gadget_info->dependencies, iter, reg_info) {
|
||
if (RZ_STR_NE(reg_name, reg_info->name)) {
|
||
continue;
|
||
}
|
||
if (event == RZ_GADGET_EVENT_COUNT) {
|
||
return true;
|
||
}
|
||
if (rz_gadget_event_functions[event](reg_info)) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* \brief Find all dependencies based on a specific event in the dependencies of a RzGadgetInfo object.
|
||
* \param gadget_info Pointer to the RzGadgetInfo object.
|
||
* \param event The RzGadgetEvent to check.
|
||
* \return A pointer to a list of RzGadgetRegInfo objects matching the given event, or NULL if none are found or if gadget_info is NULL.
|
||
*/
|
||
RZ_API RZ_OWN RzPVector /*<RzGadgetRegInfo *>*/ *rz_core_gadget_get_reg_info_by_event(const RZ_NONNULL RzGadgetInfo *gadget_info,
|
||
const RzGadgetEvent event) {
|
||
rz_return_val_if_fail(gadget_info, NULL);
|
||
if (event >= RZ_GADGET_EVENT_COUNT) {
|
||
return NULL;
|
||
}
|
||
RzPVector *matches = rz_pvector_new((RzPVectorFree)rz_core_gadget_reg_info_free);
|
||
if (!matches) {
|
||
return NULL;
|
||
}
|
||
RzListIter *iter;
|
||
RzGadgetRegInfo *reg_info;
|
||
rz_list_foreach (gadget_info->dependencies, iter, reg_info) {
|
||
if (rz_gadget_event_functions[event](reg_info)) {
|
||
rz_pvector_push(matches, rz_core_gadget_reg_info_dup(reg_info));
|
||
}
|
||
}
|
||
return matches;
|
||
}
|
||
|
||
/**
|
||
* \brief Find all registers with specific names in the modified registers of a RzGadgetInfo object.
|
||
* \param gadget_info Pointer to the RzGadgetInfo object.
|
||
* \param registers Pointer to a RzPVector of register names to search for.
|
||
* \return A pointer to a RzPVector of RzGadgetRegInfo objects matching the given names, or NULL if none are found or if gadget_info is NULL.
|
||
*
|
||
* Searches the modified registers in the RzGadgetInfo object for all registers matching the given register names and returns their info in a vector.
|
||
*/
|
||
RZ_API RZ_OWN RzPVector /*<RzGadgetRegInfo *>*/ *rz_core_gadget_get_reg_info_by_reg_names(const RZ_NONNULL RzGadgetInfo *gadget_info,
|
||
const RZ_NONNULL RzPVector /*<char *>*/ *registers) {
|
||
rz_return_val_if_fail(gadget_info && registers, NULL);
|
||
|
||
RzPVector *result = rz_pvector_new((RzPVectorFree)rz_core_gadget_reg_info_free);
|
||
if (!result) {
|
||
return NULL;
|
||
}
|
||
void **it;
|
||
rz_pvector_foreach (gadget_info->modified_registers, it) {
|
||
RzGadgetRegInfo *reg_info = *it;
|
||
void **reg_it;
|
||
rz_pvector_foreach (registers, reg_it) {
|
||
const char *reg = *reg_it;
|
||
if (RZ_STR_EQ(reg_info->name, reg)) {
|
||
RzGadgetRegInfo *gadget_reg_info_dup = rz_core_gadget_reg_info_dup(reg_info);
|
||
if (!gadget_reg_info_dup) {
|
||
continue;
|
||
}
|
||
rz_pvector_push(result, gadget_reg_info_dup);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (rz_pvector_empty(result)) {
|
||
rz_pvector_free(result);
|
||
return NULL;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
static void gadget_info_add_dependency(const RzCore *core, RzGadgetInfo *gadget_info, const RzILEvent *evt, RzGadgetRegInfo *reg_info) {
|
||
rz_return_if_fail(core && core->analysis);
|
||
|
||
RzReg *rreg = rz_analysis_get_reg(core->analysis);
|
||
if (!reg_info || !rreg) {
|
||
return;
|
||
}
|
||
RzGadgetRegInfo *reg_info_dup = rz_core_gadget_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_dup->is_mem_read = true;
|
||
reg_info_dup->is_mem_write = false;
|
||
reg_info_dup->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_dup->is_mem_write = true;
|
||
reg_info_dup->is_mem_read = false;
|
||
reg_info_dup->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 (rz_reg_is_role(rreg, reg_info->name, RZ_REG_NAME_SP)) {
|
||
RzBitVector *temp = rz_bv_sub(new_val, init_val, NULL);
|
||
gadget_info->stack_change += rz_bv_to_ut64(temp);
|
||
rz_bv_free(temp);
|
||
}
|
||
rz_bv_free(init_val);
|
||
rz_bv_free(new_val);
|
||
|
||
break;
|
||
}
|
||
default:
|
||
break;
|
||
}
|
||
rz_list_append(gadget_info->dependencies, reg_info_dup);
|
||
}
|
||
|
||
static void var_read_add_reg_info(const RzCore *core, const RzILEvent *event,
|
||
const RzILEventVarRead *var_read, RZ_OUT RzGadgetRegInfo **reg_info) {
|
||
rz_return_if_fail(core && event && var_read && reg_info);
|
||
RzBitVector *val = rz_il_value_to_bv(var_read->value);
|
||
if (!val) {
|
||
return;
|
||
}
|
||
*reg_info = rz_core_gadget_reg_info_new(core, event, rz_bv_to_ut64(val), rz_bv_to_ut64(val));
|
||
if (!*reg_info) {
|
||
rz_bv_free(val);
|
||
return;
|
||
}
|
||
rz_bv_free(val);
|
||
}
|
||
|
||
static bool is_handle_il_event_read(const RzCore *core, const RzILEventVarRead *var_read,
|
||
RzGadgetRegInfo *reg_info, RzGadgetInfo *gadget_info, const RzILEvent *event, const RzILEvent *curr_event) {
|
||
if (rz_core_gadget_reg_info_has_event(gadget_info, RZ_GADGET_EVENT_VAR_READ, event->data.var_read.variable)) {
|
||
return false;
|
||
}
|
||
var_read_add_reg_info(core, event, var_read, ®_info);
|
||
gadget_info_add_dependency(core, gadget_info, curr_event, reg_info);
|
||
rz_core_gadget_reg_info_free(reg_info);
|
||
return true;
|
||
}
|
||
|
||
static bool fill_gadget_info_from_events(RzCore *core, RzGadgetInfo *gadget_info, const RzILEvent *curr_event,
|
||
RzILEvent *event, RzPVector /*<RzILEvent *>*/ *vec, const bool is_dependency) {
|
||
rz_return_val_if_fail(core, false);
|
||
if (!gadget_info) {
|
||
return false;
|
||
}
|
||
RzReg *rreg = rz_analysis_get_reg(core->analysis);
|
||
const RzList *head = rz_reg_get_list(rreg, RZ_REG_TYPE_GPR);
|
||
if (!head) {
|
||
return false;
|
||
}
|
||
switch (event->type) {
|
||
case RZ_IL_EVENT_VAR_READ: {
|
||
const RzILEventVarRead *var_read = &event->data.var_read;
|
||
RzGadgetRegInfo *reg_info = rz_core_gadget_info_get_modified_register(gadget_info, var_read->variable);
|
||
if (reg_info && !is_dependency) {
|
||
RzGadgetRegInfo *new_reg_info = rz_core_gadget_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);
|
||
rz_core_gadget_info_update_register(gadget_info, new_reg_info);
|
||
rz_core_gadget_reg_info_free(new_reg_info);
|
||
rz_pvector_push(vec, event);
|
||
rz_bv_free(val);
|
||
break;
|
||
}
|
||
if (is_dependency && curr_event) {
|
||
bool is_stack_evt = false;
|
||
if (curr_event->type == RZ_IL_EVENT_VAR_READ) {
|
||
break;
|
||
}
|
||
// Stack reads during pop, push
|
||
if (rz_reg_is_role(rreg, var_read->variable, RZ_REG_NAME_SP)) {
|
||
is_stack_evt = true;
|
||
var_read_add_reg_info(core, event, var_read, ®_info);
|
||
}
|
||
if (event->type == RZ_IL_EVENT_VAR_READ && !is_stack_evt) {
|
||
if (is_handle_il_event_read(core, var_read, reg_info, gadget_info, event, curr_event)) {
|
||
break;
|
||
}
|
||
}
|
||
gadget_info_add_dependency(core, gadget_info, curr_event, reg_info);
|
||
if (is_stack_evt) {
|
||
rz_core_gadget_reg_info_free(reg_info);
|
||
}
|
||
break;
|
||
}
|
||
if (reg_info) {
|
||
break;
|
||
}
|
||
RzBitVector *val = rz_il_value_to_bv(var_read->value);
|
||
if (!val) {
|
||
break;
|
||
}
|
||
if (!is_dependency) {
|
||
rz_pvector_push(vec, event);
|
||
}
|
||
rz_bv_free(val);
|
||
} break;
|
||
case RZ_IL_EVENT_VAR_WRITE: {
|
||
RzListIter *iter_dst;
|
||
RzRegItem *item_dst;
|
||
if (is_dependency) {
|
||
break;
|
||
}
|
||
const RzILEventVarWrite *var_write = &event->data.var_write;
|
||
bool is_reg = false;
|
||
rz_list_foreach (head, iter_dst, item_dst) {
|
||
if (RZ_STR_EQ(var_write->variable, item_dst->name) && item_dst->type == RZ_REG_TYPE_GPR) {
|
||
is_reg = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!is_reg) {
|
||
break;
|
||
}
|
||
while (!rz_pvector_empty(vec)) {
|
||
RzILEvent *evt = rz_pvector_pop(vec);
|
||
fill_gadget_info_from_events(core, gadget_info, event, evt, vec, true);
|
||
}
|
||
RzGadgetRegInfo *reg_info = rz_core_gadget_info_get_modified_register(gadget_info, var_write->variable);
|
||
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_gadget_reg_info_new(core, event, rz_bv_to_ut64(old_val),
|
||
rz_bv_to_ut64(new_val));
|
||
rz_core_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_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_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;
|
||
} else {
|
||
gadget_info->is_syscall = true;
|
||
}
|
||
} break;
|
||
default:
|
||
break;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static bool analyze_gadget(RzCore *core, const RzCoreAsmHit *hit, RzGadgetInfo *gadget_info) {
|
||
rz_return_val_if_fail(core && core->analysis, false);
|
||
int ret = true;
|
||
|
||
const ut64 old_addr = core->offset;
|
||
rz_core_seek(core, hit->addr, true);
|
||
rz_core_analysis_il_reinit(core);
|
||
RzAnalysisILVM *il_vm = rz_analysis_get_il_vm(core->analysis);
|
||
if (!il_vm) {
|
||
ret = false;
|
||
goto cleanup;
|
||
}
|
||
|
||
rz_config_set(core->config, "io.cache", "true");
|
||
rz_core_il_step(core, 1);
|
||
|
||
RzPVector vec = { 0 };
|
||
RzILVM *vm = il_vm->vm;
|
||
if (!vm) {
|
||
ret = false;
|
||
goto cleanup;
|
||
}
|
||
void **it;
|
||
|
||
// vec only borrows ptrs from vm->events.
|
||
rz_pvector_init(&vec, NULL);
|
||
rz_pvector_foreach (vm->events, it) {
|
||
RzILEvent *evt = *it;
|
||
if (!fill_gadget_info_from_events(core, gadget_info, NULL, evt,
|
||
&vec, false)) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
cleanup:
|
||
rz_pvector_fini(&vec);
|
||
rz_analysis_il_vm_cleanup(core->analysis);
|
||
rz_core_seek(core, old_addr, true);
|
||
return ret;
|
||
}
|
||
|
||
static void gadget_print_standard_mode(const RzCore *core, const RzGadgetInfo *gadget_info, bool is_conditional) {
|
||
rz_return_if_fail(core && core->analysis && gadget_info);
|
||
RzReg *rreg = rz_analysis_get_reg(core->analysis);
|
||
if (!rreg) {
|
||
return;
|
||
}
|
||
|
||
const bool colorize = rz_config_get_i(core->config, "scr.color");
|
||
const char *highlight_color = colorize ? Color_CYAN : "";
|
||
const char *reset_color = colorize ? Color_RESET : "";
|
||
if (is_conditional) {
|
||
rz_cons_printf("%sGadget 0x%" PFMT64x " [Conditional]%s\n", highlight_color, gadget_info->address, reset_color);
|
||
} else {
|
||
rz_cons_printf("Gadget 0x%" PFMT64x "\n", gadget_info->address);
|
||
}
|
||
rz_cons_printf("Stack change: 0x%" PFMT64x "\n", gadget_info->stack_change);
|
||
|
||
rz_cons_printf("Changed registers: ");
|
||
void **it;
|
||
RzGadgetRegInfo *reg_info;
|
||
rz_pvector_foreach (gadget_info->modified_registers, it) {
|
||
reg_info = *it;
|
||
rz_cons_printf("%s ", reg_info->name);
|
||
}
|
||
rz_cons_printf("\n");
|
||
|
||
rz_cons_printf("Register dependencies:\n");
|
||
RzListIter *iter;
|
||
rz_list_foreach (gadget_info->dependencies, iter, reg_info) {
|
||
if (rz_reg_is_role(rreg, reg_info->name, RZ_REG_NAME_SP) ||
|
||
rz_reg_is_role(rreg, reg_info->name, RZ_REG_NAME_BP)) {
|
||
continue;
|
||
}
|
||
if (reg_info->is_var_write) {
|
||
rz_cons_printf("Var write: %s Initial value: 0x%" PFMT64x " New Value: 0x%" PFMT64x "\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 Value: 0x%" PFMT64x "\n", reg_info->name, reg_info->new_val);
|
||
} else if (reg_info->is_mem_write) {
|
||
rz_cons_printf("Memory Write: %s Initial Value: 0x%" PFMT64x " New Value: 0x%" PFMT64x "\n",
|
||
reg_info->name, reg_info->init_val, reg_info->new_val);
|
||
} else if (reg_info->is_var_read) {
|
||
// Var read needed for cases like mov dst, src kind of instructions
|
||
rz_cons_printf("Var Read: %s\n", reg_info->name);
|
||
}
|
||
}
|
||
|
||
rz_cons_printf("\n");
|
||
}
|
||
|
||
static void gadget_print_json_mode(const RzCore *core, const RzGadgetInfo *gadget_info, PJ *pj, bool is_conditional) {
|
||
rz_return_if_fail(gadget_info && pj);
|
||
|
||
RzReg *rreg = rz_analysis_get_reg(core->analysis);
|
||
if (!rreg) {
|
||
return;
|
||
}
|
||
pj_o(pj);
|
||
pj_kn(pj, "address", gadget_info->address);
|
||
pj_kn(pj, "stack_change", gadget_info->stack_change);
|
||
if (is_conditional) {
|
||
pj_kb(pj, "is_conditional", true);
|
||
}
|
||
|
||
pj_k(pj, "modified_registers");
|
||
pj_a(pj);
|
||
void **it;
|
||
RzGadgetRegInfo *reg_info;
|
||
rz_pvector_foreach (gadget_info->modified_registers, it) {
|
||
reg_info = *it;
|
||
pj_o(pj);
|
||
pj_ks(pj, "name", reg_info->name);
|
||
pj_ks(pj, "type", "var_write");
|
||
pj_end(pj);
|
||
}
|
||
pj_end(pj);
|
||
|
||
pj_k(pj, "dependencies");
|
||
pj_a(pj);
|
||
RzListIter *iter;
|
||
rz_list_foreach (gadget_info->dependencies, iter, reg_info) {
|
||
if (rz_reg_is_role(rreg, reg_info->name, RZ_REG_NAME_SP) ||
|
||
rz_reg_is_role(rreg, reg_info->name, RZ_REG_NAME_BP)) {
|
||
continue;
|
||
}
|
||
pj_o(pj);
|
||
pj_ks(pj, "name", reg_info->name);
|
||
if (reg_info->is_var_write) {
|
||
pj_ks(pj, "type", "var_write");
|
||
pj_kn(pj, "init_val", reg_info->init_val);
|
||
pj_kn(pj, "new_val", reg_info->new_val);
|
||
} else if (reg_info->is_mem_read) {
|
||
pj_ks(pj, "type", "mem_read");
|
||
pj_kn(pj, "new_val", reg_info->new_val);
|
||
} else if (reg_info->is_mem_write) {
|
||
pj_ks(pj, "type", "mem_write");
|
||
pj_kn(pj, "init_val", reg_info->init_val);
|
||
pj_kn(pj, "new_val", reg_info->new_val);
|
||
}
|
||
pj_end(pj);
|
||
}
|
||
pj_end(pj);
|
||
}
|
||
|
||
static void print_modified_reg(const RzGadgetInfo *gadget_info) {
|
||
rz_cons_printf("Modified regs: ");
|
||
if (gadget_info->modified_registers) {
|
||
void **it;
|
||
bool first = true;
|
||
rz_pvector_foreach (gadget_info->modified_registers, it) {
|
||
RzGadgetRegInfo *reg_info = (RzGadgetRegInfo *)*it;
|
||
if (!reg_info || !reg_info->name) {
|
||
continue;
|
||
}
|
||
if (!first) {
|
||
rz_cons_printf(" ");
|
||
}
|
||
rz_cons_printf("%s", reg_info->name);
|
||
first = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void print_gadget_dependencies(const RzGadgetInfo *gadget_info) {
|
||
rz_cons_printf("Dependencies: ");
|
||
if (gadget_info->dependencies) {
|
||
RzListIter *iter;
|
||
RzGadgetRegInfo *dep_info;
|
||
bool first = true;
|
||
rz_list_foreach (gadget_info->dependencies, iter, dep_info) {
|
||
if (!dep_info || !dep_info->name) {
|
||
continue;
|
||
}
|
||
if (!first) {
|
||
rz_cons_printf(" ");
|
||
}
|
||
rz_cons_printf("%s", dep_info->name);
|
||
first = false;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void print_gadget_long_info(const RzGadgetInfo *gadget_info, RzVector /*<size_t>*/ *lens, RzVector /*<ut64>*/ *add, RzPVector /*<char *>*/ *asm_strs, RzPVector /*<char *>*/ *hex_strs, int high_pad, bool utf8, bool colorize) {
|
||
ut32 size = gadget_info->size;
|
||
size_t instr_count = 0;
|
||
int pad = 0;
|
||
|
||
for (size_t idx = 0; idx < size && instr_count < rz_vector_len(lens);) {
|
||
const size_t *lens_elem = (const size_t *)rz_vector_index_ptr(lens, instr_count);
|
||
|
||
if (!lens_elem) {
|
||
break;
|
||
}
|
||
const ut64 *addr_elem = (const ut64 *)rz_vector_index_ptr(add, instr_count);
|
||
ut64 addr = *addr_elem;
|
||
const char *hex = (const char *)rz_pvector_at(hex_strs, instr_count);
|
||
const char *asm_str = (const char *)rz_pvector_at(asm_strs, instr_count);
|
||
const char *reset_color = colorize ? Color_RESET : "";
|
||
rz_cons_printf(" 0x%08" PFMT64x " %-16s %s%s", addr + idx, hex, asm_str, reset_color);
|
||
int cur_asm_len = rz_str_ansi_len(asm_str);
|
||
pad = (high_pad - cur_asm_len);
|
||
if (pad > 0) {
|
||
rz_cons_printf("%*s", pad, "");
|
||
}
|
||
rz_cons_print(utf8 ? " │ " : " | ");
|
||
if (instr_count < 1) {
|
||
rz_cons_printf("Stack change: 0x%" PFMT64x "\n", gadget_info->stack_change);
|
||
} else if (instr_count == 1) {
|
||
print_modified_reg(gadget_info);
|
||
rz_cons_newline();
|
||
} else if (instr_count == 2) {
|
||
print_gadget_dependencies(gadget_info);
|
||
rz_cons_newline();
|
||
} else {
|
||
rz_cons_newline();
|
||
}
|
||
idx += *lens_elem;
|
||
instr_count++;
|
||
}
|
||
}
|
||
|
||
static void gadget_print_long_mode(const RzCore *core, const RzGadgetInfo *gadget_info, const RzGadgetSearchContext *context, bool is_conditional) {
|
||
rz_return_if_fail(core && core->analysis);
|
||
|
||
ut64 addr = gadget_info->address;
|
||
ut32 size = gadget_info->size;
|
||
ut8 *buf = RZ_NEWS0(ut8, size);
|
||
int high_pad = 0;
|
||
if ((!buf || rz_io_read_at_mapped(core->io, addr, buf, size) < 1)) {
|
||
free(buf);
|
||
return;
|
||
}
|
||
RzVector *lens = rz_vector_new(sizeof(size_t), NULL, NULL);
|
||
RzVector *add = rz_vector_new(sizeof(ut64), NULL, NULL);
|
||
RzPVector *asm_strs = rz_pvector_new(free);
|
||
RzPVector *hex_strs = rz_pvector_new(free);
|
||
size_t instr_len = 0;
|
||
ut64 current_addr = 0;
|
||
const int req_width = 50;
|
||
char *rep_str = NULL;
|
||
bool utf8 = rz_config_get_b(core->config, "scr.utf8");
|
||
const bool colorize = rz_config_get_i(core->config, "scr.color");
|
||
const char *highlight_color = colorize ? Color_CYAN : "";
|
||
const char *reset_color = colorize ? Color_RESET : "";
|
||
if (is_conditional) {
|
||
rz_cons_printf("%sGadget 0x%" PFMT64x " (size %d bytes) [Conditional]%s\n", highlight_color, addr, size, reset_color);
|
||
} else {
|
||
rz_cons_printf("Gadget 0x%" PFMT64x " (size %d bytes)\n", addr, size);
|
||
}
|
||
if (utf8) {
|
||
rep_str = rz_str_repeat("–", req_width);
|
||
rz_cons_printf("%s––%s\n", rep_str, rep_str);
|
||
} else {
|
||
rep_str = rz_str_repeat("-", req_width);
|
||
rz_cons_printf("%s--%s\n", rep_str, rep_str);
|
||
}
|
||
RzAsmOp asmop = RZ_EMPTY;
|
||
RzAnalysisOp aop = RZ_EMPTY;
|
||
for (size_t idx = 0; idx < size;) {
|
||
rz_asm_set_pc(core->rasm, addr + idx);
|
||
int len = rz_asm_disassemble(core->rasm, &asmop, buf + idx, size - idx);
|
||
if (len < 1) {
|
||
break;
|
||
}
|
||
instr_len = (size_t)len;
|
||
rz_vector_push(lens, &instr_len);
|
||
rz_analysis_op(core->analysis, &aop, addr + idx, buf + idx, size - idx, RZ_ANALYSIS_OP_MASK_BASIC);
|
||
char *hex = rz_hex_bin2strdup(buf + idx, len);
|
||
char *asm_str = get_colored_asm(core, &asmop, &aop);
|
||
rz_pvector_push(asm_strs, rz_str_dup(asm_str));
|
||
rz_pvector_push(hex_strs, rz_str_dup(hex));
|
||
current_addr = addr;
|
||
rz_vector_push(add, ¤t_addr);
|
||
size_t asm_len_clean = rz_str_ansi_len(asm_str);
|
||
int temp = asm_len_clean;
|
||
if (high_pad == 0 || temp > high_pad) {
|
||
high_pad = temp;
|
||
}
|
||
idx += len;
|
||
free(asm_str);
|
||
free(hex);
|
||
rz_analysis_op_fini(&aop);
|
||
rz_asm_op_fini(&asmop);
|
||
}
|
||
print_gadget_long_info(gadget_info, lens, add, asm_strs, hex_strs, high_pad, utf8, colorize);
|
||
free(rep_str);
|
||
rz_asm_op_fini(&asmop);
|
||
rz_vector_free(lens);
|
||
rz_vector_free(add);
|
||
rz_pvector_free(asm_strs);
|
||
rz_pvector_free(hex_strs);
|
||
free(buf);
|
||
rz_cons_newline();
|
||
}
|
||
|
||
static void print_gadget_info(const RzCore *core, const RzGadgetInfo *gadget_info, const RzGadgetSearchContext *context, bool is_conditional) {
|
||
rz_return_if_fail(gadget_info && context);
|
||
if (!context->state) {
|
||
return;
|
||
}
|
||
if (RZ_STR_NE(context->greparg, "") && !(context->detail_mask | RZ_GADGET_DETAIL_SEARCH_NON)) {
|
||
const ut64 addr = rz_num_math(core->num, context->greparg);
|
||
if (!addr || gadget_info->address != addr) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
switch (context->state->mode) {
|
||
case RZ_OUTPUT_MODE_JSON:
|
||
gadget_print_json_mode(core, gadget_info, context->state->d.pj, is_conditional);
|
||
break;
|
||
case RZ_OUTPUT_MODE_STANDARD:
|
||
gadget_print_standard_mode(core, gadget_info, is_conditional);
|
||
break;
|
||
case RZ_OUTPUT_MODE_LONG:
|
||
gadget_print_long_mode(core, gadget_info, context, is_conditional);
|
||
break;
|
||
default:
|
||
rz_warn_if_reached();
|
||
break;
|
||
}
|
||
}
|
||
|
||
static bool print_gadget_hitlist(const RzCore *core, RzPVector /*<RzCoreAsmHit *>*/ *hitlist, size_t start_idx, RzGadgetSearchContext *context, bool is_conditional) {
|
||
rz_return_val_if_fail(core && hitlist && context, false);
|
||
RzCmdStateOutput *state = context->state;
|
||
if (!state) {
|
||
return false;
|
||
}
|
||
rz_cmd_state_output_set_columnsf(state, "XXs", "addr", "bytes", "disasm");
|
||
RzCoreAsmHit *hit = (RzCoreAsmHit *)rz_pvector_at(hitlist, start_idx);
|
||
if (!hit) {
|
||
return false;
|
||
}
|
||
|
||
const bool colorize = rz_config_get_i(core->config, "scr.color");
|
||
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) {
|
||
const char *addr_color = "";
|
||
const char *reset_color = "";
|
||
if (is_conditional) {
|
||
if (colorize) {
|
||
addr_color = Color_CYAN;
|
||
reset_color = Color_RESET;
|
||
}
|
||
}
|
||
if (context->ret_val) {
|
||
rz_strbuf_appendf(context->buf, "%s0x%08" PFMT64x "%s:", addr_color, hit->addr, reset_color);
|
||
} else {
|
||
rz_cons_printf("%s0x%08" PFMT64x "%s:", addr_color, hit->addr, reset_color);
|
||
}
|
||
}
|
||
const ut64 addr = hit->addr;
|
||
|
||
bool result = 0;
|
||
hit = NULL;
|
||
ut32 size = 0;
|
||
char *asmop_str = NULL, *asmop_hex_str = NULL;
|
||
if (!rz_pvector_empty(hitlist)) {
|
||
for (size_t i = start_idx; i < rz_pvector_len(hitlist); i++) {
|
||
hit = rz_pvector_at(hitlist, i);
|
||
switch (state->mode) {
|
||
case RZ_OUTPUT_MODE_JSON:
|
||
if (!state->d.pj) {
|
||
break;
|
||
}
|
||
result = gadget_hitlist_print_json_mode(core, hit, &size, state->d.pj);
|
||
break;
|
||
case RZ_OUTPUT_MODE_QUIET:
|
||
result = gadget_hitlist_print_quiet_mode(core, hit, &size, context);
|
||
break;
|
||
case RZ_OUTPUT_MODE_STANDARD:
|
||
result = gadget_hitlist_print_standard_mode(core, hit, &size, context, is_conditional);
|
||
break;
|
||
case RZ_OUTPUT_MODE_TABLE:
|
||
result = gadget_hitlist_print_table_mode(core, hit, hitlist, &size, &asmop_str, &asmop_hex_str, context);
|
||
break;
|
||
default:
|
||
rz_warn_if_reached();
|
||
break;
|
||
}
|
||
if (!result) {
|
||
return result;
|
||
}
|
||
}
|
||
}
|
||
const char *highlight_color = colorize ? Color_CYAN : "";
|
||
const char *reset_color = colorize ? Color_RESET : "";
|
||
switch (state->mode) {
|
||
case RZ_OUTPUT_MODE_JSON:
|
||
if (!state->d.pj) {
|
||
break;
|
||
}
|
||
pj_end(state->d.pj);
|
||
if (context->ret_val) {
|
||
break;
|
||
}
|
||
if (hit) {
|
||
pj_kn(state->d.pj, "retaddr", hit->addr);
|
||
pj_ki(state->d.pj, "size", size);
|
||
if (is_conditional) {
|
||
pj_kb(state->d.pj, "is_conditional", true);
|
||
}
|
||
}
|
||
pj_end(state->d.pj);
|
||
break;
|
||
case RZ_OUTPUT_MODE_QUIET:
|
||
if (is_conditional) {
|
||
if (context->ret_val) {
|
||
rz_strbuf_appendf(context->buf, " %s[Conditional]%s\n", highlight_color, reset_color);
|
||
} else {
|
||
rz_cons_printf(" %s[Conditional]%s\n", highlight_color, reset_color);
|
||
}
|
||
} else {
|
||
if (context->ret_val) {
|
||
rz_strbuf_appendf(context->buf, "\n");
|
||
} else {
|
||
rz_cons_newline();
|
||
}
|
||
}
|
||
break;
|
||
case RZ_OUTPUT_MODE_STANDARD:
|
||
if (hit) {
|
||
if (is_conditional) {
|
||
rz_cons_printf("%sGadget size: %d [Conditional]%s\n", highlight_color, (int)size, reset_color);
|
||
} else {
|
||
rz_cons_printf("Gadget size: %d\n", (int)size);
|
||
}
|
||
}
|
||
if (context->ret_val) {
|
||
break;
|
||
}
|
||
rz_cons_newline();
|
||
break;
|
||
case RZ_OUTPUT_MODE_TABLE:
|
||
if (is_conditional) {
|
||
char *new_str = rz_str_newf("%s[Conditional]%s %s", highlight_color, reset_color, asmop_str);
|
||
free(asmop_str);
|
||
asmop_str = new_str;
|
||
}
|
||
if (!context->ret_val) {
|
||
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 true;
|
||
}
|
||
|
||
static bool handle_gadget_list(RzStrBuf *sb, const RzGadgetSearchContext *context,
|
||
const RzGadgetEndListPair *end_gadget, RZ_BORROW RzPVector /*<RzCoreAsmHit *>*/ *hitlist) {
|
||
rz_return_val_if_fail(sb && context && context->unique_hitlists, false);
|
||
if (end_gadget->delay_size && rz_pvector_len(hitlist) < 1 + end_gadget->delay_size) {
|
||
return false;
|
||
}
|
||
|
||
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 {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static void init_grep_context(const RzGadgetSearchContext *context, char **grep_str,
|
||
const char **start, const char **end, const RzList /*<char *>*/ *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 RzGadgetType type, 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 | RZ_ANALYSIS_OP_MASK_IL);
|
||
if (!aop) {
|
||
return false;
|
||
}
|
||
if (error < 0 || (aop->type == RZ_ANALYSIS_OP_TYPE_NOP && aop->size == 0)) {
|
||
return false;
|
||
}
|
||
if (gadget_is_valid_end_gadget(type, aop, 0)) {
|
||
(*end_gadget_cnt)++;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static bool is_invalid_instruction(const char *opst, const int end_gadget_cnt) {
|
||
rz_return_val_if_fail(opst, false);
|
||
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 RzGadgetSearchContext *context, const char **start, const char **end,
|
||
char **grep_str, const RzList /*<char *>*/ *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 bool filter_gadget(RzCore *core, const ut8 *buf, RzGadgetSearchContext *context,
|
||
RzList /*<char *>*/ *rx_list, RzPVector /*<RzCoreAsmHit *>*/ *hitlist) {
|
||
|
||
bool is_greparg = !(context->mask & (RZ_GADGET_PRINT_DETAIL | RZ_GADGET_ANALYZE)) && !(context->detail_mask | RZ_GADGET_DETAIL_SEARCH_NON) && context->greparg;
|
||
if (!is_greparg) {
|
||
return true;
|
||
}
|
||
|
||
const char *start = NULL, *end = NULL;
|
||
int count = 0;
|
||
char *rx = NULL;
|
||
char *grep_str = NULL;
|
||
init_grep_context(context, &grep_str, &start, &end, rx_list, &rx, &count);
|
||
|
||
ut64 delta = context->to - context->from;
|
||
void **it;
|
||
rz_pvector_foreach (hitlist, it) {
|
||
RzCoreAsmHit *hit = *it;
|
||
const ut8 *op_buf;
|
||
ut64 op_size;
|
||
ut8 local_buf[32];
|
||
|
||
if (buf) {
|
||
st64 buf_offset = (st64)(hit->addr - context->from);
|
||
if (buf_offset < 0 || (ut64)buf_offset >= delta) {
|
||
continue;
|
||
}
|
||
op_buf = buf + buf_offset;
|
||
op_size = delta - buf_offset;
|
||
} else {
|
||
// if buf is not provided, read 32bytes (sufficient for single instruction) from hit->addr
|
||
if (rz_io_nread_at(core->io, hit->addr, local_buf, sizeof(local_buf)) < 0) {
|
||
continue;
|
||
}
|
||
op_buf = local_buf;
|
||
op_size = sizeof(local_buf);
|
||
}
|
||
|
||
RzAnalysisOp aop = { 0 };
|
||
rz_analysis_op_init(&aop);
|
||
if (rz_analysis_op(core->analysis, &aop, hit->addr, op_buf,
|
||
op_size, RZ_ANALYSIS_OP_MASK_DISASM) < 0) {
|
||
rz_analysis_op_fini(&aop);
|
||
continue;
|
||
}
|
||
const char *opst = aop.mnemonic;
|
||
if (opst) {
|
||
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);
|
||
}
|
||
}
|
||
rz_analysis_op_fini(&aop);
|
||
}
|
||
|
||
bool pass = true;
|
||
if (context->regexp && rx) {
|
||
pass = false;
|
||
} else if (is_greparg && end) {
|
||
pass = false;
|
||
}
|
||
|
||
free(grep_str);
|
||
return pass;
|
||
}
|
||
|
||
static RzPVector /*<RzCoreAsmHit *>*/ *build_gadget_hitlist_raw(RzCore *core, ut8 *buf, ssize_t idx,
|
||
RzGadgetSearchContext *context, RzGadgetEndListPair *end_gadget, RZ_OUT RzStrBuf **sb_out) {
|
||
|
||
RzPVector *hitlist = rz_pvector_new((RzPVectorFree)rz_core_asm_hit_free);
|
||
if (!hitlist) {
|
||
return NULL;
|
||
}
|
||
|
||
size_t nb_instr = 0;
|
||
ut64 addr = context->from + idx;
|
||
ut64 delta = context->to - context->from;
|
||
ut32 end_gadget_cnt = 0;
|
||
|
||
RzAnalysisOp aop = { 0 };
|
||
bool valid = false;
|
||
RzStrBuf *sb = rz_strbuf_new("");
|
||
while (nb_instr < context->max_instr) {
|
||
rz_analysis_op_init(&aop);
|
||
if (idx >= delta || !process_instruction(context->type, 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);
|
||
if (ret < 0) {
|
||
valid = false;
|
||
goto cleanup;
|
||
}
|
||
|
||
if (is_invalid_instruction(opst, end_gadget_cnt)) {
|
||
valid = false;
|
||
goto cleanup;
|
||
}
|
||
|
||
RzCoreAsmHit *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_pvector_push(hitlist, hit);
|
||
if (ret >= 0) {
|
||
rz_asm_op_fini(&asmop);
|
||
}
|
||
idx += aop.size;
|
||
addr += aop.size;
|
||
|
||
if (end_gadget->instr_offset <= idx - aop.size) {
|
||
valid = end_gadget->instr_offset == idx - aop.size;
|
||
goto cleanup;
|
||
}
|
||
rz_analysis_op_fini(&aop);
|
||
nb_instr++;
|
||
}
|
||
cleanup:
|
||
rz_analysis_op_fini(&aop);
|
||
|
||
if (!valid) {
|
||
rz_pvector_free(hitlist);
|
||
rz_strbuf_free(sb);
|
||
if (sb_out) {
|
||
*sb_out = NULL;
|
||
}
|
||
return NULL;
|
||
}
|
||
if (sb_out) {
|
||
*sb_out = sb;
|
||
} else {
|
||
rz_strbuf_free(sb);
|
||
}
|
||
return hitlist;
|
||
}
|
||
|
||
static void free_gadget_cache_node(RBNode *node, void *user) {
|
||
if (!node) {
|
||
return;
|
||
}
|
||
RzGadgetCacheNode *n = container_of(node, RzGadgetCacheNode, rb);
|
||
rz_pvector_free(n->hitlist);
|
||
free(n);
|
||
}
|
||
|
||
static int gadget_cache_node_cmp(const void *data, const RBNode *x, void *user) {
|
||
const ut64 addr = *(const ut64 *)data;
|
||
const RzGadgetCacheNode *node = container_of(x, const RzGadgetCacheNode, rb);
|
||
return addr - node->addr;
|
||
}
|
||
|
||
static RzPVector /*<RzCoreAsmHit *>*/ *deep_copy_hitlist(const RzPVector /*<RzCoreAsmHit *>*/ *hitlist) {
|
||
if (!hitlist) {
|
||
return NULL;
|
||
}
|
||
RzPVector *new_hitlist = rz_pvector_new((RzPVectorFree)rz_core_asm_hit_free);
|
||
if (!new_hitlist) {
|
||
return NULL;
|
||
}
|
||
void **it;
|
||
rz_pvector_foreach (hitlist, it) {
|
||
const RzCoreAsmHit *hit = *it;
|
||
RzCoreAsmHit *new_hit = rz_core_asm_hit_new();
|
||
if (!new_hit) {
|
||
rz_pvector_free(new_hitlist);
|
||
return NULL;
|
||
}
|
||
new_hit->addr = hit->addr;
|
||
new_hit->len = hit->len;
|
||
new_hit->valid = hit->valid;
|
||
if (hit->code) {
|
||
new_hit->code = strdup(hit->code);
|
||
}
|
||
rz_pvector_push(new_hitlist, new_hit);
|
||
}
|
||
return new_hitlist;
|
||
}
|
||
|
||
static bool insert_gadget_in_cache(RzCore *core, RzGadgetSearchContext *context, ssize_t idx,
|
||
RzGadgetEndListPair *end_gadget, RzPVector /*<RzCoreAsmHit *>*/ *hitlist) {
|
||
|
||
ut64 gadget_addr = context->from + idx;
|
||
RzGadgetCache *gadget_cache = rz_analysis_get_gadget_cache(core->analysis, context->type);
|
||
if (!gadget_cache) {
|
||
return false;
|
||
}
|
||
RzPVector *hitlist_copy = deep_copy_hitlist(hitlist);
|
||
if (!hitlist_copy) {
|
||
return false;
|
||
}
|
||
|
||
RzGadgetCacheNode *node = RZ_NEW0(RzGadgetCacheNode);
|
||
if (!node) {
|
||
rz_pvector_free(hitlist_copy);
|
||
return false;
|
||
}
|
||
node->addr = gadget_addr;
|
||
node->hitlist = hitlist_copy;
|
||
node->delay_size = end_gadget->delay_size;
|
||
void *data = &node->addr;
|
||
if (!rz_rbtree_insert(&gadget_cache->tree, data, &node->rb, gadget_cache_node_cmp, NULL)) {
|
||
RZ_LOG_ERROR("Failed to cache gadget for address 0x%" PFMT64x "\n", gadget_addr);
|
||
free_gadget_cache_node(&node->rb, NULL);
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static RzPVector /*<RzCoreAsmHit *>*/ *construct_gadget(RzCore *core, ut8 *buf, ssize_t idx, RzGadgetSearchContext *context,
|
||
RzList /*<char *>*/ *rx_list, RzGadgetEndListPair *end_gadget) {
|
||
|
||
RzStrBuf *sb = NULL;
|
||
RzPVector *hitlist = build_gadget_hitlist_raw(core, buf, idx, context, end_gadget, &sb);
|
||
if (!hitlist) {
|
||
return NULL;
|
||
}
|
||
|
||
if (!handle_gadget_list(sb, context, end_gadget, hitlist)) {
|
||
rz_pvector_free(hitlist);
|
||
rz_strbuf_free(sb);
|
||
return NULL;
|
||
}
|
||
rz_strbuf_free(sb);
|
||
|
||
if (context->cache) {
|
||
if (!insert_gadget_in_cache(core, context, idx, end_gadget, hitlist)) {
|
||
rz_pvector_free(hitlist);
|
||
return NULL;
|
||
}
|
||
}
|
||
|
||
if (!filter_gadget(core, buf, context, rx_list, hitlist)) {
|
||
rz_pvector_free(hitlist);
|
||
return NULL;
|
||
}
|
||
|
||
return hitlist;
|
||
}
|
||
|
||
static RzGadgetInfo *perform_gadget_analysis(const RzGadgetType type, RzCore *core, const bool allow_conditional, const RzPVector /*<RzCoreAsmHit *>*/ *hitlist, int delay_size) {
|
||
rz_return_val_if_fail(core && core->analysis && hitlist, NULL);
|
||
RzGadgetInfo *gadget_info = NULL;
|
||
|
||
HtUP *ht_gadget_semantics = rz_analysis_get_gadget_semantics(core->analysis);
|
||
if (!ht_gadget_semantics) {
|
||
ht_gadget_semantics = ht_up_new(NULL, (HtUPFreeValue)rz_core_gadget_info_free);
|
||
rz_analysis_set_gadget_semantics(core->analysis, ht_gadget_semantics);
|
||
}
|
||
|
||
const RzCoreAsmHit *terminator_hit = find_gadget_terminator(hitlist, delay_size);
|
||
if (!gadget_is_valid_terminator(type, core, terminator_hit, allow_conditional)) {
|
||
return gadget_info;
|
||
}
|
||
const ut64 addr_start = ((RzCoreAsmHit *)rz_pvector_at(hitlist, 0))->addr;
|
||
gadget_info = ht_up_find(ht_gadget_semantics, addr_start, NULL);
|
||
if (gadget_info) {
|
||
return gadget_info;
|
||
}
|
||
void **iter;
|
||
RzCoreAsmHit *hit;
|
||
gadget_info = rz_core_gadget_info_new(addr_start);
|
||
if (!gadget_info) {
|
||
return NULL;
|
||
}
|
||
ut32 gadget_size = 0;
|
||
rz_pvector_foreach (hitlist, iter) {
|
||
hit = *iter;
|
||
if (!analyze_gadget(core, hit, gadget_info)) {
|
||
RZ_LOG_WARN("Failed to analyze gadget at 0x%" PFMT64x "\n", hit->addr);
|
||
}
|
||
gadget_size += hit->len;
|
||
}
|
||
gadget_info->size = gadget_size;
|
||
ht_up_insert(ht_gadget_semantics, addr_start, gadget_info);
|
||
return gadget_info;
|
||
}
|
||
|
||
/**
|
||
* \brief Perform Gadget operations based on the given \p context and \p hitlist.
|
||
* \param core Pointer to the RzCore structure.
|
||
* \param context Pointer to the RzGadgetSearchContext structure.
|
||
* \param hitlist Pointer to the RzPVector structure containing the gadget instructions.
|
||
* \param delay_size Number of delay-slot instructions following the terminator; used to locate the actual gadget terminator in delay-slot architectures (0 for non-delay-slot archs).
|
||
* \return true if the operation was successful, false otherwise.
|
||
*
|
||
* This function performs Gadget operations based on the given \p context and \p hitlist, including printing and analyzing the gadget sequence.
|
||
*/
|
||
|
||
RZ_API bool rz_core_handle_gadget_request_type(RZ_NONNULL RzCore *core, RZ_NONNULL RzGadgetSearchContext *context,
|
||
RZ_NONNULL RzPVector /*<RzCoreAsmHit *>*/ *hitlist, int delay_size) {
|
||
rz_return_val_if_fail(core && core->analysis && hitlist && context, false);
|
||
|
||
bool is_conditional = false;
|
||
if (context->allow_conditional) {
|
||
RzCoreAsmHit *terminator = find_gadget_terminator(hitlist, delay_size);
|
||
if (terminator) {
|
||
is_conditional = gadget_is_valid_terminator(context->type, core, terminator, true) && !gadget_is_valid_terminator(context->type, core, terminator, false);
|
||
}
|
||
}
|
||
if (context->mask & RZ_GADGET_PRINT) {
|
||
if (context->subchains) {
|
||
const size_t len = rz_pvector_len(hitlist);
|
||
const size_t limit = (len > 1) ? (len - 1) : 1;
|
||
for (size_t i = 0; i < limit; i++) {
|
||
if (!print_gadget_hitlist(core, hitlist, i, context, is_conditional)) {
|
||
return false;
|
||
}
|
||
}
|
||
} else {
|
||
if (!print_gadget_hitlist(core, hitlist, 0, context, is_conditional)) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
RzGadgetInfo *gadget_info = NULL;
|
||
bool is_analysis = false;
|
||
if (context->mask & RZ_GADGET_ANALYZE) {
|
||
gadget_info = perform_gadget_analysis(context->type, core, context->allow_conditional, hitlist, delay_size);
|
||
is_analysis = true;
|
||
}
|
||
|
||
if (context->mask & RZ_GADGET_PRINT_DETAIL) {
|
||
if (!gadget_info && is_analysis) {
|
||
return false;
|
||
}
|
||
print_gadget_info(core, gadget_info, context, is_conditional);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static bool fetch_search_itv(const RzCore *core, RzInterval *search_itv) {
|
||
rz_return_val_if_fail(core && core->config && search_itv, false);
|
||
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 false;
|
||
}
|
||
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 false;
|
||
}
|
||
// 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 true;
|
||
}
|
||
|
||
static RzList /*<RzGadgetEndListPair *>*/ *compute_end_gadget_list(const RzCore *core, const ut8 *buf, const RzGadgetSearchContext *context) {
|
||
RzList /*<RzGadgetEndListPair *>*/ *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 (gadget_is_valid_end_gadget(context->type, &end_gadget, context->allow_conditional)) {
|
||
RzGadgetEndListPair *epair = RZ_NEW0(RzGadgetEndListPair);
|
||
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;
|
||
} else if (RZ_STR_EQ(arch, "riscv")) {
|
||
*increment = rz_analysis_get_pc_align(core->analysis);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* \brief Parses and tokenizes the grep argument string for regular expression matching.
|
||
* \param greparg The grep argument string to process.
|
||
* \param regexp A boolean indicating whether regular expression processing is enabled.
|
||
* \return A pointer to an RzList of `char *` containing the processed grep tokens, or NULL.
|
||
*
|
||
* This function processes the provided grep argument string and returns a list of tokens.
|
||
*/
|
||
RZ_API RZ_NULLABLE RZ_OWN RzList /*<char *>*/ *rz_core_gadget_handle_grep_args(RZ_NULLABLE const char *greparg, const bool regexp) {
|
||
if (!greparg || !regexp) {
|
||
return NULL;
|
||
}
|
||
|
||
char *grep_arg = rz_str_dup(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 = rz_str_dup(tok);
|
||
if (!rx) {
|
||
break;
|
||
}
|
||
rz_list_append(rx_list, rx);
|
||
tok = strtok(NULL, ";");
|
||
}
|
||
|
||
free(gregexp);
|
||
return rx_list;
|
||
}
|
||
|
||
static bool parse_detail_search_arg(RzCore *core, const char *str, RzGadgetDetailSearchCmpOp *op, st64 *value) {
|
||
|
||
if (RZ_STR_ISEMPTY(str)) {
|
||
*op = RZ_GADGET_DETAIL_CMP_EQ;
|
||
*value = 0;
|
||
return true;
|
||
}
|
||
|
||
const char *p = str;
|
||
|
||
while (isspace((unsigned char)*p)) {
|
||
p++;
|
||
}
|
||
|
||
if (*p == '\0') {
|
||
*op = RZ_GADGET_DETAIL_CMP_EQ;
|
||
*value = 0;
|
||
return true;
|
||
}
|
||
|
||
if (rz_str_startswith(p, ">=")) {
|
||
*op = RZ_GADGET_DETAIL_CMP_GE;
|
||
p += 2;
|
||
} else if (rz_str_startswith(p, "<=")) {
|
||
*op = RZ_GADGET_DETAIL_CMP_LE;
|
||
p += 2;
|
||
} else if (rz_str_startswith(p, "==")) {
|
||
*op = RZ_GADGET_DETAIL_CMP_EQ;
|
||
p += 2;
|
||
} else if (p[0] == '>') {
|
||
*op = RZ_GADGET_DETAIL_CMP_GT;
|
||
p++;
|
||
} else if (p[0] == '<') {
|
||
*op = RZ_GADGET_DETAIL_CMP_LT;
|
||
p++;
|
||
} else if (p[0] == '=') {
|
||
*op = RZ_GADGET_DETAIL_CMP_EQ;
|
||
p++;
|
||
} else {
|
||
*op = RZ_GADGET_DETAIL_CMP_EQ;
|
||
}
|
||
|
||
// skip possible spaces between operator and number
|
||
while (isspace((unsigned char)*p)) {
|
||
p++;
|
||
}
|
||
|
||
if (*p == '\0') {
|
||
RZ_LOG_ERROR("Missing value after comparison operator\n");
|
||
return false;
|
||
}
|
||
|
||
*value = (st64)rz_num_math(core->num, p);
|
||
return true;
|
||
}
|
||
|
||
static bool match_detail_search(st64 gadget_val, RzGadgetDetailSearchCmpOp op, st64 target) {
|
||
switch (op) {
|
||
case RZ_GADGET_DETAIL_CMP_EQ:
|
||
return gadget_val == target;
|
||
case RZ_GADGET_DETAIL_CMP_GT:
|
||
return gadget_val > target;
|
||
case RZ_GADGET_DETAIL_CMP_GE:
|
||
return gadget_val >= target;
|
||
case RZ_GADGET_DETAIL_CMP_LT:
|
||
return gadget_val < target;
|
||
case RZ_GADGET_DETAIL_CMP_LE:
|
||
return gadget_val <= target;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
static bool match_gadget_constraint(const RzGadgetInfo *gadget_info, const RzGadgetConstraint *constraint) {
|
||
if (!gadget_info || !constraint) {
|
||
return false;
|
||
}
|
||
|
||
const char *dst_reg = constraint->args[RZ_GADGET_ARG_DST_REG];
|
||
if (!dst_reg) {
|
||
return false;
|
||
}
|
||
|
||
RzGadgetRegInfo *reg_info = rz_core_gadget_info_get_modified_register(gadget_info, dst_reg);
|
||
if (!reg_info) {
|
||
return false;
|
||
}
|
||
|
||
switch (constraint->type) {
|
||
case RZ_GADGET_IL_INSTR_MOV_CONST: {
|
||
const char *const_str = constraint->args[RZ_GADGET_ARG_SRC_CONST];
|
||
if (!const_str) {
|
||
return false;
|
||
}
|
||
ut64 expected_val = strtoull(const_str, NULL, 0);
|
||
return reg_info->new_val == expected_val;
|
||
}
|
||
case RZ_GADGET_IL_INSTR_MOV_REG: {
|
||
const char *src_reg = constraint->args[RZ_GADGET_ARG_SRC_REG];
|
||
if (!src_reg) {
|
||
return false;
|
||
}
|
||
return rz_core_gadget_reg_info_has_event(gadget_info, RZ_GADGET_EVENT_VAR_READ, src_reg);
|
||
}
|
||
case RZ_GADGET_IL_INSTR_MOV_OP_CONST: {
|
||
const char *src_reg = constraint->args[RZ_GADGET_ARG_SRC_REG];
|
||
const char *src_const = constraint->args[RZ_GADGET_ARG_SRC_CONST];
|
||
if (!src_reg || !src_const) {
|
||
return false;
|
||
}
|
||
return rz_core_gadget_reg_info_has_event(gadget_info, RZ_GADGET_EVENT_VAR_READ, src_reg);
|
||
}
|
||
case RZ_GADGET_IL_INSTR_MOV_OP_REG: {
|
||
const char *src_reg = constraint->args[RZ_GADGET_ARG_SRC_REG];
|
||
const char *src_reg_second = constraint->args[RZ_GADGET_ARG_SRC_REG_SECOND];
|
||
if (!src_reg || !src_reg_second) {
|
||
return false;
|
||
}
|
||
return rz_core_gadget_reg_info_has_event(gadget_info, RZ_GADGET_EVENT_VAR_READ, src_reg) &&
|
||
rz_core_gadget_reg_info_has_event(gadget_info, RZ_GADGET_EVENT_VAR_READ, src_reg_second);
|
||
}
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
static bool match_constraints(const RzGadgetInfo *gadget_info, const RzPVector /*<RzGadgetConstraint *>*/ *constraints) {
|
||
if (!constraints || rz_pvector_empty(constraints)) {
|
||
return true;
|
||
}
|
||
|
||
void **it;
|
||
rz_pvector_foreach (constraints, it) {
|
||
RzGadgetConstraint *constraint = *it;
|
||
if (!match_gadget_constraint(gadget_info, constraint)) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static bool apply_post_build_filters(RzCore *core, RzGadgetSearchContext *context,
|
||
RzPVector /*<RzCoreAsmHit *>*/ *hitlist, int delay_size, ut64 gadget_addr) {
|
||
|
||
// constraint filtering
|
||
if (context->constraints && !rz_pvector_empty(context->constraints)) {
|
||
RzGadgetInfo *gadget_info = perform_gadget_analysis(context->type, core, context->allow_conditional, hitlist, delay_size);
|
||
if (!gadget_info || !match_constraints(gadget_info, context->constraints)) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// detail mask filtering
|
||
if (context->detail_mask) {
|
||
RzGadgetInfo *gadget_info = perform_gadget_analysis(context->type, core, context->allow_conditional, hitlist, delay_size);
|
||
if (!gadget_info) {
|
||
return false;
|
||
}
|
||
if (context->detail_mask & (RZ_GADGET_DETAIL_SEARCH_STACK | RZ_GADGET_DETAIL_SEARCH_SIZE)) {
|
||
RzGadgetDetailSearchCmpOp cmp_op;
|
||
st64 target;
|
||
// RZ_GADGET_DETAIL_SEARCH_STACK -> stack_change, RZ_GADGET_DETAIL_SEARCH_SIZE -> size
|
||
ut64 search_val = context->detail_mask & RZ_GADGET_DETAIL_SEARCH_STACK ? gadget_info->stack_change : gadget_info->size;
|
||
if (!parse_detail_search_arg(core, context->greparg, &cmp_op, &target)) {
|
||
return false;
|
||
}
|
||
if (!match_detail_search(search_val, cmp_op, target)) {
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
// alignment check
|
||
if (core->search->align && gadget_addr % core->search->align != 0) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static bool process_disassembly(RzCore *core, ut8 *buf, const ssize_t idx, RzGadgetSearchContext *context,
|
||
RzList /*<char *>*/ *rx_list, RzGadgetEndListPair *end_gadget) {
|
||
RzAsmOp *asmop = rz_asm_op_new();
|
||
bool status = false;
|
||
ut64 orig_pc = rz_asm_get_pc(core->rasm);
|
||
|
||
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);
|
||
RzPVector *hitlist = construct_gadget(core, buf, idx, context, rx_list, end_gadget);
|
||
if (!hitlist) {
|
||
goto fini;
|
||
}
|
||
|
||
// when caching and max_count exhausts, skip output BUT continue building cache
|
||
if (context->cache && context->max_count == 0) {
|
||
rz_pvector_free(hitlist);
|
||
goto fini;
|
||
}
|
||
|
||
if (!apply_post_build_filters(core, context, hitlist, end_gadget->delay_size, context->from + idx)) {
|
||
rz_pvector_free(hitlist);
|
||
goto fini;
|
||
}
|
||
|
||
if (!rz_core_handle_gadget_request_type(core, context, hitlist, end_gadget->delay_size)) {
|
||
rz_pvector_free(hitlist);
|
||
goto fini;
|
||
}
|
||
rz_pvector_free(hitlist);
|
||
|
||
if (context->max_count > 0) {
|
||
context->max_count--;
|
||
if (context->max_count < 1) {
|
||
status = !context->cache;
|
||
}
|
||
}
|
||
|
||
fini:
|
||
rz_asm_set_pc(core->rasm, orig_pc);
|
||
rz_asm_op_free(asmop);
|
||
return status;
|
||
}
|
||
|
||
static bool update_end_gadget(ssize_t *i, const int gadget_depth, RzGadgetEndListPair **end_gadget, const RzGadgetSearchContext *context) {
|
||
rz_return_val_if_fail(end_gadget && context, false);
|
||
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 = (RzGadgetEndListPair *)rz_list_pop(context->end_list);
|
||
*i = (*end_gadget)->instr_offset - gadget_depth;
|
||
if (*i < 0) {
|
||
*i = 0;
|
||
}
|
||
} else {
|
||
*end_gadget = NULL;
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static bool print_gadgets_from_cache(RzCore *core, RzGadgetCache *gadget_cache, RzGadgetSearchContext *context, RzList /*<char *>*/ *rx_list) {
|
||
void *addr = &context->from;
|
||
RBIter iter = rz_rbtree_lower_bound_forward(gadget_cache->tree, addr, gadget_cache_node_cmp, NULL);
|
||
while (rz_rbtree_iter_has(&iter)) {
|
||
RzGadgetCacheNode *node = rz_rbtree_iter_get(&iter, RzGadgetCacheNode, rb);
|
||
if (!node) {
|
||
rz_rbtree_iter_next(&iter);
|
||
continue;
|
||
}
|
||
if (node->addr > context->to) {
|
||
break;
|
||
}
|
||
if (node->addr < context->from || node->addr >= context->to) {
|
||
rz_rbtree_iter_next(&iter);
|
||
continue;
|
||
}
|
||
|
||
if (!filter_gadget(core, NULL, context, rx_list, node->hitlist)) {
|
||
rz_rbtree_iter_next(&iter);
|
||
continue;
|
||
}
|
||
|
||
if (!apply_post_build_filters(core, context, node->hitlist, node->delay_size, node->addr)) {
|
||
rz_rbtree_iter_next(&iter);
|
||
continue;
|
||
}
|
||
|
||
if (!rz_core_handle_gadget_request_type(core, context, node->hitlist, node->delay_size)) {
|
||
return false;
|
||
}
|
||
|
||
if (context->max_count > 0) {
|
||
context->max_count--;
|
||
if (context->max_count < 1) {
|
||
break;
|
||
}
|
||
}
|
||
rz_rbtree_iter_next(&iter);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static bool is_gadget_cache_valid(RzCore *core, RzGadgetCache *gadget_cache, RzGadgetSearchContext *context) {
|
||
if (context->from < gadget_cache->from || context->to > gadget_cache->to) {
|
||
return false;
|
||
}
|
||
if (context->max_instr != gadget_cache->max_instr) {
|
||
return false;
|
||
}
|
||
if (context->allow_conditional != gadget_cache->allow_conditional) {
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
static int handle_gadget_search_address(RzCore *core, RzGadgetSearchContext *context, RzList /*<char *>*/ *rx_list) {
|
||
const ut64 delta = context->to - context->from;
|
||
ut8 *buf = RZ_NEWS0(ut8, delta);
|
||
if (!buf) {
|
||
return -1;
|
||
}
|
||
if (rz_io_nread_at(core->io, context->from, buf, delta) < 0) {
|
||
free(buf);
|
||
return -1;
|
||
}
|
||
|
||
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);
|
||
return -1;
|
||
}
|
||
rz_list_reverse(context->end_list);
|
||
const int max_inst_size_x86 = 15;
|
||
// Get the depth of gadget search window, should just be max_instr
|
||
// instructions, x86 and friends are weird length instructions, so
|
||
// we'll just assume 15 byte instructions.
|
||
const int gadget_depth = context->increment == 1 ? context->max_instr * max_inst_size_x86 /* wow, x86 is long */ : context->max_instr * context->increment;
|
||
if (rz_cons_is_breaked()) {
|
||
return -2;
|
||
}
|
||
RzGadgetEndListPair *end_gadget = rz_list_pop(context->end_list);
|
||
// Start at just before the first end gadget.
|
||
const int next = end_gadget->instr_offset;
|
||
for (ssize_t i = 0; i < delta && (context->cache || 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, gadget_depth, &end_gadget, context)) {
|
||
break;
|
||
}
|
||
if (process_disassembly(core, buf, i, context, rx_list, end_gadget)) {
|
||
break;
|
||
}
|
||
}
|
||
free(end_gadget);
|
||
free(buf);
|
||
rz_list_free(context->end_list);
|
||
return 0;
|
||
}
|
||
|
||
static int handle_gadget_cache(RzCore *core, RzGadgetSearchContext *context, RzList /*<char *>*/ *rx_list) {
|
||
|
||
RzGadgetCache *gadget_cache = rz_analysis_get_gadget_cache(core->analysis, context->type);
|
||
|
||
// cache hit
|
||
if (gadget_cache) {
|
||
if (is_gadget_cache_valid(core, gadget_cache, context)) {
|
||
RZ_LOG_INFO("core: Using gadget cache\n");
|
||
if (print_gadgets_from_cache(core, gadget_cache, context, rx_list)) {
|
||
return 0;
|
||
} else {
|
||
RZ_LOG_ERROR("core: Failed to print gadgets from cache\n");
|
||
return -1;
|
||
}
|
||
} else {
|
||
RZ_LOG_INFO("core: Parameters change detected, Rebuilding cache\n");
|
||
}
|
||
}
|
||
|
||
// cache miss
|
||
RzGadgetCache *gadget_cache_new = RZ_NEW0(RzGadgetCache);
|
||
if (!gadget_cache_new) {
|
||
RZ_LOG_ERROR("core: Failed to allocate gadget cache\n");
|
||
return -1;
|
||
}
|
||
RZ_LOG_DEBUG("core: Building gadget cache for address range 0x%" PFMT64x " - 0x%" PFMT64x "\n", context->from, context->to);
|
||
gadget_cache_new->tree = NULL;
|
||
gadget_cache_new->free = free_gadget_cache_node;
|
||
gadget_cache_new->from = context->from;
|
||
gadget_cache_new->to = context->to;
|
||
gadget_cache_new->max_instr = context->max_instr;
|
||
gadget_cache_new->allow_conditional = context->allow_conditional;
|
||
|
||
rz_analysis_set_gadget_cache(core->analysis, gadget_cache_new, context->type);
|
||
|
||
return 1; // set gadget cache and continue normal flow to build cache and print gadgets
|
||
}
|
||
|
||
/**
|
||
* \brief Search for gadgets.
|
||
* \param core Pointer to the RzCore object.
|
||
* \param context Pointer to the RzGadgetSearchContext object.
|
||
* \return true if the search is successful, false otherwise.
|
||
*
|
||
* Searches for gadgets within the address range specified by configuration.
|
||
* Disassembles instructions, identifies end gadgets, constructs gadgets, and
|
||
* filters results based on the grep argument and request mask. Outputs results to
|
||
* the provided state object.
|
||
*/
|
||
RZ_API RzCmdStatus rz_core_gadget_search(RZ_NONNULL RzCore *core, RZ_NONNULL RzGadgetSearchContext *context) {
|
||
rz_return_val_if_fail(core && core->search && context, RZ_CMD_STATUS_ERROR);
|
||
|
||
RzInterval search_itv = { 0 };
|
||
if (!fetch_search_itv(core, &search_itv)) {
|
||
return RZ_CMD_STATUS_ERROR;
|
||
}
|
||
|
||
if (context->max_instr <= 1) {
|
||
RZ_LOG_ERROR("core: Gadget length (gadget.len) must be greater than 1.\n");
|
||
if (context->max_instr == 1) {
|
||
RZ_LOG_ERROR("core: For gadget.len = 1, use /c to search for single "
|
||
"instructions. See /c? for help.\n");
|
||
}
|
||
return RZ_CMD_STATUS_ERROR;
|
||
}
|
||
set_increment_based_on_arch(core, context->arch, &context->increment);
|
||
if (context->state) {
|
||
rz_cmd_state_output_array_start(context->state);
|
||
}
|
||
rz_cons_break_push(NULL, NULL);
|
||
if (context->max_count == 0) {
|
||
context->max_count = -1;
|
||
}
|
||
context->unique_hitlists = ht_su_new(HT_STR_DUP);
|
||
if (context->ret_val) {
|
||
context->buf = rz_strbuf_new(NULL);
|
||
}
|
||
RzList /*<char *>*/ *rx_list = rz_core_gadget_handle_grep_args(context->greparg, context->regexp);
|
||
int status = 0;
|
||
|
||
// If specific address range is provided, constrain the search interval.
|
||
// This path hits only when context->from/to is set using RZ_API
|
||
// in normal flow of /[RJC] commands this will never be hit
|
||
if (context->to || context->from) {
|
||
RzInterval custom_itv = { context->from, context->to - context->from };
|
||
search_itv = rz_itv_intersect(search_itv, custom_itv);
|
||
}
|
||
|
||
// if cache is enabled, try to print gadgets from cache first, if cache is valid, else rebuild cache
|
||
if (context->cache) {
|
||
context->from = search_itv.addr;
|
||
context->to = search_itv.addr + search_itv.size;
|
||
int result = handle_gadget_cache(core, context, rx_list);
|
||
if (result == 0) {
|
||
status = 0;
|
||
goto cleanup;
|
||
} else if (result == -1) {
|
||
status = -1;
|
||
goto cleanup;
|
||
}
|
||
}
|
||
|
||
RzList *boundaries = rz_core_get_boundaries_select(core, "search.from", "search.to", "search.in");
|
||
if (!boundaries && context->state) {
|
||
rz_cmd_state_output_array_end(context->state);
|
||
}
|
||
RzIOMap *map;
|
||
RzListIter *itermap;
|
||
|
||
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;
|
||
}
|
||
status = handle_gadget_search_address(core, context, rx_list);
|
||
if (status == -2) {
|
||
break;
|
||
}
|
||
if (status < 0) {
|
||
continue;
|
||
}
|
||
}
|
||
rz_list_free(boundaries);
|
||
|
||
cleanup:
|
||
ht_su_free(context->unique_hitlists);
|
||
if (rz_cons_is_breaked()) {
|
||
eprintf("\n");
|
||
}
|
||
|
||
if (context->state) {
|
||
rz_cmd_state_output_array_end(context->state);
|
||
}
|
||
rz_cons_break_pop();
|
||
rz_list_free(rx_list);
|
||
return !status ? RZ_CMD_STATUS_OK : RZ_CMD_STATUS_ERROR;
|
||
}
|
||
|
||
/**
|
||
* \brief Display gadget information.
|
||
* \param core Pointer to the RzCore object.
|
||
* \param context Pointer to the RzGadgetSearchContext object.
|
||
* \return RZ_CMD_STATUS_OK on success.
|
||
*
|
||
* Displays gadgets from the gadgetSdb.
|
||
* If unavailable, performs a gadget search with the input.
|
||
*/
|
||
RZ_API RzCmdStatus rz_core_gadget_info(RZ_NONNULL RzCore *core, RZ_NONNULL RZ_BORROW RzGadgetSearchContext *context) {
|
||
rz_return_val_if_fail(core && core->analysis && context, RZ_CMD_STATUS_ERROR);
|
||
|
||
// TODO: resolve this logic later.
|
||
RzCmdStatus status = rz_core_gadget_search(core, context);
|
||
return status;
|
||
}
|
||
|
||
/**
|
||
* \brief Free an RzGadgetConstraint object.
|
||
* \param data Pointer to the RzGadgetConstraint object to free.
|
||
*
|
||
* Frees the memory allocated for an RzGadgetConstraint object.
|
||
*/
|
||
RZ_API void rz_core_gadget_constraint_free(RZ_NULLABLE void *data) {
|
||
RzGadgetConstraint *constraint = data;
|
||
if (!constraint) {
|
||
return;
|
||
}
|
||
for (size_t i = 0; i < RZ_GADGET_ARG_NUM_ARGS; i++) {
|
||
free(constraint->args[i]);
|
||
}
|
||
free(constraint);
|
||
}
|