Exemple #1
0
    def extract_info_from_IDA(self):
        prots = ida_bytes.get_qword(self.ea + 0x10)
        if prots:
            count = ida_bytes.get_qword(prots)
            entrysize = 0x8
            p_ea = prots + 8
            for i in range(count):
                proto_ea = idc.get_qword(p_ea)
                self.prots.append(proto_ea)
                p_ea += entrysize

        type_info = ida_bytes.get_qword(self.ea + 0x48)
        for idx in range(0, 4):
            # 0: inst_meths
            # 1: class_meths
            # 2: opt_inst_meths
            # 3: opt_class_meths
            meth_list = ida_bytes.get_qword(self.ea + 0x18 + idx * 8)
            if meth_list:
                entrysize = ida_bytes.get_dword(meth_list)
                count = ida_bytes.get_dword(meth_list + 4)
                ea = meth_list + 8
                for i in range(0, count):
                    sel = idc.get_bytes(idc.Qword(ea), idc.get_item_size(idc.Qword(ea)) - 1)
                    meth_type = idc.get_bytes(idc.Qword(type_info), idc.get_item_size(idc.Qword(type_info)) - 1)
                    self.meths[sel] = meth_type
                    ea += entrysize
                    type_info += 8
Exemple #2
0
 def __init__(self, ea):
     self.ea = ea  # ea: objc_data address
     self.info = ida_bytes.get_qword(ea + 0x20)
     self.superclass = ida_bytes.get_qword(
         ida_bytes.get_qword(ea) + 0x08)  # idc.Name(self.superclass): _OBJC_METACLASS_$_UIView
     self.name = idc.get_bytes(idc.Qword(self.info + 0x18), idc.get_item_size(idc.Qword(self.info + 0x18)) - 1)
     self.classref = None
     self.superref = None
     self.prots = []
     self.ivars = dict()
     self.props = dict()
 def handle_protocol(self, ea):
     protocol_ea = ida_bytes.get_qword(ea)
     protocol_name = cstr(ida_bytes.get_qword(protocol_ea + 8))
     method_list_ea = ida_bytes.get_qword(protocol_ea + 3 * 8)
     p = Protocol(protocol_name, ea=protocol_ea)
     self.print('@protocol', protocol_name)
     # todo: support class methods
     for method in method_list(method_list_ea):
         key = '- ' + cstr(method.name)
         p.methods.append(key)
         self.print(key)
     self.print('@end')
     self.print()
     self.protocols.append(p)
     self.protocol_lookup[p.name] = p
     self.lookup[ea] = p
Exemple #4
0
def find_pointers(start, end):
    for va in range(start, end - 0x8):
        ptr = ida_bytes.get_qword(va)
        if idc.get_segm_start(ptr) == idc.BADADDR:
            continue

        yield va, ptr
    def handle_class(self, ea):
        clazz_ea = ida_bytes.get_qword(ea)
        clazz = Objc2Class(ida_bytes.get_bytes(clazz_ea, Objc2Class.length))
        # if clazz.info & 7 != 0:
        # swift

        meta_class = Objc2Class(
            ida_bytes.get_bytes(clazz.isa, Objc2Class.length))
        meta_class.info = (meta_class.info >> 3) << 3
        meta_info = Objc2ClassRo(
            ida_bytes.get_bytes(meta_class.info, Objc2ClassRo.length))

        clazz.info = (clazz.info >> 3) << 3
        clazz_info = Objc2ClassRo(
            ida_bytes.get_bytes(clazz.info, Objc2ClassRo.length))

        c = Clazz(cstr(clazz_info.name), ea=clazz_ea)

        self.print('@interface', cstr(clazz_info.name))
        for method in method_list(meta_info.base_meths):
            key = '+ ' + cstr(method.name)
            c.methods[key] = method.imp
            self.print(key)

        for method in method_list(clazz_info.base_meths):
            key = '- ' + cstr(method.name)
            c.methods[key] = method.imp
            self.print(key)

        self.print('@end')
        self.print()

        self.classes.append(c)
        self.class_lookup[c.name] = c
        self.lookup[ea] = c
