From c41ebb3a5fff3955412c0d7a2ec38996b46cccff Mon Sep 17 00:00:00 2001 From: Naren Sirigere Date: Wed, 1 Jul 2026 17:09:14 +0530 Subject: [PATCH] Add Rust trait-object devirtualization Recover Rust trait-object dispatch targets from demangled impl metadata, vtable layouts, and core::any::Any type_id methods. Track IL state in the current function, replay callers up to call sites to seed ABI argument registers, and use discovered vtable pointers to resolve virtual calls. Add recovered xrefs and comments for trait dispatch and Any downcast/type_id cases. --- librz/arch/meson.build | 1 + librz/arch/rtti.c | 3 + librz/arch/rtti_rust.c | 335 +++++++++ librz/core/cmd/cmd_analysis.c | 5 +- librz/core/core_private.h | 1 + librz/core/devirtualize_rust.c | 1196 ++++++++++++++++++++++++++++++++ librz/core/meson.build | 1 + librz/include/rz_analysis.h | 1 + test/db/cmd/cmd_ac | 70 ++ test/db/cmd/cmd_acll | 2 +- test/db/cmd/cmd_avD | 201 ++++++ 11 files changed, 1814 insertions(+), 2 deletions(-) create mode 100644 librz/arch/rtti_rust.c create mode 100644 librz/core/devirtualize_rust.c diff --git a/librz/arch/meson.build b/librz/arch/meson.build index 7d86bee3b17..29a57b95734 100644 --- a/librz/arch/meson.build +++ b/librz/arch/meson.build @@ -475,6 +475,7 @@ arch_common_sources = [ 'rtti_msvc.c', 'serialize_analysis.c', 'similarity.c', + 'rtti_rust.c', 'swift_rtti.c', 'switch.c', 'types.c', diff --git a/librz/arch/rtti.c b/librz/arch/rtti.c index 627eb32e526..1de007e642e 100644 --- a/librz/arch/rtti.c +++ b/librz/arch/rtti.c @@ -90,6 +90,9 @@ RZ_API void rz_analysis_rtti_recover_all(RzAnalysis *analysis) { return; } switch (bin_obj->lang) { + case RZ_BIN_LANGUAGE_RUST: + rz_analysis_rtti_rust(analysis); + break; case RZ_BIN_LANGUAGE_SWIFT: rz_analysis_rtti_swift(analysis); break; diff --git a/librz/arch/rtti_rust.c b/librz/arch/rtti_rust.c new file mode 100644 index 00000000000..6cee586c7eb --- /dev/null +++ b/librz/arch/rtti_rust.c @@ -0,0 +1,335 @@ +// SPDX-FileCopyrightText: 2026 historicattle +// SPDX-License-Identifier: LGPL-3.0-only + +#include "analysis_private.h" + +#define RUST_VTABLE_HEADER_SLOTS 3 +#define RUST_MAX_TRAIT_METHOD_SLOTS 128 + +typedef struct rust_trait_method_t { + ut64 addr; + ut64 vtable_offset; + char *name; + char *real_name; + char *impl_type; + char *trait_name; +} RustTraitMethod; + +static void rust_trait_method_fini(void *e, void *user) { + RustTraitMethod *method = e; + RZ_FREE(method->name); + RZ_FREE(method->real_name); + RZ_FREE(method->impl_type); + RZ_FREE(method->trait_name); +} + +static bool section_can_contain_rust_vtable(RzBinSection *section) { + if (!section || section->is_segment || RZ_STR_ISEMPTY(section->name)) { + return false; + } + + return !strcmp(section->name, ".rodata") || + !strcmp(section->name, ".rdata") || + !strcmp(section->name, ".data.rel.ro") || + !strcmp(section->name, ".data.rel.ro.local") || + rz_str_endswith(section->name, "__const"); +} + +static bool addr_in_text_section(RzAnalysis *analysis, ut64 addr) { + RzBinSection *section = analysis->binb.get_vsect_at(analysis->binb.bin, addr); + return section && (section->perm & RZ_PERM_X); +} + +static bool rust_vtable_header_is_valid(RVTableContext *context, ut64 addr) { + ut64 drop = UT64_MAX; + ut64 size = UT64_MAX; + ut64 align = UT64_MAX; + if (!context->read_addr(context->analysis, addr, &drop) || + !context->read_addr(context->analysis, addr + context->word_size, &size) || + !context->read_addr(context->analysis, addr + (2 * context->word_size), &align)) { + return false; + } + if (drop && !addr_in_text_section(context->analysis, drop)) { + return false; + } + if (rz_bits_count_ones_ut64(align) != 1) { + return false; + } + return !size || (!(size % align) && align <= size); +} + +static char *demangled_name_at(RzAnalysis *analysis, ut64 addr) { + RzBinObject *obj = rz_bin_cur_object(analysis->binb.bin); + if (!obj) { + return NULL; + } + + RzBinSymbol *sym = rz_bin_object_get_symbol_at(obj, addr, true); + if (sym && RZ_STR_ISNOTEMPTY(sym->dname)) { + return rz_str_dup(sym->dname); + } + + if (sym && RZ_STR_ISNOTEMPTY(sym->name)) { + char *demangled = NULL; + if (analysis->binb.demangle) { + demangled = analysis->binb.demangle(analysis->binb.bin, "rust", sym->name); + } + if (demangled) { + return demangled; + } + RZ_FREE(demangled); + } + return NULL; +} + +static char *dup_trimmed_range(const char *start, const char *end) { + while (start < end && IS_WHITESPACE(*start)) { + start++; + } + while (end > start && IS_WHITESPACE(end[-1])) { + end--; + } + if (start < end) { + return rz_str_ndup(start, (int)(end - start)); + } + return NULL; +} + +static bool parse_demangled_trait_method(const char *demangled, RustTraitMethod *method) { + if (RZ_STR_ISEMPTY(demangled) || demangled[0] != '<') { + return false; + } + + const char *inside = demangled + 1; + const char *impl_end = NULL; + const char *trait_start = NULL; + const char *trait_end = NULL; + int depth = 0; + + for (const char *p = inside; *p; p++) { + if (*p == '<') { + depth++; + continue; + } + if (*p == '>') { + if (!depth) { + if (strncmp(p, ">::", 3)) { + return false; + } + trait_end = p; + break; + } + depth--; + continue; + } + if (!depth && !trait_start && !strncmp(p, " as ", 4)) { + impl_end = p; + trait_start = p + 4; + p += 3; + } + } + + if (!impl_end || !trait_start || !trait_end || trait_start >= trait_end) { + return false; + } + + const char *suffix = trait_end + 3; + if (RZ_STR_ISEMPTY(suffix)) { + return false; + } + const char *last_sep = rz_str_rstr(suffix, "::"); + method->impl_type = dup_trimmed_range(inside, impl_end); + method->trait_name = dup_trimmed_range(trait_start, trait_end); + if (last_sep) { + method->name = rz_str_dup(last_sep + 2); + } else { + method->name = rz_str_dup(suffix); + } + + return method->impl_type && method->trait_name && method->name; +} + +static bool parse_rust_method_name(RzAnalysis *analysis, ut64 addr, RustTraitMethod *method) { + char *name = demangled_name_at(analysis, addr); + if (!name) { + return false; + } + + bool ok = parse_demangled_trait_method(name, method); + if (ok) { + method->real_name = name; + } else { + rust_trait_method_fini(method, NULL); + RZ_FREE(name); + } + return ok; +} + +static RZ_OWN RzVector /**/ *parse_rust_vtable(RVTableContext *context, ut64 vtable_addr) { + if (!rust_vtable_header_is_valid(context, vtable_addr)) { + return NULL; + } + + RzVector *methods = rz_vector_new(sizeof(RustTraitMethod), rust_trait_method_fini, NULL); + if (!methods) { + return NULL; + } + + ut64 slot_addr = vtable_addr + (RUST_VTABLE_HEADER_SLOTS * context->word_size); + for (ut64 i = 0; i < RUST_MAX_TRAIT_METHOD_SLOTS; i++, slot_addr += context->word_size) { + ut64 method_addr = UT64_MAX; + if (!context->read_addr(context->analysis, slot_addr, &method_addr)) { + break; + } + + bool next_header = rust_vtable_header_is_valid(context, slot_addr); + if (method_addr == 0) { + break; + } + + if (!addr_in_text_section(context->analysis, method_addr)) { + break; + } + + RustTraitMethod method = { + .addr = method_addr, + .vtable_offset = slot_addr - vtable_addr, + }; + if (!parse_rust_method_name(context->analysis, method_addr, &method)) { + if (next_header) { + break; + } + continue; + } + + if (!rz_vector_push(methods, &method)) { + rust_trait_method_fini(&method, NULL); + break; + } + } + + if (rz_vector_empty(methods)) { + rz_vector_free(methods); + return NULL; + } + return methods; +} + +static char *class_name_from_methods(RzVector /**/ *methods) { + RustTraitMethod *best = NULL; + ut64 best_count = 0; + RustTraitMethod *candidate; + rz_vector_foreach (methods, candidate) { + ut64 count = 0; + RustTraitMethod *method; + rz_vector_foreach (methods, method) { + if (method->trait_name && !strcmp(candidate->trait_name, method->trait_name)) { + count++; + } + } + if (count > best_count) { + best = candidate; + best_count = count; + } + } + if (best) { + return rz_str_newf("impl %s as %s", best->impl_type, best->trait_name); + } + return NULL; +} + +static void apply_rust_vtable(RVTableContext *context, ut64 vtable_addr, RzVector /**/ *methods) { + RzAnalysis *analysis = context->analysis; + char *class_name = class_name_from_methods(methods); + if (!class_name) { + return; + } + + if (!rz_analysis_class_exists(analysis, class_name)) { + rz_analysis_class_create(analysis, class_name); + } + + RustTraitMethod *last_method = rz_vector_index_ptr(methods, rz_vector_len(methods) - 1); + RzAnalysisVTable vtable = { + .addr = vtable_addr, + .offset = 0, + .size = last_method->vtable_offset + context->word_size, + }; + + rz_analysis_class_vtable_set(analysis, class_name, &vtable); + rz_analysis_class_vtable_fini(&vtable); + RustTraitMethod *rmethod; + rz_vector_foreach (methods, rmethod) { + if (rz_analysis_class_method_exists_by_addr(analysis, class_name, rmethod->addr)) { + continue; + } + RzAnalysisMethod method = { + .name = rz_str_dup(rmethod->name), + .real_name = rz_str_dup(rmethod->real_name), + .addr = rmethod->addr, + .vtable_offset = rmethod->vtable_offset, + .method_type = RZ_ANALYSIS_CLASS_METHOD_VIRTUAL, + }; + rz_analysis_class_method_set(analysis, class_name, &method); + rz_analysis_class_method_fini(&method); + } + RZ_FREE(class_name); +} + +/** + * Scan the binary for Rust trait-object vtables and recover class metadata. + * + * This function iterates through read-only sections of the binary, attempting to identify + * and parse Rust trait-object vtables based on their memory layout and symbol metadata. + */ +RZ_API void rz_analysis_rtti_rust(RZ_NONNULL RzAnalysis *analysis) { + RVTableContext context; + if (!rz_analysis_vtable_begin(analysis, &context)) { + return; + } + + RzBinObject *obj = rz_bin_cur_object(analysis->binb.bin); + const RzPVector *sections = NULL; + if (obj) { + sections = analysis->binb.get_sections(obj); + } + if (!sections) { + return; + } + + rz_cons_break_push(NULL, NULL); + void **iter; + RzBinSection *section; + rz_pvector_foreach (sections, iter) { + if (rz_cons_is_breaked()) { + break; + } + section = *iter; + if (!section_can_contain_rust_vtable(section)) { + continue; + } + if (section->vsize < RUST_VTABLE_HEADER_SLOTS * context.word_size) { + continue; + } + + ut64 addr = RZ_ROUND(section->vaddr, context.word_size); + ut64 end = section->vaddr + section->vsize - (RUST_VTABLE_HEADER_SLOTS * context.word_size); + while (addr <= end) { + if (rz_cons_is_breaked()) { + break; + } + if (!analysis->iob.is_valid_offset(analysis->iob.io, addr, 0)) { + addr += context.word_size; + continue; + } + + RzVector *methods = parse_rust_vtable(&context, addr); + if (methods) { + apply_rust_vtable(&context, addr, methods); + rz_vector_free(methods); + } + addr += context.word_size; + } + } + rz_cons_break_pop(); +} diff --git a/librz/core/cmd/cmd_analysis.c b/librz/core/cmd/cmd_analysis.c index 1eca5574951..f0e9730b68b 100644 --- a/librz/core/cmd/cmd_analysis.c +++ b/librz/core/cmd/cmd_analysis.c @@ -6276,8 +6276,11 @@ RZ_IPI RzCmdStatus rz_analysis_devirtualize_handler(RzCore *core, int argc, cons case RZ_BIN_LANGUAGE_SWIFT: rz_core_analysis_devirtualize_objc_methods(core); break; + case RZ_BIN_LANGUAGE_RUST: + rz_core_analysis_devirtualize_rust_methods(core); + break; default: - RZ_LOG_ERROR("Devirtualization is only supported for C++ and Objective-C binaries.\n"); + RZ_LOG_ERROR("Devirtualization is only supported for C++, Objective-C, and Rust binaries.\n"); break; } return RZ_CMD_STATUS_OK; diff --git a/librz/core/core_private.h b/librz/core/core_private.h index 71580dbefa1..f74cc1f274b 100644 --- a/librz/core/core_private.h +++ b/librz/core/core_private.h @@ -47,6 +47,7 @@ RZ_IPI void rz_core_il_colorize_body(RZ_NONNULL RzConsContext *ctx, RZ_NULLABLE RZ_IPI void rz_core_analysis_devirtualize_cxx_methods(RZ_NULLABLE RzCore *core); RZ_IPI void rz_core_analysis_devirtualize_objc_methods(RZ_NULLABLE RzCore *core); +RZ_IPI void rz_core_analysis_devirtualize_rust_methods(RZ_NULLABLE RzCore *core); RZ_IPI void rz_core_analysis_virtual_xrefs_print(RZ_NONNULL RzCore *core, RZ_NONNULL const char *vfunc); RZ_IPI void rz_core_analysis_virtual_xrefs_print_table(RZ_NONNULL RzCore *core, RZ_NONNULL const char *vfunc, RZ_NONNULL RzTable *table); diff --git a/librz/core/devirtualize_rust.c b/librz/core/devirtualize_rust.c new file mode 100644 index 00000000000..97b6410347c --- /dev/null +++ b/librz/core/devirtualize_rust.c @@ -0,0 +1,1196 @@ +// SPDX-FileCopyrightText: 2026 historicattle +// SPDX-License-Identifier: LGPL-3.0-only + +#include +#include +#include +#include "core_private.h" + +#define RUST_VTABLE_HEADER_SLOTS 3 +#define RUST_MAX_TRAIT_METHOD_SLOTS 128 +#define RUST_TYPE_ID_BYTES 16 +#define RUST_MAX_CALL_ARGS 8 +#define RUST_TRACK_MEM_ADDR 0x10000000 +#define RUST_TRACK_MEM_SIZE 0x50000 +#define RUST_STACK_PTR (RUST_TRACK_MEM_ADDR + (RUST_TRACK_MEM_SIZE / 2)) +#define RUST_SCRATCH_HEAP_ADDR (RUST_TRACK_MEM_ADDR + 0x30000) +#define RUST_SCRATCH_HEAP_SIZE 0x20000 +#define RUST_SCRATCH_ALLOC_STEP 0x100 + +typedef struct rust_any_vtable_t { + ut64 vtable_addr; + ut64 type_id_method; + ut64 type_id_low; + ut64 type_id_high; + bool has_type_id; + bool has_type_id_high; + char *concrete_type; +} RustAnyVTable; + +typedef struct rust_arg_regs_t { + const char *regs[RUST_MAX_CALL_ARGS]; + ut64 count; +} RustArgRegs; + +typedef struct rust_call_seed_t { + const char *regs[RUST_MAX_CALL_ARGS]; + ut64 values[RUST_MAX_CALL_ARGS]; + bool has_value[RUST_MAX_CALL_ARGS]; + ut64 count; +} RustCallSeed; + +static void rust_any_vtable_fini(void *e, void *user) { + RustAnyVTable *vtable = e; + RZ_FREE(vtable->concrete_type); +} + +static ut64 ptr_size(RzCore *core) { + return rz_asm_get_bits(core->rasm) == 64 ? 8 : 4; +} + +static bool read_ptr(RzCore *core, ut64 addr, ut64 *out) { + return rz_io_read_i(core->io, addr, out, ptr_size(core), rz_asm_is_big_endian_set(core->rasm)); +} + +static bool addr_is_executable(RzCore *core, ut64 addr) { + RzBinObject *obj = rz_bin_cur_object(core->bin); + if (!obj) { + return false; + } + RzBinSection *section = rz_bin_get_section_at(obj, addr, true); + return section && (section->perm & RZ_PERM_X); +} + +static RZ_OWN ut8 *read_mapped_range(RzCore *core, ut64 start, ut64 end) { + if (end <= start) { + return NULL; + } + ut8 *bytes = malloc(end - start); + if (!bytes) { + return NULL; + } + if (!rz_io_read_at_mapped(core->io, start, bytes, end - start)) { + RZ_FREE(bytes); + return NULL; + } + return bytes; +} + +static RZ_OWN char *get_method_name(RzCore *core, ut64 addr) { + if (!addr || addr == UT64_MAX || !rz_io_is_valid_offset(core->io, addr, RZ_PERM_R)) { + return NULL; + } + RzAnalysisFunction *function = rz_analysis_get_fcn_in(core->analysis, addr, RZ_ANALYSIS_FCN_TYPE_NULL); + if (function && RZ_STR_ISNOTEMPTY(function->name)) { + return rz_str_dup(function->name); + } + RzFlagItem *flag = rz_flag_get_i(core->flags, addr); + if (flag) { + return rz_str_dup(flag->name); + } + return NULL; +} + +static bool is_any_trait_name(const char *trait_name) { + if (RZ_STR_ISEMPTY(trait_name)) { + return false; + } + + return (rz_str_startswith(trait_name, "core") || rz_str_startswith(trait_name, "std")) && + (rz_str_endswith(trait_name, "any::Any") || rz_str_endswith(trait_name, "any::Any_")); +} + +static const char *rust_trait_name_from_class_name(const char *class_name) { + if (RZ_STR_ISEMPTY(class_name)) { + return NULL; + } + if (rz_str_startswith(class_name, "impl ")) { + const char *as = rz_str_rstr(class_name, " as "); + if (!as) { + return NULL; + } + const char *trait_name = as + strlen(" as "); + if (RZ_STR_ISNOTEMPTY(trait_name)) { + return trait_name; + } + return NULL; + } + + const char *as = rz_str_rstr(class_name, "_as_"); + if (!as || as == class_name) { + return NULL; + } + const char *trait_name = as + strlen("_as_"); + if (RZ_STR_ISNOTEMPTY(trait_name)) { + return trait_name; + } + return NULL; +} + +static bool is_any_class_name(const char *class_name) { + return is_any_trait_name(rust_trait_name_from_class_name(class_name)); +} + +static RZ_OWN char *concrete_type_from_any_class_name(const char *class_name) { + if (!is_any_class_name(class_name)) { + return NULL; + } + if (rz_str_startswith(class_name, "impl ")) { + const char *impl_type = class_name + strlen("impl "); + const char *as = rz_str_rstr(class_name, " as "); + if (as > impl_type) { + return rz_str_ndup(impl_type, (as - impl_type)); + } + return NULL; + } + + const char *as = rz_str_rstr(class_name, "_as_"); + if (as > class_name) { + return rz_str_ndup(class_name, (as - class_name)); + } + return NULL; +} + +static bool is_rust_vtable(RzCore *core, ut64 vtable_addr) { + ut64 drop = UT64_MAX; + ut64 size = UT64_MAX; + ut64 align = UT64_MAX; + ut64 psize = ptr_size(core); + if (!read_ptr(core, vtable_addr, &drop) || !read_ptr(core, vtable_addr + psize, &size) || + !read_ptr(core, vtable_addr + (2 * psize), &align)) { + return false; + } + + if (drop && !addr_is_executable(core, drop)) { + return false; + } + if (rz_bits_count_ones_ut64(align) != 1) { + return false; + } + return !size || (!(size % align) && align <= size); +} + +static bool rust_type_id_push_imm(ut64 values[2], ut64 *count, ut64 value) { + if (value == UT64_MAX) { + return false; + } + + for (ut64 i = 0; i < *count; i++) { + if (values[i] == value) { + return true; + } + } + + if (*count >= 2) { + return false; + } + + values[(*count)++] = value; + return true; +} + +static bool rust_type_id_push_data(RzCore *core, ut64 values[2], ut64 *count, ut64 addr) { + ut8 buf[RUST_TYPE_ID_BYTES] = { 0 }; + if (!addr || addr == UT64_MAX || !rz_io_read_at_mapped(core->io, addr, buf, sizeof(buf))) { + return false; + } + + bool big_endian = rz_asm_is_big_endian_set(core->rasm); + values[0] = rz_read_ble64(buf, big_endian); + values[1] = rz_read_ble64(buf + 8, big_endian); + *count = 2; + return true; +} + +static bool rust_type_id_from_method(RzCore *core, ut64 method_addr, RZ_OUT ut64 *low, RZ_OUT ut64 *high, RZ_OUT bool *has_high) { + RzAnalysisFunction *function = rz_analysis_get_fcn_in(core->analysis, method_addr, RZ_ANALYSIS_FCN_TYPE_NULL); + if (!function) { + return false; + } + + ut64 start = function->addr; + ut64 end = rz_analysis_function_max_addr(function); + if (end <= start) { + return false; + } + + ut8 *bytes = read_mapped_range(core, start, end); + if (!bytes) { + return false; + } + + ut64 values[2] = { 0 }; + ut64 count = 0; + ut64 offset = 0; + RzAnalysisOp *op = rz_analysis_op_new(); + if (!op) { + RZ_FREE(bytes); + return false; + } + + while (start < end && count < 2) { + if (rz_analysis_op(core->analysis, op, start, bytes + offset, end - start, RZ_ANALYSIS_OP_MASK_ALL) <= 0 || op->size < 1) { + break; + } + ut32 type = op->type & RZ_ANALYSIS_OP_TYPE_MASK; + if (op->ptr && op->ptr != UT64_MAX && op->refptr >= RUST_TYPE_ID_BYTES && + rust_type_id_push_data(core, values, &count, op->ptr)) { + rz_analysis_op_fini(op); + break; + } + if (type == RZ_ANALYSIS_OP_TYPE_MOV || type == RZ_ANALYSIS_OP_TYPE_CMOV) { + if (op->val != UT64_MAX) { + rust_type_id_push_imm(values, &count, op->val); + } + for (ut64 i = 0; i < RZ_ARRAY_SIZE(op->analysis_vals); i++) { + if (op->analysis_vals[i].imm != ST64_MAX && op->analysis_vals[i].imm) { + rust_type_id_push_imm(values, &count, op->analysis_vals[i].imm); + } + } + } + if (type == RZ_ANALYSIS_OP_TYPE_RET) { + rz_analysis_op_fini(op); + break; + } + start += op->size; + offset += op->size; + rz_analysis_op_fini(op); + } + + rz_analysis_op_free(op); + RZ_FREE(bytes); + if (!count) { + return false; + } + + *low = values[0]; + *high = 0; + if (count > 1) { + *high = values[1]; + } + *has_high = count > 1; + return true; +} + +static bool rust_method_is_type_id(RzAnalysisMethod *method) { + if (!method) { + return false; + } + if (rz_str_startswith(method->name, "type_id") || rz_str_startswith(method->name, "get_type_id")) { + return true; + } + return method->real_name && (strstr(method->real_name, "::type_id") || strstr(method->real_name, "::get_type_id")); +} + +static RzAnalysisMethod *rust_any_type_id_method(RzCore *core, RzVector /**/ *methods) { + if (!methods) { + return NULL; + } + + RzAnalysisMethod *method; + rz_vector_foreach (methods, method) { + if (rust_method_is_type_id(method)) { + return method; + } + } + return NULL; +} + +static bool push_any_vtable_from_class(RzCore *core, RzVector /**/ *any_vtables, const char *class_name, RzAnalysisVTable *vtable, RzAnalysisMethod *type_id_method) { + if (!type_id_method || !type_id_method->addr || type_id_method->addr == UT64_MAX || + (vtable && (!vtable->addr || vtable->addr == UT64_MAX))) { + return false; + } + + RustAnyVTable any = { + .vtable_addr = UT64_MAX, + .type_id_method = type_id_method->addr, + .concrete_type = concrete_type_from_any_class_name(class_name), + }; + if (vtable) { + any.vtable_addr = vtable->addr; + } + + if (!any.concrete_type) { + return false; + } + + any.has_type_id = rust_type_id_from_method(core, any.type_id_method, &any.type_id_low, &any.type_id_high, &any.has_type_id_high); + if (!rz_vector_push(any_vtables, &any)) { + rust_any_vtable_fini(&any, NULL); + return false; + } + return true; +} + +static RZ_OWN RzVector /**/ *rust_any_vtables_from_classes(RzCore *core) { + RzPVector *classes = rz_analysis_class_get_all(core->analysis, false); + if (!classes) { + return NULL; + } + + RzVector *any_vtables = rz_vector_new(sizeof(RustAnyVTable), rust_any_vtable_fini, NULL); + if (!any_vtables) { + rz_pvector_free(classes); + return NULL; + } + + void **iter; + rz_pvector_foreach (classes, iter) { + SdbKv *kv = *iter; + const char *class_name = sdbkv_key(kv); + if (!is_any_class_name(class_name)) { + continue; + } + + RzVector *methods = rz_analysis_class_method_get_all(core->analysis, class_name); + RzAnalysisMethod *type_id_method = rust_any_type_id_method(core, methods); + if (!type_id_method) { + rz_vector_free(methods); + continue; + } + + RzVector *vtables = rz_analysis_class_vtable_get_all(core->analysis, class_name); + if (!vtables || rz_vector_empty(vtables)) { + push_any_vtable_from_class(core, any_vtables, class_name, NULL, type_id_method); + rz_vector_free(vtables); + rz_vector_free(methods); + continue; + } + + RzAnalysisVTable *vtable; + rz_vector_foreach (vtables, vtable) { + push_any_vtable_from_class(core, any_vtables, class_name, vtable, type_id_method); + } + rz_vector_free(vtables); + rz_vector_free(methods); + } + + rz_pvector_free(classes); + if (rz_vector_empty(any_vtables)) { + rz_vector_free(any_vtables); + return NULL; + } + return any_vtables; +} + +static RustAnyVTable *any_vtable_by_addr(RzVector /**/ *vtables, ut64 vtable_addr) { + if (!vtables || !vtable_addr || vtable_addr == UT64_MAX) { + return NULL; + } + + RustAnyVTable *any; + rz_vector_foreach (vtables, any) { + if (any->vtable_addr == vtable_addr) { + return any; + } + } + return NULL; +} + +static RustAnyVTable *any_vtable_by_type_id_method(RzVector /**/ *vtables, ut64 method_addr) { + if (!vtables) { + return NULL; + } + + RustAnyVTable *any; + rz_vector_foreach (vtables, any) { + if (any->type_id_method == method_addr) { + return any; + } + } + return NULL; +} + +static bool type_id_values_match(ut64 low, ut64 high, bool has_high, RustAnyVTable *any) { + if (!any || !any->has_type_id) { + return false; + } + if (has_high && any->has_type_id_high) { + return low == any->type_id_low && high == any->type_id_high; + } + + return low == any->type_id_low; +} + +static RustAnyVTable *any_vtable_by_type_id_values(RzVector /**/ *vtables, ut64 low, ut64 high, bool has_high) { + if (!vtables) { + return NULL; + } + + RustAnyVTable *any; + rz_vector_foreach (vtables, any) { + if (type_id_values_match(low, high, has_high, any)) { + return any; + } + } + return NULL; +} + +static bool resolve_slot(RzCore *core, ut64 vtable_addr, ut64 slot_addr, RZ_OUT ut64 *target) { + ut64 psize = ptr_size(core); + if (slot_addr < vtable_addr) { + return false; + } + + ut64 method_offset = slot_addr - vtable_addr; + if (method_offset < RUST_VTABLE_HEADER_SLOTS * psize || method_offset % psize) { + return false; + } + + ut64 method_slot = (method_offset / psize) - RUST_VTABLE_HEADER_SLOTS; + if (method_slot >= RUST_MAX_TRAIT_METHOD_SLOTS || !is_rust_vtable(core, vtable_addr)) { + return false; + } + + return read_ptr(core, slot_addr, target) && *target && *target != UT64_MAX && addr_is_executable(core, *target); +} + +static ut64 il_value_to_ut64(RZ_NULLABLE RzILVal *val) { + if (!val) { + return UT64_MAX; + } + + RzBitVector *bv = rz_il_value_to_bv(val); + if (!bv) { + return UT64_MAX; + } + + ut64 ret = rz_bv_to_ut64(bv); + rz_bv_free(bv); + return ret; +} + +static ut64 get_reg_value(RzAnalysis *analysis, const char *reg_name) { + if (!reg_name) { + return UT64_MAX; + } + + RzAnalysisILVM *vm = rz_analysis_get_il_vm(analysis); + if (!vm) { + return UT64_MAX; + } + + RzILVal *il_reg = rz_il_vm_get_var_value(vm->vm, RZ_IL_VAR_KIND_GLOBAL, reg_name); + return il_value_to_ut64(il_reg); +} + +static bool is_real_reg_name(RzCore *core, const char *reg_name) { + if (RZ_STR_ISEMPTY(reg_name) || !strcmp(reg_name, "none")) { + return false; + } + + RzReg *reg = rz_analysis_get_reg(core->analysis); + return reg && rz_reg_get(reg, reg_name, RZ_REG_TYPE_ANY); +} + +static void advance_il_pc(RzCore *core, ut64 addr) { + RzReg *reg = rz_analysis_get_reg(core->analysis); + if (reg) { + rz_reg_set_value_by_role(reg, RZ_REG_NAME_PC, addr); + } +} + +static bool analysis_value_is_mem(RzAnalysisValue *value) { + return value && value->memref > 0; +} + +static bool reg_name_is_pc(RzCore *core, const char *reg_name) { + RzReg *reg = rz_analysis_get_reg(core->analysis); + if (!reg) { + return false; + } + const char *pc = rz_reg_get_name(reg, RZ_REG_NAME_PC); + return pc && !strcmp(reg_name, pc); +} + +static bool analysis_value_addr(RzCore *core, RZ_NULLABLE const RzAnalysisOp *op, RzAnalysisValue *value, RZ_OUT ut64 *addr) { + if (!analysis_value_is_mem(value)) { + return false; + } + + ut64 result = value->base; + const char *base_reg = NULL; + if (value && value->reg) { + base_reg = value->reg->name; + } + if (base_reg) { + ut64 base = UT64_MAX; + if (op && reg_name_is_pc(core, base_reg)) { + base = op->addr + op->size; + } else { + base = get_reg_value(core->analysis, base_reg); + } + if (!base || base == UT64_MAX) { + return false; + } + result += base; + } + + if (value->regdelta) { + ut64 index = get_reg_value(core->analysis, value->regdelta->name); + if (index == UT64_MAX) { + return false; + } + ut64 scale = value->mul; + if (!scale) { + scale = 1; + } + result += index * scale; + } + + result += value->delta; + if (!result) { + return false; + } + *addr = result; + return true; +} + +static bool value_mem_access_is_safe(RzCore *core, RzAnalysisOp *op, RzAnalysisValue *value, bool write) { + if (!analysis_value_is_mem(value)) { + return true; + } + + ut64 addr = UT64_MAX; + if (!analysis_value_addr(core, op, value, &addr)) { + return false; + } + + ut64 access_size = 1; + if (value->memref > 0) { + access_size = value->memref; + } + if (access_size && addr >= RUST_TRACK_MEM_ADDR) { + ut64 offset = addr - RUST_TRACK_MEM_ADDR; + if (offset < RUST_TRACK_MEM_SIZE && access_size <= RUST_TRACK_MEM_SIZE - offset) { + return true; + } + } + + if (write || !rz_io_is_valid_offset(core->io, addr, RZ_PERM_R)) { + return false; + } + + RzBinObject *obj = rz_bin_cur_object(core->bin); + RzBinSection *section = NULL; + if (obj) { + section = rz_bin_get_section_at(obj, addr, true); + } + return section && !(section->perm & RZ_PERM_X); +} + +static bool op_memory_access_is_safe(RzCore *core, RzAnalysisOp *op) { + if ((op->type & RZ_ANALYSIS_OP_TYPE_MASK) == RZ_ANALYSIS_OP_TYPE_LEA) { + return true; + } + if (op->dst && analysis_value_is_mem(op->dst) && !value_mem_access_is_safe(core, op, op->dst, true)) { + return false; + } + + for (ut64 i = 0; i < RZ_ARRAY_SIZE(op->src); i++) { + if (op->src[i] && analysis_value_is_mem(op->src[i]) && + !value_mem_access_is_safe(core, op, op->src[i], false)) { + return false; + } + } + return true; +} + +static const char *op_dst_reg_name(RzAnalysisOp *op) { + if (!op->dst || op->dst->type != RZ_ANALYSIS_VAL_REG || !op->dst->reg) { + return NULL; + } + return op->dst->reg->name; +} + +static void get_arg_regs(RzCore *core, RzAnalysisFunction *function, RZ_OUT RustArgRegs *args) { + const char *cc = rz_analysis_cc_default(core->analysis); + for (ut64 i = 0; i < RUST_MAX_CALL_ARGS; i++) { + const char *reg = rz_analysis_cc_arg(core->analysis, cc, (int)i); + if (!reg) { + break; + } + if (!is_real_reg_name(core, reg)) { + break; + } + args->regs[args->count++] = reg; + } +} + +static bool seed_contains_rust_vtable(RzCore *core, RustCallSeed *seed) { + for (ut64 i = 0; i < seed->count; i++) { + if (seed->has_value[i] && is_rust_vtable(core, seed->values[i])) { + return true; + } + } + return false; +} + +static bool seed_equals(RustCallSeed *a, RustCallSeed *b) { + if (a->count != b->count) { + return false; + } + + for (ut64 i = 0; i < a->count; i++) { + if (a->has_value[i] != b->has_value[i]) { + return false; + } + if (a->has_value[i] && a->values[i] != b->values[i]) { + return false; + } + } + return true; +} + +static void push_unique_seed(RzCore *core, RzVector /**/ *seeds, RustCallSeed *seed) { + if (!seed_contains_rust_vtable(core, seed)) { + return; + } + + RustCallSeed *it; + rz_vector_foreach (seeds, it) { + if (seed_equals(it, seed)) { + return; + } + } + rz_vector_push(seeds, seed); +} + +static bool is_vtable_slot_disp(RzCore *core, ut64 disp) { + ut64 psize = ptr_size(core); + ut64 min = RUST_VTABLE_HEADER_SLOTS * psize; + ut64 max = (RUST_VTABLE_HEADER_SLOTS + RUST_MAX_TRAIT_METHOD_SLOTS) * psize; + return disp >= min && disp < max && !(disp % psize); +} + +static bool op_type_is_unknown_call_or_jump(ut32 type) { + ut32 base = type & RZ_ANALYSIS_OP_TYPE_MASK; + return base == RZ_ANALYSIS_OP_TYPE_UCALL || base == RZ_ANALYSIS_OP_TYPE_UCCALL || base == RZ_ANALYSIS_OP_TYPE_UJMP || base == RZ_ANALYSIS_OP_TYPE_UCJMP; +} + +static bool op_type_is_memory_jump(ut32 type) { + ut32 base = type & RZ_ANALYSIS_OP_TYPE_MASK; + return (type & RZ_ANALYSIS_OP_TYPE_MEM) && (base == RZ_ANALYSIS_OP_TYPE_JMP || base == RZ_ANALYSIS_OP_TYPE_CJMP); +} + +static bool op_is_vtable_slot_dispatch(RzCore *core, RzAnalysisOp *op) { + return is_real_reg_name(core, op->reg) && is_vtable_slot_disp(core, op->disp) && + (op->type & (RZ_ANALYSIS_OP_TYPE_IND | RZ_ANALYSIS_OP_TYPE_MEM)) && + (op_type_is_unknown_call_or_jump(op->type) || op_type_is_memory_jump(op->type)); +} + +static bool op_is_register_target_dispatch(RzCore *core, RzAnalysisOp *op) { + return is_real_reg_name(core, op->reg) && (op->type & RZ_ANALYSIS_OP_TYPE_REG) && + !(op->type & (RZ_ANALYSIS_OP_TYPE_IND | RZ_ANALYSIS_OP_TYPE_MEM)) && op_type_is_unknown_call_or_jump(op->type); +} + +static bool op_is_dispatch_candidate(RzCore *core, RzAnalysisOp *op) { + return op_is_vtable_slot_dispatch(core, op) || op_is_register_target_dispatch(core, op); +} + +static void track_init(RzCore *core, RZ_NULLABLE const RustCallSeed *seed) { + rz_core_analysis_esil_init_mem(core, NULL, RUST_TRACK_MEM_ADDR, RUST_TRACK_MEM_SIZE); + rz_core_analysis_il_reinit(core); + + if (rz_asm_is_arch(core->rasm, "x86")) { + rz_analysis_il_vm_set_unsigned(core->analysis, "rbp", RUST_STACK_PTR); + rz_analysis_il_vm_set_unsigned(core->analysis, "rsp", RUST_STACK_PTR); + } else if (rz_asm_is_arch(core->rasm, "arm")) { + rz_analysis_il_vm_set_unsigned(core->analysis, "fp", RUST_STACK_PTR); + rz_analysis_il_vm_set_unsigned(core->analysis, "sp", RUST_STACK_PTR); + } else { + const char *arch = rz_core_get_arch(core); + RZ_LOG_WARN("arch %s is not supported\n", arch); + } + + if (!seed) { + return; + } + + for (ut64 i = 0; i < seed->count; i++) { + if (seed->has_value[i]) { + rz_analysis_il_vm_set_unsigned(core->analysis, seed->regs[i], seed->values[i]); + } + } +} + +static void track_fini(RzCore *core) { + rz_core_analysis_il_reinit(core); + rz_core_analysis_esil_init_mem_del(core, NULL, RUST_TRACK_MEM_ADDR, RUST_TRACK_MEM_SIZE); +} + +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); + if (!ht_virtual_xrefs) { + return; + } + + RzSetU *set = ht_sp_find(ht_virtual_xrefs, method_name, &found); + if (!found) { + set = rz_set_u_new(); + if (!set) { + return; + } + if (!ht_sp_insert(ht_virtual_xrefs, method_name, set)) { + rz_set_u_free(set); + return; + } + } + if (!set) { + return; + } + rz_set_u_add(set, addr); +} + +static void add_virtual_xrefs_for_method(RzCore *core, const char *method_name, ut64 method_addr, ut64 xref_addr) { + add_virtual_xrefs(core->analysis, method_name, xref_addr); + const RzList *flags = rz_flag_get_list(core->flags, method_addr); + RzListIter *it; + RzFlagItem *flag; + rz_list_foreach (flags, it, flag) { + if (RZ_STR_ISNOTEMPTY(flag->name) && strcmp(flag->name, method_name)) { + add_virtual_xrefs(core->analysis, flag->name, xref_addr); + } + } +} + +static bool type_id_data_matches(RzCore *core, ut64 addr, ut64 low, ut64 high, bool has_high) { + ut8 buf[RUST_TYPE_ID_BYTES] = { 0 }; + if (!addr || addr == UT64_MAX || !rz_io_read_at_mapped(core->io, addr, buf, sizeof(buf))) { + return false; + } + + bool big_endian = rz_asm_is_big_endian_set(core->rasm); + ut64 data_low = rz_read_ble64(buf, big_endian); + ut64 data_high = rz_read_ble64(buf + 8, big_endian); + if (has_high) { + return data_low == low && data_high == high; + } + return data_low == low || data_high == low; +} + +static bool op_mem_value_has_type_id(RzCore *core, RzAnalysisOp *op, RzAnalysisValue *value, ut64 low, ut64 high, bool has_high) { + if (!analysis_value_is_mem(value) || value->memref < RUST_TYPE_ID_BYTES) { + return false; + } + + ut64 addr = UT64_MAX; + return analysis_value_addr(core, op, value, &addr) && type_id_data_matches(core, addr, low, high, has_high); +} + +static bool op_has_scalar_imm(RzAnalysisOp *op, ut64 value) { + if (op->val != UT64_MAX && op->val == value) { + return true; + } + + for (ut64 i = 0; i < RZ_ARRAY_SIZE(op->analysis_vals); i++) { + if (op->analysis_vals[i].imm != ST64_MAX && op->analysis_vals[i].imm == value) { + return true; + } + } + return false; +} + +static bool op_has_type_id(RzCore *core, RzAnalysisOp *op, ut64 low, ut64 high, bool has_high) { + if (op->ptr && op->ptr != UT64_MAX && op->refptr >= RUST_TYPE_ID_BYTES && + type_id_data_matches(core, op->ptr, low, high, has_high)) { + return true; + } + + for (ut64 i = 0; i < RZ_ARRAY_SIZE(op->src); i++) { + if (op_mem_value_has_type_id(core, op, op->src[i], low, high, has_high)) { + return true; + } + } + if (op_mem_value_has_type_id(core, op, op->dst, low, high, has_high)) { + return true; + } + + return op_has_scalar_imm(op, low) || (has_high && op_has_scalar_imm(op, high)); +} + +static void annotate_any_type_id_compare(RzCore *core, RzAnalysisOp *op, RzVector /**/ *any_vtables) { + if (!any_vtables) { + return; + } + + ut32 type = op->type & RZ_ANALYSIS_OP_TYPE_MASK; + if (type != RZ_ANALYSIS_OP_TYPE_CMP && type != RZ_ANALYSIS_OP_TYPE_ACMP) { + return; + } + + RustAnyVTable *any; + rz_vector_foreach (any_vtables, any) { + if (!any->has_type_id) { + continue; + } + if (!op_has_type_id(core, op, any->type_id_low, any->type_id_high, any->has_type_id_high)) { + continue; + } + char *comment = rz_str_newf("Any downcast: %s", any->concrete_type); + if (comment) { + rz_core_meta_comment_add(core, comment, op->addr); + free(comment); + } + break; + } +} + +static void devirtualize_step(RzCore *core, RzAnalysisOp *op, RzVector /**/ *any_vtables) { + ut64 target = UT64_MAX; + ut64 vtable_addr = UT64_MAX; + RustAnyVTable *target_any = NULL; + bool target_is_any_type_id = false; + bool found = false; + if (op_is_vtable_slot_dispatch(core, op)) { + ut64 slot_addr = UT64_MAX; + vtable_addr = get_reg_value(core->analysis, op->reg); + if (!vtable_addr || vtable_addr == UT64_MAX) { + return; + } + + slot_addr = vtable_addr + op->disp; + if (slot_addr == UT64_MAX) { + return; + } + + if (is_real_reg_name(core, op->ireg)) { + ut64 index = get_reg_value(core->analysis, op->ireg); + if (index == UT64_MAX) { + return; + } + ut64 scale = op->scale; + if (!scale) { + scale = 1; + } + slot_addr += index * scale; + } + found = resolve_slot(core, vtable_addr, slot_addr, &target); + } else if (op_is_register_target_dispatch(core, op)) { + target = get_reg_value(core->analysis, op->reg); + if (!target || target == UT64_MAX) { + return; + } + + target_any = any_vtable_by_type_id_method(any_vtables, target); + if (!target_any) { + ut64 type_id_low = 0; + ut64 type_id_high = 0; + bool type_id_has_high = false; + if (rust_type_id_from_method(core, target, &type_id_low, &type_id_high, &type_id_has_high)) { + target_any = any_vtable_by_type_id_values(any_vtables, type_id_low, type_id_high, type_id_has_high); + } + } + if (target_any) { + vtable_addr = target_any->vtable_addr; + target_is_any_type_id = true; + } + found = true; + } else { + return; + } + + if (!found) { + return; + } + + char *method_name = get_method_name(core, target); + if (!method_name) { + return; + } + + RustAnyVTable *any = target_any; + if (!any) { + any = any_vtable_by_addr(any_vtables, vtable_addr); + } + char *comment = NULL; + if (any && (target_is_any_type_id || target == any->type_id_method)) { + comment = rz_str_newf("Any::type_id: %s", any->concrete_type); + } else { + comment = rz_str_newf("Virtual call: %s", method_name); + } + + if (comment) { + rz_core_meta_comment_add(core, comment, op->addr); + RZ_FREE(comment); + } + + add_virtual_xrefs_for_method(core, method_name, target, op->addr); + if (target_is_any_type_id && any->type_id_method != target) { + char *class_method_name = get_method_name(core, any->type_id_method); + if (class_method_name) { + add_virtual_xrefs_for_method(core, class_method_name, any->type_id_method, op->addr); + RZ_FREE(class_method_name); + } + } + RZ_FREE(method_name); +} + +static void clear_dst_reg_for_skipped_op(RzCore *core, RzAnalysisOp *op) { + const char *dst = op_dst_reg_name(op); + if (is_real_reg_name(core, dst)) { + rz_analysis_il_vm_set_unsigned(core->analysis, dst, 0); + } +} + +static void track_step_or_skip(RzCore *core, RzAnalysisOp *op, ut64 next_addr) { + if (!op->il_op || !op_memory_access_is_safe(core, op)) { + clear_dst_reg_for_skipped_op(core, op); + advance_il_pc(core, next_addr); + return; + } + + advance_il_pc(core, op->addr); + if (!rz_core_il_step(core, 1)) { + advance_il_pc(core, next_addr); + return; + } +} + +static bool function_has_rust_dispatch_candidate(RzCore *core, RzAnalysisFunction *function) { + ut64 start = function->addr; + ut64 end = rz_analysis_function_max_addr(function); + if (!addr_is_executable(core, start)) { + return false; + } + + ut8 *bytes = read_mapped_range(core, start, end); + if (!bytes) { + return false; + } + + bool found = false; + ut64 offset = 0; + RzAnalysisOp *op = rz_analysis_op_new(); + if (!op) { + RZ_FREE(bytes); + return false; + } + + while (start < end) { + if (rz_analysis_op(core->analysis, op, start, bytes + offset, end - start, RZ_ANALYSIS_OP_MASK_ALL) <= 0 || op->size < 1) { + break; + } + if (op_is_dispatch_candidate(core, op)) { + found = true; + rz_analysis_op_fini(op); + break; + } + start += op->size; + offset += op->size; + rz_analysis_op_fini(op); + } + + rz_analysis_op_free(op); + RZ_FREE(bytes); + return found; +} + +static void devirtualize_rust_function(RzCore *core, RzAnalysisFunction *function, RzVector /**/ *any_vtables, RZ_NULLABLE const RustCallSeed *seed) { + ut64 start = function->addr; + ut64 end = rz_analysis_function_max_addr(function); + if (!addr_is_executable(core, start)) { + return; + } + + RzAnalysisOp *op = rz_analysis_op_new(); + if (!op) { + return; + } + + ut8 *bytes = read_mapped_range(core, start, end); + if (!bytes) { + RZ_LOG_ERROR("Cannot read at offset 0x%08" PFMT64x "\n", start); + rz_analysis_op_free(op); + return; + } + + ut64 old_offset = core->offset; + ut64 offset = 0; + core->offset = start; + track_init(core, seed); + while (start < end) { + if (rz_analysis_op(core->analysis, op, start, bytes + offset, end - start, RZ_ANALYSIS_OP_MASK_ALL) <= 0 || op->size < 1) { + break; + } + + devirtualize_step(core, op, any_vtables); + annotate_any_type_id_compare(core, op, any_vtables); + ut64 next = start + op->size; + if (rz_analysis_op_is_eob(op) || rz_analysis_op_is_call(op)) { + advance_il_pc(core, next); + } else { + track_step_or_skip(core, op, next); + } + + start = next; + offset += op->size; + core->offset = start; + rz_analysis_op_fini(op); + } + + core->offset = old_offset; + track_fini(core); + rz_analysis_op_free(op); + RZ_FREE(bytes); +} + +static void caller_replay_step(RzCore *core, RzAnalysisOp *op, ut64 next_addr, const char *ret_reg, RZ_INOUT ut64 *next_scratch) { + if (rz_analysis_op_is_call(op)) { + if (is_real_reg_name(core, ret_reg)) { + ut64 heap_end = RUST_SCRATCH_HEAP_ADDR + RUST_SCRATCH_HEAP_SIZE; + if (*next_scratch <= heap_end - RUST_SCRATCH_ALLOC_STEP) { + rz_analysis_il_vm_set_unsigned(core->analysis, ret_reg, *next_scratch); + *next_scratch += RUST_SCRATCH_ALLOC_STEP; + } else { + rz_analysis_il_vm_set_unsigned(core->analysis, ret_reg, 0); + } + } + advance_il_pc(core, next_addr); + return; + } + + if (rz_analysis_op_is_eob(op)) { + advance_il_pc(core, next_addr); + return; + } + + track_step_or_skip(core, op, next_addr); +} + +static void collect_caller_args_from_site(RzCore *core, RzAnalysisFunction *caller, ut64 call_addr, RustArgRegs *args, RzVector /**/ *seeds) { + ut64 start = caller->addr; + ut64 end = RZ_MIN(call_addr, rz_analysis_function_max_addr(caller)); + if (!addr_is_executable(core, start)) { + return; + } + + ut8 *bytes = read_mapped_range(core, start, end); + if (!bytes) { + return; + } + + RzAnalysisOp *op = rz_analysis_op_new(); + if (!op) { + RZ_FREE(bytes); + return; + } + + RustCallSeed seed = { + .count = args->count, + }; + + for (ut64 i = 0; i < args->count; i++) { + seed.regs[i] = args->regs[i]; + } + + const char *cc = rz_analysis_cc_default(core->analysis); + const char *ret_reg = rz_analysis_cc_ret(core->analysis, cc); + ut64 old_offset = core->offset; + ut64 next_scratch = RUST_SCRATCH_HEAP_ADDR; + ut64 offset = 0; + core->offset = start; + track_init(core, NULL); + while (start < end) { + if (rz_analysis_op(core->analysis, op, start, bytes + offset, end - start, RZ_ANALYSIS_OP_MASK_ALL) <= 0 || op->size < 1) { + break; + } + + ut64 next = start + op->size; + caller_replay_step(core, op, next, ret_reg, &next_scratch); + start = next; + offset += op->size; + core->offset = start; + rz_analysis_op_fini(op); + } + + for (ut64 i = 0; i < args->count; i++) { + ut64 value = get_reg_value(core->analysis, args->regs[i]); + if (value && value != UT64_MAX) { + seed.values[i] = value; + seed.has_value[i] = true; + } + } + + push_unique_seed(core, seeds, &seed); + core->offset = old_offset; + track_fini(core); + rz_analysis_op_free(op); + RZ_FREE(bytes); +} + +static RZ_OWN RzVector /**/ *collect_caller_args(RzCore *core, RzAnalysisFunction *function, RustArgRegs *args) { + RzVector *seeds = rz_vector_new(sizeof(RustCallSeed), NULL, NULL); + if (!seeds) { + return NULL; + } + + RzList *xrefs = rz_analysis_xrefs_get_to(core->analysis, function->addr); + if (!xrefs) { + rz_vector_free(seeds); + return NULL; + } + + RzListIter *it; + RzAnalysisXRef *xref; + rz_list_foreach (xrefs, it, xref) { + if (xref->type != RZ_ANALYSIS_XREF_TYPE_CALL && xref->type != RZ_ANALYSIS_XREF_TYPE_CODE) { + continue; + } + RzAnalysisFunction *caller = rz_analysis_get_fcn_in(core->analysis, xref->from, RZ_ANALYSIS_FCN_TYPE_NULL); + if (!caller) { + continue; + } + collect_caller_args_from_site(core, caller, xref->from, args, seeds); + } + rz_list_free(xrefs); + + if (rz_vector_empty(seeds)) { + rz_vector_free(seeds); + return NULL; + } + return seeds; +} + +static void devirtualize_rust_trait_object(RzCore *core) { + RzAnalysisFunction *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; + } + + if (!function_has_rust_dispatch_candidate(core, function)) { + return; + } + + RustArgRegs args = { 0 }; + get_arg_regs(core, function, &args); + RzVector *any_vtables = rust_any_vtables_from_classes(core); + devirtualize_rust_function(core, function, any_vtables, NULL); + + RzVector *seeds = NULL; + if (args.count) { + seeds = collect_caller_args(core, function, &args); + } + RustCallSeed *seed; + if (seeds) { + rz_vector_foreach (seeds, seed) { + devirtualize_rust_function(core, function, any_vtables, seed); + } + } + + rz_vector_free(seeds); + rz_vector_free(any_vtables); +} + +/** + * \brief devirtualize Rust trait object calls in the current function + */ +RZ_IPI void rz_core_analysis_devirtualize_rust_methods(RZ_NULLABLE RzCore *core) { + if (!core) { + return; + } + devirtualize_rust_trait_object(core); +} diff --git a/librz/core/meson.build b/librz/core/meson.build index 9db4c9736d4..cd7e2342eee 100644 --- a/librz/core/meson.build +++ b/librz/core/meson.build @@ -53,6 +53,7 @@ rz_core_sources = [ 'cvfile.c', 'devirtualize_cxx.c', 'devirtualize_objc.c', + 'devirtualize_rust.c', 'disasm.c', 'fortune.c', 'golang.c', diff --git a/librz/include/rz_analysis.h b/librz/include/rz_analysis.h index f6782431f75..797337cea86 100644 --- a/librz/include/rz_analysis.h +++ b/librz/include/rz_analysis.h @@ -2093,6 +2093,7 @@ RZ_API bool rz_analysis_rtti_msvc_print_at_vtable(RVTableContext *context, ut64 RZ_API void rz_analysis_rtti_msvc_recover_all(RVTableContext *vt_context, RzList /**/ *vtables); RZ_API void rz_analysis_rtti_swift(RzAnalysis *analysis); RZ_API void rz_analysis_rtti_objc(RZ_NONNULL RzAnalysis *analysis); +RZ_API void rz_analysis_rtti_rust(RZ_NONNULL RzAnalysis *analysis); RZ_API char *rz_analysis_rtti_itanium_demangle_class_name(RVTableContext *context, const char *name); RZ_API bool rz_analysis_rtti_itanium_print_at_vtable(RVTableContext *context, ut64 addr, RzOutputMode mode); diff --git a/test/db/cmd/cmd_ac b/test/db/cmd/cmd_ac index a404b89e1e5..5fbae7fd3a1 100644 --- a/test/db/cmd/cmd_ac +++ b/test/db/cmd/cmd_ac @@ -639,3 +639,73 @@ attr.Flaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa attrtypes.Flaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaatline=vtable,method EOF RUN + +NAME=Rust tree traversal +FILE=bins/abi_bins/elf/languages/rust/tree_traversal +CMDS=<::say::h068f985a4f49ea94 +impl_rust_any::Dog_as_rust_any::Pet: + 0 vtable 0x512f8 @ +0x0 size:+0x20 +EOF +RUN \ No newline at end of file diff --git a/test/db/cmd/cmd_acll b/test/db/cmd/cmd_acll index 6bd98a8d2a6..e7c73f2447f 100644 --- a/test/db/cmd/cmd_acll +++ b/test/db/cmd/cmd_acll @@ -597,4 +597,4 @@ nth addr vt_offset type name 1 0x100003e48 ---------- DEFAULT speak EOF -RUN \ No newline at end of file +RUN diff --git a/test/db/cmd/cmd_avD b/test/db/cmd/cmd_avD index 5ce577a795c..bfe46803039 100644 --- a/test/db/cmd/cmd_avD +++ b/test/db/cmd/cmd_avD @@ -1258,4 +1258,205 @@ EXPECT=<(struct &mut [i32] values) +| ; var int64_t arg1 @ rdi +| ; var int64_t arg2 @ rsi +| ; var long long unsigned int arg3 @ stack - 0xb8 +| ; var int64_t var_a9h @ stack - 0xa9 +| ; var int64_t var_a8h @ stack - 0xa8 +| ; var int64_t var_a0h @ stack - 0xa0 +| ; var int64_t arg4 @ stack - 0x98 +| ; var int64_t var_90h @ stack - 0x90 +| ; var int64_t var_88h @ stack - 0x88 +| ; var int64_t var_80h @ stack - 0x80 +| ; var long long unsigned int var_78h @ stack - 0x78 +| ; var long long unsigned int var_70h @ stack - 0x70 +| ; var int64_t var_68h @ stack - 0x68 +| ; var int64_t var_59h @ stack - 0x59 +| ; var int64_t var_58h @ stack - 0x58 +| ; var int64_t var_50h @ stack - 0x50 +| ; var int64_t arg_47h @ stack - 0x48 +| ; var int64_t var_40h @ stack - 0x40 +| ; var int64_t var_38h @ stack - 0x38 +| ; var int64_t var_30h @ stack - 0x30 +| ; var int64_t var_28h @ stack - 0x28 +| ; var int64_t var_20h @ stack - 0x20 +| ; var int64_t var_18h @ stack - 0x18 +| ; var int64_t var_10h @ stack - 0x10 +| ; var int64_t var_8h @ stack - 0x8 +| ; arg struct &mut [i32] values @ stack + 0x90 +| 0x00005270 sub rsp, 0xb8 ; rust.rs:1 ; rust::bubble_sort::h0777bc845caabc60 +| 0x00005277 mov qword [var_28h], rdi ; arg1 +| 0x0000527f mov qword [var_20h], rsi ; arg2 +| 0x00005287 mov qword [var_70h], rdi ; arg1 +| 0x0000528c mov qword [var_78h], rsi ; arg2 +| 0x00005291 call dbg.len ; rust.rs:2 ; usize len(struct &[i32] self) +| 0x00005296 mov qword [var_68h], rax +| 0x0000529b mov byte [var_59h], 0x01 ; rust.rs:3 +| ; CODE XREF from dbg.bubble_sort @ 0x5441 +| .-> 0x000052a0 test byte [var_59h], 0x01 ; rust.rs:5 +| ,==< 0x000052a5 jnz 0x52af +| |: 0x000052a7 add rsp, 0xb8 ; rust.rs:17 +| |: 0x000052ae ret +| `--> 0x000052af mov byte [var_59h], 0x00 ; rust.rs:6 +| : 0x000052b4 mov rax, qword [var_68h] ; rust.rs:8 +| : 0x000052b9 mov qword [var_58h], 0x01 +| : 0x000052c2 mov qword [var_50h], rax +| : 0x000052c7 mov rdi, qword [var_58h] ; int64_t arg1 +| : 0x000052cc mov rsi, qword [var_50h] ; int64_t arg2 +| : 0x000052d1 call dbg.into_iter> ; struct Range into_iter>(struct Range self) +| : 0x000052d6 mov qword [var_80h], rax +| : 0x000052db mov qword [var_88h], rdx +| : 0x000052e0 mov rax, qword [var_80h] ; rust.rs +| : 0x000052e5 mov qword [arg_47h], rax ; rust.rs:8 +| : 0x000052ea mov rcx, qword [var_88h] +| : 0x000052ef mov qword [var_40h], rcx +| : ; CODE XREF from dbg.bubble_sort @ 0x5432 +| .--> 0x000052f4 lea rdi, qword [arg_47h] ; rust.rs ; int64_t arg_47h +| :: 0x000052f9 call dbg.next ; rust.rs:8 ; struct Option next(struct Range *self) +| :: 0x000052fe mov qword [var_30h], rdx +| :: 0x00005306 mov qword [var_38h], rax +| :: 0x0000530e mov rax, qword [var_38h] +| :: 0x00005316 test rax, rax +| ,===< 0x00005319 jz 0x531f +| ,====< 0x0000531b jmp 0x531d +| ||:: ; CODE XREF from dbg.bubble_sort @ 0x531b +| ,`----> 0x0000531d jmp 0x5342 +| | `---> 0x0000531f mov rax, qword [var_68h] ; rust.rs:15 +| | :: 0x00005324 sub rax, 0x01 +| | :: 0x0000532a setb cl +| | :: 0x0000532d test cl, 0x01 ; r8 ; "ELF\U00000002\U00000001\U00000001" +| | :: 0x00005330 mov qword [var_90h], rax +| | ,===< 0x00005335 jnz 0x54ba +| |,====< 0x0000533b jmp 0x5437 +.. +| |||:: ; CODE XREF from dbg.bubble_sort @ 0x531d +| `-----> 0x00005342 mov rax, qword [var_30h] ; rust.rs:8 +| ||:: 0x0000534a mov qword [var_18h], rax +| ||:: 0x00005352 mov qword [var_10h], rax +| ||:: 0x0000535a mov qword [var_8h], rax +| ||:: 0x00005362 mov rcx, rax ; rust.rs:9 +| ||:: 0x00005365 sub rcx, 0x01 +| ||:: 0x0000536c setb dl +| ||:: 0x0000536f test dl, 0x01 ; r8 ; "ELF\U00000002\U00000001\U00000001" +| ||:: 0x00005372 mov qword [arg4], rax +| ||:: 0x00005377 mov qword [var_a0h], rcx +| ,=====< 0x0000537c jnz 0x5446 +| |||:: 0x00005382 mov rax, qword [var_a0h] ; rust.rs +| |||:: 0x00005387 mov rcx, qword [var_78h] +| |||:: 0x0000538c cmp rax, rcx ; rust.rs:9 +| |||:: 0x0000538f setb dl +| |||:: 0x00005392 test dl, 0x01 ; r8 ; "ELF\U00000002\U00000001\U00000001" +| ,======< 0x00005395 jnz 0x539c +| ,=======< 0x00005397 jmp 0x5464 +| |`------> 0x0000539c mov rax, qword [var_a0h] ; rust.rs +| | |||:: 0x000053a1 shl rax, 0x02 ; rust.rs:9 +| | |||:: 0x000053a5 mov rcx, qword [var_70h] +| | |||:: 0x000053aa add rcx, rax +| | |||:: 0x000053ad mov rax, qword [arg4] +| | |||:: 0x000053b2 mov rdx, qword [var_78h] +| | |||:: 0x000053b7 cmp rax, rdx +| | |||:: 0x000053ba setb sil +| | |||:: 0x000053be test sil, 0x01 ; r8 ; "ELF\U00000002\U00000001\U00000001" +| | |||:: 0x000053c2 mov qword [var_a8h], rcx +| |,======< 0x000053c7 jnz 0x53ce +| ========< 0x000053c9 jmp 0x5480 +| |`------> 0x000053ce mov rax, qword [arg4] ; rust.rs +| | |||:: 0x000053d3 shl rax, 0x02 ; rust.rs:9 +| | |||:: 0x000053d7 mov rcx, qword [var_70h] +| | |||:: 0x000053dc add rcx, rax +| | |||:: 0x000053df mov rdi, qword [var_a8h] ; int64_t arg1 +| | |||:: 0x000053e4 mov rsi, rcx ; long long unsigned int arg2 +| | |||:: 0x000053e7 call dbg.gt +| | |||:: 0x000053ec mov byte [var_a9h], al +| | |||:: 0x000053f0 mov al, byte [var_a9h] ; rust.rs +| | |||:: 0x000053f4 test al, 0x01 ; rust.rs:9 ; r8 ; "ELF\U00000002\U00000001\U00000001" +| |,======< 0x000053f6 jnz 0x53fa +| ========< 0x000053f8 jmp 0x5432 +| |`------> 0x000053fa mov rax, qword [arg4] ; rust.rs +| | |||:: 0x000053ff sub rax, 0x01 ; rust.rs:10 +| | |||:: 0x00005405 setb cl +| | |||:: 0x00005408 test cl, 0x01 ; r8 ; "ELF\U00000002\U00000001\U00000001" +| | |||:: 0x0000540b mov qword [rsp], rax +| |,======< 0x0000540f jnz 0x549c +| |||||:: 0x00005415 mov rdi, qword [var_70h] ; rust.rs ; int64_t arg1 +| |||||:: 0x0000541a mov rsi, qword [var_78h] ; long long unsigned int arg2 +| |||||:: 0x0000541f mov rdx, qword [rsp] ; long long unsigned int arg3 +| |||||:: 0x00005423 mov rcx, qword [arg4] ; int64_t arg4 +| |||||:: 0x00005428 call dbg.swap ; rust.rs:10 ; swap(struct &mut [i32] self, usize a, usize b) +| |||||:: 0x0000542d mov byte [var_59h], 0x01 ; rust.rs:11 +| ||||||: ; CODE XREF from dbg.bubble_sort @ 0x53f8 +| -----`==< 0x00005432 jmp 0x52f4 ; rust.rs:8 +| ||||| : ; CODE XREF from dbg.bubble_sort @ 0x533b +| |||`----> 0x00005437 mov rax, qword [var_90h] ; rust.rs +| ||| | : 0x0000543c mov qword [var_68h], rax ; rust.rs:15 +| ||| | `=< 0x00005441 jmp 0x52a0 ; rust.rs:5 +| ||| | ; DATA XREF from method._rustc_demangle::legacy::Demangle_as_core::fmt::Display_::fmt.hbbd9e1822966e201 @ 0x2385c +| ||| | ;-- (0x00005447) data.00005447: +| ||`-----> 0x00005446 ~ lea rdi, qword obj.str.0 ; rust.rs:9 ; 0x2a010 ; "attempt to subtract with overflowBefore: \nAfter: beachartcardealempty/usr/src/rustc-1.43.0/src/libcore/slice/mod.rscalled `Opti" +| || | 0x0000544d lea rdx, qword sym..data.rel.ro ; 0x382b8 ; "" +| || | 0x00005454 lea rax, qword [dbg.panic] ; 0x25930 ; "H\x83\xecHH\x89|$\bH\x89t$\U00000010H\x8dD$\bH\x89D$\U00000018H\xc7D$ \U00000001" +| || | 0x0000545b mov esi, 0x21 ; '!' +| || | 0x00005460 call rax ; Virtual call: dbg.panic +| || | 0x00005462 ud2 +| || | ; CODE XREF from dbg.bubble_sort @ 0x5397 +| `-------> 0x00005464 lea rdi, qword data.000382d0 ; 0x382d0 ; "" +| | | 0x0000546b lea rax, qword [dbg.panic_bounds_check] ; 0x25980 ; "H\x83\xechH\x89\xf8H\x89t$\bH\x89T$\U00000010H\x8dL$\U00000010H\x89L$\U00000018H\x8d\r\U0000001e\xfe\xff\xffH\x89L$ H\x8dT$\bH\x" +| | | 0x00005472 mov rsi, qword [var_a0h] +| | | 0x00005477 mov rdx, qword [var_78h] +| | | 0x0000547c call rax ; Virtual call: dbg.panic_bounds_check +| | | 0x0000547e ud2 +| | | ; CODE XREF from dbg.bubble_sort @ 0x53c9 +| --------> 0x00005480 lea rdi, qword data.000382e8 ; 0x382e8 ; "" +| | | 0x00005487 lea rax, qword [dbg.panic_bounds_check] ; 0x25980 ; "H\x83\xechH\x89\xf8H\x89t$\bH\x89T$\U00000010H\x8dL$\U00000010H\x89L$\U00000018H\x8d\r\U0000001e\xfe\xff\xffH\x89L$ H\x8dT$\bH\x" +| | | 0x0000548e mov rsi, qword [arg4] +| | | 0x00005493 mov rdx, qword [var_78h] +| | | 0x00005498 call rax ; Virtual call: dbg.panic_bounds_check +| | | 0x0000549a ud2 +| `------> 0x0000549c lea rdi, qword obj.str.0 ; rust.rs:10 ; 0x2a010 ; "attempt to subtract with overflowBefore: \nAfter: beachartcardealempty/usr/src/rustc-1.43.0/src/libcore/slice/mod.rscalled `Opti" +| | 0x000054a3 lea rdx, qword data.00038300 ; 0x38300 ; "" +| | 0x000054aa lea rax, qword [dbg.panic] ; 0x25930 ; "H\x83\xecHH\x89|$\bH\x89t$\U00000010H\x8dD$\bH\x89D$\U00000018H\xc7D$ \U00000001" +| | 0x000054b1 mov esi, 0x21 ; '!' +| | 0x000054b6 call rax ; Virtual call: dbg.panic +| | 0x000054b8 ud2 +| `---> 0x000054ba lea rdi, qword obj.str.0 ; rust.rs:15 ; 0x2a010 ; "attempt to subtract with overflowBefore: \nAfter: beachartcardealempty/usr/src/rustc-1.43.0/src/libcore/slice/mod.rscalled `Opti" +| 0x000054c1 lea rdx, qword data.00038318 ; 0x38318 ; "" +| 0x000054c8 lea rax, qword [dbg.panic] ; 0x25930 ; "H\x83\xecHH\x89|$\bH\x89t$\U00000010H\x8dD$\bH\x89D$\U00000018H\xc7D$ \U00000001" +| 0x000054cf mov esi, 0x21 ; '!' +| 0x000054d4 call rax ; Virtual call: dbg.panic +\ 0x000054d6 ud2 +EOF RUN \ No newline at end of file