def __init__(self): self.logger = logging.getLogger(__name__) self.iat = StaticImports() # Static IAT self.die_db = DIE.Lib.DIEDb.get_db() # DIE DB # Walked function list is used with dynamic (runtime) breakpointing # it keeps track of previously walked functions in order to avoid walking them again. self.walked_functions = {}
def __init__(self): self.logger = logging.getLogger(__name__) self.iat = StaticImports() # Static IAT self.die_db = DIE.Lib.DIEDb.get_db() # DIE DB self.ret_bps = {} # Return breakpoint list # Walked function list is used with dynamic (runtime) breakpointing # it keeps track of previously walked functions in order to avoid walking them again. self.walked_functions = {}
class BpHandler(): """ Main breakpoint handling class. Used to control all breakpoint related operations, such as dynamic\static breakpoints setting, breakpoint exceptions and serialization. """ def __init__(self): self.logger = logging.getLogger(__name__) self.iat = StaticImports() # Static IAT self.die_db = DIE.Lib.DIEDb.get_db() # DIE DB self.ret_bps = {} # Return breakpoint list # Walked function list is used with dynamic (runtime) breakpointing # it keeps track of previously walked functions in order to avoid walking them again. self.walked_functions = {} ############################################################################################### # Public properties @property def bp_list(self): return self.die_db.bp_list @property def excluded_modules(self): return self.die_db.excluded_modules @property def excluded_funcNames(self): return self.die_db.excluded_funcNames @property def excluded_bp_ea(self): return self.die_db.excluded_bp_ea @property def excluded_funcNames_part(self): return self.die_db.excluded_funcNames_part ############################################################################################### # Breakpoint handling functions def setBPs(self): """ Set breakpoints on all CALL and RET instructions in all of the executable sections. """ for seg_ea in idautils.Segments(): for head in idautils.Heads(seg_ea, idc.SegEnd(seg_ea)): if idc.isCode(idc.GetFlags(head)): # Add BP if instruction is a CALL if is_call(head): self.addBP(head) def unsetBPs(self): """ Remove all DIE set BreakPoints @return: Returns True if successful, otherwise return False """ try: for ea in self.die_db.bp_list: (bp_flags, bp_desc) = self.die_db.bp_list[ea] if bp_flags & WAS_USER_BREAKPOINT: return idc.DelBpt(ea) # Remove breakpoint # Remove all return breakpoints for ea in self.ret_bps: idc.DelBpt(ea) self.die_db.bp_list.clear() # Clear the breakpoint list. self.ret_bps.clear() # Clear the return breakpoint list # Clear walked function list if necessary. if self.walked_functions is not None: self.walked_functions.clear() except Exception as ex: self.logger.exception("Failed to remove breakpoints: %s", ex) def addBP(self, ea, bp_description=None): """ Add a breakpoint @param ea: The location address to add the breakpoint @param bp_description: A breakpoint description @return: True if breakpoint was added, otherwise False. Returns -1 if an error occurred. """ try: if idc.CheckBpt(ea) > 0: # If our breakpoint already exist if ea in self.die_db.bp_list: return False # Must be a user defined breakpoint then.. self.die_db.bp_list[ea] = (WAS_USER_BREAKPOINT, bp_description) else: # Check if breakpoint is not excluded. if self.is_exception_call(ea): return False # TODO: better replace with a named tuple. self.die_db.bp_list[ea] = (0, bp_description) idc.AddBpt(ea) return True except Exception as ex: self.logger.exception("Could not add breakpoint: %s", ex) return -1 def removeBP(self, ea): """ Remove a breakpoint @param ea: @return: True if breakpoint was removed, otherwise False. Returns -1 if an error occurred. """ try: if not ea in self.die_db.bp_list: return False (bp_flags, bp_desc) = self.die_db.bp_list[ea] if bp_flags & WAS_USER_BREAKPOINT: return True idc.DelBpt(ea) # Remove breakpoint self.die_db.bp_list.pop(ea) # Remove from breakpoint list return True except Exception as ex: self.logger.exception("Could not remove breakpoint: %s", ex) return -1 def addRetBP(self, ea): """ Add a breakpoint for a return instruction. This will place a breakpoint on the next instruction, e.g - the instruction that will be hit after the function returns. @param ea: effective address of a CALL instruction @return: True if breakpoint was successfully added, otherwise return False """ if not is_call(ea): self.logger.error("The instruction at address %s is not recognized as a CALL instruction", hex(ea)) raise UnrecognizedCallInstruction() next_inst = idc.NextHead(ea) # Add breakpoint on next instruction if next_inst not in self.ret_bps: idc.AddBpt(next_inst) self.ret_bps[next_inst] = 0 return True return False def removeRetBP(self, ea): """ Remove a breakpoint for a return instruction @param ea: effective address of a previously placed Return Breakpoint @return: True if breakpoint was successfully removed, otherwise return False """ if ea in self.ret_bps: idc.DelBpt(ea) del self.ret_bps[ea] return True return False def isRetBP(self, ea): """ Check if the current Breakpoint marks a Return Breakpoint, e.g - was a function just returned from @param ea: effective address @return: True if this is a return BP, otherwise False """ return ea in self.ret_bps ############################################################################################### # Breakpoint exception handling functions def is_exception_call(self, ea): """ Main exception checking function. Checks if the requested call instruction ea is part of exception list. @param ea: The address to check @return: True if the given address is to be excepted from breakpoint list, otherwise False """ try: # Check if in excluded breakpoint addresses list if ea in self.die_db.excluded_bp_ea: return True # Try to extract function name. if extraction failed no further checks can be made. func_ea, func_name = self.get_called_func_data(ea) if func_name is None and func_ea is None: return False # Check if called function name is part of an excluded module for module_name in self.die_db.excluded_modules: if self.iat.is_module_call(func_name, module_name, func_ea): return True # Next checks check for function name matching. they are irrelevant if no function name is available. if func_name is None: return False # Check if called function name is in excluded function name list. if func_name in self.die_db.excluded_funcNames: return True for func_part, match_type in self.die_db.excluded_funcNames_part: if match_type == STARTS_WITH: if func_name.startswith(func_part): return True if match_type == ENDS_WITH: if func_name.endswith(func_part): return True if match_type == CONTAINS: if func_name.find(func_part) != -1: return True # Feeeew, Nothing matched.. return False except Exception as ex: self.logger.exception("Failed to check for breakpoint exception: %s", ex) return False def is_exception_func(self, ea, iatEA): """ Check if the address is part of an excluded function. @param ea: An address from within the function boundaries @param iatEA: An address of the function in the IAT @return: True if the given address is excepted from breakpoint list, otherwise False """ try: func_adr = ea if iatEA is not None: func_adr = iatEA func_name = get_function_name(func_adr) if func_name in self.die_db.excluded_funcNames: return True return False except Exception as ex: self.logger.exception("Failed checking if function at address %s is excepted: %s", hex(ea), ex) return False def reload_bps(self): """ Reload current breakpoints according to current exception lists @return: True if bps were reloaded sucessfully, otherwise False. """ try: self.logger.debug("Reloading breakpoint exceptions") for bp_ea in self.die_db.bp_list.keys(): if self.is_exception_call(bp_ea): if self.removeBP(bp_ea): self.logger.debug("breakpoint exception removed from %s", hex(bp_ea)) self.logger.info("Breakpoints were reloaded successfully") return True except Exception as ex: self.logger.exception("Failed while reloading exceptions: %s", ex) return False def add_module_exception(self, module_name, reload_bps=False): """ Add a loaded module name (i.e "user32") to be excepted. no breakpoints will be set on functions contained in this module. @param module_name: The excepted module @param reload_bps: reload breakpoints @return: True if added successfully, otherwise False """ try: if module_name is None: return False module_name = module_name.lower() if module_name in self.die_db.excluded_modules: self.logger.debug("Cannot add module %s to excluded module list. module already exist", module_name) return True self.logger.info("Module %s added to exception list.", module_name) self.die_db.excluded_modules.append(module_name) if reload_bps: self.reload_bps() return True except Exception as ex: self.logger.exception("Could not add module \"%s\" to excluded module list:", module_name, ex) return False def add_bp_ea_exception(self, ea, reload_bps=False): """ Add excluded address. no breakpoints will be set on this address. @param ea: Address to be excluded @param reload_bps: reload breakpoints @return: True if added successfully, otherwise False """ try: if ea is None: return False if ea in self.die_db.excluded_bp_ea: self.logger.debug("Cannot add address %s to excluded address list. address already exist", hex(ea)) return True self.logger.info("Address %s added to exception list", hex(ea)) self.die_db.excluded_bp_ea.append(ea) if reload_bps: self.reload_bps() except Exception as ex: self.logger.exception("Could not add address %s to excluded address list: %s", hex(ea), ex) return False def add_bp_funcname_exception(self, funcName, reload_bps=False): """ Add excluded function name. no breakpoint will be set on CALL statements to this function. Note: at some cases (like indirect calls) call destination is unknown prior to runtime and cannot be excluded. @param funcName: Function name to be excluded @param reload_bps: reload breakpoints @return: True if added successfully, otherwise False """ try: if funcName is None: return False funcName = funcName.lower() if funcName in self.die_db.excluded_funcNames: self.logger.debug("Cannot add function name %s to excluded function name list. " "function name already exist", funcName) return True self.logger.info("Function name \"%s\" was added to exception list", funcName) self.die_db.excluded_funcNames.append(funcName) if reload_bps: self.reload_bps() except Exception as ex: self.logger.exception("Could not add function name %s to excluded function names list:", funcName, ex) return False def add_bp_funcname_part_exception(self, func_name_part, match_type=STARTS_WITH, reload_bps=False): """ Add excluded function partial name. no breakpoint will be set on CALL statements to function that match. @param func_name_part: partial function name to match @param match_type: type of matching to preform on partial name. possible values are: STARTS_WITH = 0 (Default) ENDS_WITH = 1 CONTAINS = 2 @param reload_bps: reload breakpoints @return: True if added successfully, otherwise False """ try: if func_name_part is None: return False func_name_part = func_name_part.lower() match_tup = (func_name_part, match_type) if match_tup in self.die_db.excluded_funcNames_part: self.logger.debug("Cannot add partial function name %s to excluded partial function name list. " "function name already exist", func_name_part) return True if match_type == STARTS_WITH: self.logger.info("Function names that start with \"%s\" were added to exception list", func_name_part) if match_type == ENDS_WITH: self.logger.info("Function names that end with \"%s\" were added to exception list", func_name_part) if match_type == CONTAINS: self.logger.info("Function names that contain \"%s\" were added to exception list", func_name_part) self.die_db.excluded_funcNames_part.append(match_tup) if reload_bps: self.reload_bps() except Exception as ex: self.logger.exception("Could not add partial function name %s " "to excluded partial function names list: %s", func_name_part, ex) return False def get_called_func_data(self, ea): """ Try to get the called function name and address. @param ea: Address to the CALL instruction @return: On success a tuple of called function data (Function_ea, Demangled_Function_Name). otherwise (None,None) tuple will be returned """ try: func_name = None call_dest = None if idc.isCode(idc.GetFlags(ea)): if is_call(ea): operand_type = idc.GetOpType(ea, 0) if operand_type in (5, 6, 7, 2): call_dest = idc.GetOperandValue(ea, 0) # Call destination func_name = get_function_name(call_dest).lower() return call_dest, func_name except Exception as ex: self.logger.exception("Failed to get called function data: %s", ex) return None, None ############################################################################################### # Dynamic (RunTime) Breakpoints def walk_function(self, ea): """ Walk function and place breakpoints on every call function found within it. @param ea: An effective address within the function. @return: True if function walked succeeded or False otherwise """ try: function_name = get_function_name(ea) self.logger.debug("Walking function %s at address %s for breakpoints", function_name, hex(ea)) if function_name in self.walked_functions: self.logger.debug("No breakpoints will be set in function %s, " "since it was already walked before.", function_name) return True # Add function to walked function list self.walked_functions[function_name] = ea # function = sark.Function(ea) # for line in function.lines: # if line.is_code and line.insn.is_call: # self.addBP(line.ea) start_adrs = get_function_start_address(ea) end_adrs = get_function_end_address(ea) # Walk function and place breakpoints on every call instruction found. for head in idautils.Heads(start_adrs, end_adrs): if idc.isCode(idc.GetFlags(head)): # Add BP if instruction is a CALL if is_call(head): self.addBP(head) self.logger.debug("Function %s was successfully walked for breakpoints", function_name) return True except Exception as ex: self.logger.exception("Failed walking function at address %s for breakpoints.", hex(ea)) return False def flush_walked_funcs(self): """ Erase all previously walked function history @return: True on success, otherwise False """ self.walked_functions.clear() return True ############################################################################################### # Load\Save Exceptions to DIE DB def load_exceptions(self, die_db): """ Load exceptions from db @return: """ try: if not isinstance(die_db, DIE.Lib.DIEDb.DIE_DB): raise TypeError("Wrong type. expected DIE_DB.") self.die_db.excluded_bp_ea = die_db.excluded_bp_ea self.die_db.excluded_funcNames = die_db.excluded_funcNames self.die_db.excluded_modules = die_db.excluded_modules self.die_db.excluded_funcNames_part = die_db.excluded_funcNames_part return True except Exception as ex: self.logger.exception("Failed while loading breakpoint exception data from db: %s", ex) return False def save_exceptions(self, die_db): """ Save exceptions to db @return: """ try: if not isinstance(die_db, DIE.Lib.DIEDb.DIE_DB): raise TypeError("Wrong type. expected DIE_DB.") die_db.excluded_bp_ea = self.die_db.excluded_bp_ea die_db.excluded_funcNames = self.die_db.excluded_funcNames die_db.excluded_modules = self.die_db.excluded_modules die_db.excluded_funcNames_part = self.die_db.excluded_funcNames_part return True except Exception as ex: logging.exception("Failed while saving exception data to db: %s", ex) return False