/* Patchdiff2 Portions (C) 2010 - 2011 Nicolas Pouvesle Portions (C) 2007 - 2009 Tenable Network Security, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "precomp.hpp" #include "hash.hpp" #include "sig.hpp" #include "diff.hpp" #include "clist.hpp" #include "display.hpp" #include "backup.hpp" #include "options.hpp" /*------------------------------------------------*/ /* function : diff_init_hash */ /* description: Initializes a hash structure and */ /* creates successor xrefs */ /*------------------------------------------------*/ static hpsig_t * diff_init_hash(slist_t * sl) { fref_t * fref; psig_t * sig; hpsig_t * h; size_t i; h = hash_init(sl->num); if (!h) return NULL; for(i=0; inum; i++) { if (hash_add_ea(h, sl->sigs[i]) < 0) { hash_free(h); return NULL; } } // adds xrefs for(i=0; inum; i++) { if (sl->sigs[i]->srefs) { fref = sl->sigs[i]->srefs->list; while (fref) { if (!fref->rtype) { sig = hash_find_ea(h, fref->ea); if (sig) sig_add_pref(sig, sl->sigs[i]->startEA, fref->type, DO_NOT_CHECK_REF); } fref = fref->next; } } if (sl->sigs[i]->prefs) { fref = sl->sigs[i]->prefs->list; while (fref) { if (!fref->rtype) { sig = hash_find_ea(h, fref->ea); if (sig) sig_add_sref(sig, sl->sigs[i]->startEA, fref->type, DO_NOT_CHECK_REF); } fref = fref->next; } } } return h; } /*------------------------------------------------*/ /* function : slist_init_crefs */ /* description: Initializes slist crefs */ /*------------------------------------------------*/ static int slist_init_crefs(slist_t * l) { hpsig_t * h = NULL; clist_t * cl1, * cl2; size_t i; h = diff_init_hash(l); if(!h) return -1; for (i=0; inum; i++) { cl1 = clist_init_from_refs(h, sig_get_preds(l->sigs[i])); cl2 = clist_init_from_refs(h, sig_get_succs(l->sigs[i])); sig_set_crefs(l->sigs[i], SIG_PRED, cl1); sig_set_crefs(l->sigs[i], SIG_SUCC, cl2); } return 0; } /*------------------------------------------------*/ /* function : diff_engine_initialize */ /* description: Initializes engine structures */ /*------------------------------------------------*/ static deng_t * diff_engine_initialize(slist_t * l1, slist_t * l2, options_t * opt) { deng_t * eng; if (slist_init_crefs(l1) != 0) return NULL; if (slist_init_crefs(l2) != 0) return NULL; eng = (deng_t *)qalloc(sizeof(*eng)); if (!eng) return NULL; eng->magic = 0x0BADF00D; eng->wnum = 0; eng->identical = 0; eng->matched = 0; eng->unmatched = l1->num + l2->num; eng->opt = opt; return eng; } /*------------------------------------------------*/ /* function : diff_engine_initialize */ /* description: Initializes engine structures */ /*------------------------------------------------*/ void diff_engine_free(deng_t * eng) { if (eng->ilist) siglist_free(eng->ilist); if (eng->mlist) siglist_free(eng->mlist); if (eng->ulist) siglist_free(eng->ulist); qfree(eng); } /*------------------------------------------------*/ /* function : sig_equal */ /* description: Checks if 2 sigs are equal */ /*------------------------------------------------*/ bool sig_equal(psig_t * s1, psig_t * s2, int type) { if (s1->sig != s2->sig || s1->hash != s2->hash) return false; if (type == DIFF_EQUAL_SIG_HASH_CRC_STR) { if (s1->str_hash != s2->str_hash) return false; } if (type <= DIFF_EQUAL_SIG_HASH_CRC) { if (s1->crc_hash != s2->crc_hash) return false; } return true; } /*------------------------------------------------*/ /* function : sig_name_equal */ /* description: Checks if 2 sig names are equal */ /*------------------------------------------------*/ static bool sig_name_equal(psig_t * s1, psig_t * s2) { if (!strncmp(s1->name, "sub_", 4) || strcmp(s1->name, s2->name)) return false; return true; } /*------------------------------------------------*/ /* function : clist_equal_match */ /* description: Checks if all the elements of a */ /* clist match */ /*------------------------------------------------*/ static bool clist_equal_match(clist_t * cl1, clist_t * cl2) { dpsig_t * s1, * s2; size_t i; if (!cl1 || !cl2 || cl1->nmatch == 0 || cl2->nmatch == 0) return false; if (cl1->nmatch != cl2->nmatch) return false; s1 = cl1->msigs; s2 = cl2->msigs; for (i=0; inmatch; i++) { if ((sig_get_matched_type(s1->sig) == DIFF_UNMATCHED) || (s1->sig->msig->startEA != s2->sig->startEA)) return false; s1 = s1->next; s2 = s2->next; } return true; } /*------------------------------------------------*/ /* function : clist_almost_equal_match */ /* description: Checks if at lest one element of a*/ /* clist match */ /*------------------------------------------------*/ static bool clist_almost_equal_match(clist_t * cl1, clist_t * cl2) { dpsig_t * s1, * s2; size_t i, k; if (!cl1 || !cl2 || cl1->nmatch == 0 || cl2->nmatch == 0) return false; if (cl1->nmatch != cl2->nmatch) return false; s1 = cl1->msigs; for (i=0; inmatch; i++) { s2 = cl2->msigs; for (k=0; knmatch; k++) { if (s1->sig->msig->startEA == s2->sig->startEA) return true; s2 = s2->next; } s1 = s1->next; } return false; } /*------------------------------------------------*/ /* function : clist_get_unique_sig */ /* description: Returns first unique signature in */ /* list starting at ds */ /* note: changes ds if ds already matched */ /*------------------------------------------------*/ static dpsig_t * clist_get_unique_sig(clist_t * cl, dpsig_t ** ds, int type) { dpsig_t * ptr, * tmp; if (!*ds) return NULL; ptr = *ds; // do not keep the current signature if not unique while (ptr) { if (sig_get_matched_type(ptr->sig) != DIFF_UNMATCHED) { if (ptr == *ds) { *ds = ptr->next; if (!*ds) return NULL; } tmp = ptr->next; clist_remove(cl, ptr); ptr = tmp; } else { if (!ptr->next) break; if (type == DIFF_NEQUAL_SUCC) { if (sig_equal(ptr->sig, (*ds)->sig, type) && sig_equal(ptr->next->sig, (*ds)->sig, type)) return NULL; if (( (!sig_equal(ptr->next->sig, (*ds)->sig, type) && (!ptr->prev || !sig_equal(ptr->prev->sig, (*ds)->sig, type))) || !clist_equal_match((*ds)->sig->cs, ptr->next->sig->cs)) && ptr->sig->cs->nmatch > 0 && ptr->sig->cs->num == ptr->sig->cs->nmatch) break; } else if (type == DIFF_NEQUAL_PRED) { if (sig_equal(ptr->sig, (*ds)->sig, type) && sig_equal(ptr->next->sig, (*ds)->sig, type)) return NULL; if (( (!sig_equal(ptr->next->sig, (*ds)->sig, type) && (!ptr->prev || !sig_equal(ptr->prev->sig, (*ds)->sig, type))) || !clist_equal_match((*ds)->sig->cp, ptr->next->sig->cp)) && ptr->sig->cp->nmatch > 0 && ptr->sig->cp->num == ptr->sig->cp->nmatch) break; } else if (type == DIFF_EQUAL_NAME) { if (!sig_equal(ptr->next->sig, (*ds)->sig, type) || !sig_name_equal((*ds)->sig, ptr->next->sig)) break; } else if (type == DIFF_NEQUAL_STR) { bool b = false; tmp = *ds; if (ptr->sig->str_hash != 0) { // slow: need to improve while (tmp) { if (tmp->sig->startEA != ptr->sig->startEA && tmp->sig->str_hash == ptr->sig->str_hash) { b = true; break; } tmp = tmp->next; } if (!b) break; } } else { bool b = sig_equal(ptr->next->sig, (*ds)->sig, type); if (!b) break; } ptr = ptr->next; } } return ptr; } /*------------------------------------------------*/ /* function : clist_get_best_sig */ /* description: Returns best unique signature in */ /* list */ /* note: position pointer is incremented to the */ /* next signature in the list */ /*------------------------------------------------*/ static dpsig_t * clist_get_best_sig(clist_t * cl, int type) { dpsig_t * best, * ptr; best = cl->pos; ptr = clist_get_unique_sig(cl, &best, type); // no more signature if (!best) return NULL; if (ptr == best) { cl->pos = best->next; return best; } cl->pos = ptr; return clist_get_best_sig(cl, type); } /*------------------------------------------------*/ /* function : clist_get_eq_sig */ /* description: Returns signature if sig presents */ /* in list and unique */ /*------------------------------------------------*/ static dpsig_t * clist_get_eq_sig(clist_t * cl, dpsig_t * dsig, int type) { dpsig_t * ds, * ptr; bool b2, b1 = sig_is_class(dsig->sig); ds = cl->sigs; while (ds) { if (type == DIFF_NEQUAL_SUCC) { ptr = clist_get_unique_sig(cl, &ds, type); if (!ds || !ptr) return NULL; b2 = sig_is_class(ptr->sig); if (b1 ^ b2) return NULL; if (clist_equal_match(ptr->sig->cs, dsig->sig->cs)) { if (ptr->next && (ptr->next->sig->sig == ptr->sig->sig || clist_equal_match(ptr->next->sig->cs, dsig->sig->cs))) return NULL; return ptr; } } else if (type == DIFF_NEQUAL_PRED ) { ptr = clist_get_unique_sig(cl, &ds, type); if (!ds || !ptr) return NULL; b2 = sig_is_class(ptr->sig); if (b1 ^ b2) return NULL; if (clist_equal_match(ptr->sig->cp, dsig->sig->cp)) { if (ptr->next && (ptr->next->sig->sig == ptr->sig->sig || clist_equal_match(ptr->next->sig->cp, dsig->sig->cp))) return NULL; return ptr; } } else if (type == DIFF_EQUAL_NAME) { ptr = clist_get_unique_sig(cl, &ds, type); if (!ds || !ptr) return NULL; if (sig_name_equal(ptr->sig, dsig->sig)) return ptr; } else if (type == DIFF_NEQUAL_STR) { ptr = clist_get_unique_sig(cl, &ds, type); if (!ds || !ptr) return NULL; if (ptr->sig->str_hash != 0 && ptr->sig->str_hash == dsig->sig->str_hash) return ptr; } else { if (sig_equal(ds->sig, dsig->sig, type)) { ptr = clist_get_unique_sig(cl, &ds, type); if (!ds) return NULL; if (ptr != ds || !sig_equal(ds->sig, dsig->sig, type)) return NULL; return ds; } else if (ds->sig->sig < dsig->sig->sig) { return NULL; } } ds = ds->next; } return NULL; } static void clist_update_crefs(clist_t * cl, dpsig_t * ds, int type) { dpsig_t * tmp, * next; dpsig_t * tmp2, * next2; clist_t * tcl; tmp = cl->sigs; while(tmp) { next = tmp->next; if (type == SIG_SUCC) tcl = tmp->sig->cs; else tcl = tmp->sig->cp; tmp2 = tcl->sigs; while(tmp2) { next2 = tmp2->next; if (tmp2->sig->startEA == ds->sig->startEA) clist_remove(tcl, tmp2); tmp2 = next2; } tmp = next; } } static void clist_update_and_remove(clist_t * cl, dpsig_t * ds) { if (ds->removed) return; clist_update_crefs(ds->sig->cp, ds, SIG_SUCC); clist_update_crefs(ds->sig->cs, ds, SIG_PRED); clist_remove(cl, ds); } /*------------------------------------------------*/ /* function : diff_run */ /* description: Runs binary analysis */ /*------------------------------------------------*/ static int diff_run(deng_t * eng, clist_t * cl1, clist_t * cl2, int min_type, int max_type, bool pclass) { dpsig_t * dsig, * dsig2; int changed = 0; int type = min_type; int mtype = max_type; bool b; if (pclass && max_type > DIFF_EQUAL_SIG_HASH) mtype = DIFF_EQUAL_SIG_HASH; do { clist_reset(cl1); clist_reset(cl2); changed = 0; while ( (dsig = clist_get_best_sig(cl1, type)) != NULL) { clist_reset(cl2); dsig2 = clist_get_eq_sig(cl2, dsig, type); if (dsig2) { sig_set_matched_sig(dsig->sig, dsig2->sig, type); eng->unmatched -= 2; if (dsig->sig->hash2 == dsig2->sig->hash2 || sig_equal(dsig->sig, dsig2->sig, DIFF_EQUAL_SIG_HASH)) eng->identical++; else eng->matched++; changed = 1; clist_update_and_remove(cl1, dsig); clist_update_and_remove(cl2, dsig2); b = sig_is_class(dsig->sig); // string matching is not 100% reliable so we only match on crc/hash if (mtype == DIFF_NEQUAL_STR) b = true; diff_run(eng, sig_get_crefs(dsig->sig, SIG_PRED), sig_get_crefs(dsig2->sig, SIG_PRED), min_type, max_type, b); diff_run(eng, sig_get_crefs(dsig->sig, SIG_SUCC), sig_get_crefs(dsig2->sig, SIG_SUCC), min_type, max_type, b); } } if (changed == 0) type++; } while(type <= mtype); return 0; } /*------------------------------------------------*/ /* function : generate_diff */ /* description: Generates binary diff */ /*------------------------------------------------*/ int generate_diff(deng_t ** d, slist_t * l1, slist_t * l2, char * file, bool display, options_t * opt) { int ret; clist_t * cl1, * cl2; int un1, un2, idf, mf; deng_t * eng; eng = diff_engine_initialize(l1, l2, opt); if (eng == NULL) return -1; cl1 = clist_init(l1); cl2 = clist_init(l2); if (file) ret = diff_run(eng, cl1, cl2, DIFF_EQUAL_NAME, DIFF_NEQUAL_STR, false); else { ret = diff_run(eng, cl1, cl2, DIFF_EQUAL_SIG_HASH_CRC, DIFF_EQUAL_SIG_HASH, false); ret = diff_run(eng, cl1, cl2, DIFF_NEQUAL_PRED, DIFF_NEQUAL_STR, false); } if (display) { eng->mlist = siglist_init(eng->matched, file); eng->ulist = siglist_init(eng->unmatched, file); eng->ilist = siglist_init(eng->identical, file); un1 = un2 = idf = mf = 0; for (size_t i=0; inum; i++) { if (sig_is_class(l1->sigs[i])) { sig_free(l1->sigs[i]); continue; } if (sig_get_matched_type(l1->sigs[i]) == DIFF_UNMATCHED) { sig_set_nfile(l1->sigs[i], 1); siglist_add(eng->ulist, l1->sigs[i]); un1++; } else { if (l1->sigs[i]->hash2 == l1->sigs[i]->msig->hash2 || sig_equal(l1->sigs[i], l1->sigs[i]->msig, DIFF_EQUAL_SIG_HASH)) { siglist_add(eng->ilist, l1->sigs[i]); idf++; } else { siglist_add(eng->mlist, l1->sigs[i]); mf++; } } } for (size_t i=0; inum; i++) { if (sig_is_class(l2->sigs[i])) { sig_free(l2->sigs[i]); continue; } if (sig_get_matched_type(l2->sigs[i]) == DIFF_UNMATCHED) { sig_set_nfile(l2->sigs[i], 2); siglist_add(eng->ulist, l2->sigs[i]); un2++; } } msg("Identical functions: %d\n", idf); msg("Matched functions: %d\n", mf); msg("Unmatched functions 1: %d\n", un1); msg("Unmatched functions 2: %d\n", un2); display_results(eng); } if (d) *d = eng; return 0; }