def __init__(self): test = Test.Test("IDAItems.GameFile") # dictionary of test data dictionaries self.testData = dict() # test data 1 -- LocalNamed File d1 = dict() d1['ea'] = 0x080E5964 d1['name'] = "t" d1['size'] = 0x080E5A08 - d1['ea'] d1['items'] = [Function.Function(0x080E5964), Data.Data(0x080E5972), Data.Data(0x080E5974), Data.Data(0x080E5978), Function.Function(0x080E5988), Function.Function(0x080E59C6), Function.Function(0x080E59E8), Data.Data(0x080E59F6), Data.Data(0x080E59F8), Data.Data(0x080E59FC)] d1['mode'] = GameFile.Mode.NAME d1['content'] = self.readROM(d1['ea'], d1['size']) d1['xrefsTo'] = ([],[d1['poolFuncEA']+0x02]) d1['xrefsFrom'] = ([], []) # add data to manually computed test data self.testData['pointer'] = d1 test.add(Test.Test("testBasic()", self.testBasic)) self.test = test
def testBasic(self): # type: () -> None """ Tests that InvalidFunctionException is raised if instantiated with invalid EA. And tests that valid functions give valid behavior """ try: f = Function.Function(0x00) Test.fail("InvalidFunctionException not raised") except (Function.FunctionException): pass for td in self.testData: f = Function.Function(td['ea']) Test.assertEquals(f.func_ea, td['ea'], "Function EA mistmatch: 0x%08X" % f.func_ea) # getName() Test.assertEquals(f.getName(), td['name'], "Function name mismatch") # setName() f.setName(td['name'] + "0") Test.assertEquals(f.getName(), td['name'] + "0", "setName() not working") f.setName(td['name']) Test.assertEquals(f.getName(), td['name'], "could not set name back to normal") # getSize() Test.assertEquals(f.getSize(withPool=True), td['size_pool'], "invalid pool size") Test.assertEquals(f.getSize(), td['size_nopool'], "invalid no pool size")
def rngExterns(start_ea, end_ea, toStr=True): """ creates .equs for all external symbols used in the range :param start_ea: start ea of the range, inclusive :param end_ea: end ea of the range, exclusive :return: a string containing all the external symbol .equs, or just the refs if not disp """ ea = start_ea xrefs = [] # if end_ea is mid-way through a function, include all of its refs if Function.isFunction(end_ea) and Function.Function(end_ea).func_ea != end_ea: f = Function.Function(end_ea) end_ea = f.func_ea + f.getSize(withPool=True) # obtain xrefs of every data item, filtering out internal ones and duplicates while ea < end_ea: d = Data.Data(ea) # append crefs ands xrefs for xref in d.getXRefsFrom()[0]: # all code refs shouldn't have a +1 in them. The thumb switch isn't involved with the symbol itself if (idc.isCode(idc.GetFlags(xref)) or idc.isCode(idc.GetFlags(xref-1))) and xref & 1 == 1: xref = xref - 1 if ((xref < start_ea or xref >= end_ea) # filter internal (not external; within range) and xref not in xrefs): # filter duplicate xrefs.append(xref) for xref in d.getXRefsFrom()[1]: # all code refs shouldn't have a +1 in them. The thumb switch isn't involved with the symbol itself if (idc.isCode(idc.GetFlags(xref)) or idc.isCode(idc.GetFlags(xref-1))) and xref & 1 == 1: xref = xref - 1 if ((xref < start_ea or xref >= end_ea) # filter internal (not external; within range) and xref not in xrefs # filter duplicate and d.isPointer(xref) # filter non-pointer symbols, like byte_50 and Data.Data(xref).getName()): # an xref has to have a name to be defined as a symbol xrefs.append(xref) # advance to next item ea = ea + d.getSize() xrefs.sort() if not toStr: return xrefs output = '' # output file formats to include symbols into linking process for xref in xrefs: d = Data.Data(xref) name = d.getName() xref = d.ea if name: output += '.equ %s, 0x%07X\n' % (name, xref) return output
def markStructFromToolkit(func_ea, structOff, structName): f = Function.Function(func_ea) toolkitMovs = FuncAnalyzer.traceRegVar(f.func_ea, f.func_ea + f.getSize(withPool=False), 10, 0) for trace_ea in toolkitMovs: # lock into the register in question insn = Instruction.Insn(trace_ea) if insn.isComputationalInsn(): regWrites = FuncAnalyzer.traceRegWrites(f.func_ea, f.func_ea + f.getSize(withPool=False), insn.ops[0].reg) # TODO: traceRegWrites messes up insn insn = Instruction.Insn(trace_ea) # find the relevent register variable to trace regVarIdx = FuncAnalyzer.getRegWriteIndex(trace_ea, regWrites) insn = Instruction.Insn(trace_ea) # trace all reads to this register variable in particular # print('args', f.func_ea, f.func_ea + f.getSize(withPool=False), # insn.ops[0].reg, regVarIdx) toolkitAccesses = FuncAnalyzer.traceRegVar(f.func_ea, f.func_ea + f.getSize(withPool=False), insn.ops[0].reg, regVarIdx) # those now will represent all registers R10 moved to, and thus, all of those accesses # are accesses to the R10 struct for access in toolkitAccesses: # now mark with Toolkit enum accessInsn = Instruction.Insn(access) if accessInsn.ops[1].type == ida_ua.o_displ: if accessInsn.ops[1].addr == structOff: # print(hex(access), idc.GetDisasm(access)) structAcccesses = FuncAnalyzer.traceRegVar(f.func_ea, f.func_ea + f.getSize(withPool=False), accessInsn.ops[0].reg, regVarIdx+1) for structAccess in structAcccesses: # print(hex(structAccess), idc.GetDisasm(structAccess)) idc.op_enum(structAccess, 1, idc.get_enum(structName), 0) return True
def read(self, accesses_path): # type: (str) -> None """ Reads in the memory accesses defined by the MemAccessScanner protocol :param accesses_path: path to a file containing the accesses :return: """ f = open(accesses_path, "r") s = f.read() items = s.split(' ') self.accesses = [] for i in range(len(items)): if '::' in items[i]: access_ea = int(items[i][items[i].index('::') + 2:], 16) access = items[i + 1][:-1] self.accesses.append((access_ea, access)) self.accesses.sort(key=lambda tup: tup[0]) for access_ea, access in self.accesses: if Function.isFunction(access_ea): func_ea = Function.Function(access_ea).func_ea if func_ea not in self.funcs: self.funcs.append(func_ea) else: data_ea = Data.Data(access_ea).ea if data_ea not in self.data: self.data.append(data_ea) self.funcs.sort() self.data.sort() f.close()
def decomp(self, decompPath, gameFiles=None): if not gameFiles: gameFiles = self.gameFiles for file in sorted(gameFiles.keys(), key=gameFiles.__getitem__): if file.endswith('.s'): filename = file[:file.rindex('.')] decomp = '' # decompile all functions within file print("> Decompiling %s... " % (self._getBaseFilename(file)[:-2] + '.c')) ea = gameFiles[file][0] while ea < gameFiles[file][1]: if Function.isFunction(ea): func = Function.Function(ea) # include address for ease of search in case of label name changes decomp += '// 0x%07x\n' % func.func_ea # try to decompile, or put the asm try: decomp += str(idaapi.decompile(func.func_ea)) + '\n\n' except ida_hexrays.DecompilationFailure as e: print('\tFailed to decompile %07X <%s>' % (func.func_ea, func.getName())) decomp += 'int %s() { // could not decompile\n' % func.getName() decomp += '\tasm("%s"\n\t);\n}\n\n' % func.getFormattedDisasm().replace('\n', '\\\n\t').rstrip() ea = func.func_ea + func.getSize(withPool=True) else: ea += idaapi.get_item_size(ea) # write decomp to file.c if decomp: decomp = decomp.replace(' ', ' ') decomp = decomp.replace('\t', ' ') print(self.projPath, decompPath) cFile = open(self.projPath[0] + decompPath + self._getBaseFilename(file)[:-2] + '.c', 'w') cFile.write(decomp) cFile.close() print("Decompilation complete!")
def rng(start_ea, end_ea, debug=False): # type: (int, int) -> str """ disassembles all data elements within a range if a function is detected within the range, the function itself is disassembled as a whole item. (meaning it could surpass end_ea, but it would be the last item) :param start_ea: the start ea of the range :param end_ea: the end ea, not included :return: the disassembly of the range, in optimal format """ ea = start_ea disasm = '' # disassemble the range ea = start_ea while ea < end_ea: if Function.isFunction(ea): f = Function.Function(ea) if debug: print("%07X: disass function %s @ %07X" % (ea, f.getName(), f.func_ea)) disasm += f.getFormattedDisasm(start_ea, end_ea) + "\n\n" ea = ea + f.getSize(withPool=True) else: d = Data.Data(ea) if debug: print("%07X: disass data %s @ %07X" % (ea, d.getName(), d.ea)) disasm += d.getFormattedDisasm(start_ea, end_ea) + "\n" ea = ea + d.getSize() # add comment for debugging purposes # disasm += "/*For debugging purposes, connect comment at any range!*/\n" return disasm
def find_synced_units(source_units, addr_space): """ :returns: units which are synced in type, size, and starting address. This only applies to Function or Data. All else is treated as Unknown it returns a list of synced function addresses, synced data addresses, and synced unknown addresses """ synced_functions = [] synced_data = [] synced_unk = [] for i, ea in enumerate(addr_space): unit = source_unit.get_physical_unit(source_units, ea) unit_size = source_unit.compute_unit_size_from_index(addr_space, i) if unit['unit']['id'] is AsmFile.UNIT_IDS.FUNCTION: if Function.isFunction(ea): func = Function.Function(ea) if func.func_ea == ea and func.getSize(withPool=True) == unit_size: synced_functions.append(ea) else: data = Data.Data(ea) if data.ea == ea: if data.getSize() == unit_size: if unit['unit']['id'] == AsmFile.UNIT_IDS.DATA: synced_data.append(ea) else: synced_unk.append(ea) else: # get size until another label is defined till_label_size = _next.name(ea, ui=False, hexOut=False) - ea if till_label_size == unit_size: if unit['unit']['id'] == AsmFile.UNIT_IDS.DATA: synced_data.append(ea) else: synced_unk.append(ea) return synced_functions, synced_data, synced_unk
def sync_identified_units_names(source_units, addr_space): def test_matching_name(unit, ea, verbose=False): if idc.get_name(ea) != unit['name']: err_msg = '0x{:X}: found differing unit name: unit {} != ida {}'.format(ea, unit['name'], idc.get_name(ea)) if verbose: return err_msg return False return True print(os.getcwd()) log_file = open('../a.log.ign', 'w') synced_function_addrs, synced_data_addrs, synced_unk_addrs = cache_find_synced_units(source_units, addr_space, recache=True) for ea in synced_function_addrs: unit = source_unit.get_physical_unit(source_units, ea) func = Function.Function(ea) out = test_matching_name(unit, func.func_ea, verbose=True) if type(out) is str: print(out) log_file.write(out + '\n') for ea in synced_data_addrs: unit = source_unit.get_physical_unit(source_units, ea) data = Data.Data(ea) out = test_matching_name(unit, data.ea, verbose=True) if type(out) is str: print(out) log_file.write(out + '\n') log_file.close()
def find_function(self, func_ea): """ This searches the binary for an occurrance of the hex data of this function. The hex data to be searched for is found in the Analysis ROM. :param func_ea: long: The effective address of the function to search :return: Address of occurrance of function in binary if found, or None. (Error Cases) - If func_ea is not found to be within a function, INVALID_FUNCTION is returned. (See constants) - if func_ea < the ROM's starting addr, RANGE_OUT_OF_ROM is returned. (See constants) """ # All functions to be found have to actually be within the ROM region if func_ea < self.ROM_start_addr: return RANGE_OUT_OF_ROM try: func = Function.Function(func_ea) start_ea, end_ea = func.getBoundaries() funcData = self._get_func_data(func) # Search Binary for the data try: output = self.binData.index(funcData) except ValueError: output = None return output except Function.FunctionException: return INVALID_FUNCTION
def guessFuncSig(func_ea): # type: (int) -> (list[str], list[str]) """ Guesses the signature of the current function based on the input registers it uses (R0-R3) and based on the output registers it may return. (most likely R0, but sometimes multiple registers are returned) It also checks for whether the zero flag have been written to by the function without being used, then it would return the zero flag. :param func_ea: the linear address of the function to analyze :return: the list of types for the input parameters, and for the returns. 'zf' can be included, in the return types list. """ # input register parameters -- r0 through r3. If they are identified as an input, this will store # a string of their type. (States -- None: No param found yet. '': Parameter. But Unknown Type. paramTypes = [None, None, None, None] retType = None zf = False # flags updatedRegs = 0b0000 # if the register is updated, its flag is set # make sure to recognize push/pop like patterns. A register can be saved to be used later. savedRegs = 0b0000 # This flag is cleared whenever R0 is updated. # It is set whenever R0 is used. This indicated whether the return is used or not usedRet = False # the returns of calls are remembered since their type can match with the current function callRets = None func = Function.Function(func_ea) ea = func.func_ea while ea < func.func_ea + func.getSize(): insn = Instruction.Insn(ea) if insn.size != 0: # parse destination and source registers, if any if insn.ops[0].type == idaapi.o_reg: destReg = insn.ops[0].reg # now parse all source registers sourceRegisters = insn.getSourceRegisters() # if a destReg is R0~R3, set its flag raise (NotImplemented()) # update return status to know whether the register is used after being set at the end of the function # traverse function calls if parameters weren't identified yet if insn.ops[0].type in [idaapi.o_far, idaapi.o_near ] and None in paramTypes: callParams, callRets = guessFuncSig(insn.ops[0].addr) # deduce input parameters for this function from input parameters for the called function for i in range(len(callParams)): if not updatedRegs & i: # register is passed in as input to callee! Register it as an input of this function too! raise (NotImplemented()) # handle the push/pop register saving pattern if insn.itype == idaapi.NN_push: raise (NotImplemented()) if insn.itype == idaapi.NN_pop: raise (NotImplemented()) ea += insn.size else: ea += idc.get_item_size(ea)
def findMostUsedFunctions(count, notModified=False, disp=True): # type: (int, bool, bool) -> list[int] """ Returns the functions with the highest count of xrefsTo. if notModified, only those that are in the format *_xxxxxxx are returned. if disp, the output is formatted and printed as well :param count: the number of the most used functions to find :param notModified: only functions with names ending in func_ea, or all functions :param disp: print the output :return: list of function linear addresses to the most used functions """ funcXrefs = [] if count <= 0: count = 1 for i in range(count): funcXrefs.append((0, 0)) for seg_ea in idautils.Segments(): for func_ea in idautils.Functions(seg_ea, idc_bc695.SegEnd(seg_ea)): if notModified: name = Function.Function(func_ea).getName() if not ('_' in name and name[name.rindex('_'):] == ('_%X' % func_ea)): continue xrefs = Function.Function(func_ea).getXRefsTo() numXrefs = len(xrefs[0]) + len(xrefs[1]) # add if more than least in the list and sort if numXrefs > funcXrefs[0][1]: funcXrefs[0] = (func_ea, numXrefs) funcXrefs = sorted(funcXrefs, key=lambda tup: tup[1]) # reverse to display most common first funcXrefs = sorted(funcXrefs, key=lambda tup: tup[1], reverse=True) if disp: for func_ea, xrefCount in funcXrefs: print('%07x <%s::%s>: %d' % (func_ea, mtcomm.ea2gf(func_ea), Function.Function(func_ea).getName(), xrefCount)) output = [] for func_ea, xrefCount in funcXrefs: output.append(func_ea) return output
def fcmp(cmd, *args, **kwargs): """ This is to be executed through the run module :param cmd: Not used. There because python. Thank you! :param args: (binaryPath) binaryPath: (str) the path to the binary being function compared with :param kwargs: (wr=True, q=False, wrPath=PYTOOLS_PATH + 'FE8.txt') wr: (bool) whether also to write the analysis to a file, or only return the value. Default to write. q: (bool) supresses info messages, False if not specified. wrPath: (str) path to file to output the results in. If not specified, analysis ouput is returned :return: Files detected within the module """ binaryPath = args[0] wr = kwargs['wr'] if 'wr' in kwargs else True wrPath = ( 'wrPath' in kwargs and wr) and kwargs['wrPath'] or wr and IDATOOLS_PATH + 'FE8.txt' or None wrFile = open(wrPath, 'w') if wrPath else None suppressed = 'q' in kwargs and kwargs['q'] or False if not suppressed: print("Starting Binary Search Analysis...") searcher = BinarySearcher(binaryPath) # Perform and time Analysis if not suppressed: stopwatch = time.time() matchedFunctions = searcher.scan_for_known_functions() if not suppressed: stopwatch = time.time() - stopwatch if wrFile: wrFile.write("Analysis took %d s\n" % int(stopwatch)) else: print("Analysis took %d s\n" % int(stopwatch)) # Simply output all entries matchedFunctions_filtered = [] for x in matchedFunctions: filters = [ # x["ROM_Addr"] == ROM_seg + x["Bin_Addr"], # Detect only functions identical in content and location Function.Function(x["ROM_Addr"]).getSize() >= 2 * INSTRUCTION_WIDTH, # Filter out empty/weird small funcs 'nullsub' not in x["Name"], # filter out all nullsubs, they don't give a lot of information. 'jumpout' not in x["Name"], # jumpout functions are very small, and just support various usages of a func 'bx_R' not in x["Name"], # Those weird 'functions' are so small and introduce noise to the analysis ] filtered = True for f in filters: filtered = filtered and f if filtered: if wrFile: wrFile.write(str(x["Name"]) + ": " + hex(x["Bin_Addr"]) + '\n') matchedFunctions_filtered.append(x) if wrFile: wrFile.close() if not suppressed: print("Binary Search Analysis Complete!")
def markStructOffsets(): ea = here() while ea < 0x8800000: if Function.isFunction(ea): f = Function.Function(ea) print(f.getName()) markToolkit(f.func_ea) markStructFromToolkit(f.func_ea, 0x3C, 'oGameState') ea += f.getSize(withPool=True) else: ea += Data.Data(ea).getSize()
def testFunctionDefinition(self): # type: () -> None """ This decompiles the function and tries to get a C-style pointer #define macro It also tests just getting the prototype. This requires decompilation. """ for td in self.testData: f = Function.Function(td['ea']) macro = "#define %s ((void (*) ()) (0x%08X +1))" % (td['name'], td['ea']) Test.assertEquals(f.getFuncPtrCMacro(), macro, "macro mismatch: %s" % f.getFuncPtrCMacro())
def testXRefsFrom(self): # type: () -> None """ Tests valid code/data xrefs to the function """ for td in self.testData: f = Function.Function(td['ea']) # testing xrefs from function Test.assertEquals( f.getXRefsFrom(), td['xrefsFrom'], "XrefsFrom Mismatch. Expected: '%s', Actual: '%s'" % (self.xrefs2str( td['xrefsFrom']), self.xrefs2str(f.getXRefsFrom())))
def testPoolData(self): for td in self.testData: f = Function.Function(td['ea']) i = 0 for data in f.getPoolData(): Test.assertEquals( td['pool'][i].ea, data.ea, "Pool data item %d mismatch. Expected: %08X, Actual: %08X" % (i, td['pool'][i].ea, data.ea)) Test.assertEquals( td['pool'][i].getContent(), data.getContent(), "Pool data item %d mismatch. Expected: %08X, Actual: %08X" % (i, td['pool'][i].getContent(), data.getContent())) i += 1
def formatAccessSources(self): # type: () -> str output = '' if self.funcs: output += 'Functions:\n' for func_ea in self.funcs: output += '\t%07X <%s>\n' % ( func_ea, Function.Function(func_ea).getName()) if self.data: output += 'Data:\n' for data_ea in self.data: output += '\t%07X <%s>\n' % (data_ea, Data.Data(data_ea).getName()) return output
def testComments(self): # type: () -> None """ Makes sure that function comments are viewable and modifiable """ # there's an issue where GUI comments filter out system input comments, but # both exist anyway. Only one is showed in the GUI. for td in self.testData: f = Function.Function(td['ea']) Test.assertEquals(f.getComment(), td['cmt'], "comment mismatch: '%s'" % f.getComment()) f.setComment(td['cmt'] + "0") Test.assertEquals(f.getComment(), td['cmt'] + "0", "setComment() not modifying") f.setComment(td['cmt']) Test.assertEquals(f.getComment(), td['cmt'], "comment didn't return to original")
def getModuleFunctions(self): """ This traverses all segments, and all defined modules to retrieve all functions with that module name. :return: a list of Functions that are in this module, saved in the database. """ output = [] for seg_ea in idautils.Segments(): for func_ea in idautils.Functions(idc_bc695.SegStart(seg_ea), idc_bc695.SegEnd(seg_ea)): func = Function.Function(func_ea) # if the function starts with '<moduleName>'... funcName = func.getName() inModel = len(funcName) >= len( self.name) + 1 and funcName[0:len(self.name) + 1] == self.name + '_' if inModel: output.append(func) return output
def fixThumbPushPopFuncRanges(start_ea, end_ea, verbose=True): """ This is heusterical, it fixes problems that occur in the IDA anlysis. This fix only applies to thumb functions started with PUSH {LR}. Two cases are considered valid: - A function is cut off at a BL. Change its range to the nearest POP {PC} - A function is cut off at a BX, and a CPU mode change error occurs. - A function is cut off at a POP {PC}, but there is no PUSH {LR} before teh occurrance of the next POP{PC} The fixing process involves turning data into code, if needed, and changing to thumb mode until the next POP. :param start_ea: start of the range to look for broken functions in :param end_ea: end of the range to look for functions in :param verbose: prints info messages :return: fix status. False if any problems occur """ ea = start_ea while ea < end_ea: if Function.isFunction(ea): func = Function.Function(ea) if func.isThumb(): # ensure it's a PUSH/POP function firstInsn = Instruction.Insn(func.func_ea) # print(idc.GetDisasm(firstInsn.ea)) if (firstInsn.itype == idaapi.ARM_push and (firstInsn.getPushPopFlags() & (1 << 14)) != 0): # check the last instruction. make sure it's a POP {..., PC}, BL, or BX. lastInsn_ea = func.func_ea + func.getSize(withPool=False) if idc.get_item_size(lastInsn_ea - 4) == 4: lastInsn_ea -= 4 # in case of BL, which is of size 4 else: lastInsn_ea -= 2 lastInsn = Instruction.Insn(lastInsn_ea) # print(idc.GetDisasm(lastInsn.ea)) if ((lastInsn.itype == idaapi.ARM_pop and (lastInsn.getPushPopFlags() & (1 << 15)) != 0) or lastInsn.itype == idaapi.ARM_bl or lastInsn.itype == idaapi.ARM_bx): # print('OK') extendThumbFuncToLastPop(func.func_ea, lastInsn_ea, verbose) ea += func.getSize(withPool=True) else: ea += Data.Data(ea).getSize()
def fix_unsynced_functions(source_units, address_space): synced_functions, synced_data, synced_unk = find_synced_units(source_units, address_space) count = 0 ea_mismatch_count = 0 size_mismatch_count = 0 unidentified_count = 0 for i, ea in enumerate(address_space): if not common.found_in_any(ea, [synced_functions, synced_data, synced_unk]): unit = source_unit.get_physical_unit(source_units, ea) unit_size = source_unit.compute_unit_size_from_index(address_space, i) if unit['unit']['id'] == AsmFile.UNIT_IDS.FUNCTION: # check if it's thumb or arm # print(unit['unit']['content']) if 'arm' in unit['unit']['content']: print('ARM function found!') is_thumb = 0 else: is_thumb = 1 if Function.isFunction(ea): func = Function.Function(ea) if not func.func_ea == ea: report = '{}::{}. {:X} <{}>: IDA func_ea=0x{:X} != ea=0x{:X}'.format(count, ea_mismatch_count, ea, unit['name'], func.func_ea, ea) ops.delete_and_make_function(ea, unit_size, unit['name'], is_thumb) ea_mismatch_count += 1 count += 1 print(report) elif not func.getSize(withPool=True) == unit_size: report = '{}::{}. {:X} <{}>: size mismatch: unit size {} != {}'.format(count, size_mismatch_count, ea, unit['name'], unit_size, func.getSize(withPool=True)) ops.delete_and_make_function(ea, unit_size, unit['name'], is_thumb) size_mismatch_count += 1 count += 1 print(report) else: report = '{}::{}. {:X} <{}>: unidentified function in IDA'.format(count, unidentified_count, ea, unit['name']) ops.delete_and_make_function(ea, unit_size, unit['name'], is_thumb) unidentified_count += 1 count += 1 print(report)
def fnrepl(start_ea, end_ea, oldstr, newstr, log=True): """ replace a string once if detected in the names of all functions within range :param start_ea: start of the range :param end_ea: end of the range :param oldstr: string to replace :param newstr: replacement string """ ea = start_ea while ea < end_ea: if Function.isFunction(ea): f = Function.Function(ea) if oldstr in f.getName(): name = f.getName() f.setName(f.getName().replace(oldstr, newstr)) if log: print("Renamed %s -> %s" % (name, f.getName())) ea += f.getSize(withPool=True) else: ea += Data.Data(ea).getSize()
def fixFunctionRanges(start_ea, end_ea): """ Fixes all functions with improper returns, by finding their returns and changing their ranges For each function, it will ensure that it ends properly until the start of another function, or a data element with data xrefs to it. If it ends improperly, or there exists a matching return that is not part of the function, it's made part of the function This may not behave correctly around dead functions or null_subs. Run tools to Detect and fix those first. :param start_ea: start of the range to fix functions in :param end_ea: end of the range to fix functions in :return: """ # only look 50 instructions ahead, for range change searchLimit = 50 for func_ea in idautils.Functions(start_ea, end_ea): f = Function.Function(func_ea) # absolute end address of the function (if another function is detected or a data item with data xrefs is) stop_ea = f.func_ea + f.getSize() for i in range(stop_ea, stop_ea + searchLimit): if Function.isFunction(i) or Data.Data(i).getXRefsTo()[1]: stop_ea = i break # figure out the first return, and return type of this function. That should be consistent ret_ea = next.ret(f.func_ea, ui=False, hexOut=False) try: retType = InstDecoder.Inst(ret_ea).fields['magic'] # modify the function range to include all returns if Function.isFunction(ret_ea): ret_ea = next.unkret(f.func_ea, ui=False, hexOut=False) # this ret_ea is not within the function, if the return type is different if InstDecoder.Inst(ret_ea).fields['magic'] != retType: continue while f.func_ea < ret_ea < stop_ea: # detected that the function range is invalid, fix range print('ret %07X' % ret_ea) ret_ea = next.unkret(ret_ea, ui=False, hexOut=False) # this ret_ea is not within the function, if the return type is different if InstDecoder.Inst(ret_ea).fields['magic'] != retType: break idc.add_func(func_ea, ret_ea + 2) except ValueError: continue
def removeStackVarUsages(self_ea, end_ea): madeChanges = False for func_ea in idautils.Functions(self_ea, end_ea): changedStackVar = False if Function.hasStackVars(func_ea): stackVars = Function.getStackVars(func_ea) # traverse data items of function and op_bin all ea = func_ea while ea < func_ea + Function.Function(func_ea).getSize( withPool=False): d = Data.Data(ea) for name, off in stackVars: if name != ' s' and name in d.getOrigDisasm(): changedStackVar = True if not madeChanges: madeChanges = True idc.op_hex(d.ea, 1) ea += d.getSize() if changedStackVar: print('%07X: stackvars op -> hex' % func_ea) if madeChanges: print("Removed all stack variable usages!") else: print("No stack variable usages to remove!")