def main(): kbase = min(Segments()) define_funcs = [] define_data = [] # Add functions for ea in Functions(): fname = idc.get_name(ea) ftype = idc.get_type(ea) fslide = ea - kbase if fname.startswith('sub_'): continue if ftype is None: if TYPED_FUNCS_ONLY: continue ftype = 'uint64_t (...)' define_funcs.append((fname, fslide, ftype)) # Add data for ea, _ in Names(): dname = idc.get_name(ea) dtype = idc.get_type(ea) dslide = ea - kbase flags = GetFlags(ea) if idc.is_code(flags) or idc.is_strlit(flags): continue if dtype is None: if TYPED_DATA_ONLY: continue dtype = 'void' define_data.append((dname, dslide, dtype)) # Generate source files generate_sdk(define_funcs, define_data)
def _is_func_type(ea): """Determines if data item at address is a function type.""" try: idc.get_type(ea) except TypeError: return False tif = ida_typeinf.tinfo_t() ida_nalt.get_tinfo(tif, ea) func_type_data = ida_typeinf.func_type_data_t() return bool(tif.get_func_details(func_type_data))
def _is_func_type(ea): """Determines if data item at address is a function type.""" try: idc.get_type(ea) except TypeError: return False tif = ida_typeinf.tinfo_t() ida_nalt.get_tinfo(tif, ea) func_type_data = ida_typeinf.func_type_data_t() # In IDA 7.6, imported functions are now function pointers. # To handle this, check if we need to pull out a pointed object first if tif.is_funcptr(): tif = tif.get_pointed_object() return bool(tif.get_func_details(func_type_data))
def str_type(self): """ Property which return the type (prototype) of the function. :return str: String representing the type of the function. """ return idc.get_type(self.ea)
def __init__(self, show_extra_fields): self.addr = None self.flags = None self.show_extra_fields = show_extra_fields self.names = [ 'Name', 'Address', 'Segment', 'Length', 'Locals', 'Arguments' ] self.handlers = { 0: lambda: None, 1: lambda: self.fmt(self.addr), 2: lambda: '{}'.format(idc.get_segm_name(self.addr)), 3: lambda: self.fmt(idc.get_func_attr(self.addr, idc.FUNCATTR_END) - self.addr), 4: lambda: self.fmt(idc.get_func_attr(self.addr, idc.FUNCATTR_FRSIZE)), 5: lambda: self.fmt(idc.get_func_attr(self.addr, idc.FUNCATTR_ARGSIZE)) } if self.show_extra_fields: self.names.extend(['R', 'F', 'L', 'S', 'B', 'T', '=']) # TODO: add Lumina column info self.handlers.update({ 6: lambda: self.is_true(not self.flags & idc.FUNC_NORET, 'R'), 7: lambda: self.is_true(self.flags & idc.FUNC_FAR, 'F'), 8: lambda: self.is_true(self.flags & idc.FUNC_LIB, 'L'), 9: lambda: self.is_true(self.flags & idc.FUNC_STATIC, 'S'), 10: lambda: self.is_true(self.flags & idc.FUNC_FRAME, 'B'), 11: lambda: self.is_true(idc.get_type(self.addr), 'T'), 12: lambda: self.is_true(self.flags & idc.FUNC_BOTTOMBP, '=') })
def collect_function_vars(func_ea, blockset): DEBUG_PUSH() if is_function_unsafe(func_ea, blockset): _FUNC_UNSAFE_LIST.add(get_symbol_name(func_ea)) # Check for the variadic function type; Add the variadic function # to the list of unsafe functions func_type = idc.get_type(func_ea) 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: _FUNC_UNSAFE_LIST.add(get_symbol_name(func_ea)) stack_vars = build_stack_variable(func_ea) processed_blocks = set() while len(blockset) > 0: block_ea = blockset.pop() if block_ea in processed_blocks: DEBUG("ERROR: Attempting to add same block twice: {0:x}".format( block_ea)) continue processed_blocks.add(block_ea) _process_basic_block(func_ea, block_ea, {"stack_vars": stack_vars}) DEBUG_POP() return stack_vars
def _getFunctionType(start: int, end: Optional[int] = None) -> str: type = get_type(start) if type is None: return parse_function_type(start, end) args_start = type.find('(') if args_start is not None: type = type[:args_start] return type
def _getArgsDescription(ea: int) -> str: name = demangle_name(get_func_name(ea), get_inf_attr(INF_SHORT_DN)) # get from mangled name if not name: name = get_type(ea) # get from type if not name: return parse_function_args(ea) # cannot get params from the mangled name args_start = name.find('(') if args_start is not None and args_start != (-1): return name[args_start:] return ""
def set_types(self): ''' handle (EFI_BOOT_SERVICES *) type and (EFI_SYSTEM_TABLE *) for x64 images ''' RAX = 0 O_REG = 1 O_MEM = 2 EFI_BOOT_SERVICES = 'EFI_BOOT_SERVICES *' EFI_SYSTEM_TABLE = 'EFI_SYSTEM_TABLE *' empty = True for service in self.gBServices: for address in self.gBServices[service]: ea = address num_of_attempts = 10 for _ in range(num_of_attempts): ea = idc.prev_head(ea) if (idc.print_insn_mnem(ea) == 'mov' and idc.get_operand_type(ea, 1) == O_MEM): if (idc.get_operand_type(ea, 0) == O_REG and idc.get_operand_value(ea, 0) == RAX): gvar = idc.get_operand_value(ea, 1) gvar_type = idc.get_type(gvar) ''' if (EFI_SYSTEM_TABLE *) ''' if ((gvar_type != 'EFI_SYSTEM_TABLE *') and \ (idc.print_operand(address, 0).find('rax') == 1) ): if self._find_est(gvar, ea, address): print( '[ {0} ] Type ({type}) successfully applied' .format( '{addr:#010x}'.format(addr=gvar), type=EFI_SYSTEM_TABLE)) empty = False break ''' otherwise it (EFI_BOOT_SERVICES *) ''' if (gvar_type != 'EFI_BOOT_SERVICES *' and gvar_type != 'EFI_SYSTEM_TABLE *'): if idc.SetType(gvar, EFI_BOOT_SERVICES): empty = False idc.set_name( gvar, 'gBs_{addr:#x}'.format(addr=gvar)) print( '[ {0} ] Type ({type}) successfully applied' .format( '{addr:#010x}'.format(addr=gvar), type=EFI_BOOT_SERVICES)) break if empty: print(' * list is empty')
def set_types(self): """ handle (EFI_BOOT_SERVICES *) type """ RAX = 0 O_REG = 1 O_MEM = 2 EFI_BOOT_SERVICES = "EFI_BOOT_SERVICES *" empty = True for service in self.gBServices: for address in self.gBServices[service]: ea = address num_of_attempts = 10 for _ in range(num_of_attempts): ea = idc.prev_head(ea) if (idc.GetMnem(ea) == "mov" and idc.get_operand_type(ea, 1) == O_MEM): if (idc.get_operand_type(ea, 0) == O_REG and idc.get_operand_value(ea, 0) == RAX): gBs_var = idc.get_operand_value(ea, 1) gBs_var_type = idc.get_type(gBs_var) if (gBs_var_type == "EFI_BOOT_SERVICES *"): empty = False print("[{0}] EFI_BOOT_SERVICES->{1}".format( "{addr:#010x}".format(addr=address), service)) print("\t [address] {0}".format( "{addr:#010x}".format(addr=gBs_var))) print("\t [message] type already applied") break if idc.SetType(gBs_var, EFI_BOOT_SERVICES): empty = False idc.MakeName( gBs_var, "gBs_{addr:#x}".format(addr=gBs_var)) print("[{0}] EFI_BOOT_SERVICES->{1}".format( "{addr:#010x}".format(addr=address), service)) print("\t [address] {0}".format( "{addr:#010x}".format(addr=gBs_var))) print("\t [message] type successfully applied") else: empty = False print("[{0}] EFI_BOOT_SERVICES->{1}".format( "{addr:#010x}".format(addr=address), service)) print("\t [address] {0}".format( "{addr:#010x}".format(addr=gBs_var))) print("\t [message] type not applied") break if empty: print(" * list is empty")
def export_range(start, end): data = "" count = 0 for ea in ya.get_all_items(start, end): type = idc.get_type(ea) type = " " + type if type else "" name = idaapi.get_name(ea) name = " " + name if name else "" data += "0x%x: %s: %s\\n" % (ea, dump_type(ea), dump_flags(ea)) count += 1 if count > 100: data += "%d item(s)" % count return data
def set_types(self): """ handle (EFI_BOOT_SERVICES *) type and (EFI_SYSTEM_TABLE *) for x64 images """ REG_RAX = 0 EFI_BOOT_SERVICES = "EFI_BOOT_SERVICES *" EFI_SYSTEM_TABLE = "EFI_SYSTEM_TABLE *" empty = True for service in self.gBServices: for address in self.gBServices[service]: ea = address num_of_attempts = 10 for _ in range(num_of_attempts): ea = idc.prev_head(ea) if ( idc.print_insn_mnem(ea) == "mov" and idc.get_operand_type(ea, 1) == idc.o_mem ): if ( idc.get_operand_type(ea, 0) == idc.o_reg and idc.get_operand_value(ea, 0) == REG_RAX ): gvar = idc.get_operand_value(ea, 1) gvar_type = idc.get_type(gvar) # if (EFI_SYSTEM_TABLE *) if (gvar_type != "EFI_SYSTEM_TABLE *") and ( idc.print_operand(address, 0).find("rax") == 1 ): if self._find_est(gvar, ea, address): print( f"[ {gvar:016X} ] Type ({EFI_SYSTEM_TABLE}) successfully applied" ) empty = False break # otherwise it (EFI_BOOT_SERVICES *) if ( gvar_type != "EFI_BOOT_SERVICES *" and gvar_type != "EFI_SYSTEM_TABLE *" ): if idc.SetType(gvar, EFI_BOOT_SERVICES): empty = False idc.set_name(gvar, f"gBS_{gvar:X}") print( f"[ {gvar:016X} ] Type ({EFI_BOOT_SERVICES}) successfully applied" ) break if empty: print(" * list is empty")
def set_types(self): """ handle (EFI_BOOT_SERVICES *) type and (EFI_SYSTEM_TABLE *) for x64 images """ RAX = 0 O_REG = 1 O_MEM = 2 EFI_BOOT_SERVICES = 'EFI_BOOT_SERVICES *' EFI_SYSTEM_TABLE = 'EFI_SYSTEM_TABLE *' empty = True for service in self.gBServices: for address in self.gBServices[service]: ea = address num_of_attempts = 10 for _ in range(num_of_attempts): ea = idc.prev_head(ea) if (idc.print_insn_mnem(ea) == 'mov' and idc.get_operand_type(ea, 1) == O_MEM): if (idc.get_operand_type(ea, 0) == O_REG and idc.get_operand_value(ea, 0) == RAX): gvar = idc.get_operand_value(ea, 1) gvar_type = idc.get_type(gvar) # if (EFI_SYSTEM_TABLE *) if ((gvar_type != 'EFI_SYSTEM_TABLE *') and (idc.print_operand( address, 0).find('rax') == 1)): if self._find_est(gvar, ea, address): print( f'[ {gvar:016X} ] Type ({EFI_SYSTEM_TABLE}) successfully applied' ) empty = False break # otherwise it (EFI_BOOT_SERVICES *) if (gvar_type != 'EFI_BOOT_SERVICES *' and gvar_type != 'EFI_SYSTEM_TABLE *'): if idc.SetType(gvar, EFI_BOOT_SERVICES): empty = False idc.set_name(gvar, f'gBS_{gvar:X}') print( f'[ {gvar:016X} ] Type ({EFI_BOOT_SERVICES}) successfully applied' ) break if empty: print(' * list is empty')
def getTypeName(self): # type: () -> str """ :return: the type of the data item, if it's a struct/enum/const, the name of it. a number of stars can follow, indicating that it's a pointer. """ type = idc.get_type(self.ea) flags = idc.GetFlags(self.ea) typeName = "INVALID" if idc.isCode(flags): typeName = "code" elif idc.isData(flags): if idc.is_byte(flags) and self.getSize() == 1: typeName = "u8" elif idc.is_word(flags) and self.getSize() == 2: typeName = "u16" elif idc.is_dword(flags) and self.getSize() == 4: if self.isPointer(self.getContent()): typeName = "void*" else: typeName = "u32" else: # The weird case... an array. I don't know why it's weird. IDA doesn't like it! # It is assumed this is an array, but the type is unknown. Imply type based on disasm of first line! firstLineSplitDisasm = list( filter(None, re.split('[ ,]', idc.GetDisasm(self.ea)))) dataType = firstLineSplitDisasm[0] if dataType == "DCB": typeName = "u8[%d]" % (self.getSize()) if dataType == "DCW": typeName = "u16[%d]" % (self.getSize() / 2) if dataType == "DCD": if self.hasPointer(): typeName = "void*[%d]" % (self.getSize() / 4) else: typeName = "u32[%d]" % (self.getSize() / 4) elif idc.isUnknown(flags): typeName = "u8" elif idc.isStruct(flags): typeName = idc.GetStrucName return typeName
def type(self): return get_type(self.mid)
# func_sigs maps from function address to a record of the function's name and # type. func_sigs = {} func_addrs = [] for seg in idautils.Segments(): # Skip extern segment; as used by IDA for external functions. if idc.get_segm_attr(seg, SEGATTR_TYPE) == idc.SEG_XTRN: #print("skipping segment ", idc.get_segm_name(seg)) continue for fn in idautils.Functions(seg, idc.get_segm_end(seg)): func_addr = fn func_name = idc.get_name(func_addr) if func_name is None: func_name = "" func_type = idc.get_type(func_addr) if func_type is None: func_type = "" func_sig = {"func_name": func_name, "func_type": func_type} func_sigs[func_addr] = func_sig func_addrs.append(func_addr) # Sort function addresses to be used as key. func_addrs.sort() # Example output: # # [ # { # "func_addr": "0x0", # "func_name": "foo",
def cmd_get_type(self, a): t = idc.get_type(int(a, 0)) if not t: t = "" return t
def type(self): type = get_type(self.__ea) if type is None: type = "" return type
def signature(self): '''The C signature of the function.''' return idc.get_type(self.start_ea)
def get_function_data(offset, operand: Operand = None): """ Obtain a idaapi.func_type_data_t object for the function with the provided start EA. :param int offset: start EA of function :param operand: operand containing function address in it's value. This can be provided when function is dynamically generated at runtime. (e.g. call eax) :return: idaapi.func_type_data_t object :raise RuntimeError: if func_type_data_t object cannot be obtained """ global _func_types tif = ida_typeinf.tinfo_t() try: func_type = idc.get_type(offset) except TypeError: raise RuntimeError("Not a valid offset: {!r}".format(offset)) # First see if it's a type we already set before. if func_type and offset in _func_types: ida_nalt.get_tinfo(tif, offset) else: # Otherwise, try to use the Hexrays decompiler to determine function signature. # (It's better than IDA's guess_type) try: # This requires Hexrays decompiler, load it and make sure it's available before continuing. if not idaapi.init_hexrays_plugin(): idc.load_and_run_plugin("hexrays", 0) or idc.load_and_run_plugin("hexx64", 0) if not idaapi.init_hexrays_plugin(): raise RuntimeError("Unable to load Hexrays decompiler.") # Pull type from decompiled C code. try: decompiled = idaapi.decompile(offset) except idaapi.DecompilationFailure: decompiled = None if decompiled is None: raise RuntimeError("Cannot decompile function at 0x{:X}".format(offset)) decompiled.get_func_type(tif) # Save type for next time. fmt = decompiled.print_dcl() fmt = "".join(c for c in fmt if c in string.printable and c not in ("\t", "!")) # The 2's remove the unknown bytes always found at the start and end. set_type_result = idc.SetType(offset, "{};".format(fmt)) if not set_type_result: logger.warning("Failed to SetType for function at 0x{:X} with decompiler type {!r}".format(offset, fmt)) # If we fail, resort to using guess_type+ except RuntimeError: if func_type: # If IDA's disassembler set it already, go with that. ida_nalt.get_tinfo(tif, offset) else: # Otherwise try to pull it from guess_type() guessed_type = idc.guess_type(offset) if guessed_type is None: raise RuntimeError("failed to guess function type for offset 0x{:X}".format(offset)) func_name = idc.get_func_name(offset) if func_name is None: raise RuntimeError("failed to get function name for offset 0x{:X}".format(offset)) # Documentation states the type must be ';' terminated, also the function name must be inserted guessed_type = re.sub(r"\(", " {}(".format(func_name), "{};".format(guessed_type)) set_type_result = idc.SetType(offset, guessed_type) if not set_type_result: logger.warning( "Failed to SetType for function at 0x{:X} with guessed type {!r}".format(offset, guessed_type) ) # Try one more time to get the tinfo_t object if not ida_nalt.get_tinfo(tif, offset): raise RuntimeError("failed to obtain tinfo_t object for offset 0x{:X}".format(offset)) funcdata = ida_typeinf.func_type_data_t() success = tif.get_func_details(funcdata) if success: # record that we have processed this function before. (and that we can grab it from the offset) _func_types.add(offset) return funcdata # If we have still failed, we have one more trick under our sleeve. # Try to pull the type information from the operand of the call instruction. # This could be set if the function has been dynamically created. if operand: tif = operand._tif success = tif.get_func_details(funcdata) if success: return funcdata raise RuntimeError("failed to obtain func_type_data_t object for offset 0x{:X}".format(offset))
def main(): global til til = ida_typeinf.get_idati() capstone_init() unicorn_init() all_names = list(idautils.Names()) # First, get inheritance tree for all classes # We do this in two passes: # First pass: for each class, gather class name, parent class name, # and superclass name, and connect them # Second pass: for each inheritance hierarchy object, we check # if the parent pointer is None (signaling a top-level # class), and add that to the `ihs` list # We can do this whole thing by processing xrefs to OSMetaClass::OSMetaClass OSMetaClass_ctor = get_OSMetaClass_ctor() if OSMetaClass_ctor == 0: print("Could not find OSMetaClass::OSMetaClass") return print("Got OSMetaClass::OSMetaClass at {}".format(hex(OSMetaClass_ctor))) # key,value pairs of all inheritance hierarchy objects ih_dict = {} # only those inheritance hierarchy objects which represent # a top-level parent class (aka inheriting from OSObject) ihs = [] xrefs = list(idautils.XrefsTo(OSMetaClass_ctor)) num = 0 for xref in xrefs: frm = xref.frm # test # frm = 0x63920 # print("xref from {}".format(hex(frm))) args = get_OSMetaClass_ctor_args(frm) pname = args[0] cname = args[1] if cname == "OSMetaClassBase": print("xref from {}".format(hex(frm))) print(args) if pname == cname: continue csz = args[2] # if pname == "AUAUnitDictionary" and cname == "AUAMixerUnitDictionary": # print(args) # return new_parent = pname is not None and pname not in ih_dict new_child = cname not in ih_dict if new_parent: ih_dict[pname] = InheritanceHierarchy(None, pname, csz, csz) if new_child: ih_dict[cname] = InheritanceHierarchy(None, cname, csz) else: # Update class size for only child classes ih_dict[cname].sz = csz if pname == None: # If this class has no superclass, it must be parent class, # so make its InheritanceHierarchy object ih_dict[cname] = InheritanceHierarchy(None, cname, csz) else: child_ih = ih_dict[cname] parent_ih = ih_dict[pname] parent_ih.add_child(child_ih) child_ih.parent = parent_ih child_ih.totsz = child_ih.sz + parent_ih.totsz # if cname == "AUAUnitDictionary": # print("AUAUnitDictionary sz: {}".format(ih_dict[pname].sz)) # print(args) # return # if cname == "AUAMixerUnitDictionary": # print("AUAMixerUnitDictionary sz: {}".format(ih_dict[cname].sz)) # print(args) # return num += 1 # if num == 10: # break print("First pass: {} classes processed".format(num)) num = 0 # Second pass for ih in ih_dict.values(): if ih.parent == None: # print("Adding {} to the ihs list".format(ih.name)) num += 1 ihs.append(ih) print("Second pass: {} classes added to ihs list".format(num)) num = 0 wants_class_hierarchy = ida_kernwin.ask_yn(0, "Dump class hierarchy?") if wants_class_hierarchy: hierch_file = open( "{}/iOS/Scripts/iokit_hier.txt".format(str(Path.home())), "w") for ih in ihs: dump_hierarchy_to_file(hierch_file, ih, 0) print("File written to {}".format(hierch_file.name)) hierch_file.close() return vtables = [] for cur_name in all_names: ea = cur_name[0] name = cur_name[1] if "ZTV" in name: vtables.append(cur_name) struct_file = open( "{}/iOS/Scripts/structs_from_vtabs.h".format(str(Path.home())), "w") is_standalone_kext = ida_kernwin.ask_yn(0, "Standalone kext?") if is_standalone_kext: # If this is from a standalone kext, I need to write some # definitions for common objects that follow my struct format, # otherwise, things get really screwed struct_file.write( "struct __cppobj ExpansionData {};\n\n" "struct __cppobj OSMetaClassBase_vtbl;\n\n" "struct __cppobj OSMetaClassBase_mbrs {};\n\n" "struct __cppobj OSMetaClassBase {\n" "\tOSMetaClassBase_vtbl *__vftable /*VFT*/;\n" "\tOSMetaClassBase_mbrs m;\n" "};\n\n" "struct __cppobj OSObject_mbrs : OSMetaClassBase_mbrs {\n" "\tint retainCount;\n" "};\n\n" "struct __cppobj OSObject_vtbl : OSMetaClassBase_vtbl {};\n\n" "struct __cppobj OSObject {\n" "\tOSObject_vtbl *__vftable;\n" "\tOSObject_mbrs m;\n" "};\n\n" "struct __cppobj OSMetaClass_mbrs : OSMetaClassBase_mbrs {\n" "\tExpansionData *reserved;\n" "\tconst OSMetaClass *superClassLink;\n" "\tconst OSSymbol *className;\n" "\tunsigned int classSize;\n" "\tunsigned int instanceCount;\n" "};\n\n" "struct __cppobj OSMetaClass {\n" "\tOSMetaClassBase_vtbl *__vftable;\n" "\tOSMetaClass_mbrs m;\n" "};\n\n" "struct __cppobj OSCollection_vtbl;\n" "struct __cppobj OSCollection_mbrs : OSObject_mbrs {\n" "\tunsigned int updateStamp;\n" "\tunsigned int fOptions;\n" "};\n\n" "struct __cppobj OSCollection {\n" "\tOSCollection_vtbl *__vftable;\n" "\tOSCollection_mbrs m;\n" "};\n\n" "struct __cppobj OSArray_vtbl;\n" "struct __cppobj OSArray_mbrs : OSCollection_mbrs {\n" "\tunsigned int count;\n" "\tunsigned int capacity;\n" "\tunsigned int capacityIncrement;\n" "\tvoid *array;\n" "};\n\n" "struct __cppobj OSArray {\n" "\tOSArray_vtbl *__vftable;\n" "\tOSArray_mbrs m;\n" "};\n\n" "struct __cppobj OSDictionary::dictEntry {\n" "\tconst OSSymbol *key;\n" "\tconst OSMetaClassBase *value;\n" "};\n\n" "struct __cppobj OSDictionary_vtbl;\n" "struct __cppobj OSDictionary_mbrs : OSCollection_mbrs {\n" "\tunsigned int count;\n" "\tunsigned int capacity;\n" "\tunsigned int capacityIncrement;\n" "\tOSDictionary::dictEntry *dict;\n" "};\n\n" "struct __cppobj OSDictionary {\n" "\tOSDictionary_vtbl *__vftable;\n" "\tOSDictionary_mbrs m;\n" "};\n\n" "struct __cppobj OSSet_vtbl;\n" "struct __cppobj OSSet_mbrs : OSCollection_mbrs {\n" "\tOSArray *members;\n" "};\n\n" "struct __cppobj OSSet {\n" "\tOSSet_vtbl *__vftable;\n" "\tOSSet_mbrs m;\n" "};\n\n" "struct __cppobj OSString_mbrs : OSObject_mbrs {\n" "\tunsigned __int32 flags : 14;\n" "\tunsigned __int32 length : 18;\n" "\tchar *string;\n" "};\n\n" "struct __cppobj OSString {\n" "\tOSObject_vtbl *__vftable;\n" "\tOSString_mbrs m;\n" "};\n\n" "struct __cppobj OSSymbol : OSString {};\n\n") num_failed_get_type = 0 cnt = 0 for vtable in vtables: demangled_name = idc.demangle_name(vtable[1], get_inf_attr(idc.INF_LONG_DN)) # unless this is a vtable for OSMetaClassBase, OSMetaClassMeta, # or OSMetaClass, skip anything metaclass related if "::MetaClass" in demangled_name: continue class_name = ida_name.extract_name(demangled_name, len("`vtable for'")) if class_name in BLACKLIST: continue ea = vtable[0] #+ 8; while ida_bytes.get_qword(ea) == 0: ea += 8 # print("EA: {}".format(hex(ea))) if is_unknown(ida_bytes.get_flags(ea)): continue # if class_name != "IOSkywalkPacket": # continue # if class_name != "AHTHSBufferStatic": # continue # if class_name != "HSMSPITest": # continue # if class_name != "AppleMesa": # continue # if class_name != "AppleUSBHostController": # continue # if class_name != "AppleEmbeddedPCIE": # continue # if class_name != "SimpleEval": # continue # if class_name != "AppleUSBCDCControl": # continue # if class_name != "IOHDCP2TransmitterAuthSession": # continue # if class_name != "IOAVService::DisplayIDParser::readDisplayID": # continue # if class_name != "IOMFB::TypedProp": # continue # if class_name != "IOMFB::UPBlock_GenPipe_v2": # continue # if class_name != "AppleMesaSEPDriver": # continue # if class_name != "IOAVController": # continue # if class_name != "AppleConvergedIPCICEBBBTIInterface": # continue # if class_name != "ApplePPM": # continue cnt += 1 # print("{}".format(class_name)) # skip NULL pointers until we hit a function while ida_bytes.get_qword(ea) == 0: ea += 8 num_virts = 0 num_dtors = 0 num_untyped = 0 num_noname = 0 fxn_name_list = [] fxn_name_dict = {} struct_fields = [] fwd_decls = set() fwd_decls.add(class_name) svt = SymbolicatedVtable(class_name) ioc = IOKitClass(svt) ioc.svt = svt # vtables seem to be NULL terminated while True: fxn_ea = ida_bytes.get_qword(ea) # end of vtable for this class if fxn_ea == 0: break # print("Type for {}/{} @ {}: {}".format(hex(fxn_ea), fxn_name, hex(ea), fxn_type)) # if fxn_type == None: # num_failed_get_type += 1 # ea += 8 # continue # default to this for ___cxa_pure_virtual fxn_args = "void" fxn_call_conv = "" fxn_mangled_name = ida_name.get_ea_name(fxn_ea) fxn_name = ida_name.demangle_name(fxn_mangled_name, get_inf_attr(idc.INF_LONG_DN)) if fxn_name == None: # ___cxa_pure_virtual fxn_name = ida_name.get_ea_name(fxn_ea) # some other thing? if len(fxn_name) == 0: fxn_name = "noname{}".format(num_noname) else: fxn_type = idc.get_type(fxn_ea) if fxn_type == None: # sometimes this happens, don't know why # the only thing fxn_type would have provided was # the calling convention, so we need to manually # reconstruct parameter list, and assume calling # convention is __fastcall # if mangled_fxn_name == "__ZN25IOGeneralMemoryDescriptor7doUnmapEP7_vm_mapyy": # exit(0) fxn_return_type = "__int64" fxn_call_conv = "__fastcall" fxn_args_string = fxn_name[fxn_name.find("(") + 1:fxn_name.rfind(")")] # if fxn_args_string == "IOService *, unsigned int, void *, void (*)(OSObject *, AppleUSBCDCControl*, void *, USBCDCNotification *)": # # print("Hello") # fxn_args_string = "IOService *, unsigned int, void *, void (*)(OSObject *, void (*)(TestType *, AppleUSBCDCControl*, void *, USBCDCNotification *), AppleUSBCDCControl*, void *, USBCDCNotification *)" # print("fxn args: {}".format(fxn_args_string)) # if fxn_args_string == "OSObject *, void (*)(OSObject *, IOHDCPAuthSession *), IOHDCPMessageTransport *, IOHDCPInterface *": # fxn_args_string = "OSObject *, void (*)(OSObject *, IOHDCPAuthSession *), IOHDCPMessageTransport *, IOHDCPInterface *, unsigned long long" fxn_args_string = fxn_args_string.replace( "{block_pointer}", "*") if fxn_args_string.find(",") != -1: # print("More than one arg: {}".format(fxn_args_list)) # extra comma makes the parser happy fxn_args_types_list = get_arg_type_list( fxn_args_string + ",") # print("More than one arg for {}: {}".format(fxn_name, fxn_args_types_list)) # print() fxn_args = "" argnum = 0 # print(type(fxn_args_types_list)) to_fwd_decl = get_fwd_decls(fxn_args_types_list) if len(to_fwd_decl) > 0: fwd_decls.update(to_fwd_decl) for arg_type in fxn_args_types_list: if argnum == 0: fxn_args += "{} *__hidden this, ".format( class_name) else: fxn_args += "{}, ".format(fix_type(arg_type)) argnum += 1 fxn_args = fxn_args[:-2] else: fxn_args = "{} *__hidden this".format(class_name) arg_type = fxn_name[fxn_name.find("(") + 1:fxn_name.rfind(")")] # print("Only one arg for {}: {}".format(fxn_name, arg_type)) arg_type_list = [arg_type] # print("Only one arg: {}".format(arg_type_list)) to_fwd_decl = get_fwd_decls(arg_type_list) if len(to_fwd_decl) > 0: fwd_decls.update(to_fwd_decl) if arg_type != "void" and len(arg_type) > 0: fxn_args += ", {}".format(fix_type(arg_type)) else: all_except_args = fxn_type[:fxn_type.find("(")] # first, if there's no spaces, there's no calling # convention specifed if all_except_args.find(" ") == -1: fxn_return_type = all_except_args # Also, this having no spaces could mean IDA messed # up, so we should use the demangled name instead # and parse that fxn_type = "(" + fxn_name[fxn_name.find("(") + 1:] # print("No spaces in args, using {} as fxn_type".format(fxn_type)) else: double_underscore = all_except_args.rfind("__") if double_underscore != -1: fxn_return_type = all_except_args[: double_underscore] fxn_call_conv = all_except_args[double_underscore:] else: fxn_return_type = all_except_args # get args # print("fxn_type: {}".format(fxn_type)) fxn_args = fxn_type[fxn_type.find("(") + 1:fxn_type.rfind(")")] fxn_args_type_list = get_arg_type_list(fxn_args + ",") fixed_fxn_args_type_list = [] # Fix up args for arg_type in fxn_args_type_list: # Remove __hidden arg_type = arg_type.replace("__hidden", "") # Check for a pointer. This is an easy case, we # just delete everything from the first * star = arg_type.find("*") if star != -1: arg_type = arg_type[0:star] else: # Otherwise, find the last space, and delete # from there # But in case there was no name for this # parameter, check if the token after the last # space is not an IDA type or primitive type lspace = arg_type.rfind(" ") if lspace != -1: token = arg_type[lspace:].replace(" ", "") if not is_primitive_type( token) and not is_ida_type(token): arg_type = arg_type[0:lspace] # print("arg_type: {}".format(arg_type)) fixed_fxn_args_type_list.append(arg_type) # to_fwd_decl = get_fwd_decls(fxn_args_type_list) to_fwd_decl = get_fwd_decls(fixed_fxn_args_type_list) if len(to_fwd_decl) > 0: fwd_decls.update(to_fwd_decl) # print("fxn_type is not None for {}: fxn args: {}".format(fxn_name, fxn_args_type_list)) # print("fxn_type is not None: will fwd declare: {}".format(to_fwd_decl)) # get function name # remove 'classname::' and params fxn_name = fxn_name[fxn_name.find("::") + 2:fxn_name.find("(") + 1] fxn_name = fxn_name[:fxn_name.find("(")] # replace any '~' fxn_name = fxn_name.replace("~", "DTOR{}_".format(num_dtors)) # remove any < and > fxn_name = fxn_name.replace("<", "") fxn_name = fxn_name.replace(">", "") if fxn_name in list(fxn_name_dict.keys()): fxn_name_dict[fxn_name] += 1 fxn_name += "_{}".format(fxn_name_dict[fxn_name]) else: fxn_name_dict[fxn_name] = -1 if "DTOR" in fxn_name: num_dtors += 1 curfield = "" if fxn_name == "___cxa_pure_virtual": # struct_fields.append("\tvoid __noreturn (__cdecl *___cxa_pure_virtual{})({});".format(num_virts, # fxn_args)) curfield = "\tvoid __noreturn (__cdecl *___cxa_pure_virtual{})({});".format( num_virts, fxn_args) num_virts += 1 else: # struct_fields.append("\t{} ({} *{})({});".format(fxn_return_type, # fxn_call_conv, fxn_name, fxn_args)) curfield = "\t{} ({} *{})({});".format(fxn_return_type, fxn_call_conv, fxn_name, fxn_args) svt.add(curfield) ea += 8 # return # Some classes won't have xrefs to OSMetaClass::OSMetaClass, # like OSMetaClassBase if class_name in ih_dict: ih_dict[class_name].ioc = ioc # Just write forward decls for now for decl in fwd_decls: struct_file.write("struct __cppobj {};\n".format(decl)) struct_file.write("\n") # cnt += 1 # if cnt == 5: # break print("{} IOKit vtables".format(len(vtables))) # Now create the header file to import into IDA # for ih in ihs: # dump_ih(ih, 0) generate_header_file(struct_file, ihs) # fixup_header_file(struct_file) struct_file.close()
def get_function_data(offset): """ Obtain a idaapi.func_type_data_t object for the function with the provided start EA. :param int offset: start EA of function :return: idaapi.func_type_data_t object :raise RuntimeError: if func_type_data_t object cannot be obtained """ global _func_types tif = ida_typeinf.tinfo_t() try: func_type = idc.get_type(offset) except TypeError: raise RuntimeError('Not a valid offset: {!r}'.format(offset)) # First see if it's a type we already set before. if func_type and offset in _func_types: ida_nalt.get_tinfo(tif, offset) else: # Otherwise, try to use the Hexrays decompiler to determine function signature. # (It's better than IDA's guess_type) try: # This requires Hexrays decompiler, load it and make sure it's available before continuing. if not idaapi.init_hexrays_plugin(): idc.load_and_run_plugin( "hexrays", 0) or idc.load_and_run_plugin("hexx64", 0) if not idaapi.init_hexrays_plugin(): raise RuntimeError('Unable to load Hexrays decompiler.') # Pull type from decompiled C code. try: decompiled = idaapi.decompile(offset) except idaapi.DecompilationFailure: decompiled = None if decompiled is None: raise RuntimeError( "Cannot decompile function at 0x{:X}".format(offset)) decompiled.get_func_type(tif) # Save type for next time. format = decompiled.print_dcl() # The 2's remove the unknown bytes always found at the start and end. idc.SetType(offset, "{};".format(format[2:-2])) # If we fail, resort to using guess_type+ except RuntimeError: if func_type: # If IDA's disassembler set it already, go with that. ida_nalt.get_tinfo(tif, offset) else: # Otherwise try to pull it from guess_type() guessed_type = idc.guess_type(offset) if guessed_type is None: raise RuntimeError( "failed to guess function type for offset 0x{:X}". format(offset)) func_name = idc.get_func_name(offset) if func_name is None: raise RuntimeError( "failed to get function name for offset 0x{:X}".format( offset)) # Documentation states the type must be ';' terminated, also the function name must be inserted guessed_type = re.sub("\(", " {}(".format(func_name), "{};".format(guessed_type)) idc.SetType(offset, guessed_type) # Try one more time to get the tinfo_t object if not ida_nalt.get_tinfo(tif, offset): raise RuntimeError( "failed to obtain tinfo_t object for offset 0x{:X}". format(offset)) funcdata = ida_typeinf.func_type_data_t() if not tif.get_func_details(funcdata): raise RuntimeError( "failed to obtain func_type_data_t object for offset 0x{:X}". format(offset)) # record that we have processed this function before. _func_types.add(offset) return funcdata
def get_function_data(offset, operand: Operand = None): """ Obtain a idaapi.func_type_data_t object for the function with the provided start EA. :param int offset: start EA of function :param operand: operand containing function address in it's value. This can be provided when function is dynamically generated at runtime. (e.g. call eax) :return: ida_typeinf.func_type_data_t object, ida_typeinf.tinfo_t object :raise RuntimeError: if func_type_data_t object cannot be obtained """ global _func_types tif = None try: func_type = idc.get_type(offset) except TypeError: raise RuntimeError("Not a valid offset: {!r}".format(offset)) # First see if it's a type we already set before. if func_type and offset in _func_types: tif = ida_typeinf.tinfo_t() ida_nalt.get_tinfo(tif, offset) else: # Otherwise, try to use the Hexrays decompiler to determine function signature. # (It's better than IDA's guess_type) try: tif = _get_function_tif_with_hex_rays(offset) # If we fail, resort to using guess_type+ except RuntimeError: if func_type: # If IDA's disassembler set it already, go with that. tif = ida_typeinf.tinfo_t() ida_nalt.get_tinfo(tif, offset) else: try: tif = _get_function_tif_with_guess_type(offset) except RuntimeError: # Don't allow to fail if we could pull from operand. pass if tif: funcdata = ida_typeinf.func_type_data_t() # In IDA 7.6, imported functions are now function pointers. # To handle this, check if we need to pull out a pointed object first if tif.is_funcptr(): tif = tif.get_pointed_object() success = tif.get_func_details(funcdata) if success: # record that we have processed this function before. (and that we can grab it from the offset) _func_types.add(offset) return funcdata, tif # If we have still failed, we have one more trick under our sleeve. # Try to pull the type information from the operand of the call instruction. # This could be set if the function has been dynamically created. if operand: tif = operand._tif funcdata = ida_typeinf.func_type_data_t() success = tif.get_func_details(funcdata) if success: return funcdata, tif raise RuntimeError( "failed to obtain func_type_data_t object for offset 0x{:X}".format( offset))
def get_func_type_info(address: int, operand: Tuple[int, int] = None) -> Tuple[ida_typeinf.func_type_data_t, ida_typeinf.tinfo_t]: """ Obtain a idaapi.func_type_data_t object for the function with the provided start address. :param address: start address of the function :param operand: Optional address and index pair for an operand containing the function address in its value. This can be provided when function is dynamically generated at runtime. (e.g. call eax) :return: ida_typeinf.func_type_data_t object, ida_typeinf.tinfo_t object :raise RuntimeError: if func_type_data_t object cannot be obtained """ func_type = idc.get_type(address) # First see if it's a type we already set before. if func_type and address in _seen_func_types: tif = ida_typeinf.tinfo_t() ida_nalt.get_tinfo(tif, address) # Otherwise, try to use the Hexrays decompiler to determine function signature. # (It's better than IDA's guess_type) else: # First try to get type information from the decompiled code produced # by the Hex Rays plugin. tif = _get_tif_with_hex_rays(address) if not tif: # Otherwise, if IDA's disassembler set it already, go with that. if func_type: tif = ida_typeinf.tinfo_t() ida_nalt.get_tinfo(tif, address) # Finally, see if we can obtain it with guess_type() else: tif = _get_tif_with_guess_type(address) if tif: func_type_data = ida_typeinf.func_type_data_t() # In IDA 7.6, imported functions are now function pointers. # To handle this, check if we need to pull out a pointed object first if tif.is_funcptr(): tif = tif.get_pointed_object() success = tif.get_func_details(func_type_data) if success: # record that we have processed this function before. (and that we can grab it from the offset) _seen_func_types.add(address) return func_type_data, tif # If we have still failed, we have one more trick under our sleeve. # Try to pull the type information from the operand of the call instruction. # This could be set if the function has been dynamically created. if operand: tif = ida_typeinf.tinfo_t() ida_nalt.get_op_tinfo(tif, operand.address, operand.index) func_type_data = ida_typeinf.func_type_data_t() success = tif.get_func_details(func_type_data) if success: return func_type_data, tif raise RuntimeError(f"Failed to obtain func_type_data_t object for offset 0x{address:X}")