From 7398a8782d1a15acf5d83633ad6f83de3dc69109 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 14 Aug 2020 20:18:32 -0400 Subject: [PATCH 01/16] initial ugly raw checkin for vivisect support. receives and handles some things,doesn't send anything, but had the stubs for the vivisect event handlers --- viv_ext.py | 18 ++ viv_frontend.py | 777 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 795 insertions(+) create mode 100644 viv_ext.py create mode 100644 viv_frontend.py diff --git a/viv_ext.py b/viv_ext.py new file mode 100644 index 0000000..4f77db3 --- /dev/null +++ b/viv_ext.py @@ -0,0 +1,18 @@ +# hook into the analysis modules to set the fhash since we only have the file at initial analysis. store in filemeta +def get_fhash(fname): + with open(fname, 'rb') as f: + return hashlib.sha256(f.read()).hexdigest().upper() + + + +# add key start/stop capabilities in Sharing menu +# +from vqt.main import * +@idlethread +def viv_Extension(vw, vwgui): + from viv_frontend import ACT, toggle_track, toggle_visits, toggle_time, toggle_visitors, revsync_load + vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Tracking', ACT(toggle_track)) + vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Visits (RED)', ACT(toggle_visits)) + vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Time (BLUE)', ACT(toggle_time)) + vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Visitors (GREEN)', ACT(toggle_visitors)) + vwgui.vqAddMenuField('&Tools.&revsync.&load', 'load revsync!!!', ACT(revsync_load)) diff --git a/viv_frontend.py b/viv_frontend.py new file mode 100644 index 0000000..69205da --- /dev/null +++ b/viv_frontend.py @@ -0,0 +1,777 @@ +import math +import time +import hashlib +from collections import defaultdict + +from vivisect import * + +from client import Client +from config import config +from comments import Comments, NoChange +from coverage import Coverage +from threading import Lock +from collections import namedtuple + +Struct = namedtuple('Struct', 'name typedef') + +class State: + @staticmethod + def get(vw): + return vw.metadata.get('revsync') + + show_visits = True + show_time = True + show_visitors = False + track_coverage = True + color_now = False + running = False + + def __init__(self, vw): + self.cov = Coverage() + self.comments = Comments() + self.running = True + self.cmt_changes = {} + self.cmt_lock = Lock() + self.stackvar_changes = {} + self.stackvar_lock = Lock() + self.syms = get_syms(vw) + self.syms_lock = Lock() + self.structs = {} # get_structs(vw) + self.structs_lock = Lock() + + self.filedata_by_sha = {} # FIXME: since we can have multiple files in a workspace, get fhashs based on file + self.filedata_by_fname = {} # FIXME: since we can have multiple files in a workspace, get fhashs based on file + for fname in vw.getFiles(): + sha256 = vw.getFileMeta(fname, 'sha256') + mdict = dict(vw.getFileMetaDict(fname)) + mdict['name'] = fname + self.filedata_by_sha[sha256] = mdict + self.filedata_by_fname[fname] = mdict + + def close(self): + # close out the previous session + for shaval in self.genFhashes(): + client.leave(shaval) + + self.running = False + + def getMetaBySha(self, key): + return self.filedata_by_sha.get(key) + + def getMetaByFname(self, key): + return self.filedata_by_fname.get(key) + + def genFhashes(self): + for fdict in self.filedata_by_sha.values(): + try: + yield fdict['sha256'] + except KeyError: + print "keyerror: no 'sha256' key in metadict: %r" % fdict + +MIN_COLOR = 0 +MAX_COLOR = 200 + +IDLE_ASK = 250 +COLOUR_PERIOD = 20 +BB_REPORT = 50 + +''' +# this has to be done at load time, since vivisect doesn't expect to maintain the file +def get_fhash(fname): + with open(fname, 'rb') as f: + return hashlib.sha256(f.read()).hexdigest().upper() +''' + +def get_can_addr(vw, addr): + fname = vw.getFileByVa(addr) + if fname is None: + raise Exception("ARRG! get_can_addr(0x%x)" % addr) + + imagebase = vw.getFileMeta(fname, 'imagebase') + return addr - imagebase + +def get_ea(vw, sha_key, addr): + imagebase = None + for fname in vw.getFiles(): + fmeta = vw.getFileMetaDict(fname) + if fmeta.get('sha256') == sha_key: + imagebase = fmeta.get('imagebase') + break + + return addr + imagebase + +def get_func_by_addr(vw, addr): + return vw.getFunction(addr) + +def get_bb_by_addr(bv, addr): + return vw.getCodeBlock(addr) + +# in order to map IDA type sizes <-> binja types, +# take the 'size' field and attempt to divine some +# kind of type in binja that's close as possible. +# right now, that means try to use uint8_t ... uint64_t, +# anything bigger just make an array +def get_type_by_size(bv, size): + typedef = None + if size <= 8: + try: + typedef, name = bv.parse_type_string('uint{}_t'.format(8*size)) + except SyntaxError: + pass + else: + try: + typedef, name = bv.parse_type_string('char a[{}]'.format(8*size)) + except SyntaxError: + pass + return typedef + +def get_structs(bv): + d = dict() + for name, typedef in bv.types.items(): + if typedef.structure: + typeid = bv.get_type_id(name) + struct = Struct(name, typedef.structure) + d[typeid] = struct + return d + +def get_syms(vw): + syms = dict(vw.name_by_va) + return syms + +def stack_dict_from_list(stackvars): + d = {} + for var in stackvars: + d[var.storage] = (var.name, var.type) + return d + +def member_dict_from_list(members): + d = {} + for member in members: + d[member.name] = member + return d + +def rename_symbol(vw, addr, name): + vw.makeName(addr, name) + ''' + sym = bv.get_symbol_at(addr) + if sym is not None: + # symbol already exists for this address + if sym.auto is True: + bv.undefine_auto_symbol(sym) + else: + bv.undefine_user_symbol(sym) + # is it a function? + func = get_func_by_addr(bv, addr) + if func is not None: + # function + sym = types.Symbol(SymbolType.FunctionSymbol, addr, name) + else: + # data + sym = types.Symbol(SymbolType.DataSymbol, addr, name) + bv.define_user_symbol(sym) + ''' + +def rename_stackvar(bv, func_addr, offset, name): + func = get_func_by_addr(bv, func_addr) + if func is None: + vw.vprint('revsync: bad func addr %#x during rename_stackvar' % func_addr) + return + # we need to figure out the variable type before renaming + stackvars = stack_dict_from_list(func.vars) + var = stackvars.get(offset) + if var is None: + vw.vprint('revsync: could not locate stack var with offset %#x during rename_stackvar' % offset) + return + var_name, var_type = var + func.create_user_stack_var(offset, var_type, name) + return + +def publish(vw, data, fhash, **kwargs): + state = State.get(vw) + if state: + client.publish(fhash, data, **kwargs) + +def push_cv(vw, data, **kwargs): + state = State.get(vw) + if state: + client.push("%s_COVERAGE" % state.fhash, data, **kwargs) + +def map_color(x): + n = x + if x == 0: return 0 + # x = min(max(0, (x ** 2) / (2 * (x ** 2 - x) + 1)), 1) + # if x == 0: return 0 + return int(math.ceil((MAX_COLOR - MIN_COLOR) * x + MIN_COLOR)) + +def convert_color(color): + r, g, b = [map_color(x) for x in color] + return highlight.HighlightColor(red=r, green=g, blue=b) + +def colour_coverage(bv, cur_func): + state = State.get(bv) + for bb in cur_func.basic_blocks: + color = state.cov.color(get_can_addr(bv, bb.start), visits=state.show_visits, time=state.show_time, users=state.show_visitors) + if color: + bb.set_user_highlight(convert_color(color)) + else: + bb.set_user_highlight(highlight.HighlightColor(red=74, blue=74, green=74)) + +def watch_structs(bv): + """ Check structs for changes and publish diffs""" + state = State.get(bv) + + while state.running: + state.structs_lock.acquire() + structs = get_structs(bv) + if structs != state.structs: + for struct_id, struct in structs.items(): + last_struct = state.structs.get(struct_id) + struct_name = struct.name + if last_struct == None: + # new struct created, publish + vw.vprint('revsync: user created struct %s' % struct_name) + # binja can't really handle unions at this time + publish(bv, {'cmd': 'struc_created', 'struc_name': str(struct_name), 'is_union': False}) + # if there are already members, publish them + members = member_dict_from_list(struct.typedef.members) + if members: + for member_name, member_def in members.items(): + publish(bv, {'cmd': 'struc_member_created', 'struc_name': str(struct_name), 'offset': member_def.offset, 'member_name': member_name, 'size': member_def.type.width, 'flag': None}) + continue + last_name = last_struct.name + if last_name != struct_name: + # struct renamed, publish + vw.vprint('revsync: user renamed struct %s' % struct_name) + publish(bv, {'cmd': 'struc_renamed', 'old_name': str(last_name), 'new_name': str(struct_name)}) + + # check for member differences + members = member_dict_from_list(struct.typedef.members) + last_members = member_dict_from_list(last_struct.typedef.members) + + # first checks for deletions + removed_members = set(last_members.keys()) - set(members.keys()) + for member in removed_members: + vw.vprint('revsync: user deleted struct member %s in struct %s' % (last_members[member].name, str(struct_name))) + publish(bv, {'cmd': 'struc_member_deleted', 'struc_name': str(struct_name), 'offset': last_members[member].offset}) + + # now check for additions + new_members = set(members.keys()) - set(last_members.keys()) + for member in new_members: + vw.vprint('revsync: user added struct member %s in struct %s' % (members[member].name, str(struct_name))) + publish(bv, {'cmd': 'struc_member_created', 'struc_name': str(struct_name), 'offset': members[member].offset, 'member_name': str(member), 'size': members[member].type.width, 'flag': None}) + + # check for changes among intersection of members + intersec = set(members.keys()) & set(last_members.keys()) + for m in intersec: + if members[m].type.width != last_members[m].type.width: + # type (i.e., size) changed + vw.vprint('revsync: user changed struct member %s in struct %s' % (members[m].name, str(struct_name))) + publish(bv, {'cmd': 'struc_member_changed', 'struc_name': str(struct_name), 'offset': members[m].offset, 'size': members[m].type.width}) + + for struct_id, struct_def in state.structs.items(): + if structs.get(struct_id) == None: + # struct deleted, publish + vw.vprint('revsync: user deleted struct %s' % struct_def.name) + publish(bv, {'cmd': 'struc_deleted', 'struc_name': str(struct_def.name)}) + state.structs = get_structs(bv) + state.structs_lock.release() + time.sleep(0.5) + + + +''' +################################################################ unused +def watch_syms(bv, sym_type): + """ Watch symbols of a given type (e.g. DataSymbol) for changes and publish diffs """ + state = State.get(bv) + + while state.running: + state.syms_lock.acquire() + # DataSymbol + data_syms = get_syms(bv, SymbolType.DataSymbol) + if data_syms != state.data_syms: + for addr, name in data_syms.items(): + if state.data_syms.get(addr) != name: + # name changed, publish + vw.vprint('revsync: user renamed symbol at %#x: %s' % (addr, name)) + publish(bv, {'cmd': 'rename', 'addr': get_can_addr(bv, addr), 'text': name}) + + # FunctionSymbol + func_syms = get_syms(bv, SymbolType.FunctionSymbol) + if func_syms != state.func_syms: + for addr, name in func_syms.items(): + if state.func_syms.get(addr) != name: + # name changed, publish + vw.vprint('revsync: user renamed symbol at %#x: %s' % (addr, name)) + publish(bv, {'cmd': 'rename', 'addr': get_can_addr(bv, addr), 'text': name}) + + state.data_syms = get_syms(bv, SymbolType.DataSymbol) + state.func_syms = get_syms(bv, SymbolType.FunctionSymbol) + state.syms_lock.release() + time.sleep(0.5) + +def watch_cur_func(bv): + """ Watch current function (if we're in code) for comment changes and publish diffs """ + def get_cur_func(): + return get_func_by_addr(bv, bv.offset) + + def get_cur_bb(): + return get_bb_by_addr(bv, bv.offset) + + state = State.get(bv) + last_func = get_cur_func() + last_bb = get_cur_bb() + last_time = time.time() + last_bb_report = time.time() + last_bb_addr = None + last_addr = None + while state.running: + now = time.time() + if state.track_coverage and now - last_bb_report >= BB_REPORT: + last_bb_report = now + push_cv(bv, {'b': state.cov.flush()}) + + if last_addr == bv.offset: + time.sleep(0.25) + continue + else: + # were we just in a function? + if last_func: + state.cmt_lock.acquire() + comments = last_func.comments + # check for changed comments + for cmt_addr, cmt in comments.items(): + last_cmt = state.cmt_changes.get(cmt_addr) + if last_cmt == None or last_cmt != cmt: + # new/changed comment, publish + try: + addr = get_can_addr(bv, cmt_addr) + changed = state.comments.parse_comment_update(addr, client.nick, cmt) + vw.vprint('revsync: user changed comment: %#x, %s' % (addr, changed)) + publish(bv, {'cmd': 'comment', 'addr': addr, 'text': changed}) + state.cmt_changes[cmt_addr] = changed + except NoChange: + pass + continue + + # TODO: this needs to be fixed later + """ + # check for removed comments + if last_comments: + removed = set(last_comments.keys()) - set(comments.keys()) + for addr in removed: + addr = get_can_addr(bv, addr) + vw.vprint('revsync: user removed comment: %#x' % addr) + publish(bv, {'cmd': 'comment', 'addr': addr, 'text': ''}) + """ + state.cmt_lock.release() + + # similar dance, but with stackvars + state.stackvar_lock.acquire() + stackvars = stack_dict_from_list(last_func.vars) + for offset, data in stackvars.items(): + # stack variables are more difficult than comments to keep state on, since they + # exist from the beginning, and have a type. track each one. start by tracking the first + # time we see it. if there are changes after that, publish. + stackvar_name, stackvar_type = data + stackvar_val = state.stackvar_changes.get((last_func.start,offset)) + if stackvar_val == None: + # never seen before, start tracking + state.stackvar_changes[(last_func.start,offset)] = stackvar_name + elif stackvar_val != stackvar_name: + # stack var name changed, publish + vw.vprint('revsync: user changed stackvar name at offset %#x to %s' % (offset, stackvar_name)) + publish(bv, {'cmd': 'stackvar_renamed', 'addr': last_func.start, 'offset': offset, 'name': stackvar_name}) + state.stackvar_changes[(last_func.start,offset)] = stackvar_name + state.stackvar_lock.release() + + if state.track_coverage: + cur_bb = get_cur_bb() + if cur_bb != last_bb: + state.color_now = True + now = time.time() + if last_bb_addr is not None: + state.cov.visit_addr(last_bb_addr, elapsed=now - last_time, visits=1) + last_time = now + if cur_bb is None: + last_bb_addr = None + else: + last_bb_addr = get_can_addr(bv, cur_bb.start) + + # update current function/addr info + last_func = get_cur_func() + last_bb = get_cur_bb() + last_addr = bv.offset + + if state.color_now and last_func != None: + colour_coverage(bv, last_func) + state.color_now = False +################################################## ends: unused +''' + +def do_analysis_and_wait(vw): + vw.vprint('revsync: running analysis update...') + vw.analyze() + vw.vprint('revsync: analysis finished.') + return + +### handle remote events: +def onmsg(vw, key, data, replay): + print "onmsg: %r : %r (%r)" % (key, data, replay) + try: + state = State.get(vw) + meta = state.getMetaBySha(key) + if meta is None: + vw.vprint('revsync: hash mismatch, dropping command') + return + + cmd, user = data['cmd'], data['user'] + ts = int(data.get('ts', 0)) + if cmd == 'comment': + state.cmt_lock.acquire() + vw.vprint('revsync: <%s> %s %#x %s' % (user, cmd, data['addr'], data['text'])) + addr = get_ea(vw, key, int(data['addr'])) + #func = get_func_by_addr(vw, addr) + ## binja does not support comments on data symbols??? IDA does. + #if func is not None: + text = state.comments.set(addr, user, data['text'], ts) + vw.setComment(addr, text) + state.cmt_changes[addr] = text + state.cmt_lock.release() + + elif cmd == 'extra_comment': + vw.vprint('revsync: <%s> %s %#x %s' % (user, cmd, data['addr'], data['text'])) + + elif cmd == 'area_comment': + vw.vprint('revsync: <%s> %s %s %s' % (user, cmd, data['range'], data['text'])) + + elif cmd == 'rename': + state.syms_lock.acquire() + vw.vprint('revsync: <%s> %s %#x %s' % (user, cmd, data['addr'], data['text'])) + addr = get_ea(vw, key, int(data['addr'])) + rename_symbol(vw, addr, data['text']) + state.syms = get_syms(vw) + state.syms_lock.release() + + elif cmd == 'stackvar_renamed': + state.stackvar_lock.acquire() + func_name = '???' + func = get_func_by_addr(vw, data['addr']) + if func: + func_name = vw.getName(func) + vw.vprint('revsync: <%s> %s %s %#x %s' % (user, cmd, func_name, data['offset'], data['name'])) + rename_stackvar(vw, data['addr'], data['offset'], data['name']) + # save stackvar changes using the tuple (func_addr, offset) as key + state.stackvar_changes[(data['addr'],data['offset'])] = data['name'] + state.stackvar_lock.release() + + elif cmd == 'struc_created': + # note: binja does not seem to appreciate the encoding of strings from redis + struct_name = data['struc_name'].encode('ascii', 'ignore') + ''' not ready to wrap this into Viv yet. + state.structs_lock.acquire() + struct = bv.get_type_by_name(struct_name) + # if a struct with the same name already exists, undefine it + if struct: + bv.undefine_user_type(struct_name) + struct = Structure() + bv.define_user_type(struct_name, binaryninja.types.Type.structure_type(struct)) + state.structs = get_structs(bv) + state.structs_lock.release() + ''' + vw.vprint('revsync: <%s> %s %s' % (user, cmd, struct_name)) + elif cmd == 'struc_deleted': + struct_name = data['struc_name'].encode('ascii', 'ignore') + ''' not ready to wrap this into Viv yet. + state.stackvar_lock.acquire() + struct = bv.get_type_by_name(struct_name) + # make sure the type is defined first + if struct is None: + vw.vprint('revsync: unknown struct name %s during struc_deleted cmd' % struct_name) + return + bv.undefine_user_type(struct_name) + state.structs = get_structs(bv) + state.structs_lock.release() + ''' + vw.vprint('revsync: <%s> %s %s' % (user, cmd, struct_name)) + elif cmd == 'struc_renamed': + old_struct_name = data['old_name'].encode('ascii', 'ignore') + new_struct_name = data['new_name'].encode('ascii', 'ignore') + ''' not ready to wrap this into Viv yet. + state.structs_lock.acquire() + struct = bv.get_type_by_name(old_struct_name) + # make sure the type is defined first + if struct is None: + vw.vprint('revsync: unknown struct name %s during struc_renamed cmd' % old_struct_name) + return + bv.rename_type(old_struct_name, new_struct_name) + state.structs = get_structs(bv) + state.structs_lock.release() + ''' + vw.vprint('revsync: <%s> %s %s %s' % (user, cmd, old_struct_name, new_struct_name)) + elif cmd == 'struc_member_created': + struct_name = data['struc_name'].encode('ascii', 'ignore') + ''' not ready to wrap this into Viv yet. + state.structs_lock.acquire() + struct = bv.get_type_by_name(struct_name) + if struct is None: + vw.vprint('revsync: unknown struct name %s during struc_member_created cmd' % struct_name) + return + member_name = data['member_name'].encode('ascii', 'ignore') + struct_type = get_type_by_size(bv, data['size']) + if struct_type is None: + vw.vprint('revsync: bad struct member size %d for member %s during struc_member_created cmd' % (data['size'], member_name)) + return + # need actual Structure class, not Type + struct = struct.structure + struct.insert(data['offset'], struct_type, member_name) + # we must redefine the type + bv.define_user_type(struct_name, binaryninja.types.Type.structure_type(struct)) + state.structs = get_structs(bv) + state.structs_lock.release() + ''' + vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, member_name)) + elif cmd == 'struc_member_deleted': + struct_name = data['struc_name'].encode('ascii', 'ignore') + member_name = '???' + ''' not ready to wrap this into Viv yet. + state.structs_lock.acquire() + struct = bv.get_type_by_name(struct_name) + if struct is None: + vw.vprint('revsync: unknown struct name %s during struc_member_deleted cmd' % struct_name) + return + offset = data['offset'] + # need actual Structure class, not Type + struct = struct.structure + # walk the list and find the index to delete (seriously, why by index binja and not offset?) + member_name = '???' + for i,m in enumerate(struct.members): + if m.offset == offset: + # found it + member_name = m.name + struct.remove(i) + # we must redefine the type + bv.define_user_type(struct_name, binaryninja.types.Type.structure_type(struct)) + state.structs = get_structs(bv) + state.structs_lock.release() + ''' + vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, member_name)) + elif cmd == 'struc_member_renamed': + struct_name = data['struc_name'].encode('ascii', 'ignore') + member_name = data['member_name'].encode('ascii', 'ignore') + ''' not ready to wrap this into Viv yet. + state.structs_lock.acquire() + struct = bv.get_type_by_name(struct_name) + if struct is None: + vw.vprint('revsync: unknown struct name %s during struc_member_renamed cmd' % struct_name) + return + offset = data['offset'] + # need actual Structure class, not Type + struct = struct.structure + for i,m in enumerate(struct.members): + if m.offset == offset: + struct.replace(i, m.type, member_name) + bv.define_user_type(struct_name, binaryninja.types.Type.structure_type(struct)) + vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, member_name)) + break + state.structs = get_structs(bv) + state.structs_lock.release() + ''' + vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, member_name)) + elif cmd == 'struc_member_changed': + struct_name = data['struc_name'].encode('ascii', 'ignore') + ''' not ready to wrap this into Viv yet. + state.structs_lock.acquire() + struct = bv.get_type_by_name(struct_name) + if struct is None: + vw.vprint('revsync: unknown struct name %s during struc_member_renamed cmd' % struct_name) + return + # need actual Structure class, not Type + struct = struct.structure + offset = data['offset'] + for i,m in enumerate(struct.members): + if m.offset == offset: + struct.replace(i, get_type_by_size(bv, data['size']), m.name) + bv.define_user_type(struct_name, binaryninja.types.Type.structure_type(struct)) + vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, m.name)) + break + state.structs = get_structs(bv) + state.structs_lock.release() + ''' + vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, m.name)) + elif cmd == 'join': + vw.vprint('revsync: <%s> joined' % (user)) + elif cmd == 'coverage': + vw.vprint("Updating Global Coverage") + state.cov.update(json.loads(data['blocks'])) + state.color_now = True + else: + vw.vprint('revsync: unknown cmd %s' % data) + + except Exception as e: + vw.vprint('onmsg error: %r' % e) + +def revsync_callback(vw): + def callback(key, data, replay=False): + onmsg(vw, key, data, replay) + return callback + +def revsync_comment(vw, addr): + comment = interaction.get_text_line_input('Enter comment: ', 'revsync comment') + publish(vw, {'cmd': 'comment', 'addr': get_can_addr(vw, addr), 'text': comment or ''}, send_uuid=False) + get_func_by_addr(vw, addr).set_comment(addr, comment) + +def revsync_rename(vw, addr): + name = interaction.get_text_line_input('Enter symbol name: ', 'revsync rename') + publish(vw, {'cmd': 'rename', 'addr': get_can_addr(vw, addr), 'text': name}) + rename_symbol(vw, addr, name) + +### handle local events and hand up to REDIS +import vivisect.base as viv_base +class VivEventClient(viv_base.VivEventCore): + # make sure all VA's are reduced to base-addr-offsets + def VWE_COMMENT(self, vw, event, loc): + print vw, event, loc + # make sure something has changed (and that we're not repeating what we just received from revsync + # publish comment to revsync + #fname, fhash, offset = self.getFileContext(vw, loc[L_VA]) + publish(vw, {'cmd': 'comment', 'addr': get_can_addr(vw, addr), 'text': comment or ''}, send_uuid=False) + + def VWE_SETNAME(self, vw, event, loc): + print vw, event, loc + + def VWE_SETFUNCARGS(self, vw, event, loc): + print vw, event, loc + + def VWE_SETFUNCMETA(self, vw, event, loc): + print vw, event, loc + + + def VWE_ADDLOCATION(self, vw, event, loc): + # what kind of location? + # * LOC_STRUCT + # * LOC_STRING + # * LOC_UNICODE + # * LOC_POINTER + # * LOC_NUMBER + # * LOC_OP + if loc[L_LTYPE] is LOC_OP: + return + print vw, event, loc + + def VWE_DELLOCATION(self, vw, event, loc): + print vw, event, loc + + def VWE_SETMETA(self, vw, event, loc): + print vw, event, loc + + def VWE_ADDFUNCTION(self, vw, event, loc): + print vw, event, loc + + def VWE_DELFUNCTION(self, vw, event, loc): + print vw, event, loc + + def VWE_ADDCOLOR(self, vw, event, loc): + print vw, event, loc + + def VWE_DELCOLOR(self, vw, event, loc): + print vw, event, loc + + def VWE_CHAT(self, vw, event, loc): + print vw, event, loc + + +client = None +evtdist = None + + +def revsync_load(vw): + global client, evtdist + + ### hook into the viv event stream + + # lets ensure auto-analysis is finished by forcing another analysis + t0 = threading.Thread(target=do_analysis_and_wait, args=(vw,)) + t0.start() + t0.join() + + if client is None: + client = Client(**config) + + state = vw.metadata.get('revsync') + if state: + state.close() + + vw.vprint('revsync: working...') + state = vw.metadata['revsync'] = State(vw) + vw.vprint('revsync: connecting with hashes: %s' % repr([x for x in state.genFhashes()])) + + for fhash in state.genFhashes(): + client.join(fhash, revsync_callback(vw)) + + vw.vprint('revsync: connected!') + + if evtdist is None: + evtdist = VivEventClient(vw) + + +def toggle_visits(bv): + state = State.get(bv) + state.show_visits = not state.show_visits + if state.show_visits: + vw.vprint("Visit Visualization Enabled (Red)") + else: + vw.vprint("Visit Visualization Disabled (Red)") + state.color_now = True + +def toggle_time(bv): + state = State.get(bv) + state.show_time = not state.show_time + if state.show_time: + vw.vprint("Time Visualization Enabled (Blue)") + else: + vw.vprint("Time Visualization Disabled (Blue)") + state.color_now = True + +def toggle_visitors(bv): + state = State.get(bv) + state.show_visitors = not state.show_visitors + if state.show_visitors: + vw.vprint("Visitor Visualization Enabled (Green)") + else: + vw.vprint("Visitor Visualization Disabled (Green)") + state.color_now = True + +def toggle_track(bv): + state = State.get(bv) + state.track_coverage = not state.track_coverage + if state.track_coverage: + vw.vprint("Tracking Enabled") + else: + vw.vprint("Tracking Disabled") + +import sys +try: + from PyQt5 import QtGui,QtCore +except: + from PyQt4 import QtGui,QtCore + +from vqt.main import idlethread,idlethreadsync +from vqt.basics import VBox +from vqt.common import * + +@idlethread +def vivExtension(vw, vwgui): + vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Tracking', ACT(toggle_track)) + vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Visits (RED)', ACT(toggle_visits)) + vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Time (BLUE)', ACT(toggle_time)) + vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Visitors (GREEN)', ACT(toggle_visitors)) + vwgui.vqAddMenuField('&Tools.&revsync.&load', 'load revsync!!!', ACT(revsync_load)) + +def register(vw, vwgui): + import vqt.main as vq_main + vq_main.guiq.append((vivExtension, (), {'vw':vw, 'vwgui':vwgui}, )) + +if globals().get('vwgui') is not None: + register(vw, vwgui) + From c2ce7184c68dc1214bacd5e8f82d1d6e0cf19bae Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 15 Aug 2020 23:00:01 -0400 Subject: [PATCH 02/16] incremental improvements. --- viv_frontend.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/viv_frontend.py b/viv_frontend.py index 69205da..ee2e136 100644 --- a/viv_frontend.py +++ b/viv_frontend.py @@ -39,8 +39,8 @@ def __init__(self, vw): self.structs = {} # get_structs(vw) self.structs_lock = Lock() - self.filedata_by_sha = {} # FIXME: since we can have multiple files in a workspace, get fhashs based on file - self.filedata_by_fname = {} # FIXME: since we can have multiple files in a workspace, get fhashs based on file + self.filedata_by_sha = {} + self.filedata_by_fname = {} for fname in vw.getFiles(): sha256 = vw.getFileMeta(fname, 'sha256') mdict = dict(vw.getFileMetaDict(fname)) @@ -75,12 +75,6 @@ def genFhashes(self): COLOUR_PERIOD = 20 BB_REPORT = 50 -''' -# this has to be done at load time, since vivisect doesn't expect to maintain the file -def get_fhash(fname): - with open(fname, 'rb') as f: - return hashlib.sha256(f.read()).hexdigest().upper() -''' def get_can_addr(vw, addr): fname = vw.getFileByVa(addr) @@ -616,6 +610,7 @@ def callback(key, data, replay=False): onmsg(vw, key, data, replay) return callback +''' def revsync_comment(vw, addr): comment = interaction.get_text_line_input('Enter comment: ', 'revsync comment') publish(vw, {'cmd': 'comment', 'addr': get_can_addr(vw, addr), 'text': comment or ''}, send_uuid=False) @@ -625,6 +620,7 @@ def revsync_rename(vw, addr): name = interaction.get_text_line_input('Enter symbol name: ', 'revsync rename') publish(vw, {'cmd': 'rename', 'addr': get_can_addr(vw, addr), 'text': name}) rename_symbol(vw, addr, name) +''' ### handle local events and hand up to REDIS import vivisect.base as viv_base @@ -639,6 +635,7 @@ def VWE_COMMENT(self, vw, event, loc): def VWE_SETNAME(self, vw, event, loc): print vw, event, loc + publish(vw, {'cmd': 'rename', 'addr': get_can_addr(vw, addr), 'text': name}) def VWE_SETFUNCARGS(self, vw, event, loc): print vw, event, loc @@ -750,6 +747,8 @@ def toggle_track(bv): else: vw.vprint("Tracking Disabled") + +######### register the plugin ######### import sys try: from PyQt5 import QtGui,QtCore From 8c2eee104389b768dc3d45c2087f523336567daa Mon Sep 17 00:00:00 2001 From: atlas Date: Mon, 17 Aug 2020 10:40:12 -0400 Subject: [PATCH 03/16] comments and location names are working bi-directionally. --- viv_frontend.py | 63 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/viv_frontend.py b/viv_frontend.py index ee2e136..2f44f36 100644 --- a/viv_frontend.py +++ b/viv_frontend.py @@ -45,6 +45,8 @@ def __init__(self, vw): sha256 = vw.getFileMeta(fname, 'sha256') mdict = dict(vw.getFileMetaDict(fname)) mdict['name'] = fname + mdict['maps'] = [mmap for mmap in vw.getMemoryMaps() if mmap[MAP_FNAME] == fname] + mdict['sha256'] = sha256 self.filedata_by_sha[sha256] = mdict self.filedata_by_fname[fname] = mdict @@ -61,6 +63,14 @@ def getMetaBySha(self, key): def getMetaByFname(self, key): return self.filedata_by_fname.get(key) + def getHashByAddr(self, addr): + for mdict in self.filedata_by_fname.values(): + for mmap in mdict.get('maps'): + mva, msz, mperm, mfname = mmap + if addr >= mva and addr < mva+msz: + fhash = mdict.get('sha256') + return fhash, mfname + def genFhashes(self): for fdict in self.filedata_by_sha.values(): try: @@ -610,32 +620,44 @@ def callback(key, data, replay=False): onmsg(vw, key, data, replay) return callback -''' -def revsync_comment(vw, addr): - comment = interaction.get_text_line_input('Enter comment: ', 'revsync comment') - publish(vw, {'cmd': 'comment', 'addr': get_can_addr(vw, addr), 'text': comment or ''}, send_uuid=False) - get_func_by_addr(vw, addr).set_comment(addr, comment) - -def revsync_rename(vw, addr): - name = interaction.get_text_line_input('Enter symbol name: ', 'revsync rename') - publish(vw, {'cmd': 'rename', 'addr': get_can_addr(vw, addr), 'text': name}) - rename_symbol(vw, addr, name) -''' ### handle local events and hand up to REDIS import vivisect.base as viv_base class VivEventClient(viv_base.VivEventCore): + def __init__(self, vw): + viv_base.VivEventCore.__init__(self, vw) + self.mythread = self._ve_fireListener() + self.state = vw.getMeta('revsync') + # make sure all VA's are reduced to base-addr-offsets - def VWE_COMMENT(self, vw, event, loc): - print vw, event, loc + def VWE_COMMENT(self, vw, event, locinfo): + print vw, event, locinfo + cmt_addr, cmt = locinfo # make sure something has changed (and that we're not repeating what we just received from revsync # publish comment to revsync - #fname, fhash, offset = self.getFileContext(vw, loc[L_VA]) - publish(vw, {'cmd': 'comment', 'addr': get_can_addr(vw, addr), 'text': comment or ''}, send_uuid=False) - - def VWE_SETNAME(self, vw, event, loc): - print vw, event, loc - publish(vw, {'cmd': 'rename', 'addr': get_can_addr(vw, addr), 'text': name}) + last_cmt = self.state.cmt_changes.get(cmt_addr) + if last_cmt is None or last_cmt != cmt: + # new/changed comment, publish + try: + fhash, fname = self.state.getHashByAddr(cmt_addr) + addr = get_can_addr(vw, cmt_addr) + changed = self.state.comments.parse_comment_update(addr, client.nick, cmt) + vw.vprint('revsync: user changed comment: %#x, %s' % (addr, changed)) + publish(vw, {'cmd': 'comment', 'addr': addr, 'text': changed}, fhash) + self.state.cmt_changes[cmt_addr] = changed + except NoChange: + pass + + def VWE_SETNAME(self, vw, event, locinfo): + print vw, event, locinfo + name_addr, name = locinfo + addr = get_can_addr(vw, name_addr) + if self.state.syms.get(addr) != name: + # name changed, publish + fhash, fname = self.state.getHashByAddr(name_addr) + vw.vprint('revsync: user renamed symbol at %#x: %s' % (addr, name)) + publish(vw, {'cmd': 'rename', 'addr': addr, 'text': name}, fhash) + self.state.syms[addr] = name def VWE_SETFUNCARGS(self, vw, event, loc): print vw, event, loc @@ -662,6 +684,9 @@ def VWE_DELLOCATION(self, vw, event, loc): def VWE_SETMETA(self, vw, event, loc): print vw, event, loc + def VWE_ADDFILE(self, vw, event, loc): + print vw, event, loc + def VWE_ADDFUNCTION(self, vw, event, loc): print vw, event, loc From 34c2d4432f727cf190f34d8c4cb1982f52d9a57c Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 20 Nov 2020 18:56:20 -0500 Subject: [PATCH 04/16] just about ready for an alpha version. --- __init__.py | 27 ++++++++++++++++++++++++++- viv_frontend.py | 11 ++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/__init__.py b/__init__.py index 1d1a2fe..bd4b562 100644 --- a/__init__.py +++ b/__init__.py @@ -46,6 +46,31 @@ def write_config(host, port, nick, password): except ImportError: pass +try: + import vivisect + try: + import config + except ImportError: + host_f = bi.TextLineField("host") + port_f = bi.IntegerField("port") + nick_f = bi.TextLineField("nick") + password_f = bi.TextLineField("password") + success = bi.get_form_input([None, host_f, port_f, nick_f, password_f], "Configure Revsync") + if not success: + binaryninja.interaction.show_message_box(title="Revsync error", text="Failed to configure revsync") + raise + + write_config(host_f.result, port_f.result, nick_f.result, password_f.result) + import config + import viv_frontend + good = True +except ImportError: + pass + if not good: - print('Warning: both IDA and Binary Ninja plugin API imports failed') + print('Warning: IDA, Binary Ninja, and Vivisect plugin API imports failed') raise ImportError + +def vivExtension(vw, vwgui): + import viv_frontend + viv_frontend.vivExtension(vw, vwgui) diff --git a/viv_frontend.py b/viv_frontend.py index 2f44f36..ff1fba7 100644 --- a/viv_frontend.py +++ b/viv_frontend.py @@ -709,6 +709,7 @@ def VWE_CHAT(self, vw, event, loc): def revsync_load(vw): global client, evtdist + vw.vprint('Connecting to RevSync Server') ### hook into the viv event stream @@ -786,11 +787,11 @@ def toggle_track(bv): @idlethread def vivExtension(vw, vwgui): - vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Tracking', ACT(toggle_track)) - vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Visits (RED)', ACT(toggle_visits)) - vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Time (BLUE)', ACT(toggle_time)) - vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Visitors (GREEN)', ACT(toggle_visitors)) - vwgui.vqAddMenuField('&Tools.&revsync.&load', 'load revsync!!!', ACT(revsync_load)) + vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Tracking', toggle_track, args=(vw,)) + vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Visits (RED)', toggle_visits, args=(vw,)) + vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Time (BLUE)', toggle_time, args=(vw,)) + vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Visitors (GREEN)', toggle_visitors, args=(vw,)) + vwgui.vqAddMenuField('&Tools.&revsync.&load: Load revsync for binary(s) in this workspace', revsync_load, args=(vw,)) def register(vw, vwgui): import vqt.main as vq_main From c2e2c26292e36f01c842b02a699a81a5b075d4a5 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 3 Mar 2021 23:36:49 -0500 Subject: [PATCH 05/16] remove logging problems from binja_coverage name bugfix in ida_frontend --- binja_coverage.py | 1 - ida_frontend.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/binja_coverage.py b/binja_coverage.py index 229bf92..7bbc49c 100644 --- a/binja_coverage.py +++ b/binja_coverage.py @@ -4,7 +4,6 @@ import logging import random -logging.disable(logging.WARNING) COVERAGE_FIRST_LOAD = True SHOW_VISITS = True SHOW_LENGTH = True diff --git a/ida_frontend.py b/ida_frontend.py index 6874a7a..97c0703 100644 --- a/ida_frontend.py +++ b/ida_frontend.py @@ -82,7 +82,7 @@ def onmsg(key, data, replay=False): print('revsync: <%s> %s %s %s' % (user, cmd, data['range'], data['text'])) elif cmd == 'rename': print('revsync: <%s> %s %#x %s' % (user, cmd, ea, data['text'])) - set_name(ea, str(data['text'])) + set_name(ea, str(data['text']).replace(' ', '_')) elif cmd == 'join': print('revsync: <%s> joined' % (user)) elif cmd in ['stackvar_renamed', 'struc_created', 'struc_deleted', From 1c65c1330412d3e2d337984006b0de8db3928fac Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 3 Mar 2021 23:39:25 -0500 Subject: [PATCH 06/16] python3 updates and cleanup --- viv_ext.py | 18 ------------------ viv_frontend.py | 30 +++++++++++++++--------------- 2 files changed, 15 insertions(+), 33 deletions(-) delete mode 100644 viv_ext.py diff --git a/viv_ext.py b/viv_ext.py deleted file mode 100644 index 4f77db3..0000000 --- a/viv_ext.py +++ /dev/null @@ -1,18 +0,0 @@ -# hook into the analysis modules to set the fhash since we only have the file at initial analysis. store in filemeta -def get_fhash(fname): - with open(fname, 'rb') as f: - return hashlib.sha256(f.read()).hexdigest().upper() - - - -# add key start/stop capabilities in Sharing menu -# -from vqt.main import * -@idlethread -def viv_Extension(vw, vwgui): - from viv_frontend import ACT, toggle_track, toggle_visits, toggle_time, toggle_visitors, revsync_load - vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Tracking', ACT(toggle_track)) - vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Visits (RED)', ACT(toggle_visits)) - vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Time (BLUE)', ACT(toggle_time)) - vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Visitors (GREEN)', ACT(toggle_visitors)) - vwgui.vqAddMenuField('&Tools.&revsync.&load', 'load revsync!!!', ACT(revsync_load)) diff --git a/viv_frontend.py b/viv_frontend.py index ff1fba7..1a8ea29 100644 --- a/viv_frontend.py +++ b/viv_frontend.py @@ -76,7 +76,7 @@ def genFhashes(self): try: yield fdict['sha256'] except KeyError: - print "keyerror: no 'sha256' key in metadict: %r" % fdict + print("keyerror: no 'sha256' key in metadict: %r" % fdict) MIN_COLOR = 0 MAX_COLOR = 200 @@ -421,7 +421,7 @@ def do_analysis_and_wait(vw): ### handle remote events: def onmsg(vw, key, data, replay): - print "onmsg: %r : %r (%r)" % (key, data, replay) + print("onmsg: %r : %r (%r)" % (key, data, replay)) try: state = State.get(vw) meta = state.getMetaBySha(key) @@ -631,7 +631,7 @@ def __init__(self, vw): # make sure all VA's are reduced to base-addr-offsets def VWE_COMMENT(self, vw, event, locinfo): - print vw, event, locinfo + print("%r %r %r" % (vw, event, locinfo)) cmt_addr, cmt = locinfo # make sure something has changed (and that we're not repeating what we just received from revsync # publish comment to revsync @@ -649,7 +649,7 @@ def VWE_COMMENT(self, vw, event, locinfo): pass def VWE_SETNAME(self, vw, event, locinfo): - print vw, event, locinfo + print("%r %r %r" % (vw, event, locinfo)) name_addr, name = locinfo addr = get_can_addr(vw, name_addr) if self.state.syms.get(addr) != name: @@ -660,10 +660,10 @@ def VWE_SETNAME(self, vw, event, locinfo): self.state.syms[addr] = name def VWE_SETFUNCARGS(self, vw, event, loc): - print vw, event, loc + print("%r %r %r" % (vw, event, locinfo)) def VWE_SETFUNCMETA(self, vw, event, loc): - print vw, event, loc + print("%r %r %r" % (vw, event, locinfo)) def VWE_ADDLOCATION(self, vw, event, loc): @@ -676,31 +676,31 @@ def VWE_ADDLOCATION(self, vw, event, loc): # * LOC_OP if loc[L_LTYPE] is LOC_OP: return - print vw, event, loc + print("%r %r %r" % (vw, event, locinfo)) def VWE_DELLOCATION(self, vw, event, loc): - print vw, event, loc + print("%r %r %r" % (vw, event, locinfo)) def VWE_SETMETA(self, vw, event, loc): - print vw, event, loc + print("%r %r %r" % (vw, event, locinfo)) def VWE_ADDFILE(self, vw, event, loc): - print vw, event, loc + print("%r %r %r" % (vw, event, locinfo)) def VWE_ADDFUNCTION(self, vw, event, loc): - print vw, event, loc + print("%r %r %r" % (vw, event, locinfo)) def VWE_DELFUNCTION(self, vw, event, loc): - print vw, event, loc + print("%r %r %r" % (vw, event, locinfo)) def VWE_ADDCOLOR(self, vw, event, loc): - print vw, event, loc + print("%r %r %r" % (vw, event, locinfo)) def VWE_DELCOLOR(self, vw, event, loc): - print vw, event, loc + print("%r %r %r" % (vw, event, locinfo)) def VWE_CHAT(self, vw, event, loc): - print vw, event, loc + print("%r %r %r" % (vw, event, locinfo)) client = None From b8483e6bf252dcf158e041deb227d0309c61c545 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 3 Mar 2021 23:41:01 -0500 Subject: [PATCH 07/16] update plugin initializer... not quite done, but checks for binja and vivisect plugin environments before loading. --- __init__.py | 88 +++++++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 39 deletions(-) diff --git a/__init__.py b/__init__.py index bd4b562..504163d 100644 --- a/__init__.py +++ b/__init__.py @@ -18,59 +18,69 @@ def write_config(host, port, nick, password): try: import binaryninja - import binaryninja.interaction as bi - try: - import config - except ImportError: - host_f = bi.TextLineField("host") - port_f = bi.IntegerField("port") - nick_f = bi.TextLineField("nick") - password_f = bi.TextLineField("password") - success = bi.get_form_input([None, host_f, port_f, nick_f, password_f], "Configure Revsync") - if not success: - binaryninja.interaction.show_message_box(title="Revsync error", text="Failed to configure revsync") - raise + # check if running in BinaryNinja: + if binaryninja.core_ui_enabled(): + import binaryninja.interaction as bi + try: + import config + except ImportError: + host_f = bi.TextLineField("host") + port_f = bi.IntegerField("port") + nick_f = bi.TextLineField("nick") + password_f = bi.TextLineField("password") + success = bi.get_form_input([None, host_f, port_f, nick_f, password_f], "Configure Revsync") + if not success: + binaryninja.interaction.show_message_box(title="Revsync error", text="Failed to configure revsync") + raise - write_config(host_f.result, port_f.result, nick_f.result, password_f.result) - import config - import binja_frontend - #import binja_coverage - good = True -except ImportError: - pass - -try: - import idaapi - import ida_frontend - good = True + write_config(host_f.result, port_f.result, nick_f.result, password_f.result) + import config + import binja_frontend + #import binja_coverage + good = True except ImportError: pass -try: - import vivisect +# check if running in Vivisect: +if globals().get('vw') is not None: + print("vivisect startup...") try: - import config + import vivisect + try: + import config + except ImportError: + #host_f = bi.TextLineField("host") + #port_f = bi.IntegerField("port") + #nick_f = bi.TextLineField("nick") + #password_f = bi.TextLineField("password") + #success = bi.get_form_input([None, host_f, port_f, nick_f, password_f], "Configure Revsync") + #if not success: + # binaryninja.interaction.show_message_box(title="Revsync error", text="Failed to configure revsync") + # raise + + #write_config(host_f.result, port_f.result, nick_f.result, password_f.result) + #import config + print("import error importing config (revsync)") + + import viv_frontend + good = True except ImportError: - host_f = bi.TextLineField("host") - port_f = bi.IntegerField("port") - nick_f = bi.TextLineField("nick") - password_f = bi.TextLineField("password") - success = bi.get_form_input([None, host_f, port_f, nick_f, password_f], "Configure Revsync") - if not success: - binaryninja.interaction.show_message_box(title="Revsync error", text="Failed to configure revsync") - raise + pass - write_config(host_f.result, port_f.result, nick_f.result, password_f.result) - import config - import viv_frontend + +# if idaapi loads, go with it. +try: + import idaapi + import ida_frontend good = True except ImportError: pass if not good: - print('Warning: IDA, Binary Ninja, and Vivisect plugin API imports failed') + print('Warning: Could not find an appropriate plugin environment: IDA, Binary Ninja, and Vivisect plugin API imports failed') raise ImportError +# Vivisect looks for this in a plugin def vivExtension(vw, vwgui): import viv_frontend viv_frontend.vivExtension(vw, vwgui) From 62085a720862982ae11ed4ed1972f6ce827f01f7 Mon Sep 17 00:00:00 2001 From: atlas Date: Fri, 5 Mar 2021 12:25:03 -0500 Subject: [PATCH 08/16] cleanup and addition of gui elements for config --- __init__.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/__init__.py b/__init__.py index 504163d..098849b 100644 --- a/__init__.py +++ b/__init__.py @@ -43,24 +43,24 @@ def write_config(host, port, nick, password): # check if running in Vivisect: if globals().get('vw') is not None: - print("vivisect startup...") try: import vivisect try: import config except ImportError: - #host_f = bi.TextLineField("host") - #port_f = bi.IntegerField("port") - #nick_f = bi.TextLineField("nick") - #password_f = bi.TextLineField("password") - #success = bi.get_form_input([None, host_f, port_f, nick_f, password_f], "Configure Revsync") - #if not success: - # binaryninja.interaction.show_message_box(title="Revsync error", text="Failed to configure revsync") - # raise + import vqt.common as vcmn + dynd = vcmn.DynamicDialog('RevSync Config') + dynd.addTextField("host") + dynd.addIntHexField("port", dflt=6379) + dynd.addTextField("nick") + dynd.addTextField("password") + res = dynd.prompt() + if not len(res): + vcmn.warning("Revsync error", "Failed to configure revsync") + raise - #write_config(host_f.result, port_f.result, nick_f.result, password_f.result) - #import config - print("import error importing config (revsync)") + write_config(res.get('host'), res.get('port'), res.get('nick'), res.get('password')) + import config import viv_frontend good = True @@ -84,3 +84,4 @@ def write_config(host, port, nick, password): def vivExtension(vw, vwgui): import viv_frontend viv_frontend.vivExtension(vw, vwgui) + From f10b1799a7f8619bff553d1c6de4920fd5e0af86 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 13 Mar 2021 00:21:07 -0500 Subject: [PATCH 09/16] clean up and reorg. seems to work ok --- viv_frontend.py | 349 +++++++++++++++--------------------------------- 1 file changed, 104 insertions(+), 245 deletions(-) diff --git a/viv_frontend.py b/viv_frontend.py index 1a8ea29..3f9f1a5 100644 --- a/viv_frontend.py +++ b/viv_frontend.py @@ -86,7 +86,10 @@ def genFhashes(self): BB_REPORT = 50 -def get_can_addr(vw, addr): +def get_can_addr(vw, addr): #done + ''' + normalizing addresses + ''' fname = vw.getFileByVa(addr) if fname is None: raise Exception("ARRG! get_can_addr(0x%x)" % addr) @@ -94,7 +97,10 @@ def get_can_addr(vw, addr): imagebase = vw.getFileMeta(fname, 'imagebase') return addr - imagebase -def get_ea(vw, sha_key, addr): +def get_ea(vw, sha_key, addr): #done + ''' + normalizing addresses + ''' imagebase = None for fname in vw.getFiles(): fmeta = vw.getFileMetaDict(fname) @@ -104,90 +110,61 @@ def get_ea(vw, sha_key, addr): return addr + imagebase -def get_func_by_addr(vw, addr): +def get_func_by_addr(vw, addr): #done return vw.getFunction(addr) -def get_bb_by_addr(bv, addr): - return vw.getCodeBlock(addr) +def get_bb_by_addr(vw, addr): + return vw.getCodeBlock(addr) #done -# in order to map IDA type sizes <-> binja types, +# in order to map IDA type sizes <-> viv types, # take the 'size' field and attempt to divine some -# kind of type in binja that's close as possible. +# kind of type in viv that's close as possible. # right now, that means try to use uint8_t ... uint64_t, # anything bigger just make an array -def get_type_by_size(bv, size): +def get_type_by_size(vw, size): typedef = None if size <= 8: try: - typedef, name = bv.parse_type_string('uint{}_t'.format(8*size)) + typedef, name = vw.parse_type_string('uint{}_t'.format(8*size)) except SyntaxError: pass else: try: - typedef, name = bv.parse_type_string('char a[{}]'.format(8*size)) + typedef, name = vw.parse_type_string('char a[{}]'.format(8*size)) except SyntaxError: pass return typedef -def get_structs(bv): - d = dict() - for name, typedef in bv.types.items(): - if typedef.structure: - typeid = bv.get_type_id(name) - struct = Struct(name, typedef.structure) - d[typeid] = struct - return d - -def get_syms(vw): +def get_syms(vw): #done syms = dict(vw.name_by_va) return syms def stack_dict_from_list(stackvars): d = {} - for var in stackvars: - d[var.storage] = (var.name, var.type) + for fva, offset, unknown, (vartype, varname) in stackvars: + d[offset] = (varname, vartype) return d -def member_dict_from_list(members): - d = {} - for member in members: - d[member.name] = member - return d - -def rename_symbol(vw, addr, name): +def rename_symbol(vw, addr, name): # done vw.makeName(addr, name) - ''' - sym = bv.get_symbol_at(addr) - if sym is not None: - # symbol already exists for this address - if sym.auto is True: - bv.undefine_auto_symbol(sym) - else: - bv.undefine_user_symbol(sym) - # is it a function? - func = get_func_by_addr(bv, addr) - if func is not None: - # function - sym = types.Symbol(SymbolType.FunctionSymbol, addr, name) - else: - # data - sym = types.Symbol(SymbolType.DataSymbol, addr, name) - bv.define_user_symbol(sym) - ''' -def rename_stackvar(bv, func_addr, offset, name): - func = get_func_by_addr(bv, func_addr) +def rename_stackvar(vw, func_addr, offset, name): # TESTME + func = vw.getFunction(func_addr) if func is None: vw.vprint('revsync: bad func addr %#x during rename_stackvar' % func_addr) return + # we need to figure out the variable type before renaming - stackvars = stack_dict_from_list(func.vars) + stackvars = stack_dict_from_list(vw.getFunctionLocals(func_addr)) var = stackvars.get(offset) if var is None: vw.vprint('revsync: could not locate stack var with offset %#x during rename_stackvar' % offset) return var_name, var_type = var - func.create_user_stack_var(offset, var_type, name) + # CHECKME: does this set function args too? do i need to split them? + vw.setFunctionLocal(func_addr, offset, var_type, name) + #aidx = offset // vw.getPointerSize() + #vw.setFunctionArg(func_addr, aidx, var_type, name) return def publish(vw, data, fhash, **kwargs): @@ -195,38 +172,41 @@ def publish(vw, data, fhash, **kwargs): if state: client.publish(fhash, data, **kwargs) -def push_cv(vw, data, **kwargs): - state = State.get(vw) - if state: - client.push("%s_COVERAGE" % state.fhash, data, **kwargs) +def analyze(vw): #done + vw.vprint('revsync: running analysis update...') + vw.analyze() + vw.vprint('revsync: analysis finished.') + return -def map_color(x): - n = x - if x == 0: return 0 - # x = min(max(0, (x ** 2) / (2 * (x ** 2 - x) + 1)), 1) - # if x == 0: return 0 - return int(math.ceil((MAX_COLOR - MIN_COLOR) * x + MIN_COLOR)) -def convert_color(color): - r, g, b = [map_color(x) for x in color] - return highlight.HighlightColor(red=r, green=g, blue=b) -def colour_coverage(bv, cur_func): - state = State.get(bv) - for bb in cur_func.basic_blocks: - color = state.cov.color(get_can_addr(bv, bb.start), visits=state.show_visits, time=state.show_time, users=state.show_visitors) - if color: - bb.set_user_highlight(convert_color(color)) - else: - bb.set_user_highlight(highlight.HighlightColor(red=74, blue=74, green=74)) -def watch_structs(bv): + +##### structs are not currently supported +def get_structs(vw): + d = dict() + for name, typedef in vw.types.items(): + if typedef.structure: + typeid = vw.get_type_id(name) + struct = Struct(name, typedef.structure) + d[typeid] = struct + return d + +def member_dict_from_list(members): + d = {} + for member in members: + d[member.name] = member + return d + + +#### again, no struct just yet +def watch_structs(vw): """ Check structs for changes and publish diffs""" - state = State.get(bv) + state = State.get(vw) while state.running: state.structs_lock.acquire() - structs = get_structs(bv) + structs = get_structs(vw) if structs != state.structs: for struct_id, struct in structs.items(): last_struct = state.structs.get(struct_id) @@ -281,143 +261,34 @@ def watch_structs(bv): state.structs_lock.release() time.sleep(0.5) +###### Coverage not yet implemented +def push_cv(vw, data, **kwargs): + state = State.get(vw) + if state: + client.push("%s_COVERAGE" % state.fhash, data, **kwargs) +def map_color(x): + n = x + if x == 0: return 0 + # x = min(max(0, (x ** 2) / (2 * (x ** 2 - x) + 1)), 1) + # if x == 0: return 0 + return int(math.ceil((MAX_COLOR - MIN_COLOR) * x + MIN_COLOR)) -''' -################################################################ unused -def watch_syms(bv, sym_type): - """ Watch symbols of a given type (e.g. DataSymbol) for changes and publish diffs """ - state = State.get(bv) +def convert_color(color): + r, g, b = [map_color(x) for x in color] + return highlight.HighlightColor(red=r, green=g, blue=b) - while state.running: - state.syms_lock.acquire() - # DataSymbol - data_syms = get_syms(bv, SymbolType.DataSymbol) - if data_syms != state.data_syms: - for addr, name in data_syms.items(): - if state.data_syms.get(addr) != name: - # name changed, publish - vw.vprint('revsync: user renamed symbol at %#x: %s' % (addr, name)) - publish(bv, {'cmd': 'rename', 'addr': get_can_addr(bv, addr), 'text': name}) - - # FunctionSymbol - func_syms = get_syms(bv, SymbolType.FunctionSymbol) - if func_syms != state.func_syms: - for addr, name in func_syms.items(): - if state.func_syms.get(addr) != name: - # name changed, publish - vw.vprint('revsync: user renamed symbol at %#x: %s' % (addr, name)) - publish(bv, {'cmd': 'rename', 'addr': get_can_addr(bv, addr), 'text': name}) - - state.data_syms = get_syms(bv, SymbolType.DataSymbol) - state.func_syms = get_syms(bv, SymbolType.FunctionSymbol) - state.syms_lock.release() - time.sleep(0.5) +def colour_coverage(bv, cur_func): + state = State.get(bv) + for bb in cur_func.basic_blocks: + color = state.cov.color(get_can_addr(bv, bb.start), visits=state.show_visits, time=state.show_time, users=state.show_visitors) + if color: + bb.set_user_highlight(convert_color(color)) + else: + bb.set_user_highlight(highlight.HighlightColor(red=74, blue=74, green=74)) -def watch_cur_func(bv): - """ Watch current function (if we're in code) for comment changes and publish diffs """ - def get_cur_func(): - return get_func_by_addr(bv, bv.offset) - def get_cur_bb(): - return get_bb_by_addr(bv, bv.offset) - state = State.get(bv) - last_func = get_cur_func() - last_bb = get_cur_bb() - last_time = time.time() - last_bb_report = time.time() - last_bb_addr = None - last_addr = None - while state.running: - now = time.time() - if state.track_coverage and now - last_bb_report >= BB_REPORT: - last_bb_report = now - push_cv(bv, {'b': state.cov.flush()}) - - if last_addr == bv.offset: - time.sleep(0.25) - continue - else: - # were we just in a function? - if last_func: - state.cmt_lock.acquire() - comments = last_func.comments - # check for changed comments - for cmt_addr, cmt in comments.items(): - last_cmt = state.cmt_changes.get(cmt_addr) - if last_cmt == None or last_cmt != cmt: - # new/changed comment, publish - try: - addr = get_can_addr(bv, cmt_addr) - changed = state.comments.parse_comment_update(addr, client.nick, cmt) - vw.vprint('revsync: user changed comment: %#x, %s' % (addr, changed)) - publish(bv, {'cmd': 'comment', 'addr': addr, 'text': changed}) - state.cmt_changes[cmt_addr] = changed - except NoChange: - pass - continue - - # TODO: this needs to be fixed later - """ - # check for removed comments - if last_comments: - removed = set(last_comments.keys()) - set(comments.keys()) - for addr in removed: - addr = get_can_addr(bv, addr) - vw.vprint('revsync: user removed comment: %#x' % addr) - publish(bv, {'cmd': 'comment', 'addr': addr, 'text': ''}) - """ - state.cmt_lock.release() - - # similar dance, but with stackvars - state.stackvar_lock.acquire() - stackvars = stack_dict_from_list(last_func.vars) - for offset, data in stackvars.items(): - # stack variables are more difficult than comments to keep state on, since they - # exist from the beginning, and have a type. track each one. start by tracking the first - # time we see it. if there are changes after that, publish. - stackvar_name, stackvar_type = data - stackvar_val = state.stackvar_changes.get((last_func.start,offset)) - if stackvar_val == None: - # never seen before, start tracking - state.stackvar_changes[(last_func.start,offset)] = stackvar_name - elif stackvar_val != stackvar_name: - # stack var name changed, publish - vw.vprint('revsync: user changed stackvar name at offset %#x to %s' % (offset, stackvar_name)) - publish(bv, {'cmd': 'stackvar_renamed', 'addr': last_func.start, 'offset': offset, 'name': stackvar_name}) - state.stackvar_changes[(last_func.start,offset)] = stackvar_name - state.stackvar_lock.release() - - if state.track_coverage: - cur_bb = get_cur_bb() - if cur_bb != last_bb: - state.color_now = True - now = time.time() - if last_bb_addr is not None: - state.cov.visit_addr(last_bb_addr, elapsed=now - last_time, visits=1) - last_time = now - if cur_bb is None: - last_bb_addr = None - else: - last_bb_addr = get_can_addr(bv, cur_bb.start) - - # update current function/addr info - last_func = get_cur_func() - last_bb = get_cur_bb() - last_addr = bv.offset - - if state.color_now and last_func != None: - colour_coverage(bv, last_func) - state.color_now = False -################################################## ends: unused -''' - -def do_analysis_and_wait(vw): - vw.vprint('revsync: running analysis update...') - vw.analyze() - vw.vprint('revsync: analysis finished.') - return ### handle remote events: def onmsg(vw, key, data, replay): @@ -615,7 +486,7 @@ def onmsg(vw, key, data, replay): except Exception as e: vw.vprint('onmsg error: %r' % e) -def revsync_callback(vw): +def revsync_callback(vw): #done def callback(key, data, replay=False): onmsg(vw, key, data, replay) return callback @@ -630,7 +501,7 @@ def __init__(self, vw): self.state = vw.getMeta('revsync') # make sure all VA's are reduced to base-addr-offsets - def VWE_COMMENT(self, vw, event, locinfo): + def VWE_COMMENT(self, vw, event, locinfo): #done print("%r %r %r" % (vw, event, locinfo)) cmt_addr, cmt = locinfo # make sure something has changed (and that we're not repeating what we just received from revsync @@ -648,7 +519,7 @@ def VWE_COMMENT(self, vw, event, locinfo): except NoChange: pass - def VWE_SETNAME(self, vw, event, locinfo): + def VWE_SETNAME(self, vw, event, locinfo): #done print("%r %r %r" % (vw, event, locinfo)) name_addr, name = locinfo addr = get_can_addr(vw, name_addr) @@ -685,13 +556,16 @@ def VWE_SETMETA(self, vw, event, loc): print("%r %r %r" % (vw, event, locinfo)) def VWE_ADDFILE(self, vw, event, loc): - print("%r %r %r" % (vw, event, locinfo)) + #print("%r %r %r" % (vw, event, locinfo)) + pass def VWE_ADDFUNCTION(self, vw, event, loc): - print("%r %r %r" % (vw, event, locinfo)) + #print("%r %r %r" % (vw, event, locinfo)) + pass def VWE_DELFUNCTION(self, vw, event, loc): - print("%r %r %r" % (vw, event, locinfo)) + #print("%r %r %r" % (vw, event, locinfo)) + pass def VWE_ADDCOLOR(self, vw, event, loc): print("%r %r %r" % (vw, event, locinfo)) @@ -707,22 +581,22 @@ def VWE_CHAT(self, vw, event, loc): evtdist = None -def revsync_load(vw): +def revsync_load(vw): #done global client, evtdist vw.vprint('Connecting to RevSync Server') ### hook into the viv event stream # lets ensure auto-analysis is finished by forcing another analysis - t0 = threading.Thread(target=do_analysis_and_wait, args=(vw,)) - t0.start() - t0.join() + analyze(vw) if client is None: + vw.vprint("creating a new revsync connection") client = Client(**config) state = vw.metadata.get('revsync') if state: + vw.vprint("closing existing revsync state") state.close() vw.vprint('revsync: working...') @@ -738,8 +612,8 @@ def revsync_load(vw): evtdist = VivEventClient(vw) -def toggle_visits(bv): - state = State.get(bv) +def toggle_visits(vw): #done + state = State.get(vw) state.show_visits = not state.show_visits if state.show_visits: vw.vprint("Visit Visualization Enabled (Red)") @@ -747,8 +621,8 @@ def toggle_visits(bv): vw.vprint("Visit Visualization Disabled (Red)") state.color_now = True -def toggle_time(bv): - state = State.get(bv) +def toggle_time(vw): #done + state = State.get(vw) state.show_time = not state.show_time if state.show_time: vw.vprint("Time Visualization Enabled (Blue)") @@ -756,8 +630,8 @@ def toggle_time(bv): vw.vprint("Time Visualization Disabled (Blue)") state.color_now = True -def toggle_visitors(bv): - state = State.get(bv) +def toggle_visitors(vw): #done + state = State.get(vw) state.show_visitors = not state.show_visitors if state.show_visitors: vw.vprint("Visitor Visualization Enabled (Green)") @@ -765,8 +639,8 @@ def toggle_visitors(bv): vw.vprint("Visitor Visualization Disabled (Green)") state.color_now = True -def toggle_track(bv): - state = State.get(bv) +def toggle_track(vw): #done + state = State.get(vw) state.track_coverage = not state.track_coverage if state.track_coverage: vw.vprint("Tracking Enabled") @@ -775,28 +649,13 @@ def toggle_track(bv): ######### register the plugin ######### -import sys -try: - from PyQt5 import QtGui,QtCore -except: - from PyQt4 import QtGui,QtCore - -from vqt.main import idlethread,idlethreadsync -from vqt.basics import VBox -from vqt.common import * +from vqt.main import idlethread @idlethread def vivExtension(vw, vwgui): - vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Tracking', toggle_track, args=(vw,)) - vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Visits (RED)', toggle_visits, args=(vw,)) - vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Time (BLUE)', toggle_time, args=(vw,)) - vwgui.vqAddMenuField('&Tools.&revsync.&Coverage: Toggle Visitors (GREEN)', toggle_visitors, args=(vw,)) - vwgui.vqAddMenuField('&Tools.&revsync.&load: Load revsync for binary(s) in this workspace', revsync_load, args=(vw,)) - -def register(vw, vwgui): - import vqt.main as vq_main - vq_main.guiq.append((vivExtension, (), {'vw':vw, 'vwgui':vwgui}, )) - -if globals().get('vwgui') is not None: - register(vw, vwgui) + vwgui.vqAddMenuField('&Plugins.&revsync.&Coverage: Toggle Tracking', toggle_track, args=(vw,)) + vwgui.vqAddMenuField('&Plugins.&revsync.&Coverage: Toggle Visits (RED)', toggle_visits, args=(vw,)) + vwgui.vqAddMenuField('&Plugins.&revsync.&Coverage: Toggle Time (BLUE)', toggle_time, args=(vw,)) + vwgui.vqAddMenuField('&Plugins.&revsync.&Coverage: Toggle Visitors (GREEN)', toggle_visitors, args=(vw,)) + vwgui.vqAddMenuField('&Plugins.&revsync.&load: Load revsync for binary(s) in this workspace', revsync_load, args=(vw,)) From 623cace8b6eda73d484aae514dc5049e55414f12 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 13 Mar 2021 00:31:11 -0500 Subject: [PATCH 10/16] print to vw.vprint instead of stdout --- viv_frontend.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/viv_frontend.py b/viv_frontend.py index 3f9f1a5..daba2f5 100644 --- a/viv_frontend.py +++ b/viv_frontend.py @@ -76,7 +76,7 @@ def genFhashes(self): try: yield fdict['sha256'] except KeyError: - print("keyerror: no 'sha256' key in metadict: %r" % fdict) + vw.vprint("keyerror: no 'sha256' key in metadict: %r" % fdict) MIN_COLOR = 0 MAX_COLOR = 200 @@ -531,10 +531,10 @@ def VWE_SETNAME(self, vw, event, locinfo): #done self.state.syms[addr] = name def VWE_SETFUNCARGS(self, vw, event, loc): - print("%r %r %r" % (vw, event, locinfo)) + vw.vprint("%r %r %r" % (vw, event, locinfo)) def VWE_SETFUNCMETA(self, vw, event, loc): - print("%r %r %r" % (vw, event, locinfo)) + vw.vprint("%r %r %r" % (vw, event, locinfo)) def VWE_ADDLOCATION(self, vw, event, loc): @@ -547,34 +547,34 @@ def VWE_ADDLOCATION(self, vw, event, loc): # * LOC_OP if loc[L_LTYPE] is LOC_OP: return - print("%r %r %r" % (vw, event, locinfo)) + vw.vprint("%r %r %r" % (vw, event, locinfo)) def VWE_DELLOCATION(self, vw, event, loc): - print("%r %r %r" % (vw, event, locinfo)) + vw.vprint("%r %r %r" % (vw, event, locinfo)) def VWE_SETMETA(self, vw, event, loc): - print("%r %r %r" % (vw, event, locinfo)) + vw.vprint("%r %r %r" % (vw, event, locinfo)) def VWE_ADDFILE(self, vw, event, loc): - #print("%r %r %r" % (vw, event, locinfo)) + #vw.vprint("%r %r %r" % (vw, event, locinfo)) pass def VWE_ADDFUNCTION(self, vw, event, loc): - #print("%r %r %r" % (vw, event, locinfo)) + #vw.vprint("%r %r %r" % (vw, event, locinfo)) pass def VWE_DELFUNCTION(self, vw, event, loc): - #print("%r %r %r" % (vw, event, locinfo)) + #vw.vprint("%r %r %r" % (vw, event, locinfo)) pass def VWE_ADDCOLOR(self, vw, event, loc): - print("%r %r %r" % (vw, event, locinfo)) + vw.vprint("%r %r %r" % (vw, event, locinfo)) def VWE_DELCOLOR(self, vw, event, loc): - print("%r %r %r" % (vw, event, locinfo)) + vw.vprint("%r %r %r" % (vw, event, locinfo)) def VWE_CHAT(self, vw, event, loc): - print("%r %r %r" % (vw, event, locinfo)) + vw.vprint("%r %r %r" % (vw, event, locinfo)) client = None From 76db6aeda8623e32a9b18e3edfe37ad95a58d036 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 13 Mar 2021 00:42:14 -0500 Subject: [PATCH 11/16] continued cleanup and tweaking --- viv_frontend.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/viv_frontend.py b/viv_frontend.py index daba2f5..1a68fbb 100644 --- a/viv_frontend.py +++ b/viv_frontend.py @@ -355,6 +355,7 @@ def onmsg(vw, key, data, replay): state.structs_lock.release() ''' vw.vprint('revsync: <%s> %s %s' % (user, cmd, struct_name)) + elif cmd == 'struc_deleted': struct_name = data['struc_name'].encode('ascii', 'ignore') ''' not ready to wrap this into Viv yet. @@ -369,6 +370,7 @@ def onmsg(vw, key, data, replay): state.structs_lock.release() ''' vw.vprint('revsync: <%s> %s %s' % (user, cmd, struct_name)) + elif cmd == 'struc_renamed': old_struct_name = data['old_name'].encode('ascii', 'ignore') new_struct_name = data['new_name'].encode('ascii', 'ignore') @@ -384,6 +386,7 @@ def onmsg(vw, key, data, replay): state.structs_lock.release() ''' vw.vprint('revsync: <%s> %s %s %s' % (user, cmd, old_struct_name, new_struct_name)) + elif cmd == 'struc_member_created': struct_name = data['struc_name'].encode('ascii', 'ignore') ''' not ready to wrap this into Viv yet. @@ -406,6 +409,7 @@ def onmsg(vw, key, data, replay): state.structs_lock.release() ''' vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, member_name)) + elif cmd == 'struc_member_deleted': struct_name = data['struc_name'].encode('ascii', 'ignore') member_name = '???' @@ -431,6 +435,7 @@ def onmsg(vw, key, data, replay): state.structs_lock.release() ''' vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, member_name)) + elif cmd == 'struc_member_renamed': struct_name = data['struc_name'].encode('ascii', 'ignore') member_name = data['member_name'].encode('ascii', 'ignore') @@ -453,6 +458,7 @@ def onmsg(vw, key, data, replay): state.structs_lock.release() ''' vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, member_name)) + elif cmd == 'struc_member_changed': struct_name = data['struc_name'].encode('ascii', 'ignore') ''' not ready to wrap this into Viv yet. @@ -474,12 +480,15 @@ def onmsg(vw, key, data, replay): state.structs_lock.release() ''' vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, m.name)) + elif cmd == 'join': vw.vprint('revsync: <%s> joined' % (user)) + elif cmd == 'coverage': vw.vprint("Updating Global Coverage") state.cov.update(json.loads(data['blocks'])) state.color_now = True + else: vw.vprint('revsync: unknown cmd %s' % data) @@ -550,7 +559,8 @@ def VWE_ADDLOCATION(self, vw, event, loc): vw.vprint("%r %r %r" % (vw, event, locinfo)) def VWE_DELLOCATION(self, vw, event, loc): - vw.vprint("%r %r %r" % (vw, event, locinfo)) + #vw.vprint("%r %r %r" % (vw, event, locinfo)) + pass def VWE_SETMETA(self, vw, event, loc): vw.vprint("%r %r %r" % (vw, event, locinfo)) @@ -574,7 +584,8 @@ def VWE_DELCOLOR(self, vw, event, loc): vw.vprint("%r %r %r" % (vw, event, locinfo)) def VWE_CHAT(self, vw, event, loc): - vw.vprint("%r %r %r" % (vw, event, locinfo)) + #vw.vprint("%r %r %r" % (vw, event, locinfo)) + pass client = None From 6d761d7ae88ce627fa8ed80a946ccf113b488f29 Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 13 Mar 2021 00:52:01 -0500 Subject: [PATCH 12/16] update instructions --- README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0d6bc7e..0f76301 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ revsync ======= -Realtime IDA Pro and Binary Ninja sync plugin +Realtime sync plugin for IDA Pro, Binary Ninja and Vivisect Syncs: @@ -49,3 +49,17 @@ Then: - Load your binary, wait for analysis to finish - Use the Tools Menu, Right-Click or command-palette (CMD/CTL-P) to trigger revsync/Load -Done! + + +Vivisect Installation +--------------------- + +- Clone to [a plugin folder in your VIV_EXT_PATH (or ~/.viv/plugins/)](https://github.com/vivisect/vivisect/#extending-vivisect--vdb). + +Then: + +- Restart Vivisect +- Fill in config when prompted. +- Load your binary, wait for analysis to finish +- Use the Plugins -> revsync -> Load option to trigger revsync/Load +-Done! From a1fe787c74ab2a4de19b58d2e591a5b58384852d Mon Sep 17 00:00:00 2001 From: atlas Date: Sat, 13 Mar 2021 01:16:38 -0500 Subject: [PATCH 13/16] final loading tweak for cases where Binja is running and Vivisect is installed (but no VivGui) --- __init__.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/__init__.py b/__init__.py index 098849b..7b65aec 100644 --- a/__init__.py +++ b/__init__.py @@ -45,25 +45,26 @@ def write_config(host, port, nick, password): if globals().get('vw') is not None: try: import vivisect - try: - import config - except ImportError: - import vqt.common as vcmn - dynd = vcmn.DynamicDialog('RevSync Config') - dynd.addTextField("host") - dynd.addIntHexField("port", dflt=6379) - dynd.addTextField("nick") - dynd.addTextField("password") - res = dynd.prompt() - if not len(res): - vcmn.warning("Revsync error", "Failed to configure revsync") - raise + if vw.getVivGui(): + try: + import config + except ImportError: + import vqt.common as vcmn + dynd = vcmn.DynamicDialog('RevSync Config') + dynd.addTextField("host") + dynd.addIntHexField("port", dflt=6379) + dynd.addTextField("nick") + dynd.addTextField("password") + res = dynd.prompt() + if not len(res): + vcmn.warning("Revsync error", "Failed to configure revsync") + raise - write_config(res.get('host'), res.get('port'), res.get('nick'), res.get('password')) - import config + write_config(res.get('host'), res.get('port'), res.get('nick'), res.get('password')) + import config - import viv_frontend - good = True + import viv_frontend + good = True except ImportError: pass From fbe5b342f470f94817c72630622bed93868b6c30 Mon Sep 17 00:00:00 2001 From: atlas Date: Tue, 16 Mar 2021 13:54:22 -0400 Subject: [PATCH 14/16] don't require VivGui for this to pass (may want to load headless). "vw" in the environment is enough. --- __init__.py | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/__init__.py b/__init__.py index 7b65aec..098849b 100644 --- a/__init__.py +++ b/__init__.py @@ -45,26 +45,25 @@ def write_config(host, port, nick, password): if globals().get('vw') is not None: try: import vivisect - if vw.getVivGui(): - try: - import config - except ImportError: - import vqt.common as vcmn - dynd = vcmn.DynamicDialog('RevSync Config') - dynd.addTextField("host") - dynd.addIntHexField("port", dflt=6379) - dynd.addTextField("nick") - dynd.addTextField("password") - res = dynd.prompt() - if not len(res): - vcmn.warning("Revsync error", "Failed to configure revsync") - raise + try: + import config + except ImportError: + import vqt.common as vcmn + dynd = vcmn.DynamicDialog('RevSync Config') + dynd.addTextField("host") + dynd.addIntHexField("port", dflt=6379) + dynd.addTextField("nick") + dynd.addTextField("password") + res = dynd.prompt() + if not len(res): + vcmn.warning("Revsync error", "Failed to configure revsync") + raise - write_config(res.get('host'), res.get('port'), res.get('nick'), res.get('password')) - import config + write_config(res.get('host'), res.get('port'), res.get('nick'), res.get('password')) + import config - import viv_frontend - good = True + import viv_frontend + good = True except ImportError: pass From a615537120f9981d2383058a459982f9c54a7ae8 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 17 Mar 2021 16:20:57 -0400 Subject: [PATCH 15/16] mods/tweaks mostly per @lunixbochs --- __init__.py | 2 +- viv_frontend.py | 118 +++--------------------------------------------- 2 files changed, 7 insertions(+), 113 deletions(-) diff --git a/__init__.py b/__init__.py index 098849b..a9f562f 100644 --- a/__init__.py +++ b/__init__.py @@ -42,7 +42,7 @@ def write_config(host, port, nick, password): pass # check if running in Vivisect: -if globals().get('vw') is not None: +if 'vw' in globals(): try: import vivisect try: diff --git a/viv_frontend.py b/viv_frontend.py index 1a68fbb..3ea5b82 100644 --- a/viv_frontend.py +++ b/viv_frontend.py @@ -1,6 +1,7 @@ import math import time import hashlib +import logging from collections import defaultdict from vivisect import * @@ -12,6 +13,8 @@ from threading import Lock from collections import namedtuple +logger = logging.getLogger(__name__) + Struct = namedtuple('Struct', 'name typedef') class State: @@ -292,7 +295,7 @@ def colour_coverage(bv, cur_func): ### handle remote events: def onmsg(vw, key, data, replay): - print("onmsg: %r : %r (%r)" % (key, data, replay)) + logger.warning("onmsg: %r : %r (%r)" % (key, data, replay)) try: state = State.get(vw) meta = state.getMetaBySha(key) @@ -343,142 +346,33 @@ def onmsg(vw, key, data, replay): elif cmd == 'struc_created': # note: binja does not seem to appreciate the encoding of strings from redis struct_name = data['struc_name'].encode('ascii', 'ignore') - ''' not ready to wrap this into Viv yet. - state.structs_lock.acquire() - struct = bv.get_type_by_name(struct_name) - # if a struct with the same name already exists, undefine it - if struct: - bv.undefine_user_type(struct_name) - struct = Structure() - bv.define_user_type(struct_name, binaryninja.types.Type.structure_type(struct)) - state.structs = get_structs(bv) - state.structs_lock.release() - ''' vw.vprint('revsync: <%s> %s %s' % (user, cmd, struct_name)) elif cmd == 'struc_deleted': struct_name = data['struc_name'].encode('ascii', 'ignore') - ''' not ready to wrap this into Viv yet. - state.stackvar_lock.acquire() - struct = bv.get_type_by_name(struct_name) - # make sure the type is defined first - if struct is None: - vw.vprint('revsync: unknown struct name %s during struc_deleted cmd' % struct_name) - return - bv.undefine_user_type(struct_name) - state.structs = get_structs(bv) - state.structs_lock.release() - ''' vw.vprint('revsync: <%s> %s %s' % (user, cmd, struct_name)) elif cmd == 'struc_renamed': old_struct_name = data['old_name'].encode('ascii', 'ignore') new_struct_name = data['new_name'].encode('ascii', 'ignore') - ''' not ready to wrap this into Viv yet. - state.structs_lock.acquire() - struct = bv.get_type_by_name(old_struct_name) - # make sure the type is defined first - if struct is None: - vw.vprint('revsync: unknown struct name %s during struc_renamed cmd' % old_struct_name) - return - bv.rename_type(old_struct_name, new_struct_name) - state.structs = get_structs(bv) - state.structs_lock.release() - ''' vw.vprint('revsync: <%s> %s %s %s' % (user, cmd, old_struct_name, new_struct_name)) elif cmd == 'struc_member_created': struct_name = data['struc_name'].encode('ascii', 'ignore') - ''' not ready to wrap this into Viv yet. - state.structs_lock.acquire() - struct = bv.get_type_by_name(struct_name) - if struct is None: - vw.vprint('revsync: unknown struct name %s during struc_member_created cmd' % struct_name) - return - member_name = data['member_name'].encode('ascii', 'ignore') - struct_type = get_type_by_size(bv, data['size']) - if struct_type is None: - vw.vprint('revsync: bad struct member size %d for member %s during struc_member_created cmd' % (data['size'], member_name)) - return - # need actual Structure class, not Type - struct = struct.structure - struct.insert(data['offset'], struct_type, member_name) - # we must redefine the type - bv.define_user_type(struct_name, binaryninja.types.Type.structure_type(struct)) - state.structs = get_structs(bv) - state.structs_lock.release() - ''' vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, member_name)) elif cmd == 'struc_member_deleted': struct_name = data['struc_name'].encode('ascii', 'ignore') member_name = '???' - ''' not ready to wrap this into Viv yet. - state.structs_lock.acquire() - struct = bv.get_type_by_name(struct_name) - if struct is None: - vw.vprint('revsync: unknown struct name %s during struc_member_deleted cmd' % struct_name) - return - offset = data['offset'] - # need actual Structure class, not Type - struct = struct.structure - # walk the list and find the index to delete (seriously, why by index binja and not offset?) - member_name = '???' - for i,m in enumerate(struct.members): - if m.offset == offset: - # found it - member_name = m.name - struct.remove(i) - # we must redefine the type - bv.define_user_type(struct_name, binaryninja.types.Type.structure_type(struct)) - state.structs = get_structs(bv) - state.structs_lock.release() - ''' vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, member_name)) elif cmd == 'struc_member_renamed': struct_name = data['struc_name'].encode('ascii', 'ignore') member_name = data['member_name'].encode('ascii', 'ignore') - ''' not ready to wrap this into Viv yet. - state.structs_lock.acquire() - struct = bv.get_type_by_name(struct_name) - if struct is None: - vw.vprint('revsync: unknown struct name %s during struc_member_renamed cmd' % struct_name) - return - offset = data['offset'] - # need actual Structure class, not Type - struct = struct.structure - for i,m in enumerate(struct.members): - if m.offset == offset: - struct.replace(i, m.type, member_name) - bv.define_user_type(struct_name, binaryninja.types.Type.structure_type(struct)) - vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, member_name)) - break - state.structs = get_structs(bv) - state.structs_lock.release() - ''' vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, member_name)) elif cmd == 'struc_member_changed': struct_name = data['struc_name'].encode('ascii', 'ignore') - ''' not ready to wrap this into Viv yet. - state.structs_lock.acquire() - struct = bv.get_type_by_name(struct_name) - if struct is None: - vw.vprint('revsync: unknown struct name %s during struc_member_renamed cmd' % struct_name) - return - # need actual Structure class, not Type - struct = struct.structure - offset = data['offset'] - for i,m in enumerate(struct.members): - if m.offset == offset: - struct.replace(i, get_type_by_size(bv, data['size']), m.name) - bv.define_user_type(struct_name, binaryninja.types.Type.structure_type(struct)) - vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, m.name)) - break - state.structs = get_structs(bv) - state.structs_lock.release() - ''' vw.vprint('revsync: <%s> %s %s->%s' % (user, cmd, struct_name, m.name)) elif cmd == 'join': @@ -511,7 +405,7 @@ def __init__(self, vw): # make sure all VA's are reduced to base-addr-offsets def VWE_COMMENT(self, vw, event, locinfo): #done - print("%r %r %r" % (vw, event, locinfo)) + logger.warning("%r %r %r" % (vw, event, locinfo)) cmt_addr, cmt = locinfo # make sure something has changed (and that we're not repeating what we just received from revsync # publish comment to revsync @@ -529,7 +423,7 @@ def VWE_COMMENT(self, vw, event, locinfo): #done pass def VWE_SETNAME(self, vw, event, locinfo): #done - print("%r %r %r" % (vw, event, locinfo)) + logger.warning("%r %r %r" % (vw, event, locinfo)) name_addr, name = locinfo addr = get_can_addr(vw, name_addr) if self.state.syms.get(addr) != name: From b2a9a90b782c8617eb6bc85f6e94027cfd2aed37 Mon Sep 17 00:00:00 2001 From: atlas Date: Wed, 17 Mar 2021 16:25:47 -0400 Subject: [PATCH 16/16] final tweaks and cleanup --- viv_frontend.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/viv_frontend.py b/viv_frontend.py index 3ea5b82..f017cc3 100644 --- a/viv_frontend.py +++ b/viv_frontend.py @@ -89,7 +89,7 @@ def genFhashes(self): BB_REPORT = 50 -def get_can_addr(vw, addr): #done +def get_can_addr(vw, addr): ''' normalizing addresses ''' @@ -100,7 +100,7 @@ def get_can_addr(vw, addr): #done imagebase = vw.getFileMeta(fname, 'imagebase') return addr - imagebase -def get_ea(vw, sha_key, addr): #done +def get_ea(vw, sha_key, addr): ''' normalizing addresses ''' @@ -113,11 +113,11 @@ def get_ea(vw, sha_key, addr): #done return addr + imagebase -def get_func_by_addr(vw, addr): #done +def get_func_by_addr(vw, addr): return vw.getFunction(addr) def get_bb_by_addr(vw, addr): - return vw.getCodeBlock(addr) #done + return vw.getCodeBlock(addr) # in order to map IDA type sizes <-> viv types, # take the 'size' field and attempt to divine some @@ -138,7 +138,7 @@ def get_type_by_size(vw, size): pass return typedef -def get_syms(vw): #done +def get_syms(vw): syms = dict(vw.name_by_va) return syms @@ -175,7 +175,7 @@ def publish(vw, data, fhash, **kwargs): if state: client.publish(fhash, data, **kwargs) -def analyze(vw): #done +def analyze(vw): vw.vprint('revsync: running analysis update...') vw.analyze() vw.vprint('revsync: analysis finished.') @@ -295,7 +295,7 @@ def colour_coverage(bv, cur_func): ### handle remote events: def onmsg(vw, key, data, replay): - logger.warning("onmsg: %r : %r (%r)" % (key, data, replay)) + logger.info("onmsg: %r : %r (%r)" % (key, data, replay)) try: state = State.get(vw) meta = state.getMetaBySha(key) @@ -389,7 +389,7 @@ def onmsg(vw, key, data, replay): except Exception as e: vw.vprint('onmsg error: %r' % e) -def revsync_callback(vw): #done +def revsync_callback(vw): def callback(key, data, replay=False): onmsg(vw, key, data, replay) return callback @@ -404,8 +404,8 @@ def __init__(self, vw): self.state = vw.getMeta('revsync') # make sure all VA's are reduced to base-addr-offsets - def VWE_COMMENT(self, vw, event, locinfo): #done - logger.warning("%r %r %r" % (vw, event, locinfo)) + def VWE_COMMENT(self, vw, event, locinfo): + logger.info("%r %r %r" % (vw, event, locinfo)) cmt_addr, cmt = locinfo # make sure something has changed (and that we're not repeating what we just received from revsync # publish comment to revsync @@ -422,8 +422,8 @@ def VWE_COMMENT(self, vw, event, locinfo): #done except NoChange: pass - def VWE_SETNAME(self, vw, event, locinfo): #done - logger.warning("%r %r %r" % (vw, event, locinfo)) + def VWE_SETNAME(self, vw, event, locinfo): + logger.info("%r %r %r" % (vw, event, locinfo)) name_addr, name = locinfo addr = get_can_addr(vw, name_addr) if self.state.syms.get(addr) != name: @@ -486,7 +486,7 @@ def VWE_CHAT(self, vw, event, loc): evtdist = None -def revsync_load(vw): #done +def revsync_load(vw): global client, evtdist vw.vprint('Connecting to RevSync Server') @@ -517,7 +517,7 @@ def revsync_load(vw): #done evtdist = VivEventClient(vw) -def toggle_visits(vw): #done +def toggle_visits(vw): state = State.get(vw) state.show_visits = not state.show_visits if state.show_visits: @@ -526,7 +526,7 @@ def toggle_visits(vw): #done vw.vprint("Visit Visualization Disabled (Red)") state.color_now = True -def toggle_time(vw): #done +def toggle_time(vw): state = State.get(vw) state.show_time = not state.show_time if state.show_time: @@ -535,7 +535,7 @@ def toggle_time(vw): #done vw.vprint("Time Visualization Disabled (Blue)") state.color_now = True -def toggle_visitors(vw): #done +def toggle_visitors(vw): state = State.get(vw) state.show_visitors = not state.show_visitors if state.show_visitors: @@ -544,7 +544,7 @@ def toggle_visitors(vw): #done vw.vprint("Visitor Visualization Disabled (Green)") state.color_now = True -def toggle_track(vw): #done +def toggle_track(vw): state = State.get(vw) state.track_coverage = not state.track_coverage if state.track_coverage: