""" summary: react to trace notifications description: This script demonstrates using the low-level tracing hook (ida_dbg.DBG_Hooks.dbg_trace). It can be run like so: ida.exe -B -Sdbg_trace.py -Ltrace.log file.exe level: advanced """ import time import ida_dbg import ida_ida import ida_pro import ida_ua from ida_allins import NN_callni, NN_call, NN_callfi from ida_lines import generate_disasm_line, GENDSM_FORCE_CODE, GENDSM_REMOVE_TAGS # Note: this try/except block below is just there to # let us (at Hex-Rays) test this script in various # situations. try: import idc print(idc.ARGV[1]) under_test = bool(idc.ARGV[1]) except: under_test = False class TraceHook(ida_dbg.DBG_Hooks): def __init__(self): ida_dbg.DBG_Hooks.__init__(self) self.traces = 0 self.epReached = False def _log(self, msg): print(">>> %s" % msg) def dbg_trace(self, tid, ea): # Log all traced addresses if ea < ida_ida.inf_get_min_ea() or ea > ida_ida.inf_get_max_ea(): raise Exception( "Received a trace callback for an address outside this database!" ) self._log("trace %08X" % ea) self.traces += 1 insn = ida_ua.insn_t() insnlen = ida_ua.decode_insn(insn, ea) # log disassembly and ESP for call instructions if insnlen > 0 and insn.itype in [NN_callni, NN_call, NN_callfi]: self._log( "call insn: %s" % generate_disasm_line(ea, GENDSM_FORCE_CODE | GENDSM_REMOVE_TAGS) ) self._log("ESP=%08X" % ida_dbg.get_reg_val("ESP")) return 1 def dbg_run_to(self, pid, tid=0, ea=0): # this hook is called once execution reaches temporary breakpoint set by run_to(ep) below if not self.epReached: ida_dbg.refresh_debugger_memory() self._log("reached entry point at 0x%X" % ida_dbg.get_reg_val("EIP")) self._log("current step trace options: %x" % ida_dbg.get_step_trace_options()) self.epReached = True # enable step tracing (single-step the program and generate dbg_trace events) ida_dbg.request_enable_step_trace(1) # change options to only "over debugger segments" (i.e. library functions will be traced) ida_dbg.request_set_step_trace_options(ida_dbg.ST_OVER_DEBUG_SEG) ida_dbg.request_continue_process() ida_dbg.run_requests() def dbg_process_exit(self, pid, tid, ea, code): self._log("process exited with %d" % code) self._log("traced %d instructions" % self.traces) return 0 def do_trace(then_quit_ida=True): debugHook = TraceHook() debugHook.hook() # Start tracing when entry point is hit ep = ida_ida.inf_get_start_ip() ida_dbg.enable_step_trace(1) ida_dbg.set_step_trace_options(ida_dbg.ST_OVER_DEBUG_SEG | ida_dbg.ST_OVER_LIB_FUNC) print("Running to %x" % ep) ida_dbg.run_to(ep) while ida_dbg.get_process_state() != 0: ida_dbg.wait_for_next_event(1, 0) if not debugHook.epReached: raise Exception("Entry point wasn't reached!") if not debugHook.unhook(): raise Exception("Error uninstalling hooks!") del debugHook if then_quit_ida: # we're done; exit IDA ida_pro.qexit(0) # load the debugger module depending on the file type if ida_ida.inf_get_filetype() == ida_ida.f_PE: ida_dbg.load_debugger("win32", 0) elif ida_ida.inf_get_filetype() == ida_ida.f_ELF: ida_dbg.load_debugger("linux", 0) elif ida_ida.inf_get_filetype() == ida_ida.f_MACHO: ida_dbg.load_debugger("mac", 0) if not under_test: do_trace()