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 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 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 extendThumbFuncToLastPop(func_ea, lastInsn_ea, verbose=True): """ Looks for another POP {..., PC}. Stops at the start of a new function, or at the start of labeled data. Otherwise, it makes sure the code is disassembled, and is thumb, and it extends the range of the function to the found POP {..., PC}. A corner case not accounted by this algorithm, is if the data is in the middle of code, but is jumped over. :param func_ea: addr to function to fix :param lastInsn_ea: address to the last instruction within the function, as registered in the IDB. :return: whether a fix ocurred or not """ ea = lastPop_ea = lastInsn_ea while not idc.Name(ea) or not idc.isData(idc.GetFlags(ea)): if idc.GetReg(ea, 'T') == 0: idc.SetRegEx(ea, 'T', 1, idc.SR_user) # if idc.isData(idc.GetFlags(ea)): # # if not thumb, make thumb # idc.del_items(ea, 0, 2) # idc.MakeCode(ea) if Instruction.isInsn(ea): insn = Instruction.Insn(ea) # update last POP {..., PC} detected if insn.itype == idaapi.ARM_pop and ((insn.getPushPopFlags() & (1 << 15)) != 0): lastPop_ea = ea # stop condition, assuming no PUSH {..., LR} in current function if insn.itype == idaapi.ARM_push and ((insn.getPushPopFlags() & (1 << 14)) != 0): break ea += idaapi.get_item_size(ea) # extend last function to last pop detected if lastPop_ea != lastInsn_ea: if verbose: print('%07X: End -> %07X <%s>' % (func_ea, lastPop_ea, Data.Data(lastPop_ea).getDisasm())) idc.SetFunctionEnd(func_ea, lastPop_ea + 2) return True return False
def traceRegWrites(start_ea, end_ea, reg): """ Takes a list of instructions as it is unsafe to reinitiate Instruction objects Specifies all the times the register has been written :param insts: list of instructions to analyze. :param reg: int. register number to check writes to :return: list of eas of writes, or False if not in code """ writes = [] ea = start_ea while ea < end_ea: insn = Instruction.Insn(ea) # if the reg is written, its writeCount increases if insn.isComputationalInsn(): if insn.ops[0].reg == reg: writes.append(ea) ea += idc.get_item_size(ea) return writes
def traceRegVar(start_ea, end_ea, reg, writeIdx): """ Takes a list of instructions as it is unsafe to reinitiate Instruction objects Runs through the function and traces all accesses to a register with a particular writeIdx. The writeIdx with the register forms the current local variable in that register. :param insts: instructions to analyze. list of IDAItems.Instruction objects :param reg: the register to trace, 0 to 15 :param writeIdx: the counts of writes to this register before being traced. If 0, it will be traced as an input register to the function. If 1, it will have to be written to once before being traced. And so on. :return: list of EAs of register usages/reads or False if not in a valid function """ writeCount = 0 accesses = [] ea = start_ea while ea < end_ea: insn = Instruction.Insn(ea) # if the reg is written, its writeCount increases if insn.isComputationalInsn(): # check read accesses, even if a write occurs to this register, its previous value can be read for i in range(1, ida_ua.UA_MAXOP): if (insn.ops[i].type in [ida_ua.o_reg, ida_ua.o_displ ] # normal reg or ldr/str and writeCount == writeIdx and insn.ops[i].reg == reg): # print(hex(ea), idc.GetDisasm(ea)) accesses.append(ea) break if insn.ops[0].reg == reg: writeCount += 1 else: for i in range(0, ida_ua.UA_MAXOP): if (insn.ops[i].type in [ida_ua.o_reg, ida_ua.o_displ ] # normal reg or ldr/str and writeCount == writeIdx and insn.ops[i].reg == reg): accesses.append(ea) break ea += idc.get_item_size(ea) return accesses