// SPDX-FileCopyrightText: 2019-2020 pancake // SPDX-License-Identifier: LGPL-3.0-only /* This code has been based on Alvaro's * rzpipe-python script which was based on FireEye script for IDA Pro. * * https://www.fireeye.com/blog/threat-research/2017/03/introduction_to_reve.html */ #include #include "core_private.h" typedef struct { RzCore *core; HtUP *up; size_t word_size; RzBinSection *_selrefs; RzBinSection *_msgrefs; RzBinSection *_const; RzBinSection *_data; } RzCoreObjc; const size_t objc2ClassSize = 0x28; const size_t objc2ClassInfoOffs = 0x20; const size_t objc2ClassMethSize = 0x18; const size_t objc2ClassBaseMethsOffs = 0x20; const size_t objc2ClassMethImpOffs = 0x10; static void array_add(RzCoreObjc *o, ut64 va, ut64 xrefs_to) { bool found = false; RzVector *vec = ht_up_find(o->up, va, &found); if (!found || !vec) { vec = rz_vector_new(sizeof(ut64), NULL, NULL); ht_up_insert(o->up, va, vec); } ut64 *addr; rz_vector_foreach(vec, addr) { if (xrefs_to == *addr) { return; } } // extend vector and insert new element rz_vector_push(vec, &xrefs_to); } static void kv_array_free(HtUPKv *kv) { rz_vector_free(kv->value); } static inline bool isValid(ut64 addr) { return (addr != 0LL && addr != UT64_MAX); } static inline bool isInvalid(ut64 addr) { return !isValid(addr); } static inline bool inBetween(RzBinSection *s, ut64 addr) { if (!s || isInvalid(addr)) { return false; } const ut64 from = s->vaddr; const ut64 to = from + s->vsize; return RZ_BETWEEN(from, addr, to); } static ut32 readDword(RzCoreObjc *objc, ut64 addr, bool *success) { ut8 buf[4]; *success = rz_io_read_at(objc->core->io, addr, buf, sizeof(buf)); return rz_read_le32(buf); } static ut64 readQword(RzCoreObjc *objc, ut64 addr, bool *success) { ut8 buf[8] = { 0 }; *success = rz_io_read_at(objc->core->io, addr, buf, sizeof(buf)); return rz_read_le64(buf); } static void objc_analyze(RzCore *core) { const char *notify = "Analyzing code to find selfref references"; rz_core_notify_begin(core, "%s", notify); (void)rz_core_analysis_refs(core, 0); if (!strcmp("arm", rz_config_get(core->config, "asm.arch"))) { const bool emu_lazy = rz_config_get_i(core->config, "emu.lazy"); rz_config_set_i(core->config, "emu.lazy", true); rz_core_analysis_esil_default(core); rz_config_set_i(core->config, "emu.lazy", emu_lazy); } rz_core_notify_done(core, "%s", notify); } static ut64 getRefPtr(RzCoreObjc *o, ut64 classMethodsVA, bool *rfound) { *rfound = false; bool readSuccess; ut64 namePtr = readQword(o, classMethodsVA, &readSuccess); if (!readSuccess) { return UT64_MAX; } size_t cnt = 0; ut64 ref = UT64_MAX; bool isMsgRef = false; RzVector *vec = ht_up_find(o->up, namePtr, rfound); if (!*rfound || !vec) { *rfound = false; return false; } ut64 *addr; rz_vector_foreach(vec, addr) { const ut64 at = *addr; if (inBetween(o->_selrefs, at)) { isMsgRef = false; ref = at; } else if (inBetween(o->_msgrefs, at)) { isMsgRef = true; ref = at; } else if (inBetween(o->_const, at)) { cnt++; } } if (cnt > 1 || ref == 0 || ref == UT64_MAX) { *rfound = false; return UT64_MAX; } return isMsgRef ? ref - 8 : ref; } static bool objc_build_refs(RzCoreObjc *objc) { ut64 off; rz_return_val_if_fail(objc->_const && objc->_selrefs, false); const ut64 va_const = objc->_const->vaddr; size_t ss_const = objc->_const->vsize; const ut64 va_selrefs = objc->_selrefs->vaddr; size_t ss_selrefs = objc->_selrefs->vsize; // TODO: check if ss_const or ss_selrefs are too big before going further size_t maxsize = RZ_MAX(ss_const, ss_selrefs); ut8 *buf = calloc(1, maxsize); if (!buf) { return false; } const size_t word_size = objc->word_size; // assuming 8 because of the read_le64 if (!rz_io_read_at(objc->core->io, objc->_const->vaddr, buf, ss_const)) { RZ_LOG_ERROR("aao: Cannot read the whole const section %zu\n", ss_const); return false; } for (off = 0; off + word_size < ss_const; off += word_size) { ut64 va = va_const + off; ut64 xrefs_to = rz_read_le64(buf + off); if (isValid(xrefs_to)) { array_add(objc, va, xrefs_to); } } if (!rz_io_read_at(objc->core->io, va_selrefs, buf, ss_selrefs)) { RZ_LOG_ERROR("aao: Cannot read the whole selrefs section\n"); return false; } for (off = 0; off + word_size < ss_selrefs; off += word_size) { ut64 va = va_selrefs + off; ut64 xrefs_to = rz_read_le64(buf + off); if (isValid(xrefs_to)) { array_add(objc, xrefs_to, va); } } free(buf); return true; } static RzCoreObjc *core_objc_new(RzCore *core) { RzList *sections = rz_bin_get_sections(core->bin); if (!sections) { return false; } RzCoreObjc *o = RZ_NEW0(RzCoreObjc); o->core = core; o->word_size = (core->rasm->bits == 64) ? 8 : 4; if (o->word_size != 8) { RZ_LOG_WARN("aao is experimental on 32bit binaries\n"); } RzBinSection *s; RzListIter *iter; rz_list_foreach (sections, iter, s) { const char *name = s->name; if (strstr(name, "__objc_data")) { o->_data = s; } else if (strstr(name, "__objc_selrefs")) { o->_selrefs = s; } else if (strstr(name, "__objc_msgrefs")) { o->_msgrefs = s; } else if (strstr(name, "__objc_const")) { o->_const = s; } } if (!o->_const || ((o->_selrefs || o->_msgrefs) && !(o->_data && o->_const))) { free(o); return NULL; } o->up = ht_up_new(NULL, kv_array_free, NULL); return o; } static void core_objc_free(RzCoreObjc *o) { if (o) { ht_up_free(o->up); free(o); } } static bool objc_find_refs(RzCore *core) { RzCoreObjc *objc = core_objc_new(core); if (!objc) { RZ_LOG_DEBUG("Could not find necessary Objective-C sections...\n"); return false; } if (!objc_build_refs(objc)) { core_objc_free(objc); return false; } const char *notify = "Parsing metadata in ObjC to find hidden xrefs"; rz_core_notify_begin(core, "%s", notify); size_t total_xrefs = 0; bool readSuccess = true; for (ut64 off = 0; off < objc->_data->vsize && readSuccess; off += objc2ClassSize) { if (!readSuccess || rz_cons_is_breaked()) { break; } ut64 va = objc->_data->vaddr + off; // XXX do a single rz_io_read_at() and just rz_read_le64() here ut64 classRoVA = readQword(objc, va + objc2ClassInfoOffs, &readSuccess); if (!readSuccess || isInvalid(classRoVA)) { continue; } ut64 classMethodsVA = readQword(objc, classRoVA + objc2ClassBaseMethsOffs, &readSuccess); if (!readSuccess || isInvalid(classMethodsVA)) { continue; } ut32 count = readDword(objc, classMethodsVA + 4, &readSuccess); if (!readSuccess || ((ut32)count == UT32_MAX)) { continue; } classMethodsVA += 8; // advance to start of class methods array ut64 to = classMethodsVA + (objc2ClassMethSize * count); if (classMethodsVA > to || classMethodsVA + 0xfffff < to) { RZ_LOG_WARN("objc: the input binary might be malformed or this could be a bug.\n"); continue; } for (va = classMethodsVA; va < to; va += objc2ClassMethSize) { if (rz_cons_is_breaked()) { break; } bool found = false; ut64 selRefVA = getRefPtr(objc, va, &found); if (!found) { continue; } bool succ = false; ut64 funcVA = readQword(objc, va + objc2ClassMethImpOffs, &succ); if (!succ) { break; } RzList *list = rz_analysis_xrefs_get_to(core->analysis, selRefVA); if (list) { RzListIter *iter; RzAnalysisXRef *xref; rz_list_foreach (list, iter, xref) { rz_analysis_xrefs_set(core->analysis, xref->from, funcVA, RZ_ANALYSIS_XREF_TYPE_CODE); total_xrefs++; } } } } rz_core_notify_done(core, "%s", notify); const ut64 va_selrefs = objc->_selrefs->vaddr; const ut64 ss_selrefs = va_selrefs + objc->_selrefs->vsize; rz_core_notify_begin(core, "Found %zu objc xrefs...", total_xrefs); size_t total_words = 0; const size_t word_size = objc->word_size; for (ut64 a = va_selrefs; a < ss_selrefs; a += word_size) { rz_meta_set(core->analysis, RZ_META_TYPE_DATA, a, word_size, NULL); total_words++; } rz_core_notify_done(core, "Found %zu objc xrefs in %zu dwords.", total_xrefs, total_words); core_objc_free(objc); return true; } RZ_API bool cmd_analysis_objc(RzCore *core, bool auto_analysis) { rz_return_val_if_fail(core, 0); if (!auto_analysis) { objc_analyze(core); } return objc_find_refs(core); }