def trace(self, ea): ''' Given an EA where an argument register is set, attempt to trace what function call that argument is passed to. @ea - The address of an instruction that modifies a function argument register. Returns a tuple of (function EA, argv index, argument register name) on success. Returns None on failure. ''' insn = ida_shims.decode_insn(ea) features = ida_shims.get_canon_feature(insn) if self.arch.unknown: return (None, None, None) for n in range(0, len(self.CHANGE_OPND)): ops = ida_shims.get_operands(insn) if ops[n].type in [idaapi.o_reg, idaapi.o_displ, idaapi.o_phrase]: try: regname = self.arch.registers[ops[n].reg] index = self.arch.argv.index(regname) except ValueError: continue if features & self.CHANGE_OPND[n]: ea = ea - (self.arch.delay_slot * self.arch.insn_size) while True: insn = ida_shims.decode_insn(ea) if idaapi.is_call_insn(ea): for xref in idautils.XrefsFrom(ea): if xref.type in [idaapi.fl_CF, idaapi.fl_CN]: return (xref.to, index, regname) # If we couldn't figure out where the function call # was going to, just quit break try: is_block_end = idaapi.is_basic_block_end(ea) except TypeError: is_block_end = idaapi.is_basic_block_end(ea, True) if is_block_end: break # TODO: Use idc.NextHead(ea) instead... ea += self.arch.insn_size return (None, None, None)
def argv(self, func): ''' Attempts to identify what types of arguments are passed to a given function. Currently unused. ''' args = [None for x in self.arch.argv] if not self.arch.unknown: start_ea = ida_shims.start_ea(func) for xref in idautils.XrefsTo(start_ea): if idaapi.is_call_insn(xref.frm): insn = ida_shims.decode_insn(xref.frm) ea = xref.frm + (self.arch.delay_slot * self.arch.insn_size) end_ea = (xref.frm - (self.arch.insn_size * 10)) while ea >= end_ea: if idaapi.is_basic_block_end(ea) or \ (ea != xref.frm and idaapi.is_call_insn(ea)): break insn = ida_shims.decode_insn(ea) features = ida_shims.get_canon_feature(insn) for n in range(0, len(self.CHANGE_OPND)): ops = ida_shims.get_operands(insn) if ops[n].type in [ idaapi.o_reg, idaapi.o_displ, idaapi.o_phrase ]: try: regname = self.arch.registers[ops[n].reg] index = self.arch.argv.index(regname) except ValueError: continue if features & self.CHANGE_OPND[n]: for xref in idautils.XrefsFrom(ea): # TODO: Where is this xref type defined? if xref.type == 1: string = \ ida_shims.get_strlit_contents( xref.to) if string and len(string) > 4: args[index] = str break ea -= self.arch.insn_size yield args
def _find_leafs(self): # Loop through every function for func_ea in idautils.Functions(): # Count the number of xrefs to this function func = idaapi.get_func(func_ea) if func: leaf_function = True ea = ida_shims.start_ea(func) end_ea = ida_shims.end_ea(func) # Loop through all instructions in this function looking # for call instructions; if found, then this is not a leaf. while ea <= end_ea: insn = ida_shims.decode_insn(ea) if idaapi.is_call_insn(ea): leaf_function = False break ea = ida_shims.next_head(ea) if leaf_function: self.functions.append( Function(start=ida_shims.start_ea(func), end=ida_shims.end_ea(func), leaf=True, loop=self.has_loop(func), argc=self.argp.argc(func))) # Sort leafs by xref count, largest first self.functions.sort(key=lambda f: f.xrefs, reverse=True)
def argc(self, function): ''' Counts the number of arguments used by the specified function. ''' argv = set() notargv = set() ea = ida_shims.start_ea(function) end_ea = ida_shims.end_ea(function) if self.arch.unknown: return 0 while ea < end_ea: insn = ida_shims.decode_insn(ea) features = ida_shims.get_canon_feature(insn) for n in range(0, len(self.USE_OPND)): ops = ida_shims.get_operands(insn) if ops[n].type in [ idaapi.o_reg, idaapi.o_displ, idaapi.o_phrase ]: try: regname = self.arch.registers[ops[n].reg] index = self.arch.argv.index(regname) except ValueError: continue if features & self.USE_OPND[n] and regname not in notargv: argv.update(self.arch.argv[:index + 1]) for n in range(0, len(self.CHANGE_OPND)): ops = ida_shims.get_operands(insn) if ops[n].type in [ idaapi.o_reg, idaapi.o_displ, idaapi.o_phrase ]: try: regname = self.arch.registers[ops[n].reg] index = self.arch.argv.index(regname) except ValueError: continue if regname not in argv: notargv.update(self.arch.argv[index:]) if argv.union(notargv) == set(self.arch.argv): break # TODO: Use idc.NextHead(ea) instead... ea += self.arch.insn_size return len(argv)
def _is_bad_instruction(self, ea, bad_instructions=['j', 'b'], no_clobber=[]): bad = False mnem = ida_shims.print_insn_mnem(ea) if mnem and mnem[0] in bad_instructions: bad = True else: insn = ida_shims.decode_insn(ea) feature = ida_shims.get_canon_feature(insn) for register in no_clobber: if (feature & idaapi.CF_CHG1) == idaapi.CF_CHG1: if ida_shims.print_operand(ea, 0) == register: bad = True return bad
def _fix_data_offsets(self): ea = 0 count = 0 print("Fixing unresolved offset xrefs...", end=' ') while ea != idaapi.BADADDR: (ea, n) = idaapi.find_notype(ea, idaapi.SEARCH_DOWN) if ida_shims.can_decode(ea): insn = ida_shims.decode_insn(ea) ops = ida_shims.get_operands(insn) for i in range(0, len(ops)): op = ops[i] if op.type == idaapi.o_imm and idaapi.getseg(op.value): idaapi.add_dref(ea, op.value, (idaapi.dr_O | idaapi.XREF_USER)) count += 1 print("created %d new data xrefs" % count)
def _does_instruction_match(self, ea, instruction, regex=False): i = 0 op_cnt = 0 op_ok_cnt = 0 match = False insn = ida_shims.decode_insn(ea) mnem = ida_shims.print_insn_mnem(ea) if (not instruction.mnem) or (instruction.mnem == mnem) or \ (regex and re.match(instruction.mnem, mnem)): for operand in instruction.operands: if operand: op_cnt += 1 op = ida_shims.print_operand(ea, i) if regex: if re.match(operand, op): op_ok_cnt += 1 elif operand == op: op_ok_cnt += 1 i += 1 if op_cnt == op_ok_cnt: match = True return match
def block(self, block): ''' Returns a tuple: ([formal, block, signatures], [fuzzy, block, signatures], set([unique, immediate, values]), [called, function, names]) ''' formal = [] fuzzy = [] functions = [] immediates = [] ea = ida_shims.start_ea(block) while ea < ida_shims.end_ea(block): insn = ida_shims.decode_insn(ea) # Get a list of all data/code refs from the current instruction drefs = [x for x in idautils.DataRefsFrom(ea)] crefs = [x for x in idautils.CodeRefsFrom(ea, False)] # Add all instruction mnemonics to the formal block hash formal.append(ida_shims.print_insn_mnem(ea)) # If this is a call instruction, be sure to note the name of the # function being called. This is used to apply call-based # signatures to functions. # # For fuzzy signatures, we can't use the actual name or EA of the # function, but rather just want to note that a function call was # made. # # Formal signatures already have the call instruction mnemonic, # which is more specific than just saying that a call was made. if idaapi.is_call_insn(ea): for cref in crefs: func_name = ida_shims.get_name(cref) if func_name: functions.append(func_name) fuzzy.append("funcref") # If there are data references from the instruction, check to see # if any of them are strings. These are looked up in the # pre-generated strings dictionary. # # String values are easily identifiable, and are used as part of # both the fuzzy and the formal signatures. # # It is more difficult to determine if non-string values are # constants or not; for both fuzzy and formal signatures, just use # "data" to indicate that some data was referenced. elif drefs: for dref in drefs: if self.strings.has_key(dref): formal.append(self.strings[dref].value) fuzzy.append(self.strings[dref].value) else: formal.append("dataref") fuzzy.append("dataref") # If there are no data or code references from the instruction, use # every operand as part of the formal signature. # # Fuzzy signatures are only concerned with interesting immediate # values, that is, values that are greater than 65,535, are not # memory addresses, and are not displayed as negative values. elif not drefs and not crefs: ops = ida_shims.get_operands(insn) for n in range(0, len(ops)): opnd_text = ida_shims.print_operand(ea, n) formal.append(opnd_text) if ops[n].type == idaapi.o_imm and \ not opnd_text.startswith('-'): if ops[n].value >= 0xFFFF: if ida_shims.get_full_flags(ops[n].value) == 0: fuzzy.append(str(ops[n].value)) immediates.append(ops[n].value) ea = ida_shims.next_head(ea) return (self.sighash(''.join(formal)), self.sighash(''.join(fuzzy)), immediates, functions)
def _profile_function(self): current_ea = ida_shims.get_screen_ea() current_function = ida_shims.get_func_name(current_ea) current_function_ea = ida_shims.get_name_ea_simple(current_function) if current_function: self.function = current_function ea = ida_shims.get_func_attr(current_function_ea, idc.FUNCATTR_START) end_ea = ida_shims.get_func_attr(current_function_ea, idc.FUNCATTR_END) self.highlighted = ida_shims.get_highlighted_identifier() while ea < end_ea and ea != idc.BADADDR and self.highlighted: i = 0 match = False optype = self.READ insn = ida_shims.decode_insn(ea) mnem = ida_shims.print_insn_mnem(ea) if self.highlighted in mnem: match = True elif idaapi.is_call_insn(ea): for xref in idautils.XrefsFrom(ea): if xref.type != 21: name = ida_shims.get_name(xref.to) if name and self.highlighted in name: match = True break else: while True: opnd = ida_shims.print_operand(ea, i) if opnd: if self.highlighted in opnd: canon_feature = ida_shims.get_canon_feature(insn) match = True if canon_feature & self.OPND_WRITE_FLAGS[i]: optype = self.WRITE i += 1 else: break if not match: comment = idc.GetCommentEx(ea, 0) if comment and self.highlighted in comment: match = True else: comment = idc.GetCommentEx(ea, 1) if comment and self.highlighted in comment: match = True else: comment = None if match: if ea > current_ea: direction = self.DOWN elif ea < current_ea: direction = self.UP else: direction = self.THIS self.xrefs[ea] = { 'offset': ida_shims.get_func_off_str(ea), 'mnem': mnem, 'type': optype, 'direction': direction, 'text': idc.GetDisasm(ea), } ea = ida_shims.next_head(ea)
def _find_controllable_jumps(self, start_ea, end_ea): controllable_jumps = [] t9_controls = [ MIPSInstruction("move", "\$t9"), MIPSInstruction("addiu", "\$t9", "^\$"), ] t9_jumps = [ MIPSInstruction("jr", "\$t9"), ] ra_controls = [ MIPSInstruction("lw", "\$ra"), ] ra_jumps = [ # TODO: Search for jumps to registers other than $ra. MIPSInstruction("jr", "\$ra"), ] t9_musnt_clobber = ["$t9"] ra_musnt_clobber = ["$ra"] if idaapi.IDA_SDK_VERSION >= 740: t9_jumps.append(MIPSInstruction("jalr", "\$ra")) else: t9_jumps.append(MIPSInstruction("jalr", "\$t9")) for possible_control_instruction in t9_controls + ra_controls: ea = start_ea if possible_control_instruction in t9_controls: jumps = t9_jumps musnt_clobber = t9_musnt_clobber else: jumps = ra_jumps musnt_clobber = ra_musnt_clobber while ea <= end_ea: ea = self._find_next_instruction_ea( ea, possible_control_instruction, end_ea, regex=True) if ea != idc.BADADDR: insn = ida_shims.decode_insn(ea) control_instruction = self._get_instruction(ea) control_register = control_instruction.operands[1] if control_register: for jump in jumps: jump_ea = self._find_next_instruction_ea( ea + insn.size, jump, end_ea, no_baddies=True, regex=True, dont_overwrite=musnt_clobber) if jump_ea != idc.BADADDR: jump_instruction = self._get_instruction( jump_ea) controllable_jumps.append( ROPGadget(control_instruction, jump_instruction, description="Controllable Jump", base=self.base)) ea = jump_ea ea += insn.size return controllable_jumps