Exemple #6
0
    def value(self):
        """
            Property which return the value corresponding to the data of a
            numberable elements. This property works only if the
            :meth:`~BipData.is_numerable` and :meth:`~BipData.has_data`
            properties returned True. For getting value of an element which
            is not numerable use the :meth:`~BipElt.bytes` property.

            This property is link to the type defined or guessed by IDA and
            it is a good idea to assure you have the proper type before using
            it.

            :return: An integer representing the value of the data or ``None``
                if the data element is not numerable or do not have data.
        """
        if not self.has_data:
            return None
        elif self.is_unknown or self.is_byte:
            return ida_bytes.get_wide_byte(self.ea)
        elif self.is_word:
            return ida_bytes.get_wide_word(self.ea)
        elif self.is_dword:
            return ida_bytes.get_wide_dword(self.ea)
        elif self.is_qword:
            return ida_bytes.get_qword(self.ea)
        else:
            return None
Exemple #7
0
 def build_from_IDA():
     seg = ida_segment.get_segm_by_name('__objc_protolist')
     for ea in range(seg.startEA, seg.endEA, 8):
         p = Protocol(ida_bytes.get_qword(ea))
         p.extract_info_from_IDA()
         Protocol.pool_indexed_by_ea[p.ea] = p
         Protocol.pool_indexed_by_name[p.name] = Protocol.pool_indexed_by_name[p.name] + [
             p, ] if p.name in Protocol.pool_indexed_by_name else [p, ]
Exemple #8
0
 def build_from_IDA():
     seg = ida_segment.get_segm_by_name('__objc_classlist')
     for class_ea in range(seg.startEA, seg.endEA, 8):
         objc_data = ida_bytes.get_qword(class_ea)
         cls = OCClass(objc_data)
         cls.extract_info_from_IDA()
         OCClass.pool_indexed_by_name[cls.name] = cls if cls.name not in OCClass.pool_indexed_by_name else \
             OCClass.pool_indexed_by_name[cls.name]
         OCClass.pool_indexed_by_ea[cls.ea] = cls
Exemple #9
0
    def read_xword(self):
        if bitness == 32:
            res = ida_bytes.get_dword(self.addr)
        elif bitness == 64:
            res = ida_bytes.get_qword(self.addr)
        else:  # bitness == 16
            res = ida_bytes.get_word(self.addr)
        self.inc(self.bitness >> 3)

        return res
Exemple #10
0
    def extract_info_from_IDA(self):
        for xref in idautils.XrefsTo(self.ea):
            frm = xref.frm
            if idc.SegName(frm) == '__objc_classrefs':
                self.classref = frm
            if idc.SegName(frm) == '__objc_superrefs':
                self.superref = frm

        base_ivars = ida_bytes.get_qword(self.info + 0x30)
        if base_ivars and idc.SegName(base_ivars) == '__objc_const':
            entrysize = ida_bytes.get_dword(base_ivars)
            count = ida_bytes.get_dword(base_ivars + 4)
            ea = base_ivars + 8
            for i in range(count):
                offset = ida_bytes.get_dword(idc.get_qword(ea))
                _type = idc.get_bytes(idc.Qword(ea + 0X10), idc.get_item_size(idc.Qword(ea + 0X10)) - 1)
                _name = idc.get_bytes(idc.Qword(ea + 0X08), idc.get_item_size(idc.Qword(ea + 0X08)) - 1)
                # self.ivars[offset] = _type
                self.ivars[_name] = _type
                ea += entrysize

        base_props = ida_bytes.get_qword(self.info + 0x40)
        if base_props and idc.SegName(base_props) == '__objc_const':
            entrysize = ida_bytes.get_dword(base_props)
            count = ida_bytes.get_dword(base_props + 4)
            ea = base_props + 8
            for i in range(count):
                _type = idc.get_bytes(idc.Qword(ea + 0X08), idc.get_item_size(idc.Qword(ea + 0X08)) - 1)
                _name = idc.get_bytes(idc.Qword(ea), idc.get_item_size(idc.Qword(ea)) - 1)
                self.props[_name] = _type
                ea += entrysize

        base_prots = ida_bytes.get_qword(self.info + 0x28)
        if base_prots and idc.SegName(base_prots) == '__objc_const':
            count = ida_bytes.get_qword(base_prots)
            entrysize = 0x8
            p_ea = base_prots + 8
            for i in range(count):
                proto_ea = idc.get_qword(p_ea)
                self.prots.append(proto_ea)
                Protocol.add_implementing_class(proto_ea, self.ea)
                p_ea += entrysize
Exemple #11
0
    def _init_func_thunk_ctrl_flow(self):
        """Initializes the control flow redirections and targets
        using function thunks"""

        # We only support the ELF format for now
        inf = ida_idaapi.get_inf_structure()
        if inf.filetype != ida_ida.f_ELF:
            return

        # List the function thunks first
        input_file_path = ida_nalt.get_input_file_path()
        image_parser = create_elf_image_parser(input_file_path)
        function_thunk_list = image_parser.get_function_thunk_list()

        # Go through each function thunk, and look at its cross references; there
        # should always be only one user, which is the wrapper around the imported
        # function
        is_32_bit = image_parser.get_image_bitness() == 32

        for function_thunk in function_thunk_list:
            thunk_va = function_thunk.start

            redirection_dest = (ida_bytes.get_wide_dword(thunk_va) if is_32_bit
                                else ida_bytes.get_qword(thunk_va))

            caller_address = ida_xref.get_first_cref_to(redirection_dest)
            if caller_address == ida_idaapi.BADADDR:
                continue

            redirection_source = idc.get_func_attr(caller_address,
                                                   idc.FUNCATTR_START)
            caller_function_name = ida_funcs.get_func_name(redirection_source)

            if function_thunk.name in caller_function_name:
                print(
                    "anvill: Redirecting the user {:x} of thunk {} at rva {:x} to {:x}"
                    .format(
                        redirection_source,
                        function_thunk.name,
                        function_thunk.start,
                        redirection_dest,
                    ))

                self.add_control_flow_redirection(redirection_source,
                                                  redirection_dest)

            print(
                "anvill: Adding target list {:x} -> [{:x}, complete=True] for {}"
                .format(caller_address, redirection_dest, function_thunk.name))

            self.set_control_flow_targets(caller_address, [redirection_dest],
                                          True)
Exemple #12
0
 def is_method_a_getter(self, sel, f=None):
     base_props = ida_bytes.get_qword(self.info + 0x40)
     if not base_props:
         return False
     entrysize = ida_bytes.get_dword(base_props)
     count = ida_bytes.get_dword(base_props + 4)
     p_ea = base_props + 8
     for i in range(count):
         p_name = idc.get_bytes(idc.Qword(p_ea), idc.get_item_size(idc.Qword(p_ea)) - 1)
         if p_name == sel:
             return True
         p_ea += entrysize
     return False
Exemple #13
0
    def _init_ctrl_flow_redirections(self):
        """Initializes the control flow redirections using function thunks"""

        # We only support the ELF format for now
        inf = ida_idaapi.get_inf_structure()
        if inf.filetype != ida_ida.f_ELF:
            return

        # List the function thunks first
        input_file_path = ida_nalt.get_input_file_path()
        image_parser = create_elf_image_parser(input_file_path)
        function_thunk_list = image_parser.get_function_thunk_list()

        # Go through each function thunk, and look at its cross references; there
        # should always be only one user, which is the wrapper around the imported
        # function
        #
        # Note that the __libc_start_main # thunk does not need redirection since
        # it's called directly without any wrapper function from the module entry
        # point
        is_32_bit = image_parser.get_image_bitness() == 32

        for function_thunk in function_thunk_list:
            if function_thunk.name == "__libc_start_main":
                continue

            thunk_va = ida_nalt.get_imagebase() + function_thunk.start

            redirection_dest = (
                ida_bytes.get_wide_dword(thunk_va)
                if is_32_bit
                else ida_bytes.get_qword(thunk_va)
            )

            caller_address = ida_xref.get_first_cref_to(redirection_dest)
            if caller_address == ida_idaapi.BADADDR:
                continue

            redirection_source = idc.get_func_attr(caller_address, idc.FUNCATTR_START)

            print(
                "anvill: Redirecting the user {:x} of thunk {} at rva {:x} to {:x}".format(
                    redirection_source,
                    function_thunk.name,
                    function_thunk.start,
                    redirection_dest,
                )
            )

            self.add_control_flow_redirection(redirection_source, redirection_dest)
Exemple #14
0
    def get_qword(ea=None, original=False):
        """
            Static method allowing to get the value of one qword at an address.

            :param ea: The address at which recuperating the value. If
                ``None`` the screen address is used.
            :param original: If True the value recuperated will be the
                original one (before a patch). Default: False.
            :return: An integer corresponding to the value at the address.
        """
        if ea is None:
            ea = ida_kernwin.get_screen_ea()
        if original:
            return ida_bytes.get_original_qword(ea)
        else:
            return ida_bytes.get_qword(ea)
def main():
    for segstart, segend, segname in enum_segments():
        if segname not in ('.rdata', 'UPX1' ):
            continue

        for src, dst, psize in find_pointers(segstart, segend):
            if idc.get_segm_name(dst) not in (".rdata", "UPX0"):
                continue
                
            if psize == 8:
                size = ida_bytes.get_qword(src + 0x8)
            else:
                size = ida_bytes.get_dword(src + 0x4)
                
            if size > 0x100:
                continue
            if size <= 2:
                continue

            buf = ida_bytes.get_bytes(dst, size)
            if not buf:
                continue

            if b"\x00" in buf:
                continue

            try:
                s = buf.decode("ascii")
            except UnicodeDecodeError:
                continue

            print("string pointer: 0x%x -> 0x%x: %s" % (src, dst, s))

            ida_bytes.del_items(src, 1)
            ida_bytes.set_cmt(dst, s, True)

            # pointer
            ida_bytes.del_items(src, psize)
            ida_bytes.create_data(src, idc.FF_QWORD if size == 8 else idc.FF_DWORD, psize, idc.BADADDR)
            # this doesn't seem to always work :-(
            idc.op_plain_offset(src, -1, 0)
            ida_name.set_name(src, "s_%x" % (src))
            ida_bytes.set_cmt(src, s, True)

            # size
            ida_bytes.del_items(src + psize, psize)
            ida_bytes.create_data(src + psize, idc.FF_QWORD if size == 8 else idc.FF_DWORD, psize, idc.BADADDR)
Exemple #16
0
    def get_ptr(ea=None):
        """
            Recuperate the value of a pointer at an address. This will handle
            automatically the correct size of the pointer.
    
            :param int ea: the address at which get the pointer value. If
                ``None`` the screen address is used.
            :return: the pointer value
        """
        if ea is None:
            ea = ida_kernwin.get_screen_ea()

        info = ida_idaapi.get_inf_structure()
        if info.is_64bit():
            return ida_bytes.get_qword(ea)
        elif info.is_32bit():
            return ida_bytes.get_wide_dword(ea)
        else:
            return ida_bytes.get_wide_word(ea)
Exemple #17
0
 def modify_value(self):
     ea = self.get_current_expr_ea()
     if not ea or not idaapi.is_loaded(ea):
         return
     stack_val = 0
     if idaapi.inf_is_64bit():
         stack_val = ida_bytes.get_qword(ea)
     else:
         stack_val = ida_bytes.get_dword(ea)
     b = idaapi.ask_str("0x%X" % stack_val, 0, "Modify value")
     if b is not None:
         try:
             value = int(idaapi.str2ea(b))
             if idaapi.inf_is_64bit():
                 idc.patch_qword(ea, value)
             else:
                 idc.patch_dword(ea, value)
             self.reload_info()
         except:
             idaapi.warning("Invalid expression")
Exemple #18
0
def get_OSMetaClass_ctor_args(ea):
    # args = idaapi.get_arg_addrs(ea)

    # Lazy solution - just let unicorn emulate it up to the BL
    # First we gotta figure out the start of this function
    fxnstart = ida_funcs.get_func(ea).start_ea

    # Then we emulate it up to the function call
    params = emulate(fxnstart, ea)

    # for i in range(len(params)):
    #     print("x{}: {}".format(i+1, hex(params[i])))

    classname = read_cstring(params[0])
    superclass = params[1]
    superclass_name = None

    if superclass != 0:
        superclass_name = idc.demangle_name(idaapi.get_name(params[1]),
                                            get_inf_attr(idc.INF_LONG_DN))

        # In case the superclass pointer references a pointer that is
        # meant to be resolved later
        if superclass_name == None:
            superclass_name = idaapi.get_name(ida_bytes.get_qword(params[1]))
            superclass_name = idc.demangle_name(superclass_name,
                                                get_inf_attr(idc.INF_LONG_DN))

        superclass_name = superclass_name[0:superclass_name.find("::")]

    args = [superclass_name, classname.decode(), params[2]]

    # if superclass_name == "AUAUnitDictionary":
    #     for i in range(len(params)):
    #         print("x{}: {}".format(i+1, hex(params[i])))

    #     print(args)

    # return

    return args
Exemple #19
0
 def get_qword(self, offset):
     """Read qword from IDB"""
     self.ret = ida_bytes.get_qword(offset)
     return self.ret
Exemple #20
0
def get_double(ea):
    return struct.unpack("d", struct.pack("Q", ida_bytes.get_qword(ea)))[0]
Exemple #21
0
def get_int64(ea):
    return struct.unpack("q", struct.pack("Q", ida_bytes.get_qword(ea)))[0]
Exemple #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()
Exemple #23
0
 def read_qword(self):
     res = ida_bytes.get_qword(self.addr)
     self.inc(8)
     return res