def get_switch_info(ea): '''ea_t -> switch_info_ex_t''' sw = idaapi.get_switch_info_ex(ea) if sw is None: raise NoSwitchError('ea at %s has no switch info' % atoa(ea)) else: return sw
def handleJmpTable(I, inst, new_eas): si = idaapi.get_switch_info_ex(inst) jsize = si.get_jtable_element_size() jstart = si.jumps # only handle size 4 cases if jsize != 4: raise Exception("Jump table size not 4!") return DEBUG("\tJMPTable Start: {0:x}\n".format(jstart)) seg_start = idc.SegStart(jstart) if seg_start != idc.BADADDR: I.jump_table.offset_from_data = jstart - seg_start DEBUG("\tJMPTable offset from data: {:x}\n".format(I.jump_table.offset_from_data)) I.jump_table.zero_offset = 0 i = 0 entries = si.get_jtable_size() for i in xrange(entries): je = readDword(jstart+i*jsize) I.jump_table.table_entries.append(je) if je not in RECOVERED_EAS and isStartOfFunction(je): new_eas.add(je) DEBUG("\t\tAdding JMPTable {0}: {1:x}\n".format(i, je))
def handleJmpTable(I, inst, new_eas): si = idaapi.get_switch_info_ex(inst) jsize = si.get_jtable_element_size() jstart = si.jumps # accept 32-bit jump tables in 64-bit, for now valid_sizes = [4, getBitness()/8] readers = { 4: readDword, 8: readQword } if jsize not in valid_sizes: raise Exception("Jump table is not a valid size: {}".format(jsize)) return DEBUG("\tJMPTable Start: {0:x}\n".format(jstart)) seg_start = idc.SegStart(jstart) if seg_start != idc.BADADDR: I.jump_table.offset_from_data = jstart - seg_start DEBUG("\tJMPTable offset from data: {:x}\n".format(I.jump_table.offset_from_data)) I.jump_table.zero_offset = 0 i = 0 entries = si.get_jtable_size() for i in xrange(entries): je = readers[jsize](jstart+i*jsize) I.jump_table.table_entries.append(je) if je not in RECOVERED_EAS and isStartOfFunction(je): new_eas.add(je) DEBUG("\t\tAdding JMPTable {0}: {1:x}\n".format(i, je))
def _calc_cases(self): si = idaapi.get_switch_info_ex(self._ea) results = idaapi.calc_switch_cases(self._ea, si) if not results: raise SarkNotASwitch("Seems like 0x{:08X} is not a switch jump instruction.".format(self._ea)) return results
def InsSwitchInfo(i): switch_info = idaapi.get_switch_info_ex(i) if switch_info is None: return False, None, None cases = idaapi.calc_switch_cases(i, switch_info) return bool(cases), switch_info, cases
def handleJmpTable(I, inst, new_eas): si = idaapi.get_switch_info_ex(inst) jsize = si.get_jtable_element_size() jstart = si.jumps # accept 32-bit jump tables in 64-bit, for now valid_sizes = [4, getBitness() / 8] readers = {4: readDword, 8: readQword} if jsize not in valid_sizes: raise Exception("Jump table is not a valid size: {}".format(jsize)) return DEBUG("\tJMPTable Start: {0:x}\n".format(jstart)) seg_start = idc.SegStart(jstart) if seg_start != idc.BADADDR: I.jump_table.offset_from_data = jstart - seg_start DEBUG("\tJMPTable offset from data: {:x}\n".format( I.jump_table.offset_from_data)) I.jump_table.zero_offset = 0 i = 0 entries = si.get_jtable_size() for i in xrange(entries): je = readers[jsize](jstart + i * jsize) I.jump_table.table_entries.append(je) if je not in RECOVERED_EAS and isStartOfFunction(je): new_eas.add(je) DEBUG("\t\tAdding JMPTable {0}: {1:x}\n".format(i, je))
def switches(fn): fn = top(fn) for ea in iterate(fn): x = idaapi.get_switch_info_ex(ea) if x: yield switch_t(x) continue return
def find_all_switch_jumps(self): self._switch_dict = defaultdict(list) next_switch = idc.FindBinary(idc.MinEA(), idc.SEARCH_DOWN|idc.SEARCH_NEXT, "ff 24") while next_switch != idc.BADADDR: sw = idaapi.get_switch_info_ex(next_switch) if idc.GetMnem(next_switch).startswith("jmp") and sw: ic = self.get_jlocs(sw) self._switch_dict[idaapi.get_func_name(next_switch)].append((next_switch, sw.ncases, ic)) next_switch = idc.FindBinary(idc.NextHead(next_switch), idc.SEARCH_DOWN|idc.SEARCH_NEXT, "ff 24")
def _calc_cases(self): si = idaapi.get_switch_info_ex(self._ea) results = idaapi.calc_switch_cases(self._ea, si) if not results: raise exceptions.SarkNotASwitch( "Seems like 0x{:08X} is not a switch jump instruction.".format( self._ea)) return results
def isJmpTable(ea): insn_t = idautils.DecodeInstruction(ea) is_jmp = insn_t.itype in [idaapi.NN_jmp, idaapi.NN_jmpfi, idaapi.NN_jmpni] if not is_jmp: return False if idaapi.get_switch_info_ex(ea): return True return False
def find_switch(func_ea): # get all chunks that belong to a function, because apparently they're not contiguous or some shit for (start_ea, end_ea) in idautils.Chunks(func_ea): for head in idautils.Heads(start_ea, end_ea): switch = idaapi.get_switch_info_ex(head) if switch != None: log('found switch @ %x, cases: %d' % (head, switch.get_jtable_size())) return (head, switch) return (None, None)
def analyse_indirect_jump(block, jump_inst, blocks): """Analyse an indirect jump and try to determine its targets.""" log.info("Analysing indirect jump at {:08x}".format(jump_inst.ea)) si = idaapi.get_switch_info_ex(jump_inst.ea) target_eas = set() if si: num_targets = si.get_jtable_size() log.info("IDA identified a jump table at {:08x} with {} targets".format( jump_inst.ea, num_targets)) target_eas.update(idautils.CodeRefsFrom(jump_inst.ea, True)) for target_ea in target_eas: block = program.get_basic_block(target_ea) block.address_is_taken = True
def analyse_indirect_jump(block, jump_inst, blocks): """Analyse an indirect jump and try to determine its targets.""" log.info("Analysing indirect jump at {:08x}".format(jump_inst.ea)) si = idaapi.get_switch_info_ex(jump_inst.ea) target_eas = set() if si: num_targets = si.get_jtable_size() log.info( "IDA identified a jump table at {:08x} with {} targets".format( jump_inst.ea, num_targets)) target_eas.update(idautils.CodeRefsFrom(jump_inst.ea, True)) for target_ea in target_eas: block = program.get_basic_block(target_ea) block.address_is_taken = True
def handleJmpTable(I, F, inst, new_eas): si = idaapi.get_switch_info_ex(inst) jsize = si.get_jtable_element_size() jstart = si.jumps # try to fix a problem with IDA, which # doesn't recognise completely switch if jstart == 0xffffffff: jstart = list(DataRefsFrom(inst))[0] # only handle size 4 cases if jsize != 4: raise Exception("Jump table size not 4!") return DEBUG("\tJMPTable Start: {0:x}\n".format(jstart)) if I is not None: I.jump_table.zero_offset = 0 jmpt = JmpTable(addr=jstart, function=F) # Return empty object - jump tables disabled return jmpt i = 0 data = idaapi.get_many_bytes(jstart+i*jsize, 4) #TODO: fix this if data is None: return jmpt je = struct.unpack('<I', data)[0] while i < si.ncases: if I is not None: I.jump_table.table_entries.append(je) if je not in RECOVERED_EAS: new_eas.add(je) DEBUG("\t\tAdding JMPTable {0}: {1:x}\n".format( i, je)) else: new_eas.add(je) jmpt.add_entry(je) i += 1 je = struct.unpack('<I', idaapi.get_many_bytes(jstart+i*jsize, 4))[0] return jmpt
def preprocessBinary(): # loop through every instruction and # keep a list of jump tables references in the # data section. These are used so we can # avoid generating unwanted function entry points for seg_ea in idautils.Segments(): for head in idautils.Heads(seg_ea, idc.SegEnd(seg_ea)): if idc.isCode(idc.GetFlags(head)): si = idaapi.get_switch_info_ex(head) if si is not None and isUnconditionalJump(head): DEBUG("Found a jmp based switch at: {0:x}\n".format(head)) esize = si.get_jtable_element_size() base = si.jumps count = si.get_jtable_size() for i in xrange(count): fulladdr = base+i*esize DEBUG("Address accessed via JMP: {:x}\n".format(fulladdr)) ACCESSED_VIA_JMP.add(fulladdr)
def find_unanalyzed_jump_table(start_ea, end_ea): """ Return address of first unanalyzed jump table between start_ea and end_ea """ ea = start_ea while ea <= end_ea: # Find jmp, where first operand == NextHead(jmp_ea). If no # switch_info_ex_t object at jmp_ea, return jmp_ea is_jmp_insn = (idc.GetMnem(ea) == "jmp") not_switch_table = (idaapi.get_switch_info_ex(ea) == None) next_ea = idc.NextHead(ea, end_ea) jmp_target_next_insn = (next_ea == idc.GetOperandValue(ea, 0)) if is_jmp_insn and jmp_target_next_insn and not_switch_table: return ea ea = next_ea return idc.BADADDR
def handleJmpTable(I, inst, new_eas): si = idaapi.get_switch_info_ex(inst) jsize = si.get_jtable_element_size() jstart = si.jumps # only handle size 4 cases if jsize != 4: raise Exception("Jump table size not 4!") return DEBUG("\tJMPTable Start: {0:x}\n".format(jstart)) I.jump_table.zero_offset = 0 i = 0 entries = si.ncases for i in xrange(entries): je = readDword(jstart+i*jsize) I.jump_table.table_entries.append(je) if je not in RECOVERED_EAS: new_eas.add(je) DEBUG("\t\tAdding JMPTable {0}: {1:x}\n".format(i, je))
def handleJmpTable(I, inst, new_eas): si = idaapi.get_switch_info_ex(inst) jsize = si.get_jtable_element_size() jstart = si.jumps # only handle size 4 cases if jsize != 4: raise Exception("Jump table size not 4!") return DEBUG("\tJMPTable Start: {0:x}\n".format(jstart)) I.jump_table.zero_offset = 0 i = 0 je = idc.GetFixupTgtOff(jstart+i*jsize) while je != -1: I.jump_table.table_entries.append(je) if je not in RECOVERED_EAS: new_eas.add(je) DEBUG("\t\tAdding JMPTable {0}: {1:x}\n".format( i, je)) i += 1 je = idc.GetFixupTgtOff(jstart+i*jsize)
def handleJmpTable(I, inst, new_eas): si = idaapi.get_switch_info_ex(inst) jsize = si.get_jtable_element_size() jstart = si.jumps # only handle size 4 cases if jsize != 4: raise Exception("Jump table size not 4!") return DEBUG("\tJMPTable Start: {0:x}\n".format(jstart)) I.jump_table.zero_offset = 0 i = 0 je = idc.GetFixupTgtOff(jstart + i * jsize) while je != -1: I.jump_table.table_entries.append(je) if je not in RECOVERED_EAS: new_eas.add(je) DEBUG("\t\tAdding JMPTable {0}: {1:x}\n".format(i, je)) i += 1 je = idc.GetFixupTgtOff(jstart + i * jsize)
def findFuncsByInitializeMethodMetadata(): global GetMethodInfoFromIndex global GetStringLiteralFromIndex finituseage = getNamedFunc("InitializeMethodMetadata") def findFirstCall(startAddr, endAddr): while 1: line = sark.Line(startAddr) for xref in line.xrefs_from: if repr(xref.type ) != "Ordinary_Flow" and sark.Function.is_function( xref.to): return xref.to startAddr += len(line.bytes) if target >= endAddr: break return None for line in finituseage.lines: switch_info = idaapi.get_switch_info_ex(line.ea) if switch_info: case3 = idc.Dword(switch_info.jumps + 2 * 4) case5 = idc.Dword(switch_info.jumps + 4 * 4) case6 = idc.Dword(switch_info.jumps + 5 * 4) if case3 == case6: target = switch_info.jumps + case3 taddr = findFirstCall(target, finituseage.endEA) if taddr: # print "find GetMethodInfoFromIndex at", hex(int(taddr)) idc.set_name(taddr, "GetMethodInfoFromIndex", SN_NOWARN | SN_NOCHECK) GetMethodInfoFromIndex = int(taddr) target = switch_info.jumps + case5 taddr = findFirstCall(target, finituseage.endEA) if taddr: # print "find GetStringLiteralFromIndex at", hex(int(taddr)) idc.set_name(taddr, "GetStringLiteralFromIndex", SN_NOWARN | SN_NOCHECK) GetStringLiteralFromIndex = int(taddr) break
def get_static_successors(inst): """Returns the statically known successors of an instruction.""" branch_flows = tuple(idautils.CodeRefsFrom(inst.ea, False)) # Direct function call. The successor will be the fall-through instruction # unless the target of the function call looks like a `noreturn` function. if inst.is_direct_function_call(): called_ea = get_direct_branch_target(inst.ea) flags = idc.GetFunctionFlags(called_ea) if 0 < flags and (flags & idaapi.FUNC_NORET): log.debug("Call to noreturn function {:08x} at {:08x}".format( called_ea, inst.ea)) else: yield inst.next_ea # Not recognised as a `noreturn` function. if inst.is_call(): # Indirect function call, system call. yield inst.next_ea elif inst.is_conditional_branch(): yield inst.next_ea yield get_direct_branch_target(inst.ea) elif inst.is_direct_jump(): yield get_direct_branch_target(inst.ea) elif inst.is_indirect_jump(): si = idaapi.get_switch_info_ex(inst.ea) if si: for case_ea in idautils.CodeRefsFrom(inst.ea, True): yield case_ea elif inst.is_fall_through(): yield inst.next_ea else: log.debug("No static successors of {:08x}".format(inst.ea))
def __getinsn(cls, ea): res = idaapi.get_switch_info_ex(ea) if res is None: raise TypeError, "Unable to instantiate a switch_info_ex_t at branch instruction : %x" % ea return res
def name(ea=None, *args, **kwds): """name(ea), name(ea, string) First syntax returns the name at the given address. Second syntax changes the name at the given address. """ if len(args) > 1: raise TypeError, "{:s}() takes exactly {!r} arguments ({:d} given)".format( 'name', (1, 2), len(args) + 1 + len(kwds)) if kwds and tuple(kwds.keys()) != ('string', ): raise TypeError, "{:s}() got an unexpected keyword argument '{:s}'".format( 'name', filter(lambda n: n != 'string', kwds.keys())[0]) ea = ui.current.address() if ea is None else ea if len(args) == 1 or kwds.has_key('string'): string = kwds.get('string', args[0]) assert idaapi.SN_NOCHECK == 0, '%s.name : idaapi.SN_NOCHECK != 0' % __name__ SN_NOLIST = idaapi.SN_NOLIST SN_LOCAL = idaapi.SN_LOCAL SN_NON_PUBLIC = idaapi.SN_NON_PUBLIC if idaapi.has_any_name(idaapi.getFlags(ea)): pass flags = idaapi.SN_NON_AUTO flags |= 0 if idaapi.is_in_nlist(ea) else idaapi.SN_NOLIST flags |= idaapi.SN_WEAK if idaapi.is_weak_name( ea) else idaapi.SN_NON_WEAK flags |= idaapi.SN_PUBLIC if idaapi.is_public_name( ea) else idaapi.SN_NON_PUBLIC try: function.top(ea) flags |= idaapi.SN_LOCAL except Exception: flags &= ~idaapi.SN_LOCAL try: # check if we're a label of some kind f = idaapi.getFlags(ea) if idaapi.has_dummy_name(f) or idaapi.has_user_name(f): # that is referenced by an array with a correctly sized pointer inside it (r, sidata), = ((r, type.array(r)) for r in xref.data_up(ea)) if config.bits() == sidata.itemsize * 8 and ea in sidata: # which we check to see if it's a switch_info_t si, = (idaapi.get_switch_info_ex(r) for r in xref.data_up(r)) if si is not None: # because it's name has it's local flag cleared flags ^= idaapi.SN_LOCAL except: pass res, ok = name(ea), idaapi.set_name(ea, string or "", flags) tag(ea, 'name', string) assert ok, '%s.name : unable to call idaapi.set_name(%x, %r, %x)' % ( __name__, ea, string, flags) return res try: return tag(ea, 'name') except KeyError: pass return None
import idautils import idaapi import idc switches = [] for f in idautils.Functions(): func = idaapi.get_func(f) for h in idautils.Heads(func.startEA, func.endEA): res = idaapi.get_switch_info_ex(h) if res != None: # number of cases num_cases = res.get_jtable_size() else: continue print '0x%08x: switch (%d cases)' % (h, num_cases) # get cases xrefs = idautils.CodeRefsFrom(h, 1) interesting_calls = [] switches.append((h, num_cases, interesting_calls)) # http://dvlabs.tippingpoint.com/blog/2011/05/11/mindshare-extending-ida-custviews class SwitchViewer(idaapi.simplecustviewer_t): def __init__(self, data):
def observeSwitchTableFeatures(self, scs): """Observe the features of IDA-recognized switch tables, and try to detect patterns. Args: scs (list): list of (sark) code segments Notes ----- 1. Trying to observe an alignment pattern for the switch tables. 2. Trying to observe a code pattern for the instruction before each switch table. Return Value: True iff found all of the desired features (patterns) """ table_alignment_pattern = AlignmentPattern() observer = CodePattern() for sc in scs: # scan for known switch cases, and only from our desired record size for line in filter(lambda x: x.is_code, sc.lines): try: sw = idaapi.get_switch_info_ex(line.startEA) if sw is None: continue if sw.get_jtable_element_size() != self._record_size: continue # The table should be near our code (otherwise we don't care about it) if abs(line.startEA - sw.jumps) > 0x100: continue except Exception: continue # IDA recognized the switch table exactly at the last code instruction before it observer.add(line) # Going to use the easy case # 1. Find the table alignment (4) # 2. Find the command + common args for the jump line (MOV PC, ) # 3. Assume the table is right after this command, padded to alignment # 4. Count the cases as long as they point to code near us # 5. Don't define it as a switch table using IDA's structures (too complex) self._analyzer.logger.debug("Located a switch table at: 0x%x", line.startEA) self._analyzer.logger.debug("\tStart EA: 0x%x", sw.startea) self._analyzer.logger.debug("\tJump Table: 0x%x", sw.jumps) self._analyzer.logger.debug("\t%s", str(line)) # table alignment table_alignment_pattern.add(sw.jumps) # check if found any if table_alignment_pattern.size() < 2: self._analyzer.logger.error( "Couldn't find enough switch tables in this code section...") return False # print all of the statistics self._analyzer.logger.info("Switch Table Results:") self._table_alignment = table_alignment_pattern.decide() self._analyzer.logger.info("Table alignment is: %d", self._table_alignment) if not observer.decide(): self._analyzer.logger.error( "Failed to find any code pattern for the switch tables") return False else: self._analyzer.logger.info("Switch jump code instruction is: %s", observer) self._code_pattern = observer return True
for func in idautils.Functions(): # if 'PyEval_EvalFrameEx' == idc.GetFunctionName(func): if '_PyEval_EvalFrameDefault' == idc.GetFunctionName(func): print('[+] Found target function!') myfunc = func break def is_user_name(ea): f = idc.GetFlags(ea) return idc.hasUserName(f) for (startea, endea) in Chunks(myfunc): for head in Heads(startea, endea): switch_info = idaapi.get_switch_info_ex(head) if switch_info != None: num_cases = switch_info.get_jtable_size() # print(num_cases) # print 'good jump table found' results = idaapi.calc_switch_cases(head, switch_info) for idx in xrange(results.cases.size()): cur_case = results.cases[idx] ret = is_user_name(results.targets[idx]) if ret: name = idc.NameEx(BADADDR, results.targets[idx]) if "TARGET_" in name or "PRED_" in name: for cidx in xrange(len(cur_case)): number = int(cur_case[cidx]) name = name.replace("TARGET_", "").replace("PRED_", "") if name not in jump_table:
def switches(key=None): for ea in iterate(key): res = idaapi.get_switch_info_ex(ea) if res: yield switch_t(res) return
import idautils import idaapi import idc switches = [] for f in idautils.Functions(): func = idaapi.get_func(f) for h in idautils.Heads(func.startEA, func.endEA): opcodes = idc.Dword(h) & 0xFFFF if opcodes == 0x24ff: # number of cases res = idaapi.get_switch_info_ex(h) if res != None: num_cases = res.get_jtable_size() else: continue print '0x%08x: switch (%d cases)' % (h, num_cases) # get cases xrefs = idautils.CodeRefsFrom(h, 1) interesting_calls = [] switches.append((h, num_cases, interesting_calls))
for func in idautils.Functions(): # if 'PyEval_EvalFrameEx' == idc.GetFunctionName(func): if '_PyEval_EvalFrameDefault' == idc.GetFunctionName(func): print('[+] Found target function!') myfunc = func break def is_user_name(ea): f = idc.GetFlags(ea) return idc.hasUserName(f) for (startea, endea) in Chunks(myfunc): for head in Heads(startea, endea): switch_info = idaapi.get_switch_info_ex(head) if switch_info != None: num_cases = switch_info.get_jtable_size() # print(num_cases) # print 'good jump table found' results = idaapi.calc_switch_cases(head, switch_info) for idx in xrange(results.cases.size()): cur_case = results.cases[idx] ret = is_user_name(results.targets[idx]) if ret: name = idc.NameEx(BADADDR, results.targets[idx]) if "TARGET_" in name or "PRED_" in name: for cidx in xrange(len(cur_case)): number = int(cur_case[cidx]) name = name.replace("TARGET_", "").replace("PRED_", "")