def recover_variables(F, func_ea, blockset): """ Recover the stack variables from the function. It also collect the instructions referring to the stack variables. """ # Checks for the stack frame; return if it is None if not is_code_by_flags(func_ea) or \ not idc.GetFrame(func_ea): return functions = list() f_name = get_symbol_name(func_ea) f_ea = idc.GetFunctionAttr(func_ea, idc.FUNCATTR_START) f_vars = collect_function_vars(func_ea, blockset) functions.append({"ea": f_ea, "name": f_name, "stackArgs": f_vars}) for offset in f_vars.keys(): if f_vars[offset]["safe"] is False: continue var = F.stack_vars.add() var.sp_offset = offset var.name = f_vars[offset]["name"] var.size = f_vars[offset]["size"] for i in f_vars[offset]["writes"]: r = var.ref_eas.add() r.inst_ea = i["ea"] r.offset = i["offset"] for i in f_vars[offset]["reads"]: r = var.ref_eas.add() r.inst_ea = i["ea"] r.offset = i["offset"]
def getFunctionArgumentCount(ea): ''' Bit of a hack, since IDA doesn't seem to have a good way to get this information. Gets the frame for a given function, and counts named members following the 'r' member. Note: IDA won't create a frame member for an unreferenced function arg... so you've been warned. ''' rFound = False argCount = 0 sid = idc.GetFrame(ea) midx = idc.GetFirstMember(sid) while midx != idc.BADADDR: name = idc.GetMemberName(sid, midx) if rFound and name is not None: argCount += 1 #print 'Found arg at 0x%x: "%s"' % (midx, name) elif name == ' r': #print 'Found r at 0x%x:' % midx rFound = True else: #print 'Found nonarg at 0x%x: "%s"' % (midx, name) pass midx = idc.GetStrucNextOff(sid, midx) return argCount
def get_args(f): local_variables = [] arguments = [] current = local_variables frame = idc.GetFrame(f) arg_string = "" if frame == None: return None start = idc.GetFirstMember(frame) end = idc.GetLastMember(frame) count = 0 max_count = 10000 args_str = "" while start <= end and count <= max_count: size = idc.GetMemberSize(frame, start) count = count + 1 if size == None: start = start + 1 continue name = idc.GetMemberName(frame, start) start += size if name in [" r", " s"]: # Skip return address and base pointer current = arguments continue arg_string += " " + name current.append(name) if len(arguments) == 0: arguments.append("void") return arguments
def factory(cls, string_location, string_reference, size=INVALID, offset=INVALID, key=INVALID): """ Factory function to generate an EncodedString or EncodedStackString based on type. :param string_location: Data segment pointer for static strings or stack pointer for stack strings. :param string_reference: The location the string is referenced from. This is required to pull the stack frame when string_location is a stack pointer. :param size: The size of the string. Required to use self.get_bytes. :param offset: Used when there is an offset based accessing scheme. :param key: Used when there is a key that can vary by string. """ if idc.isLoaded(string_location): return EncodedString( string_location, string_reference, size=size, offset=offset, key=key) # otherwise assume string_location is a pointer within the stack # (using function_tracingutils's CPU emulator) and create an EncodedStackString object. stack = idc.GetFrame(string_reference) # FIXME: This method isn't always super accurate because... IDA stack_offset = ( string_location + function_tracingutils.RSP_OFFSET + idc.GetFrameLvarSize(string_reference) - function_tracingutils.STACK_BASE ) return EncodedStackString( stack, stack_offset, string_reference=string_reference, size=size, offset=offset, key=key)
def delete_all_function_stack_members(func_ea, force=False): if g_vars["ERASE_STACK_MEMBERS"] or force: members, base = retrieve_stack_members(func_ea) stack_id = idc.GetFrame(func_ea) for k, v in members.items(): if k != base and "arg_" not in v: idc.DelStrucMember(stack_id, k) g_functions_stack.add(func_ea.startEA)
def hasStackVars(ea): """ :param ea: address of the function :return: whether the function has stack variables or not """ id = idc.GetFrame(ea) firstMember = idc.GetFirstMember(id) return firstMember != idaapi.BADADDR and firstMember != -1
def build_stack_args(f): stackArgs = dict() name = idc.Name(f) end = idc.GetFunctionAttr(f, idc.FUNCATTR_END) _locals = idc.GetFunctionAttr(f, idc.FUNCATTR_FRSIZE) _uses_bp = 0 != (idc.GetFunctionFlags(f) & idc.FUNC_FRAME) frame = idc.GetFrame(f) if frame is None: return stackArgs func_type = idc.GetType(f) if (func_type is not None) and ("(" in func_type): args = func_type[ func_type.index('(')+1: func_type.rindex(')') ] args_list = [ x.strip() for x in args.split(',')] if "..." in args_list: return stackArgs if name in RECOVER_DEBUG_FL: return stackArgs #grab the offset of the stored frame pointer, so that #we can correlate offsets correctly in referent code # e.g., EBP+(-0x4) will match up to the -0x4 offset delta = idc.GetMemberOffset(frame, " s") if -1 == delta: #indicates that it wasn't found. Unsure exactly what to do # in that case, punting for now delta = 0 offset = idc.GetFirstMember(frame) while -1 != _signed_from_unsigned(offset): memberName = idc.GetMemberName(frame, offset) if memberName is None: # gaps in stack usage are fine, but generate trash output # gaps also could indicate a buffer that IDA doesn't recognize offset = idc.GetStrucNextOff(frame, offset) continue if (memberName == " r" or memberName == " s"): #the return pointer and start pointer, who cares offset = idc.GetStrucNextOff(frame, offset) continue memberSize = idc.GetMemberSize(frame, offset) if offset >= delta: offset = idc.GetStrucNextOff(frame, offset) continue memberFlag = idc.GetMemberFlag(frame, offset) #TODO: handle the case where a struct is encountered (FF_STRU flag) flag_str = _get_flags_from_bits(memberFlag) stackArgs[offset-delta] = {"name":memberName, "size":memberSize, "flags":flag_str, "writes":list(), "referent":list(), "reads":list(), "safe": False} offset = idc.GetStrucNextOff(frame, offset) return stackArgs
def find_WdfDriverCreate(): function_offset = OFFSET_WdfDriverCreate # If the XREF to wdfFunctions + function_offset exists.. then we're in case 1! try: call_pfnWdfDriverCreate = idautils.XrefsTo(g_vars["_WDFFUNCTIONS"]+function_offset).next().frm except StopIteration: # this is case 2! call_pfnWdfDriverCreate = find_wdf_callback_through_immediate("mov", 1,function_offset) if call_pfnWdfDriverCreate != None: idc.OpStroffEx(call_pfnWdfDriverCreate,1,(idaapi.get_struc_id("_WDFFUNCTIONS")),0) else: call_pfnWdfDriverCreate = find_wdf_callback_through_immediate("call", 0, function_offset) idc.OpStroffEx(call_pfnWdfDriverCreate,0,(idaapi.get_struc_id("_WDFFUNCTIONS")),0) if call_pfnWdfDriverCreate != None: # First identify the RealDriverEntry :) current_func = idaapi.get_func(call_pfnWdfDriverCreate) idc.MakeName(current_func.startEA, "_DriverEntry_") argument_DriverConfig_addr = find_function_arg_with_operand_value(call_pfnWdfDriverCreate, "mov", "rsp", 0x20, 0) register_DriverConfig = idc.GetOpnd(argument_DriverConfig_addr, 1) lea_DriverConfig_addr = find_function_arg(argument_DriverConfig_addr, "lea", register_DriverConfig, 0) # Get stack and the stack operand offset current_func = idaapi.get_func(lea_DriverConfig_addr) stack_id = idc.GetFrame(current_func) opnd = idc.GetOpnd(lea_DriverConfig_addr, 1) if "rsp" in opnd: stack_member_offset = idc.GetOperandValue(lea_DriverConfig_addr, 1) elif "rbp" in opnd: var_x = opnd.split("+")[-1][:-1] # [rbp+57h+var_80] -> var_80 members, _ = retrieve_stack_members(current_func) inverted_members = {v:k for k, v in members.items()} try: stack_member_offset = inverted_members[var_x] except KeyError as msg: print msg return else: print("+] WdfDriverCreate() Unidentified register stack layout") return #idc.SetMemberName(stack_id, stack_member_offset, "_DriverConfig") struct_id = idaapi.get_struc_id("_WDF_DRIVER_CONFIG") struct_size = idc.GetStrucSize(struct_id) # First check if we have already touch this function stack before #if function_stack_erased(current_func): # need to take care of the already defined structs # pass #else: delete_all_function_stack_members(current_func, force=True) idc.AddStrucMember(stack_id, "driver_config", stack_member_offset, idc.FF_BYTE|idc.FF_DATA, -1, struct_size) idc.SetMemberType(stack_id, stack_member_offset, idc.FF_STRU|idc.FF_DATA, struct_id, 1)
def build_stack_variable(func_ea): stack_vars = dict() frame = idc.GetFrame(func_ea) if not frame: return stack_vars f_name = get_symbol_name(func_ea) #grab the offset of the stored frame pointer, so that #we can correlate offsets correctly in referent code # e.g., EBP+(-0x4) will match up to the -0x4 offset delta = idc.GetMemberOffset(frame, " s") if delta == -1: delta = 0 if f_name not in _FUNC_UNSAFE_LIST: offset = idc.GetFirstMember(frame) while -1 != _signed_from_unsigned(offset): member_name = idc.GetMemberName(frame, offset) if member_name is None: offset = idc.GetStrucNextOff(frame, offset) continue if (member_name == " r" or member_name == " s"): offset = idc.GetStrucNextOff(frame, offset) continue member_size = idc.GetMemberSize(frame, offset) if offset >= delta: offset = idc.GetStrucNextOff(frame, offset) continue member_flag = idc.GetMemberFlag(frame, offset) flag_str = _get_flags_from_bits(member_flag) member_offset = offset-delta stack_vars[member_offset] = {"name": member_name, "size": member_size, "flags": flag_str, "writes": list(), "referent": list(), "reads": list(), "safe": False } offset = idc.GetStrucNextOff(frame, offset) else: offset = idc.GetFirstMember(frame) frame_size = idc.GetFunctionAttr(func_ea, idc.FUNCATTR_FRSIZE) flag_str = "" member_offset = _signed_from_unsigned(offset) - delta stack_vars[member_offset] = {"name": f_name, "size": frame_size, "flags": flag_str, "writes": list(), "referent": list(), "reads": list(), "safe": False } return stack_vars
def getStackVarDisasm(self): """ if the function uses stack variables with SP, their symbols should be defined :return: """ disasm = '' id = idc.GetFrame(self.func_ea) firstMember = idc.GetFirstMember(id) if hasStackVars(self.func_ea): # first, obtain the base by finding an instruction that uses one of the stack variables stackVars = getStackVars(self.func_ea) ea = self.func_ea base = -1 # TODO: maybe use get_min_spd_ea(func_ea) to get the base pointer? this stands for stack pointer delta! # search function instructions to find base (TODO: hacky, but i dunno how else to find base yet) while ea < self.func_ea + self.getSize(): d = Data.Data(ea) origDisasm = d.getOrigDisasm() # case where the stack frame is referenced for var, offset in stackVars: if var in origDisasm and '#' in origDisasm: # cases like LDR SP, [base+var_xx] if '[' in origDisasm: # grab the base if '+' in origDisasm: base = int( origDisasm[origDisasm.index('#') + 1:origDisasm.index('+')], 16) else: base = 0 # obtained base! no need to continue looping break # some cases like ADD SP, base+var_xx don't have '[' elif '+' in origDisasm: base = int( origDisasm[origDisasm.index('#') + 1:origDisasm.index('+')], 16) # obtained base! no need to continue looping break if base != -1: break ea += d.getSize() # if base couldn't be found still, it's likely no SP access is done with variables if base == -1: base = 0 # build up disasm based on stack vars using base-relative offsets for name, off in stackVars: relOff = base - off if relOff > 0: disasm += ".equ %s, -0x%X\n" % (name, abs(relOff)) else: disasm += ".equ %s, 0x%X\n" % (name, abs(relOff)) return disasm
def retrieve_stack_members(func_ea): members = {} base = None frame = idc.GetFrame(func_ea) for frame_member in idautils.StructMembers(frame): member_offset, member_name, _ = frame_member members[member_offset] = member_name if member_name == ' r': base = member_offset if not base: raise ValueError("Failed identifying the stack's base address using the return address hidden stack member") return members, base
def find_WdfIoQueueCreate(): function_offset = OFFSET_WdfIoQueueCreate calls_to_pfn_list = [] try: for xref in idautils.XrefsTo(g_vars["_WDFFUNCTIONS"] + function_offset): call_pfnWdfIoQueueCreate = xref.frm calls_to_pfn_list.append(call_pfnWdfIoQueueCreate) except StopIteration: # this is case 2 or 3 pass if len(calls_to_pfn_list) == 0: call_pfnWdfIoQueueCreate = find_wdf_callback_through_immediate( "call", 0, function_offset) if call_pfnWdfIoQueueCreate: calls_to_pfn_list.append(call_pfnWdfIoQueueCreate) idc.OpStroffEx(call_pfnWdfIoQueueCreate, 0, (idaapi.get_struc_id("_WDFFUNCTIONS")), 0) if len(calls_to_pfn_list) == 0: call_pfnWdfIoQueueCreate = find_wdf_callback_through_immediate( "mov", 1, function_offset) if call_pfnWdfIoQueueCreate: calls_to_pfn_list.append(call_pfnWdfIoQueueCreate) idc.OpStroffEx(call_pfnWdfIoQueueCreate, 1, (idaapi.get_struc_id("_WDFFUNCTIONS")), 0) for pfn_call in calls_to_pfn_list: lea_argument_addr = find_function_arg(pfn_call, "lea", "r8", 0) # Get stack and the stack operand offset current_func = idaapi.get_func(lea_argument_addr) stack_id = idc.GetFrame(current_func) stack_member_offset = idc.get_operand_value(lea_argument_addr, 1) struct_id = idaapi.get_struc_id("_WDF_IO_QUEUE_CONFIG") struct_size = idc.GetStrucSize(struct_id) # First check if we have already touch this function stack before if function_stack_erased(current_func): # need to take care of the already defined structs # If the arguments collide then this will fail pass else: delete_all_function_stack_members(current_func) print("Erased the stack members") idc.AddStrucMember(stack_id, "queue_config", stack_member_offset, idc.FF_BYTE | idc.FF_DATA, -1, struct_size) idc.SetMemberType(stack_id, stack_member_offset, idc.FF_STRU | idc.FF_DATA, struct_id, 1) print("IOQueue Creation at: " + hex(pfn_call))
def find_stack_members(func_ea): members = {} base = None frame = idc.GetFrame(func_ea) try: for frame_member in idautils.StructMembers(frame): member_offset, member_name, size = frame_member members[member_offset] = size if member_name == ' s': base = member_offset if not base: base = 3 except: return 0, 0 return members, base
def getStackVars(ea, base=-1): # type: (int, int) -> list[(str, int)] """ Gets the stack variables associted with the function at ea If no base is specified, the offsets don't include the base calculation in them :param ea: the address of the function :param base: the stack base, must obtain to compute the offsets relative to it :return: a list of tuples, the stack variable name and its offset """ stackVars = [] id = idc.GetFrame(ea) firstMember = idc.GetFirstMember(id) # if the function has stack variables if firstMember != idaapi.BADADDR and firstMember != -1: # build up disasm based on stack vars lastMember = idc.GetLastMember(id) i = firstMember # Stack can be offset, first member might not be found at index 0, and all offsets must be adjusted by this foundFirstElement = False stackOffset = 0 while i <= lastMember: name = idc.GetMemberName(id, i) off = idc.GetMemberOffset( id, name ) # this is the offset in the struct... which isn't always consistent! size = idc.GetMemberSize(id, i) # append if varname is found (sometimes, None is returned because the variables are not in the next index) if name: # first variable found! this is the stack of the stack variables! if not foundFirstElement: stackOffset = i foundFirstElement = True if base == -1: # absolute offsets appended stackVars.append((name, off - stackOffset)) else: # base-relative offsets appended stackVars.append((name, base - off - stackOffset)) # sometimes, for some reason, the offset for stack variables does not follow linearly if size: i += size else: # reach next var, which might not be one size unit after the last... while not idc.GetMemberSize(id, i) and i <= lastMember: i += 1 return stackVars
def append_lvar_comment(fva, frame_offset, s, repeatable=False): ''' add the given string as a (possibly repeatable) stack variable comment to the given function. does not add the comment if it already exists. adds the comment on its own line. Args: fva (int): the address of the function with the stack variable. frame_offset (int): the offset into the stack frame at which the variable is found. s (str): the comment text. repeatable (bool): if True, set a repeatable comment. Raises: UnicodeEncodeError: if the given string is not ascii. ''' s = s.encode('ascii') stack = idc.GetFrame(fva) if not stack: raise RuntimeError('failed to find stack frame for function: ' + hex(fva)) lvar_offset = idc.GetFrameLvarSize(fva) - frame_offset if not lvar_offset: raise RuntimeError('failed to compute local variable offset') if lvar_offset <= 0: raise RuntimeError('failed to compute positive local variable offset') string = idc.GetMemberComment(stack, lvar_offset, repeatable) if not string: string = s else: if s in string: # ignore duplicates return string = string + "\\n" + s if not idc.SetMemberComment(stack, lvar_offset, string, repeatable): raise RuntimeError('failed to set comment')
def stack(self): "Stack of the function" return stack.IDAStack(idc.GetFrame(self.addr), self)
def argCount(self): end = idc.GetFunctionAttr(self.addr, idc.FUNCATTR_END) start = idc.GetFunctionAttr(self.addr, idc.FUNCATTR_START) frame = idc.GetFrame(start) localv = idc.GetFunctionAttr(self.addr, idc.FUNCATTR_FRSIZE) frameSize = idc.GetFrameSize(start) #idc.GetStrucSize(frame) reg_off = 0 local_count = 0 arg_count = 0 sid = idc.GetFrame(self.addr) if sid: firstM = idc.GetFirstMember(sid) lastM = idc.GetLastMember(sid) arg_count = 0 if lastM - firstM > 0x1000: return for i in xrange(firstM, lastM): mName = idc.GetMemberName(sid, i) mSize = idc.GetMemberSize(sid, i) mFlag = idc.GetMemberFlag(sid, i) off = idc.GetMemberOffset(sid, mName) #print "%s: %d, %x, off=%x" % (mName, mSize, mFlag, off) if mName == " r": reg_off = off # XXX: just store the data, dont loop twice. for i in xrange(firstM, lastM): mName = idc.GetMemberName(sid, i) mSize = idc.GetMemberSize(sid, i) mFlag = idc.GetMemberFlag(sid, i) off = idc.GetMemberOffset(sid, mName) if off <= reg_off: local_count += 1 elif off > reg_off and reg_off != 0: arg_count += 1 if arg_count > 0: return arg_count / 4 elif arg_count == 0: return 0 # offset to return try: ret = idc.GetMemberOffset(frame, " r") except: if frameSize > localv: return (frameSize - localv) / 4 # error getting function frame (or none exists) return -1 if (ret < 0): if frameSize > localv: return (frameSize - localv) / 4 return -1 firstArg = ret + 4 args = frameSize - firstArg numArgs = args / 4 return numArgs
def run(self): try: logger.debug('Starting up') dlg = StructTyperWidget() dlg.setStructs(loadStructs()) oldTo = idaapi.set_script_timeout(0) res = dlg.exec_() idaapi.set_script_timeout(oldTo) if res == QtWidgets.QDialog.Accepted: regPrefix = dlg.getRegPrefix() sid = None struc = None if dlg.ui.rb_useStackFrame.isChecked(): ea = idc.here() if using_ida7api: sid = idc.get_frame_id(ea) else: sid = idc.GetFrame(ea) struc = idaapi.get_frame(ea) logger.debug('Dialog result: accepted stack frame') if (sid is None) or (sid == idc.BADADDR): #i should really figure out which is the correct error case raise RuntimeError( 'Failed to get sid for stack frame at 0x%x' % ea) if (struc is None) or (struc == 0) or (struc == idc.BADADDR): raise RuntimeError( 'Failed to get struc_t for stack frame at 0x%x' % ea) if using_ida7api: pass else: #need the actual pointer value, not the swig wrapped struc_t struc = long(struc.this) else: structName = dlg.getActiveStruct() if structName is None: print("No struct selected. Bailing out") return logger.debug('Dialog result: accepted %s "%s"', type(structName), structName) if using_ida7api: sid = idc.get_struc_id(structName) else: sid = idc.GetStrucIdByName(structName) if (sid is None) or (sid == idc.BADADDR): #i should really figure out which is the correct error case raise RuntimeError('Failed to get sid for %s' % structName) tid = idaapi.get_struc_id(structName) if (tid is None) or (tid == 0) or (tid == idc.BADADDR): #i should really figure out which is the correct error case raise RuntimeError('Failed to get tid_t for %s' % structName) if using_ida7api: struc = idaapi.get_struc(tid) else: struc = g_dll.get_struc(tid) if (struc is None) or (struc == 0) or (struc == idc.BADADDR): raise RuntimeError('Failed to get struc_t for %s' % structName) foundMembers = self.processStruct(regPrefix, struc, sid) if dlg.ui.rb_useStackFrame.isChecked() and (foundMembers != 0): #reanalyze current function if we're analyzing a stack frame & found some func pointers if using_ida7api: funcstart = idc.get_func_attr(idc.here(), idc.FUNCATTR_START) funcend = idc.get_func_attr(idc.here(), idc.FUNCATTR_END) else: funcstart = idc.GetFunctionAttr( idc.here(), idc.FUNCATTR_START) funcend = idc.GetFunctionAttr(idc.here(), idc.FUNCATTR_END) if (funcstart != idc.BADADDR) and (funcend != idc.BADADDR): if using_ida7api: idc.plan_and_wait(funcstart, funcend) else: idc.AnalyzeArea(funcstart, funcend) elif res == QtWidgets.QDialog.Rejected: logger.info('Dialog result: canceled by user') else: logger.debug('Unknown result') raise RuntimeError('Dialog unknown result') except Exception, err: logger.exception("Exception caught: %s", str(err))
import idautils import ida_frame import idc idc.GetFrame() # Locate local variables of function stack frames. # # based on: https://reverseengineering.stackexchange.com/a/14726 # from: https://gist.github.com/nirizr/fe0ce9948b3db05555da42bbfe0e5a1e def find_stack_members(func_ea): members = {} base = None frame = idc.GetFrame(func_ea) for frame_member in idautils.StructMembers(frame): member_offset, member_name, _ = frame_member members[member_offset] = member_name if member_name == ' r': base = member_offset if not base: raise ValueError( "Failed identifying the stack's base address using the return address hidden stack member" ) return members, base # func_frames maps from function address to a record of the function's stack # frame size and local variables. func_frames = {}