class FunctionContext(): """ Function context stores all runtime context of a given function call. A single function may be assigned to multiple FunctionContext objects, because a unique function context will be created every time this function is called. The information stored is the value of the function arguments and registers at function call and function return. It also holds various other useful information regarding this specific function call such as: 1. "was this function called indirectly" 2. "did this function exist in the original analysis" 3. "who called this function" 4. "how much time did it take to process this function" """ ID = 1 # Global function context ID (Used for indexing in DieDB) def __init__(self, ea, iatEA=None, is_new_func=False, library_name=None, parent_func_context=None, calling_ea=None): """ Ctor @param ea: Effective address of the function @param iatEA: Effective address of IAT element (For library functions) @param is_indirect: Was this function called indirectly? @param is_new_func: Is this function missing from initial function analysis? @param parent_func_context: FunctionContext object of the calling function @param calling_ea: The ea of the call instruction used to call this function """ self.logger = logging.getLogger(__name__) self.config = DieConfig.get_config() # Get a unique function context ID self.id = FunctionContext.ID FunctionContext.ID += 1 ################################################################################ ### Context Stuff # Arguments self.callValues = [] # Argument values at function call self.retValues = [] # Argument values at function return self.retArgValue = None # Return argument value # Registers self.callRegState = None # Register state at function call self.retRegState = None # Register state at function return self.total_proc_time = 0 # Total processing time in seconds. self.callingEA = calling_ea # The ea of the CALL instruction self.parent_func_context = parent_func_context # Function context of the calling function self.child_func_context = [] # Array of function contexts called bu this function self.calling_function_name = get_function_name(self.callingEA) # Calling function name ### Flags self.no_ret_context = True # empty flag is dropped when first call context is retrieved. self.is_indirect = self.check_if_indirect() # Flag indicating whether this function was called indirectly self.is_new_func = is_new_func # Flag indicating whether this function did not exist in initial analysis if self.config.function_context.add_xref: self.add_call_xrefs(ea, iatEA) try: # Get this function (The Callee) if self.config.function_context.new_func_analysis: self.function = self._getFunctionHelper(ea, iatEA, library_name=library_name) else: self.function = Function(ea, iatEA, library_name=library_name) # Get a function parser for this function # (currently only GenericFunctionParser exist, and this is used to enable future extensions) self.function_parser = GenericFunctionParser(self.function) except DIE.Lib.DIE_Exceptions.DieNoFunction: if self.config.function_context.new_func_analysis: self.logger.info("Could not retrieve function information at address: %s", hex(ea)) else: self.logger.debug("Could not retrieve function information at address: %s", hex(ea)) self.function = None @property def empty(self): """ This flag is set when this object is not associated with any function.""" return self.function is None def check_if_indirect(self): """ Check if this function is called indirectly @return: True if function was called indirectly, otherwise False """ try: if not self.callingEA: self.logger.error("Error: could not locate the calling ea for function %s", self.function.funcName) return False return is_indirect(self.callingEA) except Exception as ex: self.logger.error("Failed while checking for indirect call: %s", ex) return False def get_arg_values_call(self): """ Get the function argument values upon function call @return: True if function argument values were successfully retrieved, otherwise false. """ with context.Timer() as timer: self.no_ret_context = False # drop the empty flag # If no function arg retrieval is disabled in configuration - quit: if not self.config.function_context.get_func_args: return True self.callRegState = self.getRegisters() # Get registers state self.callValues = self.function_parser.parse_function_args_call() # Get function Arguments if self.callValues is None: self.logger.error("Failed parsing function arguments") self.no_ret_context = True return False self.total_proc_time += timer.elapsed # Add to total elapsed time return True def get_arg_values_ret(self): """ Get the function argument values upon function return @return: True if function argument values were successfully retrieved, otherwise false. """ # Is this an empty function context object? if self.empty: return False if self.no_ret_context: self.logger.error("Call values must be retrieved prior to return values.") return False # If no function arg retrieval is disabled in configuration - quit: if not self.config.function_context.get_func_args: return True with context.Timer() as timer: self.retRegState = self.getRegisters() # Get register state # Get function arguments (self.retValues, self.retArgValue) = self.function_parser.parse_function_args_ret(self.callValues) self.total_proc_time += timer.elapsed # Add to total elapsed time return True def getRegisters(self): """ Get a list of registers\value tuples. """ return idaapi.dbg_get_registers() def _getFunctionHelper(self, ea, iatEA, library_name): """ An helper class for getting function data. if function is not defines, tries to define it. @param ea: Any address within the function boundaries @return: Returns a Function object. """ try: return Function(ea, iatEA, library_name=library_name) except DIE.Lib.DIE_Exceptions.DieNoFunction as ex: self.logger.debug("Trying to define a new function at address: %s", hex(ea)) if MakeFunction(ea, BADADDR): self.logger.info("New function was defined at: %s", hex(ea)) func_start_adrs = get_function_start_address(ea) func_end_adrs = get_function_end_address(ea) self.logger.debug("Analyzing new area.") func_t = idaapi.get_func(ea) idaapi.reanalyze_function(func_t) self.logger.debug("Refresh debugger memory") invalidate_dbgmem_contents(func_start_adrs, func_end_adrs) # If this second attempt fails again, the exception should be handled by the calling function. return Function(ea, iatEA, library_name=library_name) def add_call_xrefs(self, ea, iatEA): """ If docent already exist, Add call XREFs from the calling function. @param ea: Effective address of this function context @param iatEA: Effective address of IAT address @return: True if XREF was added, otherwise False """ if not is_call_xref(self.callingEA, ea): # Only segments loaded by the loader should get xrefed calling_seg = idaapi.getseg(self.callingEA) callee_seg = idaapi.getseg(ea) if callee_seg.is_loader_segm() and calling_seg.is_loader_segm(): self.logger.info("Adding XREF from: %s to: %s", hex(self.callingEA), hex(ea)) return add_call_xref(self.callingEA, ea) return False
class FunctionContext(): """ Function context stores all runtime context of a given function call. A single function may be assigned to multiple FunctionContext objects, because a unique function context will be created every time this function is called. The information stored is the value of the function arguments and registers at function call and function return. It also holds various other useful information regarding this specific function call such as: 1. "was this function called indirectly" 2. "did this function exist in the original analysis" 3. "who called this function" 4. "how much time did it take to process this function" """ def __init__(self, ea, iatEA=None, is_new_func=False, library_name=None): """ Ctor @param ea: Effective address of the function @param iatEA: Effective address of IAT element (For library functions) @param is_indirect: Was this function called indirectly? @param is_new_func: Is this function missing from initial function analysis? """ self.logger = logging.getLogger(__name__) self.config = DieConfig.get_config() ################################################################################ ### Context Stuff # Arguments self.callValues = [] # Argument values at function call self.retValues = [] # Argument values at function return self.retArgValue = None # Return argument value # Registers self.callRegState = None # Register state at function call self.retRegState = None # Register state at function return self.total_proc_time = 0 # Total processing time in seconds. try: ### Function Data self.function = Function( ea, iatEA, library_name=library_name) # This (The Callee) function self.callingEA = get_ret_adr() # The ea of the CALL instruction self.calling_function_name = get_function_name( self.callingEA) # Calling function name ### Flags self.empty = True # empty flag is dropped when first call context is retrieved. self.is_indirect = self.check_if_indirect( ) # Flag indicating whether this function was called indirectly self.is_new_func = is_new_func # Flag indicating whether this function did not exist in initial analysis # TODO: if this is a new function, try to define it. # Get a function parser for this function # (currently only GenericFunctionParser exist, and this is used to enable future extensions) self.function_parser = GenericFunctionParser(self.function) except Exception as ex: logging.critical("Error while initializing function context: %s", ex) return None def check_if_indirect(self): """ Check if this function is called indirectly @return: True if function was called indirectly, otherwise False """ if not self.callingEA: self.logger.error( "Error: could not locate the calling ea for function %s", self.function.funcName) return False return is_indirect(self.callingEA) def get_arg_values_call(self): """ Get the function argument values upon function call @return: True if function argument values were successfully retrieved, otherwise false. """ start_time = time.time() # Start timer self.empty = False # drop the empty flag # If no function arg retrieval is disabled in configuration - quit: if not self.config.get_func_args: return True self.callRegState = self.getRegisters() # Get registers state self.callValues = self.function_parser.parse_function_args_call( ) # Get function Arguments if self.callValues is None: self.logger("Failed parsing function arguments") self.empty = True return False elapsed_time = time.time() - start_time # Get elapsed time self.total_proc_time += elapsed_time # Add to total elapsed time return True def get_arg_values_ret(self): """ Get the function argument values upon function return @return: True if function argument values were successfully retrieved, otherwise false. """ if self.empty: self.logger.error( "Call values must be retrieved prior to return values.") return False # If no function arg retrieval is disabled in configuration - quit: if not self.config.get_func_args: return True start_time = time.time() # Start timer self.retRegState = self.getRegisters() # Get register state # Get function arguments (self.retValues, self.retArgValue) = self.function_parser.parse_function_args_ret( self.callValues) elapsed_time = time.time() - start_time # Get elapsed time self.total_proc_time += elapsed_time # Add to total elapsed time return True def getRegisters(self): """ Get a list of registers\value tuples. """ return idaapi.dbg_get_registers()