Example #1
0
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)
Example #2
0
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)
Example #3
0
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))
Example #4
0
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))
Example #5
0
    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)
Example #6
0
    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, '=')
            })
Example #7
0
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
Example #8
0
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
Example #9
0
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 ""
Example #10
0
 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')
Example #11
0
 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")
Example #12
0
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
Example #13
0
 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")
Example #14
0
 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')
Example #15
0
 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
Example #16
0
 def type(self):
     return get_type(self.mid)
Example #17
0
# 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",
Example #18
0
 def cmd_get_type(self, a):
     t = idc.get_type(int(a, 0))
     if not t:
         t = ""
     return t
Example #19
0
 def type(self):
     type = get_type(self.__ea)
     if type is None:
         type = ""
     return type
Example #20
0
 def signature(self):
     '''The C signature of the function.'''
     return idc.get_type(self.start_ea)
Example #21
0
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))
Example #22
0
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()
Example #23
0
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
Example #24
0
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))
Example #25
0
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}")