/* radare - LGPL - Copyright 2009-2023 - pancake, nibble */ #include #include #include R_LIB_VERSION (r_sign); #define SIGN_DIFF_MATCH_BYTES_THRESHOLD 1.0 #define SIGN_DIFF_MATCH_GRAPH_THRESHOLD 1.0 R_VEC_TYPE (RVecAnalRef, RAnalRef); static inline const char *get_xrefname(RCore *core, ut64 addr) { RAnalFunction *f = r_anal_get_fcn_in (core->anal, addr, 0); if (f) { return f->name; } return NULL; } static const char *get_refname(RCore *core, ut64 addr) { RFlagItem *item; RListIter *iter; const RList *list = r_flag_get_list (core->flags, addr); if (!list) { return NULL; } r_list_foreach (list, iter, item) { if (!item->name || !r_str_startswith (item->name, "sym.")) { continue; } return item->name; } return NULL; } static int list_str_cmp(const void *a, const void *b) { // prevent silent failure if RListComparator changes return strcmp ((const char *)a, (const char *)b); } static ut64 valstr(const void *_a) { const char *a = _a; return r_str_hash64 (a); } R_API RList *r_sign_fcn_xrefs(RAnal *a, RAnalFunction *fcn) { r_return_val_if_fail (a && fcn, NULL); RCore *core = a->coreb.core; if (!core) { return NULL; } RList *ret = r_list_newf ((RListFree) free); RVecAnalRef *xrefs = r_anal_xrefs_get (a, fcn->addr); if (!xrefs) { return ret; } RAnalRef *refi; R_VEC_FOREACH (xrefs, refi) { // RAnalRefType rt = R_ANAL_REF_TYPE_MASK (refi->type); // if (rt == R_ANAL_REF_TYPE_CODE || rt == R_ANAL_REF_TYPE_CALL) { const char *flag = get_xrefname (core, refi->addr); if (flag) { r_list_append (ret, strdup (flag)); } // } } r_list_uniq (ret, valstr); RVecAnalRef_free (xrefs); return ret; } R_API RList *r_sign_fcn_refs(RAnal *a, RAnalFunction *fcn) { RAnalRef *refi = NULL; r_return_val_if_fail (a && fcn, NULL); RCore *core = a->coreb.core; if (!core) { return NULL; } RList *ret = r_list_newf ((RListFree) free); RVecAnalRef *refs = r_anal_function_get_refs (fcn); if (!refs) { return ret; } R_VEC_FOREACH (refs, refi) { // RAnalRefType rt = R_ANAL_REF_TYPE_MASK (refi->type); // if (rt == R_ANAL_REF_TYPE_CODE || rt == R_ANAL_REF_TYPE_CALL) { const char *flag = get_refname (core, refi->addr); if (flag) { r_list_append (ret, strdup (flag)); } // } } RVecAnalRef_free (refs); return ret; } static RSignBytes *des_bytes_norm(const char *in) { // "444444:ffffff" or "44444444" RSignBytes *b = R_NEW0 (RSignBytes); if (b && (b->size = r_hex_str2bin_until_new (in, &b->bytes)) > 0) { in += 2 * b->size; if (!*in && (b->mask = malloc (b->size))) { // no mask, set it to f's memset (b->mask, 0xff, b->size); return b; } if (*in++ == ':') { // get mask int size = r_hex_str2bin_until_new (in, &b->mask); in += 2 * size; if (size == b->size && !*in) { return b; } } } r_sign_bytes_free (b); return NULL; } static RSignBytes *deserialize_bytes(const char *in) { RSignBytes *b = des_bytes_norm (in); if (b) { return b; } // "44..44.." b = R_NEW0 (RSignBytes); size_t len = strlen (in) + 3; b->bytes = malloc (len); b->mask = malloc (len); if (b && b->bytes && b->mask) { b->size = r_hex_str2binmask (in, b->bytes, b->mask); if (b->size > 0) { return b; } } r_sign_bytes_free (b); return NULL; } static RSignBytes *deserialize_anal(RAnal *a, const char *in) { RSignBytes *b = R_NEW0 (RSignBytes); if (b && (b->size = r_hex_str2bin_until_new (in, &b->bytes)) > 0) { in += 2 * b->size; if (!*in && (b->mask = r_anal_mask (a, b->size, b->bytes, 0))) { return b; } } r_sign_bytes_free (b); return NULL; } static inline RList *sign_vars(RAnalFunction *fcn) { RList *l = r_anal_var_get_prots (fcn); if (l && r_list_empty (l)) { r_list_free (l); l = NULL; } return l; } // TODO: use primitives from r_types #define ALPH(x) (x >= 'a' && x <= 'z') || (x >= 'A' && x <= 'Z') #define VALID_TOKEN_CHR(x) (ALPH (x) || IS_DIGIT (x) || x == '_' || x == '*' || x == ' ' || x == '.') static bool types_sig_valid(const char *types) { // quick state machine parser to validate types being sent to tcc_compile int state = 0; // before, inside, or after () char ch; const char *t = types; int paren_cnt = 0; while ((ch = *(t++)) && state >= 0) { switch (state) { case 0: if (ch == '(') { state++; } else if (!VALID_TOKEN_CHR (ch) && ch != '.') { state = -1; } break; case 1: if (ch == '(') { paren_cnt++; } else if (ch == ')') { if (paren_cnt == 0) { state++; } else if (paren_cnt > 0) { paren_cnt--; } else { state = -1; } } else if (!VALID_TOKEN_CHR (ch) && ch != ',') { state = -1; } break; case 2: state = -1; break; } } return state == 2? true: false; } #undef ALPH #undef VALID_TOKEN_CHR #define DBL_VAL_FAIL(x,y) \ if (x) { \ R_LOG_WARN ("Skipping signature with multiple %c signatures (%s)", y, k); \ success = false; \ goto out; \ } R_API bool r_sign_deserialize(RAnal *a, RSignItem *it, const char *k, const char *v) { r_return_val_if_fail (a && it && k && v, false); bool success = true; char *k2 = r_str_new (k); char *v2 = r_str_new (v); if (!k2 || !v2) { success = false; goto out; } // Deserialize key: zign|space|name int n = r_str_split (k2, '|'); if (n != 3) { R_LOG_WARN ("Skipping signature with invalid key (%s)", k); success = false; goto out; } if (strcmp (r_str_word_get0 (k2, 0), "zign")) { R_LOG_WARN ("Skipping signature with invalid value (%s)", k); success = false; goto out; } it->space = r_spaces_add (&a->zign_spaces, r_str_word_get0 (k2, 1)); it->name = r_str_new (r_str_word_get0 (k2, 2)); // remove newline at end char *save_ptr = NULL; r_str_tok_r (v2, "\n", &save_ptr); // Deserialize value: |k:v|k:v|k:v|... n = r_str_split (v2, '|'); const char *token = NULL; int w; for (w = 0; w < n; w++) { const char *word = r_str_word_get0 (v2, w); if (!word) { break; } if (!*word) { continue; } token = word + 2; if (!strcmp (word, "*")) { continue; } if (strlen (word) < 3 || word[1] != ':') { R_LOG_WARN ("Skipping signature with corrupted serialization (%s:%s)", k, word); success = false; goto out; } RSignType st = (RSignType)*word; switch (st) { case R_SIGN_NAME: DBL_VAL_FAIL (it->realname, R_SIGN_NAME); it->realname = strdup (token); break; case R_SIGN_COMMENT: DBL_VAL_FAIL (it->comment, R_SIGN_COMMENT); it->comment = strdup (token); break; case R_SIGN_NEXT: DBL_VAL_FAIL (it->next, R_SIGN_NEXT); it->next = strdup (token); break; case R_SIGN_TYPES: DBL_VAL_FAIL (it->types, R_SIGN_TYPES); if (!types_sig_valid (token)) { R_LOG_ERROR ("Invalid types: ```%s``` in signatuer for %s", token, k); success = false; goto out; } it->types = strdup (token); break; case R_SIGN_GRAPH: DBL_VAL_FAIL (it->graph, R_SIGN_GRAPH); if (strlen (token) == 2 * sizeof (RSignGraph)) { it->graph = R_NEW0 (RSignGraph); if (it->graph) { r_hex_str2bin (token, (ut8 *)it->graph); } } break; case R_SIGN_OFFSET: DBL_VAL_FAIL ((it->addr != UT64_MAX), R_SIGN_OFFSET); it->addr = atoll (token); break; case R_SIGN_REFS: DBL_VAL_FAIL (it->refs, R_SIGN_REFS); if (!(it->refs = r_str_split_duplist (token, ",", true))) { success = false; goto out; } break; case R_SIGN_XREFS: DBL_VAL_FAIL (it->xrefs, R_SIGN_XREFS); if (!(it->xrefs = r_str_split_duplist (token, ",", true))) { success = false; goto out; } break; case R_SIGN_VARS: DBL_VAL_FAIL (it->vars, R_SIGN_VARS); if (!(it->vars = r_anal_var_deserialize (token))) { success = false; goto out; } break; case R_SIGN_COLLISIONS: DBL_VAL_FAIL (it->collisions, R_SIGN_COLLISIONS); if (!(it->collisions = r_str_split_duplist (token, ",", true))) { success = false; goto out; } break; case R_SIGN_BBHASH: DBL_VAL_FAIL (it->hash, R_SIGN_BBHASH); if (token[0] != 0) { it->hash = R_NEW0 (RSignHash); if (it->hash) { it->hash->bbhash = r_str_new (token); } } break; case R_SIGN_BYTES: DBL_VAL_FAIL (it->bytes, R_SIGN_BYTES); if (!(it->bytes = des_bytes_norm (token))) { success = false; goto out; } break; default: R_LOG_ERROR ("Unsupported (%c)", st); break; } } out: free (k2); free (v2); return success; } #undef DBL_VAL_FAIL static inline char *str_serialize_key(const char *sp, const char *name) { if (!sp || !name) { return NULL; } return r_str_newf ("zign|%s|%s", sp, name); } static inline char *space_serialize_key(const RSpace *space, const char *name) { const char *sp = space? space->name: "*"; return str_serialize_key (sp, name); } static inline char *item_serialize_key(RSignItem *it) { return space_serialize_key (it->space, it->name); } static bool serialize_str_list(RList *l, RStrBuf *sb, RSignType t) { if (!l || l->length == 0) { return true; } if (!r_strbuf_appendf (sb, "|%c:", t)) { return false; } char *e, *c = ""; RListIter *iter; r_list_foreach (l, iter, e) { if (strchr (e, ',')) { return false; } if (strchr (e, ',') || !r_strbuf_appendf (sb, "%s%s", c, e)) { return false; } if (!*c) { c = ","; } } return true; } static inline size_t serial_val_reserv(RSignItem *it) { // does not have to be exact, just save time on re-alloc+copy size_t reserve = 32; if (it->bytes) { reserve += it->bytes->size * 4 + 10; } if (it->graph) { reserve += sizeof (RSignGraph) * 2 + 1; } if (it->realname) { reserve += 0x10; } if (it->next) { reserve += 0x20; } if (it->types) { reserve += 0x20; } if (it->hash && it->hash->bbhash) { reserve += 64; } int mul = 10; if (it->refs) { reserve += mul * r_list_length (it->refs); } if (it->xrefs) { reserve += mul * r_list_length (it->xrefs); } if (it->vars) { reserve += mul * r_list_length (it->vars); } return reserve; } #define FreeRet_on_fail(exp, buf) \ if (!exp) { \ r_strbuf_free (buf); \ return NULL; \ } static char *serialize_value(RSignItem *it) { r_return_val_if_fail (it, false); RStrBuf *sb = r_strbuf_new (""); r_strbuf_reserve (sb, serial_val_reserv (it)); RSignBytes *bytes = it->bytes; if (bytes && bytes->bytes && bytes->mask) { RSignBytes *bytes = it->bytes; size_t len = bytes->size * 2 + 1; char *hexbytes = calloc (1, len); char *hexmask = calloc (1, len); bool success = false; if (hexbytes && hexmask) { r_hex_bin2str (bytes->bytes, bytes->size, hexbytes); r_hex_bin2str (bytes->mask, bytes->size, hexmask); success = r_strbuf_appendf (sb, "|%c:%s:%s", R_SIGN_BYTES, hexbytes, hexmask); } free (hexbytes); free (hexmask); FreeRet_on_fail (success, sb); } if (it->addr != UT64_MAX) { FreeRet_on_fail (r_strbuf_appendf (sb, "|%c:%" PFMT64d, R_SIGN_OFFSET, it->addr), sb); } if (it->graph) { char *hexgraph = calloc (1, sizeof (RSignGraph) * 2 + 1); bool success = false; if (hexgraph) { r_hex_bin2str ((ut8 *)it->graph, sizeof (RSignGraph), hexgraph); success = r_strbuf_appendf (sb, "|%c:%s", R_SIGN_GRAPH, hexgraph); free (hexgraph); } FreeRet_on_fail (success, sb); } FreeRet_on_fail (serialize_str_list (it->refs, sb, R_SIGN_REFS), sb); FreeRet_on_fail (serialize_str_list (it->xrefs, sb, R_SIGN_XREFS), sb); FreeRet_on_fail (serialize_str_list (it->collisions, sb, R_SIGN_COLLISIONS), sb); if (it->vars && !r_list_empty (it->vars)) { char *vrs = r_anal_var_prot_serialize (it->vars, false); bool vars_good = false; if (vrs) { vars_good = r_strbuf_appendf (sb, "|%c:%s", R_SIGN_VARS, vrs); free (vrs); } FreeRet_on_fail (vars_good, sb); } if (it->comment && !strchr (it->comment, '|')) { FreeRet_on_fail (r_strbuf_appendf (sb, "|%c:%s", R_SIGN_COMMENT, it->comment), sb); } if (it->realname && !strchr (it->realname, '|')) { FreeRet_on_fail (r_strbuf_appendf (sb, "|%c:%s", R_SIGN_NAME, it->realname), sb); } if (it->types && !strchr (it->types, '|')) { FreeRet_on_fail (r_strbuf_appendf (sb, "|%c:%s", R_SIGN_TYPES, it->types), sb); } if (it->next && !strchr (it->next, '|')) { FreeRet_on_fail (r_strbuf_appendf (sb, "|%c:%s", R_SIGN_NEXT, it->next), sb); } if (it->hash && it->hash->bbhash) { FreeRet_on_fail (r_strbuf_appendf (sb, "|%c:%s", R_SIGN_BBHASH, it->hash->bbhash), sb); } return r_strbuf_drain (sb); } static RList *deserialize_sign_space(RAnal *a, RSpace *space) { r_return_val_if_fail (a && space, NULL); char *key = space_serialize_key (space, ""); if (!key) { return NULL; } SdbList *zigns = sdb_foreach_match (a->sdb_zigns, key, false); free (key); SdbListIter *iter; SdbKv *kv; RList *ret = r_list_newf ((RListFree)r_sign_item_free); if (ret) { ls_foreach (zigns, iter, kv) { RSignItem *it = r_sign_item_new (); if (!it) { break; } if (r_sign_deserialize (a, it, kv->base.key, kv->base.value)) { r_list_append (ret, it); } else { r_sign_item_free (it); } } } ls_free (zigns); return ret; } #define quick_merge(x, freefunc) \ if (src->x) { \ freefunc (dst->x); \ dst->x = src->x; \ src->x = NULL; \ } // clobbers src for speed but also garentee success static inline void merge_item_clobber(RSignItem *dst, RSignItem *src) { dst->space = src->space; if (src->addr != UT64_MAX) { dst->addr = src->addr; } // uniquee free for each quick_merge (bytes, r_sign_bytes_free); quick_merge (graph, r_sign_graph_free); quick_merge (hash, r_sign_hash_free); // strings quick_merge (comment, free); quick_merge (realname, free); quick_merge (next, free); quick_merge (types, free); // lists quick_merge (refs, r_list_free); quick_merge (xrefs, r_list_free); quick_merge (vars, r_list_free); quick_merge (collisions, r_list_free); } #undef quick_merge static RSignItem *sign_get_sdb_item(RAnal *a, const char *key) { RSignItem *it = NULL; const char *value = sdb_const_get (a->sdb_zigns, key, 0); if (value && (it = r_sign_item_new ())) { if (r_sign_deserialize (a, it, key, value)) { return it; } r_sign_item_free (it); } return NULL; } static bool r_sign_set_item(Sdb *sdb, RSignItem *it, char *key_optional) { bool retval = false; char *key = NULL, *mykey = key_optional; if (!mykey) { key = mykey = item_serialize_key (it); } char *value = serialize_value (it); if (mykey && value) { sdb_set (sdb, mykey, value, 0); retval = true; } free (key); free (value); return retval; } R_API RSignItem *r_sign_get_item(RAnal *a, const char *name) { char *k = space_serialize_key (r_spaces_current (&a->zign_spaces), name); if (k) { RSignItem *it = sign_get_sdb_item (a, k); free (k); return it; } return NULL; } static bool validate_item(RSignItem *it) { r_return_val_if_fail (it, false); // TODO: validate more if (!r_name_check (it->name)) { R_LOG_ERROR ("Bad name in signature: %s", it->name); return false; } if (it->space && it->space->name && !r_name_check (it->space->name)) { R_LOG_ERROR ("Bad space name in signature: %s", it->space->name); return false; } if (it->bytes) { RSignBytes *b = it->bytes; if (!b->mask || !b->bytes || b->size <= 0) { R_LOG_ERROR ("Signature '%s' has empty byte field", it->name); return false; } if (b->mask[0] == '\0') { R_LOG_ERROR ("Signature '%s' mask starts empty", it->name); return false; } } return true; } R_API bool r_sign_add_item(RAnal *a, RSignItem *it) { r_name_filter (it->name, -1); if (!validate_item (it)) { return false; } char *key = item_serialize_key (it); if (!key) { return false; } RSignItem *current = sign_get_sdb_item (a, key); bool retval = false; if (current) { merge_item_clobber (current, it); retval = r_sign_set_item (a->sdb_zigns, current, key); r_sign_item_free (current); } else { retval = r_sign_set_item (a->sdb_zigns, it, key); } free (key); return retval; } static RSignItem *item_new_named(RAnal *a, const char *n) { RSignItem *it = r_sign_item_new (); if (it && (it->name = strdup (n))) { it->space = r_spaces_current (&a->zign_spaces); return it; } r_sign_item_free (it); return NULL; } static bool addHash(RAnal *a, const char *name, int type, const char *val) { RSignItem *it = item_new_named (a, name); if (!it) { return false; } it->hash = R_NEW0 (RSignHash); if (!it->hash) { r_sign_item_free (it); return false; } it->space = r_spaces_current (&a->zign_spaces); bool retval = false; switch (type) { case R_SIGN_BBHASH: it->hash->bbhash = strdup (val); retval = r_sign_add_item (a, it); r_sign_item_free (it); break; } return retval; } static bool addBBHash(RAnal *a, RAnalFunction *fcn, const char *name) { bool retval = false; RSignItem *it = item_new_named (a, name); if (it && r_sign_addto_item (a, it, fcn, R_SIGN_BBHASH)) { retval = r_sign_add_item (a, it); } r_sign_item_free (it); return retval; } R_API bool r_sign_add_hash(RAnal *a, const char *name, int type, const char *val, int len) { r_return_val_if_fail (a && name && type && val && len > 0, false); if (type != R_SIGN_BBHASH) { R_LOG_ERROR ("hash type unknown"); return false; } int digestsize = r_hash_size (R_ZIGN_HASH) * 2; if (len != digestsize) { R_LOG_ERROR ("invalid hash size: %d (%s digest size is %d)", len, ZIGN_HASH, digestsize); return false; } return addHash (a, name, type, val); } R_API bool r_sign_add_bb_hash(RAnal *a, RAnalFunction *fcn, const char *name) { r_return_val_if_fail (a && fcn && name, false); return addBBHash (a, fcn, name); } R_API bool r_sign_add_bytes(RAnal *a, const char *name, const char *val) { r_return_val_if_fail (a && name && val, false); bool ret = false; RSignItem *it = item_new_named (a, name); if (it && (it->bytes = deserialize_bytes (val))) { ret = r_sign_add_item (a, it); } r_sign_item_free (it); return ret; } R_API bool r_sign_add_anal(RAnal *a, const char *name, const char *val) { r_return_val_if_fail (a && name && val, false); bool ret = false; RSignItem *it = item_new_named (a, name); if (it && (it->bytes = deserialize_anal (a, val))) { ret = r_sign_add_item (a, it); } r_sign_item_free (it); return ret; } static RSignGraph *r_sign_fcn_graph(RAnalFunction *fcn) { r_return_val_if_fail (fcn, false); RSignGraph *graph = R_NEW0 (RSignGraph); if (graph) { graph->cc = r_anal_function_complexity (fcn), graph->nbbs = r_list_length (fcn->bbs); graph->edges = r_anal_function_count_edges (fcn, &graph->ebbs); graph->bbsum = r_anal_function_realsize (fcn); } return graph; } static int bb_sort_by_addr(const void *x, const void *y) { RAnalBlock *a = (RAnalBlock *)x; RAnalBlock *b = (RAnalBlock *)y; if (a->addr > b->addr) { return 1; } if (a->addr < b->addr) { return -1; } return 0; } static RSignBytes *r_sign_func_empty_mask(RAnal *a, RAnalFunction *fcn) { r_return_val_if_fail (a && fcn && fcn->bbs && fcn->bbs->head, false); // get size RCore *core = a->coreb.core; int maxsz = a->coreb.cfggeti (core, "zign.maxsz"); r_list_sort (fcn->bbs, &bb_sort_by_addr); ut64 ea = fcn->addr; RAnalBlock *bb = (RAnalBlock *)fcn->bbs->tail->data; int size = R_MIN (bb->addr + bb->size - ea, maxsz); // alloc space for signature RSignBytes *sig = R_NEW0 (RSignBytes); if (sig) { sig->bytes = malloc (size); sig->mask = R_NEWS0 (ut8, size); sig->size = size; if (sig->bytes && sig->mask && a->iob.read_at (a->iob.io, ea, sig->bytes, size)) { return sig; } } r_sign_bytes_free (sig); return NULL; } static RSignBytes *r_sign_fcn_bytes(RAnal *a, RAnalFunction *fcn) { r_return_val_if_fail (a && fcn && fcn->bbs && fcn->bbs->head, false); RSignBytes *sig = r_sign_func_empty_mask (a, fcn); if (!sig) { return NULL; } ut64 ea = fcn->addr; int size = sig->size; ut8 *tmpmask = NULL; RAnalBlock *bb; RListIter *iter; r_list_foreach (fcn->bbs, iter, bb) { if (bb->addr >= ea) { size_t delta = bb->addr - ea; size_t rsize = bb->size; // bounds check if (delta >= size) { break; } if (size - delta < rsize) { rsize = size - delta; } // get mask for block if (!(tmpmask = r_anal_mask (a, rsize, sig->bytes + delta, ea))) { r_sign_bytes_free (sig); return NULL; } if (rsize > 0) { memcpy (sig->mask + delta, tmpmask, rsize); } free (tmpmask); } } return sig; } static RSignHash *r_sign_fcn_bbhash(RAnal *a, RAnalFunction *fcn) { r_return_val_if_fail (a && fcn, NULL); RSignHash *hash = R_NEW0 (RSignHash); if (!hash) { return NULL; } char *digest_hex = r_sign_calc_bbhash (a, fcn); if (!digest_hex) { free (hash); return NULL; } hash->bbhash = digest_hex; return hash; } static RSignItem *item_from_func(RAnal *a, RAnalFunction *fcn, const char *name) { if (r_list_empty (fcn->bbs)) { R_LOG_WARN ("Function with no basic blocks at 0x%08"PFMT64x, fcn->addr); return false; } RSignItem *it = item_new_named (a, name? name: fcn->name); if (it) { r_sign_addto_item (a, it, fcn, R_SIGN_GRAPH); r_sign_addto_item (a, it, fcn, R_SIGN_BYTES); r_sign_addto_item (a, it, fcn, R_SIGN_XREFS); r_sign_addto_item (a, it, fcn, R_SIGN_REFS); r_sign_addto_item (a, it, fcn, R_SIGN_VARS); r_sign_addto_item (a, it, fcn, R_SIGN_TYPES); r_sign_addto_item (a, it, fcn, R_SIGN_BBHASH); r_sign_addto_item (a, it, fcn, R_SIGN_OFFSET); r_sign_addto_item (a, it, fcn, R_SIGN_NAME); } return it; } static int fcn_sort(const void *va, const void *vb) { ut64 a = ((const RAnalFunction *)va)->addr; ut64 b = ((const RAnalFunction *)vb)->addr; if (a < b) { return -1; } else if (a > b) { return 1; } return 0; } static bool name_exists(Sdb *sdb, const char *n, const RSpace *sp) { char *key = space_serialize_key (sp, n); bool exist = false; if (key) { exist = sdb_exists (sdb, key); free (key); } return exist; } static char *get_unique_name(Sdb *sdb, const char *name, const RSpace *sp) { ut32 i; for (i = 2; i < UT32_MAX; i++) { char *unique = r_str_newf ("%s_%d", name, i); if (!unique || !name_exists (sdb, unique, sp)) { return unique; } free (unique); } return NULL; } static char *real_function_name(RAnal *a, ut64 addr, const char *name) { RCore *core = a->coreb.core; if (a->coreb.cfggeti (core, "zign.mangled")) { // resolve the manged name char *res = a->coreb.cmdstrf (core, "is,vaddr/eq/0x%"PFMT64x",name/cols,a/head/1,:quiet", addr); if (res) { r_str_trim (res); if (*res) { return res; } } } return strdup (name); } R_API int r_sign_all_functions(RAnal *a, bool merge) { RAnalFunction *fcni = NULL; RListIter *iter = NULL; int count = 0; r_list_sort (a->fcns, fcn_sort); const RSpace *sp = r_spaces_current (&a->zign_spaces); char *prev_name = NULL; r_cons_break_push (NULL, NULL); r_list_foreach_prev (a->fcns, iter, fcni) { if (r_cons_is_breaked ()) { break; } char *realname = real_function_name (a, fcni->addr, fcni->name); RSignItem *it = NULL; if (merge || !name_exists (a->sdb_zigns, realname, sp)) { it = item_from_func (a, fcni, realname); } else { char *name = get_unique_name (a->sdb_zigns, fcni->name, sp); if (name) { it = item_from_func (a, fcni, name); } free (name); free (realname); } if (it) { if (prev_name) { it->next = prev_name; } prev_name = strdup (it->name); r_sign_add_item (a, it); r_sign_item_free (it); count++; } else { free (prev_name); prev_name = NULL; } } r_cons_break_pop (); free (prev_name); return count; } R_API bool r_sign_add_func(RAnal *a, RAnalFunction *fcn, const char *name) { r_return_val_if_fail (a && fcn, false); RSignItem *it = item_from_func (a, fcn, name); if (it) { r_sign_add_item (a, it); r_sign_item_free (it); return true; } return false; } R_API bool r_sign_addto_item(RAnal *a, RSignItem *it, RAnalFunction *fcn, RSignType type) { r_return_val_if_fail (a && it && fcn, false); switch (type) { case R_SIGN_GRAPH: return !it->graph && (it->graph = r_sign_fcn_graph (fcn)); case R_SIGN_BYTES: return !it->bytes && (it->bytes = r_sign_fcn_bytes (a, fcn)); case R_SIGN_XREFS: return !it->xrefs && (it->xrefs = r_sign_fcn_xrefs (a, fcn)); case R_SIGN_REFS: return !it->refs && (it->refs = r_sign_fcn_refs (a, fcn)); case R_SIGN_VARS: return !it->vars && (it->vars = sign_vars (fcn)); case R_SIGN_TYPES: if (!it->types) { it->types = r_anal_function_get_signature (fcn); if (it->types) { size_t l = strlen (it->types) - 1; if (l > 0 && it->types[l] == ';') { it->types[l] = '\0'; return true; } else { free (it->types); } } } return false; case R_SIGN_BBHASH: return !it->hash && (it->hash = r_sign_fcn_bbhash (a, fcn)); case R_SIGN_OFFSET: it->addr = fcn->addr; return true; case R_SIGN_NAME: if (!it->realname && it->name) { if (strcmp (it->name, fcn->name)) { it->realname = strdup (fcn->name); } return true; } break; default: R_LOG_ERROR ("%s Can not handle type %c", __FUNCTION__, type); } return false; } R_API bool r_sign_add_graph(RAnal *a, const char *name, RSignGraph graph) { r_return_val_if_fail (a && !R_STR_ISEMPTY (name), false); bool retval = false; RSignItem *it = item_new_named (a, name); if (it && (it->graph = R_NEW0 (RSignGraph))) { *it->graph = graph; retval = r_sign_add_item (a, it); } r_sign_item_free (it); return retval; } R_API bool r_sign_add_comment(RAnal *a, const char *name, const char *comment) { r_return_val_if_fail (a && name && comment, false); bool retval = false; RSignItem *it = item_new_named (a, name); if (it && (it->comment = strdup (comment))) { retval = r_sign_add_item (a, it); } r_sign_item_free (it); return retval; } R_API bool r_sign_add_name(RAnal *a, const char *name, const char *realname) { r_return_val_if_fail (a && name && realname, false); if (strchr (realname, ' ')) { R_LOG_ERROR ("Realname sig can't contain spaces"); return false; } bool retval = false; RSignItem *it = item_new_named (a, name); if (it && (it->realname = strdup (realname))) { retval = r_sign_add_item (a, it); } r_sign_item_free (it); return retval; } R_API bool r_sign_add_types(RAnal *a, const char *name, const char *types) { r_return_val_if_fail (a && name && types, false); if (!types_sig_valid (types)) { R_LOG_ERROR ("Invalid types signature: %s", types); return false; } bool retval = false; RSignItem *it = item_new_named (a, name); if (it) { it->types = strdup (types); retval = r_sign_add_item (a, it); r_sign_item_free (it); } return retval; } R_API bool r_sign_add_addr(RAnal *a, const char *name, ut64 addr) { r_return_val_if_fail (a && name && addr != UT64_MAX, false); bool retval = false; RSignItem *it = item_new_named (a, name); if (it) { it->addr = addr; retval = r_sign_add_item (a, it); r_sign_item_free (it); } return retval; } R_API bool r_sign_add_vars(RAnal *a, const char *name, const char *vars) { r_return_val_if_fail (a && name && vars, false); bool retval = false; RSignItem *it = item_new_named (a, name); if (it && (it->vars = r_anal_var_deserialize (vars))) { retval = r_sign_add_item (a, it); } r_sign_item_free (it); return retval; } R_API bool r_sign_add_refs(RAnal *a, const char *name, RList *refs) { r_return_val_if_fail (a && name && refs, false); bool retval = false; RSignItem *it = item_new_named (a, name); if (it && (it->refs = r_list_newf ((RListFree)free))) { RListIter *iter; char *ref; r_list_foreach (refs, iter, ref) { r_list_append (it->refs, strdup (ref)); } retval = r_sign_add_item (a, it); } r_sign_item_free (it); return retval; } R_API bool r_sign_add_xrefs(RAnal *a, const char *name, RList *xrefs) { r_return_val_if_fail (a && name && xrefs, false); bool retval = false; RSignItem *it = item_new_named (a, name); if (it && (it->xrefs = r_list_newf ((RListFree)free))) { RListIter *iter; char *xref; r_list_foreach (xrefs, iter, xref) { r_list_append (it->xrefs, strdup (xref)); } retval = r_sign_add_item (a, it); } r_sign_item_free (it); return retval; } struct ctxDeleteCB { RAnal *anal; char *key; size_t len; }; static bool deleteBySpaceCB(void *user, const char *k, const char *v) { struct ctxDeleteCB *ctx = (struct ctxDeleteCB *) user; if (!strncmp (k, ctx->key, ctx->len)) { sdb_remove (ctx->anal->sdb_zigns, k, 0); } return true; } R_API bool r_sign_delete(RAnal *a, const char *name) { r_return_val_if_fail (a && name, false); bool retval = false; // Remove all zigns if (*name == '*') { if (!r_spaces_current (&a->zign_spaces)) { sdb_reset (a->sdb_zigns); return true; } struct ctxDeleteCB ctx = { .anal = a }; ctx.key = space_serialize_key (r_spaces_current (&a->zign_spaces), ""); if (ctx.key) { ctx.len = strlen (ctx.key); retval = sdb_foreach (a->sdb_zigns, deleteBySpaceCB, &ctx); free (ctx.key); } } else { // Remove specific zign char *key = space_serialize_key (r_spaces_current (&a->zign_spaces), name); if (key) { retval = sdb_remove (a->sdb_zigns, key, 0); free (key); } } return retval; } static ut8 *build_combined_bytes(RSignBytes *bsig) { r_return_val_if_fail (bsig && bsig->bytes && bsig->mask, NULL); ut8 *buf = (ut8 *)malloc (bsig->size); if (buf) { size_t i; for (i = 0; i < bsig->size; i++) { buf[i] = bsig->bytes[i] & bsig->mask[i]; } } return buf; } static double cmp_bytesig_to_buff(RSignBytes *sig, ut8 *buf, int len) { r_return_val_if_fail (sig && buf && len >= 0, (double)-1.0); ut8 *sigbuf = build_combined_bytes (sig); double sim = -1.0; if (sigbuf) { r_diff_buffers_distance (NULL, sigbuf, sig->size, buf, len, NULL, &sim); free (sigbuf); } return sim; } static double matchBytes(RSignItem *a, RSignItem *b) { double result = 0.0; if (!a->bytes || !b->bytes) { return result; } size_t min_size = R_MIN ((size_t)a->bytes->size, (size_t)b->bytes->size); if (!min_size) { return result; } ut8 *combined_mask = NULL; if (a->bytes->mask || b->bytes->mask) { combined_mask = (ut8*)malloc (min_size); if (!combined_mask) { return result; } memcpy (combined_mask, a->bytes->mask, min_size); if (b->bytes->mask) { int i; for (i = 0; i != min_size; i++) { combined_mask[i] &= b->bytes->mask[i]; } } } if ((combined_mask && !r_mem_cmp_mask (a->bytes->bytes, b->bytes->bytes, combined_mask, min_size)) || (!combined_mask && !memcmp (a->bytes->bytes, b->bytes->bytes, min_size))) { result = (double)min_size / (double)R_MAX (a->bytes->size, b->bytes->size); } free (combined_mask); return result; } #define SIMILARITY(a, b) \ ((a) == (b)? 1.0: (R_MAX ((a), (b)) == 0.0? 0.0: (double)R_MIN ((a), (b)) / (double)R_MAX ((a), (b)))) static double matchGraph(RSignItem *a, RSignItem *b) { if (!a->graph || !b->graph) { return 0.0; } double total = 0.0; total += SIMILARITY (a->graph->cc, b->graph->cc); total += SIMILARITY (a->graph->nbbs, b->graph->nbbs); total += SIMILARITY (a->graph->ebbs, b->graph->ebbs); total += SIMILARITY (a->graph->edges, b->graph->edges); total += SIMILARITY (a->graph->bbsum, b->graph->bbsum); return total / 5.0; } static int score_cmpr(const void *a, const void *b) { double sa = ((RSignCloseMatch *)a)->score; double sb = ((RSignCloseMatch *)b)->score; if (sa < sb) { return 1; } if (sa > sb) { return -1; } return 0; } typedef struct { RSignItem *test; RList *output; size_t count; double score_threshold; ut8 *bytes_combined; // greatest lower bound. Thanks lattice theory for helping name variables double infimum; } ClosestMatchData; static bool closest_match_update(RSignItem *it, ClosestMatchData *data) { // quantify how close the signature matches int div = 0; double score = 0.0; double gscore = -1.0; if (it->graph && data->test->graph) { gscore = matchGraph (it, data->test); score += gscore; div++; } double bscore = -1.0; bool list_full = (r_list_length (data->output) == data->count); // value to beat to enter the list double pivot = data->score_threshold; if (list_full) { pivot = R_MAX (pivot, data->infimum); } if (it->bytes && data->bytes_combined) { int sizea = it->bytes->size; int sizeb = data->test->bytes->size; if (pivot > 0.0) { // bytes distance is slow. To avoid it, we can do quick maths to // see if the highest possible score would be good enough to change // results double maxscore = R_MIN (sizea, sizeb) / R_MAX (sizea, sizeb); if (div > 0) { maxscore = (maxscore + score) / div; } if (maxscore < pivot) { r_sign_item_free (it); return true; } } // get true byte score bscore = cmp_bytesig_to_buff (it->bytes, data->bytes_combined, sizeb); score += bscore; div++; } if (div == 0) { r_sign_item_free (it); return true; } score /= div; // score is too low, don't bother doing any more work if (score < pivot) { r_sign_item_free (it); return true; } // add new element RSignCloseMatch *row = R_NEW (RSignCloseMatch); if (!row) { r_sign_item_free (it); return false; } row->score = score; row->gscore = gscore; row->bscore = bscore; row->item = it; r_list_add_sorted (data->output, (void *)row, &score_cmpr); if (list_full) { // remove smallest element r_sign_close_match_free (r_list_pop (data->output)); // get new infimum row = r_list_last (data->output); data->infimum = row->score; } return true; } R_API void r_sign_close_match_free(RSignCloseMatch *match) { if (match) { r_sign_item_free (match->item); free (match); } } static bool _closest_match_cb(RSignItem *it, void *user) { return closest_match_update (it, (ClosestMatchData *)user); } R_API RList *r_sign_find_closest_fcn(RAnal *a, RSignItem *it, int count, double score_threshold) { r_return_val_if_fail (a && it && count > 0 && score_threshold >= 0 && score_threshold <= 1, NULL); r_return_val_if_fail (it->bytes || it->graph, NULL); RList *output = r_list_newf ((RListFree)r_sign_close_match_free); if (!output) { return NULL; } ClosestMatchData data; data.output = output; data.count = count; data.score_threshold = score_threshold; data.infimum = 0.0; data.test = it; if (it->bytes) { data.bytes_combined = build_combined_bytes (it->bytes); } else { data.bytes_combined = NULL; } RAnalFunction *fcn; RListIter *iter; r_list_foreach (a->fcns, iter, fcn) { // turn function into signature item RSignItem *fsig = r_sign_item_new (); if (!fsig) { r_list_free (output); return NULL; } if (data.bytes_combined) { r_sign_addto_item (a, fsig, fcn, R_SIGN_BYTES); } if (it->graph) { r_sign_addto_item (a, fsig, fcn, R_SIGN_GRAPH); } r_sign_addto_item (a, fsig, fcn, R_SIGN_OFFSET); fsig->name = r_str_new (fcn->name); // maybe add signature item to output list closest_match_update (fsig, &data); } free (data.bytes_combined); return output; } R_API bool r_sign_diff(RAnal *a, RSignOptions *options, const char *other_space_name) { r_return_val_if_fail (a && other_space_name, false); RSpace *current_space = r_spaces_current (&a->zign_spaces); if (!current_space) { return false; } RSpace *other_space = r_spaces_get (&a->zign_spaces, other_space_name); if (!other_space) { return false; } RList *la = deserialize_sign_space (a, current_space); if (!la) { return false; } RList *lb = deserialize_sign_space (a, other_space); if (!lb) { r_list_free (la); return false; } R_LOG_INFO ("Diff %d %d", (int)ls_length (la), (int)ls_length (lb)); RListIter *itr; RListIter *itr2; RSignItem *si; RSignItem *si2; // do the sign diff here r_list_foreach (la, itr, si) { if (strstr (si->name, "imp.")) { continue; } r_list_foreach (lb, itr2, si2) { if (strstr (si2->name, "imp.")) { continue; } double bytesScore = matchBytes (si, si2); double graphScore = matchGraph (si, si2); bool bytesMatch = bytesScore >= (options ? options->bytes_diff_threshold : SIGN_DIFF_MATCH_BYTES_THRESHOLD); bool graphMatch = graphScore >= (options ? options->graph_diff_threshold : SIGN_DIFF_MATCH_GRAPH_THRESHOLD); if (bytesMatch) { a->cb_printf ("0x%08" PFMT64x " 0x%08"PFMT64x " %02.5lf B %s\n", si->addr, si2->addr, bytesScore, si->name); } if (graphMatch) { a->cb_printf ("0x%08" PFMT64x " 0x%08"PFMT64x" %02.5lf G %s\n", si->addr, si2->addr, graphScore, si->name); } } } r_list_free (la); r_list_free (lb); return true; } R_API bool r_sign_diff_by_name(RAnal *a, RSignOptions *options, const char *other_space_name, bool not_matching) { r_return_val_if_fail (a && other_space_name, false); RSpace *current_space = r_spaces_current (&a->zign_spaces); if (!current_space) { return false; } RSpace *other_space = r_spaces_get (&a->zign_spaces, other_space_name); if (!other_space) { return false; } RList *la = deserialize_sign_space (a, current_space); if (!la) { return false; } RList *lb = deserialize_sign_space (a, other_space); if (!lb) { return false; } R_LOG_INFO ("Diff by name %d %d (%s)", (int)ls_length (la), (int)ls_length (lb), not_matching? "not matching" : "matching"); RListIter *itr; RListIter *itr2; RSignItem *si; RSignItem *si2; size_t current_space_name_len = strlen (current_space->name); size_t other_space_name_len = strlen (other_space->name); r_list_foreach (la, itr, si) { if (strstr (si->name, "imp.")) { continue; } r_list_foreach (lb, itr2, si2) { if (strcmp (si->name + current_space_name_len + 1, si2->name + other_space_name_len + 1)) { continue; } // TODO: add config variable for threshold double bytesScore = matchBytes (si, si2); double graphScore = matchGraph (si, si2); bool bytesMatch = bytesScore >= (options ? options->bytes_diff_threshold : SIGN_DIFF_MATCH_BYTES_THRESHOLD); bool graphMatch = graphScore >= (options ? options->graph_diff_threshold : SIGN_DIFF_MATCH_GRAPH_THRESHOLD); if ((bytesMatch && !not_matching) || (!bytesMatch && not_matching)) { a->cb_printf ("0x%08"PFMT64x" 0x%08"PFMT64x" %02.5f B %s\n", si->addr, si2->addr, bytesScore, si->name); } if ((graphMatch && !not_matching) || (!graphMatch && not_matching)) { a->cb_printf ("0x%08"PFMT64x" 0x%08"PFMT64x" %02.5f G %s\n", si->addr, si2->addr, graphScore, si->name); } } } r_list_free (la); r_list_free (lb); return true; } struct ctxListCB { RAnal *anal; int idx; int format; PJ *pj; }; static void list_sanitise_warn(char *s, const char *name, const char *field) { if (s) { // NULL value accepted and sane bool sanitized = false; for (; *s; s++) { switch (*name) { case '`': case '$': case '{': case '}': case '~': case '|': case '#': case '@': case '&': case '<': case '>': case ',': *s = '_'; sanitized = true; continue; } } if (sanitized) { R_LOG_INFO ("%s->%s needs to be sanitized", name, field); r_warn_if_reached (); } } } static void listBytes(RAnal *a, RSignItem *it, PJ *pj, int format) { RSignBytes *bytes = it->bytes; if (!bytes->bytes) { return; } int masked = 0, i = 0; for (i = 0; i < bytes->size; i++) { masked += bytes->mask[i] == 0xff; } char *strbytes = r_hex_bin2strdup (bytes->bytes, bytes->size); if (!strbytes) { return; } char *strmask = r_hex_bin2strdup (bytes->mask, bytes->size); if (!strmask) { free (strbytes); return; } list_sanitise_warn (strbytes, it->name, "bytes"); list_sanitise_warn (strmask, it->name, "mask"); if (format == '*') { if (masked == bytes->size) { a->cb_printf ("za %s b %s\n", it->name, strbytes); } else { a->cb_printf ("za %s b %s:%s\n", it->name, strbytes, strmask); } } else if (format == 'q') { a->cb_printf (" b(%d/%d)", masked, bytes->size); } else if (format == 'j') { pj_ks (pj, "bytes", strbytes); pj_ks (pj, "mask", strmask); } else { a->cb_printf (" bytes: %s\n", strbytes); a->cb_printf (" mask: %s\n", strmask); } free (strbytes); free (strmask); } static void listGraph(RAnal *a, RSignItem *it, PJ *pj, int format) { RSignGraph *graph = it->graph; if (format == 'q') { a->cb_printf (" g(cc=%d,nb=%d,e=%d,eb=%d,h=%d)", graph->cc, graph->nbbs, graph->edges, graph->ebbs, graph->bbsum); } else if (format == '*') { a->cb_printf ("za %s g cc=%d nbbs=%d edges=%d ebbs=%d bbsum=%d\n", it->name, graph->cc, graph->nbbs, graph->edges, graph->ebbs, graph->bbsum); } else if (format == 'j') { pj_ko (pj, "graph"); pj_kN (pj, "cc", graph->cc); pj_kN (pj, "nbbs", graph->nbbs); pj_kN (pj, "edges", graph->edges); pj_kN (pj, "ebbs", graph->ebbs); pj_kN (pj, "bbsum", graph->bbsum); pj_end (pj); } else { a->cb_printf (" graph: cc=%d nbbs=%d edges=%d ebbs=%d bbsum=%d\n", graph->cc, graph->nbbs, graph->edges, graph->ebbs, graph->bbsum); } } static void liststring(RAnal *a, RSignType t, char *value, PJ *pj, int format, char *name) { if (value) { if (format == 'j') { pj_ks (pj, r_sign_type_to_name (t), value); } else { const char *type = r_sign_type_to_name (t); list_sanitise_warn (value, name, type); if (format == 'q') { a->cb_printf ("\n ; %s\n", value); } else if (format == '*') { // comment injection via CCu.. a->cb_printf ("za %s %c %s\n", name, t, value); } else { a->cb_printf (" %s: %s\n", type, value); } } } } static void listOffset(RAnal *a, RSignItem *it, PJ *pj, int format) { if (format == 'q') { // a->cb_printf (" addr(0x%08"PFMT64x")", it->addr); } else if (format == '*') { a->cb_printf ("za %s o 0x%08"PFMT64x"\n", it->name, it->addr); } else if (format == 'j') { pj_kN (pj, "addr", it->addr); } else { a->cb_printf (" addr: 0x%08"PFMT64x"\n", it->addr); } } static void inline list_vars_abs(RAnal *a, RSignItem *it, bool rad) { if (it->vars && !r_list_empty (it->vars)) { char *ser = r_anal_var_prot_serialize (it->vars, true); if (ser) { // sholdn't do anyting, but just in case list_sanitise_warn (ser, it->name, "var"); if (rad) { a->cb_printf ("za %s %c %s\n", it->name, R_SIGN_VARS, ser); } else { a->cb_printf (" vars: %s\n", ser); } } free (ser); } } static void inline list_vars_json(RSignItem *it, PJ *pj) { pj_ka (pj, "vars"); if (it->vars) { RAnalVarProt *v; RListIter *iter; r_list_foreach (it->vars, iter, v) { char kind[] = { v->kind, '\0' }; pj_o (pj); pj_ks (pj, "name", v->name); pj_ks (pj, "type", v->type); pj_ks (pj, "kind", kind); pj_kN (pj, "delta", v->delta); pj_kb (pj, "isarg", v->isarg); pj_end (pj); } } pj_end (pj); } static void inline list_vars(RAnal *a, RSignItem *it, PJ *pj, int fmt) { switch (fmt) { case '*': list_vars_abs (a, it, true); break; case 'q': if (it->vars) { a->cb_printf (" vars[%d]", r_list_length (it->vars)); } break; case 'j': list_vars_json (it, pj); break; default: list_vars_abs (a, it, false); break; } } static void list_sign_list(RAnal *a, RList *l, PJ *pj, int fmt, int type, const char *name) { const char *tname = r_sign_type_to_name (type); switch (fmt) { case '*': a->cb_printf ("za %s %c ", name, type); break; case 'q': a->cb_printf (" %s[%d]", tname, r_list_length (l)); return; case 'j': pj_ka (pj, tname); break; default: if (l && !r_list_empty (l)) { a->cb_printf (" %s: ", tname); } } int i = 0; char *ref = NULL; RListIter *iter = NULL; r_list_foreach (l, iter, ref) { if (i > 0) { if (fmt == '*') { a->cb_printf (" "); } else if (fmt != 'j') { a->cb_printf (", "); } } if (fmt == 'j') { pj_s (pj, ref); } else { a->cb_printf ("%s", ref); } i++; } if (fmt == 'j') { pj_end (pj); } else { a->cb_printf ("\n"); } } static void listHash(RAnal *a, RSignItem *it, PJ *pj, int format) { if (!it->hash) { return; } switch (format) { case 'q': if (it->hash->bbhash) { a->cb_printf (" h(%08x)", r_str_hash (it->hash->bbhash)); } break; case '*': if (it->hash->bbhash) { list_sanitise_warn (it->hash->bbhash, it->name, "bbhash"); a->cb_printf ("za %s h %s\n", it->name, it->hash->bbhash); } break; case 'j': pj_ko (pj, "hash"); if (it->hash->bbhash) { pj_ks (pj, "bbhash", it->hash->bbhash); } pj_end (pj); break; default: if (it->hash->bbhash) { list_sanitise_warn (it->hash->bbhash, it->name, "bbhash"); a->cb_printf (" bbhash: %s\n", it->hash->bbhash); } break; } } static bool listCB(RSignItem *it, void *user) { struct ctxListCB *ctx = (struct ctxListCB *)user; RAnal *a = ctx->anal; if (!validate_item (it)) { return true; } // Start item if (ctx->format == 'j') { pj_o (ctx->pj); } // Zignspace and name (except for radare format) if (ctx->format == '*') { if (it->space) { a->cb_printf ("zs %s\n", it->space->name); } else { a->cb_printf ("zs *\n"); } } else if (ctx->format == 'q') { a->cb_printf ("0x%08" PFMT64x " ", it->addr); const char *pad = r_str_pad (' ', 30 - strlen (it->name)); a->cb_printf ("%s:%s", it->name, pad); } else if (ctx->format == 'j') { if (it->space) { pj_ks (ctx->pj, "zignspace", it->space->name); } pj_ks (ctx->pj, "name", it->name); } else { if (!r_spaces_current (&a->zign_spaces) && it->space) { a->cb_printf ("(%s) ", it->space->name); } a->cb_printf ("%s:\n", it->name); } // Bytes pattern if (it->bytes) { listBytes (a, it, ctx->pj, ctx->format); } else if (ctx->format == 'j') { pj_ks (ctx->pj, "bytes", ""); } // Graph metrics if (it->graph) { listGraph (a, it, ctx->pj, ctx->format); } else if (ctx->format == 'j') { pj_ko (ctx->pj, "graph"); pj_end (ctx->pj); } // Offset if (it->addr != UT64_MAX) { listOffset (a, it, ctx->pj, ctx->format); } else if (ctx->format == 'j') { pj_kN (ctx->pj, "addr", -1); } liststring (a, R_SIGN_NAME, it->realname, ctx->pj, ctx->format, it->name); liststring (a, R_SIGN_COMMENT, it->comment, ctx->pj, ctx->format, it->name); liststring (a, R_SIGN_NEXT, it->next, ctx->pj, ctx->format, it->name); liststring (a, R_SIGN_TYPES, it->types, ctx->pj, ctx->format, it->name); // References if (it->refs) { list_sign_list (a, it->refs, ctx->pj, ctx->format, R_SIGN_REFS, it->name); } else if (ctx->format == 'j') { pj_ka (ctx->pj, "refs"); pj_end (ctx->pj); } // XReferences if (it->xrefs) { list_sign_list (a, it->xrefs, ctx->pj, ctx->format, R_SIGN_XREFS, it->name); } else if (ctx->format == 'j') { pj_ka (ctx->pj, "xrefs"); pj_end (ctx->pj); } // Collisions if (it->collisions) { list_sign_list (a, it->collisions, ctx->pj, ctx->format, R_SIGN_COLLISIONS, it->name); } else if (ctx->format == 'j') { pj_ka (ctx->pj, "collisions"); pj_end (ctx->pj); } // Vars list_vars (a, it, ctx->pj, ctx->format); // Hash if (it->hash) { listHash (a, it, ctx->pj, ctx->format); } else if (ctx->format == 'j') { pj_ko (ctx->pj, "hash"); pj_end (ctx->pj); } // End item if (ctx->format == 'j') { pj_end (ctx->pj); } if (ctx->format == 'q') { a->cb_printf ("\n"); } ctx->idx++; return true; } R_API void r_sign_list(RAnal *a, int format) { r_return_if_fail (a); PJ *pj = NULL; if (format == 'j') { pj = a->coreb.pjWithEncoding (a->coreb.core); pj_a (pj); } struct ctxListCB ctx = { a, 0, format, pj }; r_sign_foreach (a, listCB, &ctx); if (format == 'j') { pj_end (pj); a->cb_printf ("%s\n", pj_string (pj)); pj_free (pj); } } static bool listGetCB(RSignItem *it, void *user) { r_list_append ((RList *)user, it); return true; } R_API const char *r_sign_type_to_name(int type) { switch (type) { case R_SIGN_BYTES: return "bytes"; case R_SIGN_COMMENT: return "comment"; case R_SIGN_GRAPH: return "graph"; case R_SIGN_OFFSET: return "addr"; case R_SIGN_NAME: return "realname"; case R_SIGN_REFS: return "refs"; case R_SIGN_XREFS: return "xrefs"; case R_SIGN_VARS: return "vars"; case R_SIGN_TYPES: return "types"; case R_SIGN_COLLISIONS: return "collisions"; case R_SIGN_NEXT: return "next"; case R_SIGN_BBHASH: return "bbhash"; default: r_warn_if_reached (); return "UnknownType"; } } static int cmpaddr(const void *_a, const void *_b) { const RAnalBlock *a = _a, *b = _b; return (a->addr - b->addr); } R_API char *r_sign_calc_bbhash(RAnal *a, RAnalFunction *fcn) { RListIter *iter = NULL; RAnalBlock *bbi = NULL; char *digest_hex = NULL; RHash *ctx = r_hash_new (true, R_ZIGN_HASH); if (!ctx) { goto beach; } r_list_sort (fcn->bbs, &cmpaddr); r_hash_do_begin (ctx, R_ZIGN_HASH); r_list_foreach (fcn->bbs, iter, bbi) { ut8 *buf = malloc (bbi->size); if (!buf) { goto beach; } if (!a->iob.read_at (a->iob.io, bbi->addr, buf, bbi->size)) { goto beach; } if (!r_hash_do_sha256 (ctx, buf, bbi->size)) { goto beach; } free (buf); } r_hash_do_end (ctx, R_ZIGN_HASH); digest_hex = r_hex_bin2strdup (ctx->digest, r_hash_size (R_ZIGN_HASH)); beach: free (ctx); return digest_hex; } static bool countForCB(RSignItem *it, void *user) { (*(int *)user)++; return true; } static bool unsetForCB(RSignItem *it, void *user) { Sdb *db = (Sdb *)user; char *key = item_serialize_key (it); if (key) { sdb_remove (db, key, 0); free (key); } it->space = NULL; r_sign_set_item (db, it, NULL); return true; } struct ctxRenameForCB { RAnal *anal; char *oprefix; // old prefix const char *newname; size_t oldlen; }; static bool renameForCB(void *user, const char *k, const char *v) { struct ctxRenameForCB *ctx = (struct ctxRenameForCB *) user; Sdb *db = ctx->anal->sdb_zigns; if (!strncmp (k, ctx->oprefix, ctx->oldlen)) { char *nk = str_serialize_key (ctx->newname, k + ctx->oldlen); char *nv = strdup (v); if (nk && nv) { // must remove before set, must alloc new nk and nv before hand sdb_remove (db, k, 0); sdb_set (db, nk, nv, 0); } free (nv); free (nk); } return true; } R_API void r_sign_space_rename_for(RAnal *a, const RSpace *space, const char *oname, const char *nname) { r_return_if_fail (a && space && oname && nname); struct ctxRenameForCB ctx = { .anal = a, .newname = nname }; ctx.oprefix = str_serialize_key (oname, ""); if (ctx.oprefix) { ctx.oldlen = strlen (ctx.oprefix); sdb_foreach (a->sdb_zigns, renameForCB, &ctx); } free (ctx.oprefix); } struct ctxForeachCB { RAnal *anal; RSignForeachCallback cb; const RSpace *space; bool freeit; void *user; }; static bool foreachCB(void *user, const char *k, const char *v) { struct ctxForeachCB *ctx = (struct ctxForeachCB *) user; r_return_val_if_fail (ctx && ctx->cb, false); RSignItem *it = r_sign_item_new (); RAnal *a = ctx->anal; bool keep_going = true; if (it && r_sign_deserialize (a, it, k, v)) { if (!ctx->space || ctx->space == it->space) { keep_going = ctx->cb (it, ctx->user); } } else { R_LOG_ERROR ("cannot deserialize zign"); } if (ctx->freeit) { r_sign_item_free (it); } return keep_going; } static inline bool local_foreach_item(RAnal *a, RSignForeachCallback cb, const RSpace *sp, bool freeit, void *user) { r_return_val_if_fail (a && cb, false); struct ctxForeachCB ctx = { a, cb, sp, freeit, user }; return sdb_foreach (a->sdb_zigns, foreachCB, &ctx); } static bool r_sign_foreach_nofree(RAnal *a, RSignForeachCallback cb, void *user) { return local_foreach_item (a, cb, r_spaces_current (&a->zign_spaces), false, user); } R_API int r_sign_space_count_for(RAnal *a, const RSpace *space) { int count = 0; local_foreach_item (a, countForCB, space, true, &count); return count; } R_API void r_sign_space_unset_for(RAnal *a, const RSpace *space) { local_foreach_item (a, unsetForCB, space, true, a->sdb_zigns); } R_API bool r_sign_foreach(RAnal *a, RSignForeachCallback cb, void *user) { return local_foreach_item (a, cb, r_spaces_current (&a->zign_spaces), true, user); } R_API RList *r_sign_get_list(RAnal *a) { r_return_val_if_fail (a, NULL); RList *l = r_list_newf ((RListFree)r_sign_item_free); if (l) { local_foreach_item (a, listGetCB, NULL, false, (void *)l); } return l; } R_API RSignSearch *r_sign_search_new(void) { RSignSearch *ret = R_NEW0 (RSignSearch); if (ret) { ret->search = r_search_new (R_SEARCH_KEYWORD); ret->items = r_list_newf ((RListFree) r_sign_item_free); } return ret; } R_API void r_sign_search_free(RSignSearch *ss) { if (!ss) { return; } r_search_free (ss->search); r_list_free (ss->items); free (ss); } R_API RList *r_sign_find_closest_sig(RAnal *a, RSignItem *it, int count, double score_threshold) { r_return_val_if_fail (a && it && count > 0 && score_threshold >= 0 && score_threshold <= 1, NULL); // need at least one acceptable signature type r_return_val_if_fail (it->bytes || it->graph, NULL); ClosestMatchData data; RList *output = r_list_newf ((RListFree)r_sign_close_match_free); if (!output) { return NULL; } data.output = output; data.count = count; data.score_threshold = score_threshold; data.infimum = 0.0; data.test = it; if (it->bytes) { data.bytes_combined = build_combined_bytes (it->bytes); } else { data.bytes_combined = NULL; } if (!r_sign_foreach_nofree (a, _closest_match_cb, &data)) { r_list_free (output); output = NULL; } free (data.bytes_combined); return output; } static int searchHitCB(RSearchKeyword *kw, void *user, ut64 addr) { RSignSearch *ss = (RSignSearch *) user; return ss->cb? ss->cb ((RSignItem *) kw->data, kw, addr, ss->user): 1; } struct ctxAddSearchKwCB { RSignSearch *ss; int minsz; }; static bool addSearchKwCB(RSignItem *it, void *user) { struct ctxAddSearchKwCB *ctx = (struct ctxAddSearchKwCB *) user; RSignSearch *ss = ctx->ss; RSignBytes *bytes = it->bytes; if (!bytes) { R_LOG_ERROR ("Cannot find bytes for this signature: %s", it->name); r_sign_item_free (it); return true; } if (ctx->minsz && bytes->size < ctx->minsz) { r_sign_item_free (it); return true; } r_list_append (ss->items, it); // TODO(nibble): change arg data in r_search_keyword_new to void* RSearchKeyword *kw = r_search_keyword_new (bytes->bytes, bytes->size, bytes->mask, bytes->size, (const char *)it); r_search_kw_add (ss->search, kw); return true; } R_API void r_sign_search_init(RAnal *a, RSignSearch *ss, int minsz, RSignSearchCallback cb, void *user) { struct ctxAddSearchKwCB ctx = { ss, minsz }; r_return_if_fail (a && ss && cb); ss->cb = cb; ss->user = user; r_list_purge (ss->items); r_search_reset (ss->search, R_SEARCH_KEYWORD); r_sign_foreach_nofree (a, addSearchKwCB, &ctx); r_search_begin (ss->search); r_search_set_callback (ss->search, searchHitCB, ss); } R_API int r_sign_search_update(RAnal *a, RSignSearch *ss, ut64 *at, const ut8 *buf, int len) { r_return_val_if_fail (a && ss && buf && len > 0, 0); return r_search_update (ss->search, *at, buf, len); } // allow ~10% of margin error static int matchCount(int a, int b) { int m = R_MAX (a, b); if (m > 100) { return R_ABS (a - b) < m / 10; } return a == b; } static int sig_graph_diff(RSignItem *ia, RSignItem *ib) { RSignGraph *a = ia->graph; RSignGraph *b = ib->graph; if (!a || !b) { return 1; } if (a->cc != -1 && a->cc != b->cc) { return 1; } if (a->nbbs != -1 && a->nbbs != b->nbbs) { return 1; } if (a->edges != -1 && a->edges != b->edges) { return 1; } if (a->ebbs != -1 && a->ebbs != b->ebbs) { return 1; } if (a->bbsum > 0 && !matchCount (a->bbsum, b->bbsum)) { return 1; } return 0; } #define SORT_EMPY_LAST(x, y) \ if (!x) { \ return !y? 1: 0; \ } \ if (!y) { \ return -1; \ } static int sig_graph_cmp(RSignItem *ia, RSignItem *ib) { RSignGraph *a = ia->graph; RSignGraph *b = ib->graph; SORT_EMPY_LAST (a, b); int diff = a->bbsum - b->bbsum; if (diff) { return diff; } diff = a->cc - b->cc; if (diff) { return diff; } diff = a->nbbs - b->nbbs; if (diff) { return diff; } diff = a->edges - b->edges; if (diff) { return diff; } diff = a->ebbs - b->ebbs; if (diff) { return diff; } return 0; } // this is to compare a byte signature to a function, that math is slightly // different static int sig_bytes_diff(RSignItem *isig, RSignItem *ifunc) { RSignBytes *sig = isig->bytes; RSignBytes *func = ifunc->bytes; if (!sig || !func) { return 1; } if (sig->size != func->size) { return 1; } int i; for (i = 0; i < sig->size; i++) { char m = sig->mask[i]; if (m && (sig->bytes[i] & m) != (func->bytes[i] & m)) { return 1; } } return 0; } static int sig_bytes_cmp(RSignItem *ia, RSignItem *ib) { RSignBytes *a = ia->bytes; RSignBytes *b = ib->bytes; SORT_EMPY_LAST (a, b); int i = a->size - b->size; if (i) { return i; } for (i = 0; i < a->size; i++) { int cmp = a->bytes[i] & a->mask[i]; cmp -= b->bytes[i] & b->mask[i]; if (cmp) { return cmp; } } return 0; } static int sig_addr_cmp(ut64 a, ut64 b) { if (a < b) { return 1; } return a > b? -1: 0; } static int sig_hash_cmp(RSignHash *a, RSignHash *b) { if (!a) { return b? -1: 0; } if (!b) { return 1; } return strcmp (a->bbhash, b->bbhash); } static int sig_var_diff(RList *la, RList *lb) { if (!la) { return lb? -1: 0; } if (!lb) { return 1; } int len = r_list_length (la); int dif = len - r_list_length (lb); if (dif) { return dif; } size_t i; for (i = 0; i < len; i++) { const RAnalVarProt *a = r_list_get_n (la, i); const RAnalVarProt *b = r_list_get_n (lb, i); // shouldn't happen, but try to keep it together if it does r_return_val_if_fail (a, -1); r_return_val_if_fail (b, 1); dif = a->delta - b->delta; if (dif) { return dif; } if (a->isarg != b->isarg) { return a->isarg? 1: -1; } dif = a->kind - b->kind; if (dif) { return dif; } } return 0; } static int str_list_cmp(RList *la, RList *lb) { if (!la) { return lb? -1: 0; } if (!lb) { return 1; } int len = r_list_length (la); int dif = len - r_list_length (lb); if (dif) { return dif; } size_t i; for (i = 0; i < len; i++) { const char *a = r_list_get_n (la, i); const char *b = r_list_get_n (lb, i); if ((dif = strcmp (a, b))) { return dif; } } return 0; } static bool type_in_array(RSignType *arr, RSignType needle) { while (*arr != R_SIGN_END) { if (*arr == needle) { return true; } arr++; } return false; } static RListIter *collision_skip_unused(RListIter *iter, RSignType *used) { char *n = (char *)r_list_iter_get_data (iter); if (!n) { return r_list_iter_get_next (iter); } RSignType skip = n[0]; if (type_in_array (used, skip)) { return iter; } RListIter *next; while ((next = r_list_iter_get_next (iter))) { n = r_list_iter_get_data (next); if (n) { RSignType t = n[0]; if (skip != t) { if (type_in_array (used, t)) { return iter; } } } iter = next; } return iter; } // return NULL one error, otherwise return a, possibly empty, list of // collisions. Relies on sets being ordered in groups of types. Returns NULL no // error, otherwise an RList of collisions. RList will be empty when there are // no collisions static RList *check_collisions(RList *collisions, RSignType *types) { if (!collisions || types[0] == R_SIGN_END) { return r_list_new (); } RListIter *iter = r_list_iterator (collisions); if (!iter) { return NULL; } // skip over types that were not matched against if (!(iter = collision_skip_unused (iter, types))) { return r_list_new (); } char *col = (char *)r_list_iter_get_data (iter); if (!col || col[1] != ':') { return NULL; } RList *set = r_list_new (); if (!set) { return NULL; } RList *holder = NULL; // add the names from first matched type to return set RSignType thistype = col[0]; while (iter) { r_list_append (set, col + 2); if ((iter = r_list_iter_get_next (iter))) { col = (char *)r_list_iter_get_data (iter); if (!col || col[1] != ':') { goto collerr; } RSignType t = col[1]; if (t != thistype) { break; } } } holder = r_list_new (); if (!holder) { goto collerr; } // now we interset return set with next relevent type and repeat while (iter && (iter = collision_skip_unused (iter, types))) { // loop over new type group col = (char *)r_list_iter_get_data (iter); if (!col || col[1] != ':') { goto collerr; } // interset current type RSignType t = col[0]; RSignType nexttype = t; while (t == nexttype) { char *name = col + 2; if (r_list_find (set, name, list_str_cmp)) { r_list_append (holder, col + 2); } nexttype = R_SIGN_END; if ((iter = r_list_iter_get_next (iter))) { col = (char *)r_list_iter_get_data (iter); if (!col || col[1] != ':') { goto collerr; } nexttype = col[1]; } } r_list_purge (set); RList *tmplist = set; set = holder; holder = tmplist; } r_list_free (holder); return set; collerr: r_list_free (set); r_list_free (holder); return NULL; } struct metric_ctx { int matched; RSignItem *it; RSignSearchMetrics *sm; RAnalFunction *fcn; char *suggest; // holds suggestion for next function match, must be freed }; static bool match_metrics(RSignItem *it, struct metric_ctx *ctx) { r_return_val_if_fail (it && ctx, false); RSignSearchMetrics *sm = ctx->sm; RSignItem *fit = ctx->it; RSignType types[R_SIGN_TYPEMAX]; int count = 0; if (it->bytes && (it->bytes->size >= sm->minsz || ctx->suggest) && !sig_bytes_diff (it, fit)) { types[count++] = R_SIGN_BYTES; } if (it->graph && it->graph->cc >= sm->mincc && !sig_graph_diff (it, fit)) { types[count++] = R_SIGN_GRAPH; } if (fit->addr != UT64_MAX && !sig_addr_cmp (it->addr, fit->addr)) { types[count++] = R_SIGN_OFFSET; } if (fit->hash && it->hash && !sig_hash_cmp (it->hash, fit->hash)) { types[count++] = R_SIGN_BBHASH; } if (fit->refs && it->refs && !str_list_cmp (it->refs, fit->refs)) { types[count++] = R_SIGN_REFS; } if (fit->vars && it->vars && !sig_var_diff (it->vars, fit->vars)) { types[count++] = R_SIGN_VARS; } if (fit->types && it->types && !strcmp (it->types, fit->types)) { types[count++] = R_SIGN_TYPES; } bool keep_searching = true; if (count) { RList *col = NULL; if (ctx->suggest) { // Collisions are not possible here, assuming collisions are being // used. This is b/c we would not recieve a suggestion unless the // previous match lacked a collision. types[count++] = R_SIGN_NEXT; free (ctx->suggest); ctx->suggest = NULL; col = r_list_new (); types[count] = R_SIGN_END; } else { types[count] = R_SIGN_END; col = check_collisions (it->collisions, types); } ctx->matched += count; types[count] = R_SIGN_END; sm->cb (it, ctx->fcn, types, sm->user, col); // is match unique? if (col && r_list_length (col) == 0) { keep_searching = false; // suggest next signature from this match ctx->suggest = it->next; it->next = NULL; } r_list_free (col); } else { free (ctx->suggest); ctx->suggest = NULL; } return keep_searching; } static bool _sig_to_vec_cb(RSignItem *it, void *user) { if (it->collisions) { r_list_free (it->collisions); it->collisions = NULL; } return r_pvector_push ((RPVector *)user, it)? true: false; } static bool item_addto_collisions(RSignItem *it, const char *add) { r_return_val_if_fail (it, false); if (!it->collisions) { it->collisions = r_list_newf (free); if (!it->collisions) { return false; } } RList *l = it->collisions; if (r_list_find (l, (void *)add, list_str_cmp)) { return true; } char *dup = strdup (add); if (!dup) { return false; } return r_list_append (l, dup)? true: false; } static bool update_collide(RPVector *sigs, int start, int end, int type) { r_return_val_if_fail (start >= 0 && end > 0 && sigs, false); int i, ii; for (i = start; i <= end; i++) { RSignItem *it = (RSignItem *)r_pvector_at (sigs, i); if (!it) { return false; } char *fmt = r_str_newf ("%c:%s", type, it->name); if (!fmt) { return false; } for (ii = start; ii <= end; ii++) { if (i != ii) { RSignItem *itt = (RSignItem *)r_pvector_at (sigs, ii); if (!item_addto_collisions (itt, fmt)) { free (fmt); return false; } } } free (fmt); } return true; } static bool item_has_type(RSignItem *it, RSignType t) { switch (t) { case R_SIGN_BYTES: return it->bytes? true: false; case R_SIGN_COMMENT: return it->comment? true: false; case R_SIGN_GRAPH: return it->graph? true: false; case R_SIGN_OFFSET: return it->addr != UT64_MAX? true: false; case R_SIGN_NAME: return it->realname? true: false; case R_SIGN_REFS: return it->refs? true: false; case R_SIGN_XREFS: return it->xrefs? true: false; case R_SIGN_VARS: return it->vars? true: false; case R_SIGN_TYPES: return it->types? true: false; case R_SIGN_COLLISIONS: return it->collisions? true: false; case R_SIGN_BBHASH: return (it->hash && it->hash->bbhash)? true: false; default: return false; } } typedef int (*RSignSorter) (RSignItem *, RSignItem *); static RSignSorter type_to_cmp(int type, bool exact) { switch (type) { case R_SIGN_GRAPH: if (exact) { return sig_graph_diff; } return sig_graph_cmp; case R_SIGN_BYTES: return sig_bytes_cmp; default: return NULL; } } static inline bool sign_collide_by(RPVector *sigs, RSignType type) { RSignSorter cmp = type_to_cmp (type, false); if (!cmp) { return false; } r_pvector_sort (sigs, (RPVectorComparator)cmp); // sorting and matching can be slightly different cmp = type_to_cmp (type, true); void **p; int i, start, end; RSignItem *old = NULL; i = 0; start = end = -1; r_pvector_foreach (sigs, p) { RSignItem *it = *p; if (!item_has_type (it, type)) { // sort algs should put NULL at bottom break; } if (old) { if (!cmp (old, it)) { // signature collisions if (start < 0) { start = i - 1; end = i; } else { end++; } } else if (start >= 0) { // not a collision, but had a collision before if (!update_collide (sigs, start, end, type)) { return false; } start = end = -1; } } old = it; i++; } if (start >= 0 && !update_collide (sigs, start, end, type)) { return false; } return true; } static inline RSignItem *metric_build_item(RSignSearchMetrics *sm, RAnalFunction *fcn) { RSignItem *it = r_sign_item_new (); if (it) { RSignType *t = sm->stypes; while (*t != R_SIGN_END) { if (*t == R_SIGN_BYTES) { // no need for mask it->bytes = r_sign_func_empty_mask (sm->anal, fcn); } else { r_sign_addto_item (sm->anal, it, fcn, *t); } t++; } if (it->graph && it->graph->cc < sm->mincc) { r_sign_graph_free (it->graph); it->graph = NULL; } } return it; } R_API bool r_sign_resolve_collisions(RAnal *a) { r_return_val_if_fail (a, false); RPVector *sigs = r_pvector_new ((RPVectorFree)r_sign_item_free); if (!sigs) { return false; } if (!r_sign_foreach_nofree (a, _sig_to_vec_cb, (void *)sigs)) { r_pvector_free (sigs); return false; } int i = 0; RSignType types[] = { R_SIGN_BYTES, R_SIGN_GRAPH, R_SIGN_END }; for (i = 0; types[i] != R_SIGN_END; i++) { sign_collide_by (sigs, types[i]); } // save updated signatures void **p; r_pvector_foreach (sigs, p) { RSignItem *it = *p; if (it->collisions) { r_list_sort (it->collisions, list_str_cmp); r_sign_add_item (a, it); } } r_pvector_free (sigs); return true; } // returns true if you should keep searching static inline bool suggest_check(RAnal *a, struct metric_ctx *ctx) { int ret = true; if (ctx && ctx->suggest) { RSignItem *it = r_sign_get_item (a, ctx->suggest); if (it) { ret = match_metrics (it, ctx); r_sign_item_free (it); } } return ret; } R_API int r_sign_metric_search(RAnal *a, RSignSearchMetrics *sm) { r_return_val_if_fail (a && sm, -1); RListIter *iter; r_list_sort (a->fcns, fcn_sort); r_cons_break_push (NULL, NULL); struct metric_ctx ctx = { 0, NULL, sm, NULL, NULL }; r_list_foreach (a->fcns, iter, ctx.fcn) { if (r_cons_is_breaked ()) { break; } ctx.it = metric_build_item (sm, ctx.fcn); if (ctx.it && suggest_check (sm->anal, &ctx)) { r_sign_foreach (sm->anal, (RSignForeachCallback)match_metrics, (void *)&ctx); } r_sign_item_free (ctx.it); } r_cons_break_pop (); free (ctx.suggest); return ctx.matched; } R_API int r_sign_fcn_match_metrics(RSignSearchMetrics *sm, RAnalFunction *fcn) { r_return_val_if_fail (sm && sm->mincc >= 0 && sm->anal && fcn, -1); struct metric_ctx ctx = { 0, metric_build_item (sm, fcn), sm, fcn, NULL }; if (ctx.it) { r_sign_foreach (sm->anal, (RSignForeachCallback)match_metrics, (void *)&ctx); r_sign_item_free (ctx.it); free (ctx.suggest); } return ctx.matched; } R_API RSignItem *r_sign_item_new(void) { RSignItem *ret = R_NEW0 (RSignItem); if (ret) { ret->addr = UT64_MAX; } return ret; } R_API void r_sign_item_free(RSignItem *item) { if (item) { free (item->name); free (item->next); r_sign_bytes_free (item->bytes); r_sign_hash_free (item->hash); r_sign_graph_free (item->graph); free (item->comment); free (item->realname); free (item->types); r_list_free (item->refs); r_list_free (item->vars); r_list_free (item->xrefs); r_list_free (item->collisions); free (item); } } R_API void r_sign_graph_free(RSignGraph *graph) { free (graph); } R_API void r_sign_bytes_free(RSignBytes *bytes) { if (bytes) { free (bytes->bytes); free (bytes->mask); free (bytes); } } R_API void r_sign_hash_free(RSignHash *hash) { if (hash) { free (hash->bbhash); free (hash); } } struct load_sign_data { RAnal *anal; bool merge; }; static bool loadCB(void *user, const char *k, const char *v) { struct load_sign_data *u = (struct load_sign_data *)user; RAnal *a = u->anal; RSignItem *it = r_sign_item_new (); if (it && r_sign_deserialize (a, it, k, v)) { if (u->merge || !name_exists (a->sdb_zigns, it->name, it->space)) { sdb_set (a->sdb_zigns, k, v, 0); } else { char *name = get_unique_name (a->sdb_zigns, it->name, it->space); if (name) { if (!it->realname) { it->realname = it->name; } else { free (it->name); } it->name = name; r_sign_set_item (a->sdb_zigns, it, NULL); } } } else { R_LOG_ERROR ("cannot deserialize zign"); } r_sign_item_free (it); return true; } R_API char *r_sign_path(RAnal *a, const char *file) { char *abs = r_file_abspath (file); if (abs) { if (r_file_is_regular (abs)) { return abs; } free (abs); } if (a->zign_path) { char *path = r_str_newf ("%s%s%s", a->zign_path, R_SYS_DIR, file); abs = r_file_abspath (path); free (path); if (r_file_is_regular (abs)) { return abs; } free (abs); } else { char *home = r_xdg_datadir ("zigns"); abs = r_file_new (home, file, NULL); free (home); if (r_file_is_regular (abs)) { return abs; } free (abs); } abs = r_str_newf (R_JOIN_3_PATHS ("%s", R2_ZIGNS, "%s"), r_sys_prefix (NULL), file); if (r_file_is_regular (abs)) { return abs; } free (abs); return NULL; } enum { SIGNDB_TYPE_SDB, SIGNDB_TYPE_KV, SIGNDB_TYPE_JSON, SIGNDB_TYPE_R2, SIGNDB_TYPE_INVALID = -1, }; static int signdb_type(const char *file) { if (r_str_endswith (file, ".sdb")) { return SIGNDB_TYPE_SDB; } if (r_str_endswith (file, ".sdb.txt")) { return SIGNDB_TYPE_KV; } if (r_str_endswith (file, ".json")) { return SIGNDB_TYPE_JSON; } if (r_str_endswith (file, ".r2")) { return SIGNDB_TYPE_R2; } int i, sz = 0; char *data = r_file_slurp_range (file, 0, 0x200, &sz); if (!data) { return SIGNDB_TYPE_INVALID; } if (sz < 1) { free (data); return SIGNDB_TYPE_INVALID; } data[sz - 1] = 0; sz = R_MIN (sz, 0x200); int is_sdb = 16; int is_kv = 4; int is_r2 = 2; int t = SIGNDB_TYPE_INVALID; if (r_str_startswith (data, "[{\"name\":")) { t = SIGNDB_TYPE_JSON; } else { for (i = 0; i < sz; i++) { if (!strncmp (data + i, "\nza ", 4)) { is_r2--; i += 3; continue; } if ((i & 3) == 3 && data[i] == 0) { is_sdb--; continue; } if (data[i] == '=' || data[i] == '\n') { is_kv--; continue; } } if (is_sdb < 0) { t = SIGNDB_TYPE_SDB; } else if (is_r2 < 0) { t = SIGNDB_TYPE_R2; } else if (is_kv < 0) { t = SIGNDB_TYPE_KV; } } // XXX looks like a false positive bug in gcc 9.4 (debian CI) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfree-nonheap-object" free (data); #pragma GCC diagnostic pop return t; } static bool sign_load_r2(RAnal *a, const char *path) { char *cmd = r_str_newf (". %s", path); a->coreb.cmd (a->coreb.core, cmd); free (cmd); return true; } static bool load_json_signature(RAnal *a, const RJson *child) { const char *n = r_json_type (child); if (strcmp (n, "object")) { return false; } RSignItem *it = r_sign_item_new (); const RJson *name = r_json_get (child, "name"); if (name && name->type == R_JSON_STRING) { it->name = strdup (name->str_value); } const RJson *rname = r_json_get (child, "realname"); if (rname && rname->type == R_JSON_STRING) { it->realname = strdup (rname->str_value); } const RJson *bytes = r_json_get (child, "bytes"); const RJson *mask = r_json_get (child, "mask"); if (bytes && mask) { it->bytes = R_NEW0 (RSignBytes); it->bytes->bytes = (ut8*)strdup (bytes->str_value); it->bytes->size = r_hex_str2bin (bytes->str_value, it->bytes->bytes); it->bytes->mask = (ut8*)strdup (mask->str_value); (void)r_hex_str2bin (mask->str_value, it->bytes->mask); } const RJson *types = r_json_get (child, "types"); if (types && types->type == R_JSON_STRING) { it->types = strdup (types->str_value); } const RJson *next = r_json_get (child, "next"); if (next && next->type == R_JSON_STRING) { it->next = strdup (next->str_value); } const RJson *addr = r_json_get (child, "addr"); if (addr) { it->addr = addr->num.u_value; } const RJson *graph = r_json_get (child, "graph"); if (graph && graph ->type == R_JSON_OBJECT) { const RJson *bcc = r_json_get (graph, "cc"); const RJson *nbb = r_json_get (graph, "nbbs"); const RJson *edg = r_json_get (graph, "edges"); const RJson *ebb = r_json_get (graph, "ebbs"); const RJson *bbs = r_json_get (graph, "bbsum"); it->graph = R_NEW0 (RSignGraph); it->graph->cc = bcc? bcc->num.u_value: 0; it->graph->nbbs = nbb? nbb->num.u_value: 0; it->graph->edges = edg? edg->num.u_value: 0; it->graph->ebbs = ebb? ebb->num.u_value: 0; it->graph->bbsum = bbs? bbs->num.u_value: 0; } const RJson *refs = r_json_get (child, "refs"); if (refs) { it->refs = r_list_new (); RJson *p = refs->children.first; while (p) { if (p->type == R_JSON_STRING) { r_list_append (it->refs, strdup (p->str_value)); } p = p->next; } } const RJson *xrefs = r_json_get (child, "xrefs"); if (xrefs) { it->xrefs = r_list_new (); RJson *p = xrefs->children.first; while (p) { if (p->type == R_JSON_STRING) { r_list_append (it->xrefs, strdup (p->str_value)); } p = p->next; } } const RJson *hash = r_json_get (child, "hash"); if (hash && hash->type == R_JSON_OBJECT) { const RJson *bbhash = r_json_get (hash, "bbhash"); it->hash = R_NEW0 (RSignHash); it->hash->bbhash = strdup (bbhash->str_value); } r_sign_set_item (a->sdb_zigns, it, NULL); r_sign_item_free (it); return true; } static bool sign_load_json(RAnal *a, const char *path) { size_t sz; char *text = r_file_slurp (path, &sz); if (!text) { return false; } bool res = false; RJson *rj = r_json_parse (text); if (rj->type != R_JSON_ARRAY) { R_LOG_ERROR ("Invalid json"); } else { res = true; // walk the array int i = 0; for (i = 0;; i++) { const RJson *child = r_json_item (rj, i); if (!child) { break; } if (!load_json_signature (a, child)) { R_LOG_WARN ("invalid json"); res = false; break; } } } r_json_free (rj); return res; } static bool sign_load_sdb(RAnal *a, const char *path, bool merge) { Sdb *db = sdb_new (NULL, path, 0); if (db) { struct load_sign_data u = { .anal = a, .merge = merge }; sdb_foreach (db, loadCB, &u); sdb_close (db); sdb_free (db); return true; } return false; } R_API bool r_sign_load(RAnal *a, const char *file, bool merge) { r_return_val_if_fail (a && file, false); char *path = r_sign_path (a, file); if (!path) { R_LOG_ERROR ("file %s not found in sign path", file); return false; } if (!r_file_exists (path)) { R_LOG_ERROR ("file %s does not exist", file); free (path); return false; } bool res = false; const int type = signdb_type (path); switch (type) { case SIGNDB_TYPE_R2: res = sign_load_r2 (a, path); break; case SIGNDB_TYPE_JSON: res = sign_load_json (a, path); break; case SIGNDB_TYPE_KV: case SIGNDB_TYPE_SDB: res = sign_load_sdb (a, path, merge); break; default: R_LOG_ERROR ("Unsupported signature file format"); break; } free (path); return res; } R_API bool r_sign_load_gz(RAnal *a, const char *filename, bool merge) { ut8 *buf = NULL; int size = 0; char *tmpfile = NULL; bool retval = true; char *path = r_sign_path (a, filename); if (!path) { R_LOG_ERROR ("file %s not found in sign path", filename); return false; } if (!r_file_exists (path)) { R_LOG_ERROR ("file %s does not exist", filename); retval = false; goto out; } if (!(buf = r_file_gzslurp (path, &size, 0))) { R_LOG_ERROR ("cannot decompress file"); retval = false; goto out; } if (!(tmpfile = r_file_temp ("r2zign"))) { R_LOG_ERROR ("cannot create temp file"); retval = false; goto out; } if (!r_file_dump (tmpfile, buf, size, 0)) { R_LOG_ERROR ("cannot dump file"); retval = false; goto out; } if (!r_sign_load (a, tmpfile, merge)) { R_LOG_ERROR ("cannot load file"); retval = false; goto out; } if (!r_file_rm (tmpfile)) { R_LOG_ERROR ("cannot delete temp file"); retval = false; goto out; } out: free (buf); free (tmpfile); free (path); return retval; } R_API bool r_sign_save(RAnal *a, const char *file) { r_return_val_if_fail (a && file, false); if (sdb_isempty (a->sdb_zigns)) { R_LOG_WARN ("no zignatures to save"); return false; } Sdb *db = sdb_new (NULL, file, 0); if (!db) { return false; } sdb_merge (db, a->sdb_zigns); bool retval = sdb_sync (db); sdb_close (db); sdb_free (db); return retval; } R_API RSignOptions *r_sign_options_new(const char *bytes_thresh, const char *graph_thresh) { RSignOptions *options = R_NEW0 (RSignOptions); if (!options) { return NULL; } options->bytes_diff_threshold = r_num_get_float (NULL, bytes_thresh); options->graph_diff_threshold = r_num_get_float (NULL, graph_thresh); if (options->bytes_diff_threshold > 1.0) { options->bytes_diff_threshold = 1.0; } if (options->bytes_diff_threshold < 0) { options->bytes_diff_threshold = 0.0; } if (options->graph_diff_threshold > 1.0) { options->graph_diff_threshold = 1.0; } if (options->graph_diff_threshold < 0) { options->graph_diff_threshold = 0.0; } return options; } R_API void r_sign_options_free(RSignOptions *options) { free (options); }