Пример #1
0
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
Пример #2
0
    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!")
Пример #3
0
    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 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 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 there's a function at end_ea, include all of its refs
        if Function.isFunction(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
                    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
            output += '.equ %s, 0x%07X\n' % (name, xref)

        return output
Пример #6
0
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()
Пример #7
0
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
Пример #8
0
def fakered(ea, end_ea=None, ui=True, hexOut=True):
    """
    This finds the next occurrance of a not a red code segment that has no return pattern to it, making it unlikely
    to belong to a function.
    :param ea: ea to start searching from
    :param ui: if True, jump to address automatically
    :param end_ea: the last address of the search range. If not specified, default is used.
    :param hexOut: output hex formatted ea range instead
    :return: range of ea of next fake red code segment
    """
    # TODO: change implementation to be based on return patterns?
    ea = red(ea, end_ea, ui=False, hexOut=False)
    start_ea = ea
    end_red_ea = idaapi.BADADDR
    # flag for when the whole red segment is finished and we can go to the next red code segment
    finishedSegment = False
    # condition for if the segment has already been invalidated before reaching its end
    isFake = True

    if not end_ea: end_ea = end_ea
    while ea < end_ea:
        d = Data.Data(ea)
        inst = InstDecoder.Inst(ea).fields

        # traverse red code, and find the end of the red segment
        if Function.isFunction(ea) and d.isCode() or not d.isCode():
            # update region end to this red code region
            end_red_ea = ea
            # confirm if the return is within range, then this isn't fake code. Find the next red!
            if isFake:  # or start_ea <= unkret(start_ea-instLimit, end_ea, ui=False, hexOut=False) < end_ea:
                break
            # search the next red region
            isFake = True
            start_ea = ea = red(end_red_ea, end_ea, ui=False, hexOut=False)

        # advance through the red code
        else:
            # simple function pattern,s, if it sorta looks like it can be a function, don't count it as fake.
            # this includes return patterns, and push/pop.
            if inst and (inst['magic'] == InstDecoder.INST_MOV_PC_LR
                         or inst['magic'] == InstDecoder.INST_BX
                         and inst['reg'] == 14 or inst['magic']
                         == InstDecoder.INST_PUSHPOP and inst['lr']):
                isFake = False
            ea += d.getSize()
    if ui: idc.jumpto(end_red_ea - 2)
    if hexOut: return '(%07X, %07X)' % (start_ea, end_red_ea)
    return (start_ea, end_red_ea)
Пример #9
0
def deadfunc(ea, end_ea=None, ui=True, hexOut=True):
    """
    This finds the next occurrance of a dead function not recognized as a function (ie, red code or data)
    This can only find functions ranges it can guarantee, ie, only PUSH {..., LR} POP {..., PC} patterns.
    :param ea: ea to start searching from
    :param end_ea: the last address of the search range
    :param ui: if True, jump to address automatically
    :param hexOut: output hex formatted ea range instead
    :return: range of ea of next dead function
    """
    # don't count this item
    ea = Data.Data(ea).ea + Data.Data(ea).getSize()
    foundPush = False
    push_ea = idaapi.BADADDR
    pop_ea = idaapi.BADADDR
    push_regs = None
    if not end_ea: end_ea = end_ea
    while ea < end_ea:
        # the current item must not belong to a function, or have any data xrefs
        if not Function.isFunction(ea) and not Data.Data(ea).getXRefsTo()[1]:
            try:
                inst = InstDecoder.Inst(ea).fields
            except ValueError:
                ea += 2
                continue
            # if PUSH {..., LR}
            if inst and inst['magic'] == InstDecoder.INST_PUSHPOP and not inst[
                    'pop'] and inst['lr']:
                foundPush = True
                push_ea = ea
                push_regs = inst['Rlist']
            # detected a POP {..., PC} after the PUSH {..., LR}, and the registers match
            if (foundPush and inst
                    and inst['magic'] == InstDecoder.INST_PUSHPOP
                    and inst['pop'] and inst['lr']
                    and inst['Rlist'] == push_regs):
                pop_ea = ea
                break
        else:
            foundPush = False
        ea += 2

    if ui: idc.jumpto(push_ea)
    if hexOut: return '(%07X, %07X)' % (push_ea, pop_ea)
    return (push_ea, pop_ea)
Пример #10
0
 def nextred(self, ea, ui=True):
     """
     Looks for code items outside function items. The first detected is returned
     :param ea: ea to start searching from
     :param ui: if True, jump to address automatically
     :return: hex formatted ea of next name
     """
     # don't count this item
     ea = Data.Data(ea).ea + Data.Data(ea).getSize()
     output = idaapi.BADADDR
     while ea < self.end_ea:
         d = Data.Data(ea)
         if d.isCode() and not Function.isFunction(d.ea):
             output = ea
             break
         ea += d.getSize()
     if ui: idaapi.jumpto(ea)
     return '%07X' % output
Пример #11
0
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()
Пример #12
0
def removeRedCode(start_ea, end_ea):
    """
    unconditionally removes all red code within a specified region
    :param start_ea: start of the region
    :param end_ea: end of the region
    :return:
    """
    srchNext = next
    redStart_ea = redEnd_ea = srchNext.red(start_ea, end_ea, ui=False)
    while redEnd_ea < end_ea:
        d = Data.Data(redEnd_ea)
        while d.isCode() and not Function.isFunction(d.ea):
            redEnd_ea += 2
            d = Data.Data(redEnd_ea)
        # change to bytes
        print("%07X: del red code (%07X, %07X)" %
              (redStart_ea, redStart_ea, redEnd_ea))
        idc.del_items(redStart_ea, 0, redEnd_ea - redStart_ea)
        redStart_ea = redEnd_ea = srchNext.red(redEnd_ea, end_ea, ui=False)
Пример #13
0
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)
Пример #14
0
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()
Пример #15
0
def red(ea, end_ea=None, ui=True):
    """
    Looks for code items outside function items. The first detected is returned
    :param ea: ea to start searching from
    :param end_ea: the last address of the search range, or default if None
    :param ui: if True, jump to address automatically
    :return: ea of next name
    """
    # don't count this item
    ea = Data.Data(ea).ea + Data.Data(ea).getSize()
    output = idaapi.BADADDR
    if not end_ea: end_ea = end_ea
    while ea < end_ea:
        d = Data.Data(ea)
        if d.isCode() and not Function.isFunction(d.ea):
            output = ea
            break
        ea += d.getSize()
    if ui: idaapi.jumpto(ea)
    return output
Пример #16
0
def unkret(ea, end_ea=None, ui=True, hexOut=True):
    """
    Thhs finds the next return based on the next.ret function, that is not already defined within a function.
    This counts red code, unknown bytes, and returns hidden within data.
    :param ea: ea to start searching from
    :param end_ea: the last address to look for
    :param ui: if True, jump to address automatically
    :param hexOut: output hex formatted ea instead
    :return: ea of next unknown return
    """
    ea = ret(ea, end_ea, ui=False, hexOut=False)
    output = idaapi.BADADDR
    if not end_ea: end_ea = end_ea
    while ea < end_ea:
        d = Data.Data(ea)
        if not Function.isFunction(d.ea):
            output = ea
            break
        ea = ret(ea, end_ea, ui=False, hexOut=False)
    if ui: idc.jumpto(output)
    if hexOut: return '%07X' % output
    return output