import idaapi from idaapi import * from idc import * from idautils import * import hashlib import traceback from client import Client from config import config from comments import comments, comments_extra, NoChange ida_reserved_prefix = ( 'sub_', 'locret_', 'loc_', 'off_', 'seg_', 'asc_', 'byte_', 'word_', 'dword_', 'qword_', 'byte3_', 'xmmword_', 'ymmword_', 'packreal_', 'flt_', 'dbl_', 'tbyte_', 'stru_', 'custdata_', 'algn_', 'unk_', ) fhash = None auto_wait = False client = Client(**config) netnode = idaapi.netnode() NETNODE_NAME = '$ revsync-fhash' hook1 = hook2 = hook3 = None ### Helper Functions def cached_fhash(): return netnode.getblob(0, 'I').decode('ascii') def read_fhash(): filename = idaapi.get_root_filename() if filename is None: return None with open(filename, 'rb') as f: return hashlib.sha256(f.read()).hexdigest().upper() def get_can_addr(addr): """Convert an Effective Address to a canonical address.""" return addr - get_imagebase() def get_ea(addr): """Get Effective Address from a canonical address.""" return addr + get_imagebase() ### Redis Functions ### def onmsg_safe(key, data, replay=False): def tmp(): try: onmsg(key, data, replay=replay) except Exception as e: print('error during callback for %s: %s' % (data.get('cmd'), e)) traceback.print_exc() idaapi.execute_sync(tmp, MFF_WRITE) def onmsg(key, data, replay=False): if key != fhash or key != cached_fhash(): print('revsync: hash mismatch, dropping command') return if hook1: hook1.unhook() if hook2: hook2.unhook() if hook3: hook3.unhook() try: if 'addr' in data: ea = get_ea(data['addr']) ts = int(data.get('ts', 0)) cmd, user = data['cmd'], data['user'] if cmd == 'comment': print('revsync: <%s> %s %#x %s' % (user, cmd, ea, data['text'])) text = comments.set(ea, user, str(data['text']), ts) set_cmt(ea, text, 0) elif cmd == 'extra_comment': print('revsync: <%s> %s %#x %s' % (user, cmd, ea, data['text'])) text = comments_extra.set(ea, user, str(data['text']), ts) set_cmt(ea, text, 1) elif cmd == 'area_comment': 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'])) elif cmd == 'join': print('revsync: <%s> joined' % (user)) elif cmd in ['stackvar_renamed', 'struc_created', 'struc_deleted', 'struc_renamed', 'struc_member_created', 'struc_member_deleted', 'struc_member_renamed', 'struc_member_changed', 'coverage']: if 'addr' in data: print('revsync: <%s> %s %#x (not supported in IDA revsync)' % (user, cmd, ea)) else: print('revsync: <%s> %s (not supported in IDA revsync)' % (user, cmd)) else: print('revsync: unknown cmd', data) finally: if hook1: hook1.hook() if hook2: hook2.hook() if hook3: hook3.hook() def publish(data, **kwargs): if not auto_is_ok(): return if fhash == netnode.getblob(0, 'I').decode('ascii'): client.publish(fhash, data, **kwargs) ### IDA Hook Classes ### def on_renamed(ea, new_name, local_name): if is_loaded(ea) and not new_name.startswith(ida_reserved_prefix): publish({'cmd': 'rename', 'addr': get_can_addr(ea), 'text': new_name}) def on_auto_empty_finally(): global auto_wait if auto_wait: auto_wait = False on_load() # These IDPHooks methods are for pre-IDA 7 class IDPHooks(IDP_Hooks): def renamed(self, ea, new_name, local_name): on_renamed(ea, new_name, local_name) return IDP_Hooks.renamed(self, ea, new_name, local_name) # TODO: make sure this is on 6.1 def auto_empty_finally(self): on_auto_empty_finally() return IDP_Hooks.auto_empty_finally(self) class IDBHooks(IDB_Hooks): def renamed(self, ea, new_name, local_name): on_renamed(ea, new_name, local_name) return IDB_Hooks.renamed(self, ea, new_name, local_name) def auto_empty_finally(self): on_auto_empty_finally() return IDB_Hooks.auto_empty_finally(self) def cmt_changed(self, ea, repeatable): cmt = get_cmt(ea, repeatable) try: changed = comments.parse_comment_update(ea, client.nick, cmt) publish({'cmd': 'comment', 'addr': get_can_addr(ea), 'text': changed or ''}, send_uuid=False) except NoChange: pass return IDB_Hooks.cmt_changed(self, ea, repeatable) def extra_cmt_changed(self, ea, line_idx, repeatable): try: cmt = get_cmt(ea, repeatable) changed = comments_extra.parse_comment_update(ea, client.nick, cmt) publish({'cmd': 'extra_comment', 'addr': get_can_addr(ea), 'line': line_idx, 'text': changed or ''}, send_uuid=False) except NoChange: pass return IDB_Hooks.extra_cmt_changed(self, ea, line_idx, repeatable) def area_cmt_changed(self, cb, a, cmt, repeatable): publish({'cmd': 'area_comment', 'range': [get_can_addr(a.startEA), get_can_addr(a.endEA)], 'text': cmt or ''}, send_uuid=False) return IDB_Hooks.area_cmt_changed(self, cb, a, cmt, repeatable) class UIHooks(UI_Hooks): pass ### Setup Events ### def on_load(): global fhash if fhash: client.leave(fhash) fhash = cached_fhash() print('revsync: connecting with', fhash) client.join(fhash, onmsg_safe) def wait_for_analysis(): global auto_wait if auto_is_ok(): auto_wait = False on_load() return -1 return 1000 def on_open(): global auto_wait global fhash print('revsync: file opened:', idaapi.get_root_filename()) netnode.create(NETNODE_NAME) try: fhash = netnode.getblob(0, 'I').decode('ascii') except: fhash = None if not fhash: fhash = read_fhash() try: ret = netnode.setblob(fhash.encode('ascii'), 0, 'I') except: print('saving fhash failed, this will probably break revsync') if auto_is_ok(): on_load() auto_wait = False else: auto_wait = True print('revsync: waiting for auto analysis') if not hasattr(IDP_Hooks, 'auto_empty_finally'): idaapi.register_timer(1000, wait_for_analysis) def on_close(): global fhash if fhash: client.leave(fhash) fhash = None hook1 = IDPHooks() hook2 = IDBHooks() hook3 = UIHooks() def eventhook(event, old=0): if event == idaapi.NW_OPENIDB: on_open() elif event in (idaapi.NW_CLOSEIDB, idaapi.NW_TERMIDA): on_close() if event == idaapi.NW_TERMIDA: # remove hook on way out idaapi.notify_when(idaapi.NW_OPENIDB | idaapi.NW_CLOSEIDB | idaapi.NW_TERMIDA | idaapi.NW_REMOVE, eventhook) def setup(): if idaapi.get_root_filename(): on_open() else: idaapi.notify_when(idaapi.NW_OPENIDB | idaapi.NW_CLOSEIDB | idaapi.NW_TERMIDA, eventhook) return -1 hook1.hook() hook2.hook() hook3.hook() idaapi.register_timer(1000, setup) print('revsync: starting setup timer')