def read_line(self, asmline, linenr=None): ''' Read a line of the asm file and process the information to keep track of the registers and stack. Parameters ---------- asmline: string a line of assembler linenr: int, optional the current line number in the file. If not given, it will not be used in the output. Raises ------ CpuException if any underlying exception has been raised ''' log.debug('reading line: %s', asmline) if linenr is not None: self.line_number = linenr self.address = ParseUtil.get_address_from_asmline(asmline) try: self._read_basic_block(asmline) self._read_meth_impl(asmline) self._read_assignment(asmline) self._read_call_line(asmline) except (ArgumentsException, VarAssignmentIvarRegisterWrongTypeException) as e: raise CpuException(self, e), None, sys.exc_info()[2]
def get_argument(self, register=None, objc_msgSend_stret=False): ''' Get an argument from the specified `Register`. If the `Cpu` fetches its arguments from stack, pop it from where. Parameters ---------- objc_msgSend_stret: bool, optional (default is False) indicate an objc_msgSend_stret Parameters ---------- register: Register from which `Register` to get_from_idx the argument ''' res = None if self.cpu.fetches_all_arguments_from_stack(): res = self.stack.pop() # pop address for structure return from stack if objc_msgSend_stret: log.debug( 'popped structure return address from stack due to objc_msgSend_stret(). Popped: %s', res) res = self.stack.pop() else: res = self.get_registers().get_value_for_register(register) return res
def store_self_cmd_for_stack_fetching_cpu(self, objc_class, selector): ''' Store self and _cmd in the appropriate place ''' # self and _cmd are the first arguments on the stack -> push it cpu = self.cpu cpu.memory.registers.set_value_for_register_ann_method_ead( StackVar('arg_0'), objc_class) cpu.memory.registers.set_value_for_register_ann_method_ead( StackVar('arg_4'), selector) log.debug('added self and _cmd as StackVar: %s', cpu.memory.registers)
def read_assignment(self, asmline): ''' Read an assignment and try to split it. Parameters ---------- asmline : string line of assembler ''' self.reset() sp_sub_val = self.parse_util.parse_stack_pointer_sub(asmline) # do not read e.g. "sub sp, #0x14" if sp_sub_val is None: op, val1, val2 = self.parse_util.parse_assignment_split(asmline) if None not in (op, val1, val2): # switch sides if "str" command # "str r0, [r4, r5]" -> "str [r4, r5], r0" # "str r0, [r4, r5]" means r4.r5 = r0 if op.find('str') != -1: val1, val2 = val2, val1 self._log_assignment_split(val1, val2) # stack push and fetch self._process_stack_push_via_stm(asmline) self._process_stack_push_via_str(val1) self._process_stack_fetch(val2) var_assign1 = self.parse_util.parse_var_assignment_without_ivar_ref_from_asmline(val1) pu = self.parse_util val2_remaining_types = [pu.parse_ivar, pu.parse_objc_class, pu.parse_selector, pu.parse_string, pu.parse_imp, pu.parse_var_assignment_without_ivar_ref_from_asmline] lobject = self._get_val1_value(val1, var_assign1) self.lobject = lobject robject = None if not self.is_stack_fetch: robject = self._get_val2_value(val2, val2_remaining_types) else: robject = self.get_current_stackframe_for_fetch()[self.stack_address] log.debug('fetched %s from stack at address: %s', robject, hex(self.stack_address)) self.robject = robject # save register in args_since_last_call to determine number of register arguments needed for next call if self.cpu.c_func_heuristic: self.add_reg_arg_since_last_call(lobject, robject) # set pushed objects if self.stack_push_via_str: # pushed object is right object # consider e.g. "str r5, [sp]", val2 = r5 and val1 = sp (due to reordering) self.stack_push_objects = [self.robject]
def _read_call_line(self, asmline): ''' Read a call line like e.g. "call qword [ds:objc_msg_alloc] ; @selector(alloc)" ,split to what is called and interpret the called stuff. ''' called = self.parse_util.parse_call_instruction(asmline) if called is not None: if self.c_func_heuristic: log.debug( 'args since last call: \n%s', ', '.join( str(x) for x in self.assignment_matching_system. get_usage_since_last_call())) self._read_called(asmline, called) self.assignment_matching_system.clear_usage_since_last_call()
def read_return_important_objc_call(self, imp, destination, return_register): ''' Read a line which seems to return something and store it in the return register. Returns ------- is return important ''' is_return_important = imp.is_return_important() if is_return_important: log.debug('%s = %s', return_register, destination) self.cpu.memory.registers.set_value_for_register( return_register, destination) return is_return_important
def add(self, value, address): ''' Add an object to the `StackFrame`. The stack grows to higher addresses, meaning that the top of the stack is always the value with the lowest address. The order in which the elements are added does not play any role. The order is given by the `address`. Parameters ---------- address: int ''' msg = 'stack frame at %s' % hex(address) hopanno.annotate_assignment(msg, value, self.cpu.address) log.debug(msg) self.stack[address] = value
def process_stack_push(self): ''' Push the objects onto the stack ''' if self.get_is_stack_push(): cpu = self.cpu pushed_objects = self.get_stack_push_objects() address = self.get_stack_address() try: for obj in pushed_objects: # resolve register value if isinstance(obj, Register): obj = cpu.memory.registers.resolve_current_register_value(obj) log.debug('pushing %s on StackFrame at address: %s' % (obj, address)) cpu.memory.stack.add(obj, address) address += self.get_cpu().pointer_size() self.stack_address = address except TypeError: pass
def create_and_store_remaining_method_selector_arguments( self, start_addr, method_selector): ''' Create and store the remaining arguments for the `method_selector`. There will be as many arguments created as the `Selector` still needs and stored as `StackVar`. Parameters ---------- start_addr: int the suffix for the first `StackVar` argument (e.g. arg_4) method_selector: Selector the `Selector` describing the method that is currently being read by the `Cpu` Raises ------ SelectorOverloadedException if selector has more arguments than it needs ''' cpu = self.cpu if method_selector is not None: STACKVAR_PREFIX = 'arg' addr = start_addr start = method_selector.cnt_has_arguments() + 1 for i in range(start, method_selector.cnt_needs_arguments() + 1): method_sel_arg = ObjectiveCRuntime.create_method_selector_arg( i) # cut off the 0x prefix and use upper case for rest of address stackvar_name = '%s_%s' % ( STACKVAR_PREFIX, Util.hex_string_without_0x(addr).upper()) # save argument as StackVar stackvar = StackVar(stackvar_name) cpu.memory.registers.set_value_for_register_ann_method_ead( stackvar, method_sel_arg) method_selector.add_argument(method_sel_arg) log.debug('method_selector arg %s = %s', method_sel_arg, stackvar) addr += cpu.pointer_size()
def _process_var_assignment(self, var_assign): ''' Process the `VarAssignment` and keep track of the instance variable cache as well as setting the superclass if available. Parameters ---------- var_assign: VarAssignment ''' if var_assign is not None: # set entry in IVarLookup if VarAssignment contains IVar ivar = var_assign.get_ivar_value() ivar_ref = ivar.get_ivar_ref() self.objc_runtime.set_superclass(ivar) if ivar is not None: if ivar.create_ivar_lookup_entry( self.objc_runtime.ivar_lookup): msg = '%s -> %s' % (ivar.get_ivar_class(), ivar_ref) log.debug(msg) hopanno.annotate_other_assignment( 'added ivar lookup entry: ' + msg, self.address) hopanno.annotate_assignment_method_head(msg)
def __init__(self, file_path): self.__abs_file_path = abspath(file_path) # set None to use the `reopen_file` method self.__file_obj = None log.debug('file path: %s' % (self.get_abs_file_path())) self.reopen_file()
def _read_called(self, asmline, called): ''' Read a called line like e.g. "qword [ds:objc_msg_alloc] ; @selector(alloc)" and hand it on to the appropriate read method. Parameters ---------- asmline: string called: ImpStub, Selector or string Raises ------ IvarRefWrongTypeException if the ivar_ref has the wrong type CpuException if the value of the destination register could not be resolved CpuCouldNotReadLogFuncException if the NSLog could not be read CpuCouldNotReadMsgSendEception if the objc_msgsend() could not be read ''' # called does not have to be a string due to the fact that read_register() # will recall this method with register resolved and passed as called imp = selector = destination = None if isinstance(called, str): imp = self.parse_util.parse_imp(called) # called can be e.g. _foo or sub_foo if not imp: called_method = self.parse_util.parse_called_method_name( called) if called_method is not None: called = called_method else: if self.ignore_hex_addr_call( ) and self.parse_util.parse_hex(called): return # called can be Selector or Imp else: if isinstance(called, Imp): imp = called elif isinstance(called, Selector): selector = called try: # only pop from stack if really needed for a MsgSend if imp is not None and imp.is_any_msg_send( ) and self.fetches_all_arguments_from_stack( ) or not self.fetches_all_arguments_from_stack(): is_msg_send_stret = False if imp is not None: is_msg_send_stret = imp.is_any_msg_send_stret() destination = self.get_current_destination(is_msg_send_stret) return_register = self.return_register() # if selector is not None, it's already resolved if selector is None: selector = self.parse_util.parse_selector(asmline) register = None if isinstance(called, str): # same as above, called can be anything else than a string register = self.parse_util.parse_register(called) log.debug('destination is %s', destination) # destination is VarAssignment if isinstance(destination, VarAssignment): # get ivar value from VarAssignment destination = destination.get_ivar_value() if imp is not None: msgSend = self.objc_runtime.read_msg_send(imp, destination) if msgSend is None: if self.objc_runtime.read_return_important_objc_call( imp, destination, return_register): return if self._read_formatstring_log(imp): return else: self._read_remaining_stub(imp) else: if self._read_c_func(called): return else: self._read_register(register, asmline) if not self.objc_runtime.read_selector(selector, destination): # TODO: HOW TO HANDLE (fp)([NSURLRequest class], selx, YES, host); ??? # called can be something else due to recalling of `_read_register` if isinstance(called, str): own_c_method_call = ParseUtil.parse_own_c_method_called( called) if own_c_method_call is not None: self._read_own_c_func_call(own_c_method_call) except CpuCouldNotGetDestination as e: raise CpuException(self, e), None, sys.exc_info()[2]
def _read_meth_impl(self, asmline): ''' Read a line like e.g. "methImpl_AppDelegate_applicationDidFinishLaunching_" and detect the current class (AppDelegate) as well as the current method (applicationDidFinishLaunching_) If the `Cpu` does not fetch all of its arguments from stack, self and _cmd will be set to the `Register`s defined by `CallingConventionsInterface`. Otherwise implement `store_self_cmd_for_stack_fetching_cpu` in your `Cpu`. Raises ------ CpuCouldNotGetSelfref raised if the self reference could not be detected SelectorOverloadedException raised if the selector has more arguments than it needs SelectorUnderloadedException raised if the selector has less arguments than it needs ''' method_implementation, match = self.parse_util.parse_any_method_implementation( asmline) is_meth_impl, is_own_c_method_def, is_c_method, is_category = method_implementation method_impl = None if any([is_meth_impl, is_category]): method_impl = match selector = match.fst_selector() objc_class = match.msg_receiver if not self.fetches_all_arguments_from_stack(): selfref_reg = self.destination_register() if selfref_reg is not None: # store class for self reference self.set_selfref_reg_value(objc_class) else: raise CpuCouldNotGetSelfref( self, selfref_reg, '%s.%s' % (objc_class, selector)) else: # set self and _cmd for cpu fetching its args from stack self.objc_runtime.store_self_cmd_for_stack_fetching_cpu( objc_class, selector) self.objc_runtime.create_and_store_method_selector_arguments( selector) # set method to use it in _try_log_reading_meth_impl self.method = method_impl # log method self._try_log_reading_meth_impl(selector) self.set_method_selector_reg_value(selector) elif is_c_method or is_own_c_method_def: log.debug('reading method: %s', match) method_impl = match self.create_and_store_c_func_arguments(method_impl) if any(method_implementation): clilog.info('reading method: %s', method_impl) # set method and sender for `MethodCall` self.method = method_impl self.method_call.sender = method_impl
def _log_assignment_split(self, val1, val2): ''' Use to log the assignment after splitting to its operands. Mostly values after comma ''' log.debug('assignment split: (%s, %s)', val1, val2)