diff options
-rw-r--r-- | idaSystemCalls.py | 328 |
1 files changed, 176 insertions, 152 deletions
diff --git a/idaSystemCalls.py b/idaSystemCalls.py index 9d15d71..3bd9258 100644 --- a/idaSystemCalls.py +++ b/idaSystemCalls.py @@ -1,16 +1,20 @@ """ IDAPython plugin to display all system calls of AMD/Intel 32/64bit Linux userland binaries. - + Work in progress... - + The system call ABI from the following link are supported. http://esec-lab.sogeti.com/post/2011/07/05/Linux-syscall-ABI by n0p """ - + +from miasm2.core.bin_stream_ida import bin_stream_ida +from miasm2.analysis.depgraph import DependencyGraph +from miasm2.analysis.machine import Machine + from idaapi import * - + # Taken from /usr/include/asm/unistd_32.h at Arch Linux i686 3.16.3-1. x86SystemCalls = ["sys_restart_syscall", "sys_exit", @@ -362,7 +366,7 @@ x86SystemCalls = ["sys_restart_syscall", "sys_sched_setattr", "sys_sched_getattr", "sys_renameat2"] - + # Taken from /usr/include/asm/unistd_64.h at Arch Linux x86_64 3.16.1-1. x86_64SystemCalls = ["sys_read", "sys_write", @@ -680,37 +684,78 @@ x86_64SystemCalls = ["sys_read", "sys_finit_module", "sys_sched_setattr", "sys_sched_getattr"] - + systemCallTypes = ["int 80h", "sysenter", "syscall", "gs:[10h]"] - -raxVariations = ["rax", "eax", "ax", "al"] - -regVariations = [ ["rbx", "ebx", "bx", "bl"], - ["rcx", "ecx", "cx", "cl"], - ["rdx", "edx", "dx", "dl"], - ["rsi", "esi", "si", "sil"], - ["rdi", "edi", "di", "dil"], - ["rbp", "ebp", "bp", "bpl"], - ["rsp", "esp", "sp", "spl"], - ["r8", "r8d", "r8w", "r8b"], - ["r9", "r9d", "r9w", "r9b"], - ["r10", "r10d", "r10w", "r10b"], - ["r11", "r11d", "r11w", "r11b"], - ["r12", "r12d", "r12w", "r12b"], - ["r13", "r13d", "r13w", "r13b"], - ["r14", "r14d", "r14w", "r14b"], - ["r15", "r15d", "r15w", "r15b"] ] - -hexChars = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", - "D", "E", "F", "a", "b", "c", "d", "e", "f"] - - + + +# max_size_to_size and guess_machine from miasm/example/ida/utils.py +def max_size_to_size(max_size): + for size in [16, 32, 64]: + if (1 << size) - 1 == max_size: + return size + return None + +def guess_machine(): + "Return an instance of Machine corresponding to the IDA guessed processor" + + processor_name = GetLongPrm(INF_PROCNAME) + max_size = GetLongPrm(INF_START_SP) + size = max_size_to_size(max_size) + + if processor_name == "metapc": + + # HACK: check 32/64 using INF_START_SP + if max_size == 0x80: # TODO XXX check + machine = Machine("x86_16") + elif size == 32: + machine = Machine("x86_32") + elif size == 64: + machine = Machine("x86_64") + else: + raise ValueError('cannot guess 32/64 bit! (%x)' % max_size) + elif processor_name == "ARM": + # TODO ARM/thumb + # hack for thumb: set armt = True in globals :/ + # set bigendiant = True is bigendian + # Thumb, size, endian + info2machine = {(True, 32, True): "armtb", + (True, 32, False): "armtl", + (False, 32, True): "armb", + (False, 32, False): "arml", + (False, 64, True): "aarch64b", + (False, 64, False): "aarch64l", + } + is_armt = globals().get('armt', False) + is_bigendian = globals().get('bigendian', False) + infos = (is_armt, size, is_bigendian) + if not infos in info2machine: + raise NotImplementedError('not fully functional') + machine = Machine(info2machine[infos]) + + from miasm2.analysis.disasm_cb import guess_funcs, guess_multi_cb + from miasm2.analysis.disasm_cb import arm_guess_subcall, arm_guess_jump_table + guess_funcs.append(arm_guess_subcall) + guess_funcs.append(arm_guess_jump_table) + + elif processor_name == "msp430": + machine = Machine("msp430") + elif processor_name == "mipsl": + machine = Machine("mips32l") + elif processor_name == "mipsb": + machine = Machine("mips32b") + else: + print repr(processor_name) + raise NotImplementedError('not fully functional') + + return machine + + class SystemCallView(Choose2): - + def __init__(self, systemCalls): - + self.systemCalls = systemCalls - + Choose2.__init__(self, "System call", [ ["Address", 13], @@ -718,27 +763,27 @@ class SystemCallView(Choose2): ["Number", 10], ["Name", 20], ["Pointer Size", 12] ]) - + self.items = list() - + self.nop_items = list() - + self.icon = 150 - + self.cmd_nop = None - + self.initialized = False - + def __fillView(self): - + self.systemCalls.searchSystemCalls() - + self.items = list() - + if len(self.systemCalls.x86) != 0: for call in self.systemCalls.x86: try: - callNr = int(self.systemCalls.getSystemCallNumber(call[0]), 16) + callNr = int(self.systemCalls.getSystemCallNumber(call[0])[0]) self.items.append(["0x%X" % call[0], systemCallTypes[call[1]], "0x%03X" % callNr, @@ -751,11 +796,11 @@ class SystemCallView(Choose2): "", "", "32bit"]) - + if len(self.systemCalls.x86_64) != 0: for call in self.systemCalls.x86_64: try: - callNr = int(self.systemCalls.getSystemCallNumber(call[0]), 16) + callNr = int(self.systemCalls.getSystemCallNumber(call[0])[0]) self.items.append(["0x%X" % call[0], systemCallTypes[call[1]], "0x%03X" % callNr, @@ -768,147 +813,126 @@ class SystemCallView(Choose2): "", "", "64bit"]) - + def OnClose(self): pass - + def OnCommand(self, n, cmd_id): if cmd_id == self.cmd_nop: start_ea = int(self.items[n][0], 16) end_ea = start_ea+ItemSize(start_ea) - + self.nop_items.append(self.items[n][0]) - + for i in xrange(start_ea, end_ea): PatchByte(i, 0x90) - + def OnGetIcon(self, n): if not len(self.items) > 0: return -1 - + if self.items[n][2] == "": # No system call number found => display red icon. return 59 else: # Display green icon. return 61 - + def OnGetLine(self, n): return self.items[n] - + def OnGetLineAttr(self, n): if len(self.nop_items) > 0: if self.items[n][0] in self.nop_items: return [0xB6B6B4, 0] - + def OnGetSize(self): return len(self.items) - + def OnRefresh(self, n): self.__fillView() - + def OnSelectLine(self, n): idaapi.jumpto(int(self.items[n][0], 16)) - + def show(self): if not self.initialized: self.initialized = True self.__fillView() - + if self.Show() < 0: return False - + if self.cmd_nop == None: self.cmd_nop = self.AddCommand("NOP system call", flags = idaapi.CHOOSER_POPUP_MENU, icon=50) - + return True - - + + class SystemCall(): - + def __init__(self): - + self.x86 = list() self.x86_64 = list() - + self.systemCallView = SystemCallView(self) - - def __formatHexStr(self, hexStr, hexLen): - - if hexStr[-1] == 'h': - hexStr = hexStr[:-1] - - for c in hexStr: - if not c in hexChars: - return "" - - if len(hexStr) <= hexLen: - return '0'*(hexLen-len(hexStr)) + hexStr - - return hexStr - - def __traceRegister(self, addr, targetRegVariation, xRefTo): - """ Try to get the value of targetRegVariation at addr via back tracing. - """ - - try: - funcStart = get_func(addr).startEA - except: - funcStart = 0 - - while 1: - if GetMnem(addr) == "mov": - if GetOpnd(addr, 0) in targetRegVariation: - # A MOV instruction with targetRegVariation as the destination - # register. - for regVariation in regVariations: - if GetOpnd(addr, 1) in regVariation: - # The source of the instruction is another register. - # => Get the value of the destination register. - return self.__traceRegister(addr, regVariation, addr) - - # We got the (possibly hex) value of the target register. - return GetOpnd(addr, 1) - - if GetMnem(addr) == "lea": - if GetOpnd(addr, 0) in targetRegVariation: - # The value of the target register is changed but LEA is - # currently not supported. - break - - for xref in XrefsTo(addr, 0): - # Follow all xrefs to the location and check if the target register - # is set at this path. - ret = self.__traceRegister(xref.frm, targetRegVariation, addr) - - if ret != 0xFFFFFFFF: - return ret - - addr = PrevHead(addr) - - if addr < funcStart: - break - - if addr == xRefTo: - break - if addr == BADADDR: - break - if not isCode(GetFlags(addr)): - break - - return 0xFFFFFFFF - + def getSystemCallNumber(self, addr): """ Get the value of rax/eax at the time of the system call. """ - systemCallNumber = self.__traceRegister(PrevHead(addr), raxVariations, addr) - - if systemCallNumber != 0xFFFFFFFF: - return self.__formatHexStr(systemCallNumber, 3) - - return "" - + + # Init + machine = guess_machine() + mn, dis_engine, ira = machine.mn, machine.dis_engine, machine.ira + + bs = bin_stream_ida() + mdis = dis_engine(bs, dont_dis_nulstart_bloc=True) + ir_arch = ira(mdis.symbol_pool) + + # Populate symbols with ida names + for ad, name in Names(): + if name is None: + continue + mdis.symbol_pool.add_label(name, ad) + + # Get the current function + func = idaapi.get_func(addr) + blocs = mdis.dis_multibloc(func.startEA) + + # Generate IR + for bloc in blocs: + ir_arch.add_bloc(bloc) + + cur_bloc = list(ir_arch.getby_offset(addr))[0] + cur_label = cur_bloc.label + + elements = set([mn.regs.RAX]) + + for line_nb, l in enumerate(cur_bloc.lines): + if l.offset == addr: + break + + # Get dependency graphs + dg = DependencyGraph(ir_arch, follow_call=False) + graphs = dg.get(cur_label, elements, line_nb, + set([ir_arch.symbol_pool.getby_offset(func.startEA)])) + + # Display the result + sol = list() + + while 1: + try: + graph = graphs.next() + except StopIteration: + break + + if not graph.has_loop: + sol.append(graph.emul().values()[0]) + + return sol + def searchSystemCalls(self): """ Looks for 'int 80', 'sysenter', 'syscall' and 'gs:[10h]' system calls. """ @@ -918,21 +942,21 @@ class SystemCall(): for head in Heads(segment, SegEnd(segment)): if isCode(GetFlags(head)): inst = DecodeInstruction(head) - + if ( inst.size == 2 and get_many_bytes(head, inst.size) .startswith("\xCD\x80")): # int 80h. Just on 32bit. if not [head, 0] in self.x86: self.x86.append([head, 0]) - + elif ( inst.size == 2 and get_many_bytes(head, inst.size) .startswith("\x0F\x34")): # sysenter. Just on 32bit. if not [head, 1] in self.x86: self.x86.append([head, 1]) - + elif ( inst.size == 2 and get_many_bytes(head, inst.size) .startswith("\x0F\x05")): @@ -943,45 +967,45 @@ class SystemCall(): else: if not [head, 2] in self.x86: self.x86.append([head, 2]) - + elif ( inst.size == 7 and get_many_bytes(head, inst.size) .startswith("\x65\xFF\x15\x10\x00\x00\x00")): # gs:[10h]. Just on 32bit. if not [head, 3] in self.x86: self.x86.append([head, 3]) - + def showView(self): self.systemCallView.show() - - + + class SystemCallPlugin_t(idaapi.plugin_t): flags = 0 comment = "" help = "" wanted_name = "System Calls" wanted_hotkey = "" - + def init(self): global systemCalls - + if idaapi.ph_get_id() == idaapi.PLFM_386: # Check if already initialized if not 'systemCalls' in globals(): systemCalls = SystemCall() - + return idaapi.PLUGIN_KEEP else: return idaapi.PLUGIN_SKIP - + def run(self, arg): global systemCalls - + systemCalls.showView() - + def term(self): if 'systemCalls' in globals(): del globals()['systemCalls'] - + def PLUGIN_ENTRY(): - return SystemCallPlugin_t()
\ No newline at end of file + return SystemCallPlugin_t() |