rizin/librz/core/devirtualize_objc.c
Giovanni b5fee3471c
Move RzAnalysis as private and force usage of C API (#6123)
* Hide RzAnalysis structure to force the usage of the C APIs.

* Fix rop code & test

* Fix linter

* Fix rz_analysis_free to return void
2026-04-06 16:07:53 +00:00

332 lines
No EOL
9.8 KiB
C

// SPDX-FileCopyrightText: 2025 tushar3q34 <tushar3q34@gmail.com>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_util.h>
#include <rz_analysis.h>
#include <rz_core.h>
#include "core_private.h"
/**
* \file devirtualize_objc.c
* Devirtualization for Objective-C methods
*
* This file implements the devirtualization of Objective-C methods
* by analyzing the class and method structure.
*/
static bool is_branch_type_to_method(RzAnalysisOp *op) {
switch (op->type) {
case RZ_ANALYSIS_OP_TYPE_JMP:
case RZ_ANALYSIS_OP_TYPE_RET:
case RZ_ANALYSIS_OP_TYPE_CALL:
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_CCALL:
case RZ_ANALYSIS_OP_TYPE_UCCALL:
return true;
default:
return false;
}
}
/**
* \brief initialises stack and registers for tainting
*/
static void track_init(RzCore *core) {
// Random Memory allocation representing function stack
rz_core_analysis_esil_init_mem(core, NULL, 0x1000, 0x1050);
rz_core_analysis_il_reinit(core); // initializing VM
// TODO : Make general for other archs
if (rz_asm_is_arch(core->rasm, "x86")) {
rz_analysis_il_vm_set_unsigned(core->analysis, "rbp", 0x1fff);
rz_analysis_il_vm_set_unsigned(core->analysis, "rsp", 0x1fff);
} else if (rz_asm_is_arch(core->rasm, "arm")) {
rz_analysis_il_vm_set_unsigned(core->analysis, "fp", 0x1fff);
rz_analysis_il_vm_set_unsigned(core->analysis, "sp", 0x1fff);
} else {
const char *arch = rz_core_get_arch(core);
RZ_LOG_WARN("arch %s not supported", arch);
}
}
static ut64 get_reg_value(RzAnalysis *analysis, const char *reg_name) {
RzAnalysisILVM *vm = rz_analysis_get_il_vm(analysis);
RzILVal *il_c_reg = rz_il_vm_get_var_value(vm->vm, RZ_IL_VAR_KIND_GLOBAL, reg_name);
if (!il_c_reg) {
return UT64_MAX;
}
RzBitVector *bv = rz_il_value_to_bv(il_c_reg);
if (!bv) {
return UT64_MAX;
}
ut64 val = rz_bv_to_ut64(bv);
rz_bv_free(bv);
return val;
}
static RZ_OWN char *get_message_dispatch_method(RzCore *core, ut64 meta_class_addr, ut64 meth_str_addr) {
RzBinFile *bf = rz_bin_cur(core->bin);
void **it;
const RzPVector *vec = rz_bin_object_get_strings(bf->o);
char *methname = NULL;
rz_pvector_foreach (vec, it) {
RzBinString *bin_str = *it;
if (bin_str->vaddr == meth_str_addr) {
methname = rz_str_dup(bin_str->string);
break;
}
}
const RzPVector *symbols = rz_bin_object_get_symbols(bf->o);
char *classname = NULL;
rz_pvector_foreach (symbols, it) {
RzBinSymbol *symbol = *it;
if (symbol->vaddr == meta_class_addr) {
classname = rz_str_dup(strstr(symbol->name, "CLASS_$_") + strlen("CLASS_$_"));
break;
}
}
if (!classname || !methname) {
RZ_FREE(methname);
RZ_FREE(classname);
return NULL;
}
// Modifying method name to include method_class_(name)
char *oldname = methname;
methname = rz_str_newf("method_%s_%s", classname, methname);
RZ_FREE(oldname);
RzVector *methods = rz_analysis_class_method_get_all(core->analysis, classname);
bool found = false;
if (methods) {
RzAnalysisMethod *method;
rz_vector_foreach (methods, method) {
if (!rz_str_cmp(method->name, methname, -1)) {
found = true;
break;
}
}
}
rz_vector_free(methods);
RZ_FREE(classname);
if (found) {
return methname;
}
RZ_FREE(methname);
return NULL;
}
static RzSetU *allocator_xrefs(RzAnalysis *analysis) {
RzList *list = rz_analysis_function_list(analysis);
if (!list) {
return NULL;
}
RzListIter *it;
RzAnalysisFunction *fcn;
RzSetU *xref_addrs = rz_set_u_new();
rz_list_foreach (list, it, fcn) {
char *res = strstr(fcn->name, "sym.imp.objc_alloc_init");
if (res != NULL) {
RzList *xref_list = rz_analysis_xrefs_get_to(analysis, fcn->addr);
RzListIter *itt;
RzAnalysisXRef *xref;
rz_list_foreach (xref_list, itt, xref) {
rz_set_u_add(xref_addrs, xref->from);
}
}
}
return xref_addrs;
}
static void add_virtual_xrefs(RzAnalysis *analysis, const char *method_name, ut64 addr) {
bool found = false;
HtSP *ht_virtual_xrefs = rz_analysis_get_virtual_xrefs(analysis);
RzSetU *set = ht_sp_find(ht_virtual_xrefs, method_name, &found);
if (!found) {
set = rz_set_u_new();
ht_sp_insert(ht_virtual_xrefs, method_name, set);
}
rz_set_u_add(set, addr);
}
static void devirtualize_msg_dispatch(RzCore *core, RzSetU *msg_dispatch_addr) {
RzAnalysisFunction *function = rz_analysis_get_fcn_in(core->analysis, core->offset, RZ_ANALYSIS_FCN_TYPE_ROOT);
if (!function) {
function = rz_analysis_get_fcn_in(core->analysis, core->offset, RZ_ANALYSIS_FCN_TYPE_NULL);
}
if (!function) {
RZ_LOG_ERROR("Cannot find function at 0x%08" PFMT64x "\n", core->offset);
return;
}
// The first argument is usually the register holding class meta info
const char *cl_reg = rz_analysis_cc_arg(core->analysis, function->cc, 0);
// The second argument is usually the register holding message selector
const char *m_reg = rz_analysis_cc_arg(core->analysis, function->cc, 1);
// The return argument stores the value of instance after call to allocator
const char *ret_reg = rz_analysis_cc_ret(core->analysis, function->cc);
RzSetU *xref_addrs = allocator_xrefs(core->analysis);
ut64 start = function->addr; // start of the function
ut64 end = rz_analysis_function_max_addr(function);
RzAnalysisOp *op = rz_analysis_op_new();
track_init(core);
ut8 *bytes = malloc(end - start);
if (!rz_io_read_at_mapped(core->io, start, bytes, end - start)) {
RZ_LOG_ERROR("Cannot read at offset 0x%08" PFMT64x "\n", start);
}
ut64 offset = 0;
bool refresh_vm = false;
while (start < end) {
rz_analysis_op(core->analysis, op, start, bytes + offset, end - start, RZ_ANALYSIS_OP_MASK_ALL);
if (refresh_vm) {
rz_core_analysis_il_reinit(core);
refresh_vm = false;
}
refresh_vm = is_branch_type_to_method(op);
rz_core_il_step(core, 1);
if (rz_set_u_contains(xref_addrs, start)) {
// continue track past allocator call
ut64 val = get_reg_value(core->analysis, cl_reg);
rz_analysis_il_vm_set_unsigned(core->analysis, ret_reg, val);
}
if (rz_set_u_contains(msg_dispatch_addr, op->addr)) {
// Devirtualize the message dispatch
ut64 vf_addr = get_reg_value(core->analysis, m_reg);
ut64 cmeta_addr = get_reg_value(core->analysis, cl_reg);
// Get function name called by message dispatch
char *vmethod_name = get_message_dispatch_method(core, cmeta_addr, vf_addr);
if (vmethod_name) {
RzStrBuf *comment = rz_strbuf_new(NULL);
rz_strbuf_setf(comment, "Message dispatch to %s", vmethod_name);
const char *str_comment = rz_strbuf_drain(comment);
rz_core_meta_comment_add(core, str_comment, start);
RZ_FREE(str_comment);
add_virtual_xrefs(core->analysis, vmethod_name, op->addr);
}
}
start += op->size;
offset += op->size;
core->offset = start;
rz_analysis_op_fini(op);
}
rz_set_u_free(xref_addrs);
rz_analysis_op_free(op);
free(bytes);
}
static char *construct_reloc_name(RZ_NONNULL RzBinReloc *reloc, RZ_NULLABLE const char *name, bool demangle) {
RzStrBuf *buf = rz_strbuf_new("");
if (!buf) {
return NULL;
}
// (optional) libname_
if (reloc->import && reloc->import->libname) {
rz_strbuf_appendf(buf, "%s_", reloc->import->libname);
} else if (reloc->symbol && reloc->symbol->libname) {
rz_strbuf_appendf(buf, "%s_", reloc->symbol->libname);
}
// actual name
if (name) {
rz_strbuf_append(buf, name);
} else if (demangle && reloc->import && RZ_STR_ISNOTEMPTY(reloc->import->dname)) {
rz_strbuf_append(buf, reloc->import->dname);
} else if (reloc->import && RZ_STR_ISNOTEMPTY(reloc->import->name)) {
rz_strbuf_append(buf, reloc->import->name);
} else if (demangle && reloc->symbol && RZ_STR_ISNOTEMPTY(reloc->symbol->dname)) {
rz_strbuf_appendf(buf, "%s", reloc->symbol->dname);
} else if (reloc->symbol && RZ_STR_ISNOTEMPTY(reloc->symbol->name)) {
rz_strbuf_appendf(buf, "%s", reloc->symbol->name);
} else if (reloc->is_ifunc) {
// addend is the function pointer for the resolving ifunc
rz_strbuf_appendf(buf, "ifunc_%" PFMT64x, reloc->addend);
} else {
rz_strbuf_set(buf, "");
}
return rz_strbuf_drain(buf);
}
/**
* \brief devirtualize Objective-C message dispatch methods
*
* \param core The RzCore instance to work with.
*/
RZ_IPI void rz_core_analysis_devirtualize_objc_methods(RZ_NULLABLE RzCore *core) {
if (!core) {
return;
}
RzBinFile *bf = rz_bin_cur(core->bin);
RzBinObject *obj = rz_bin_cur_object(core->bin);
if (!bf || !obj) {
return;
}
// Check if the binary file has message dispatch methods
// First find in symbols, then in relocs
bool msg_dispatch = false;
RzVector *msg_dispatch_addrs = rz_vector_new(sizeof(ut64), NULL, NULL);
const RzPVector *symbols = rz_bin_object_get_symbols(bf->o);
void **iter;
rz_pvector_foreach (symbols, iter) {
RzBinSymbol *symbol = *iter;
if (!rz_str_cmp(symbol->name, "objc_msgSend", -1)) {
msg_dispatch = true;
rz_vector_push(msg_dispatch_addrs, &(symbol->vaddr));
break;
}
}
RzBinRelocStorage *relocs = rz_bin_object_patch_relocs(bf, obj);
for (size_t i = 0; i < relocs->relocs_count; i++) {
RzBinReloc *reloc = relocs->relocs[i];
bool demangle = rz_config_get_b(core->config, "bin.demangle");
char *name = construct_reloc_name(reloc, NULL, demangle);
if (!rz_str_cmp(name, "objc_msgSend", -1)) {
msg_dispatch = true;
rz_vector_push(msg_dispatch_addrs, &(reloc->vaddr));
}
free(name);
}
RzSetU *msg_dispatch_xref_addr = rz_set_u_new();
// Note all xrefs to message dispatch
ut64 *itt;
rz_vector_foreach (msg_dispatch_addrs, itt) {
ut64 addr = *itt;
RzList *xref_list = rz_analysis_xrefs_get_to(core->analysis, addr);
RzListIter *it;
RzAnalysisXRef *xref;
rz_list_foreach (xref_list, it, xref) {
rz_set_u_add(msg_dispatch_xref_addr, xref->from);
}
rz_list_free(xref_list);
}
rz_vector_free(msg_dispatch_addrs);
if (msg_dispatch) {
devirtualize_msg_dispatch(core, msg_dispatch_xref_addr);
}
rz_set_u_free(msg_dispatch_xref_addr);
}