Ejemplo n.º 1
0
    def __process_function_typeinfo(self, info, func):

        tinfo = ida_typeinf.tinfo_t()
        func_type_data = ida_typeinf.func_type_data_t()
        if ida_pro.IDA_SDK_VERSION >= 740:
            ida_typeinf.guess_tinfo(tinfo,func.start_ea)
        else:
            ida_typeinf.guess_tinfo(func.start_ea,tinfo)
        tinfo.get_func_details(func_type_data)

        #calling convention
        info['calling_convention'] = self.__describe_callingconvention(func_type_data.cc)
        func_type_data.rettype
        
        #return tpye
        info['return_type'] = ida_typeinf.print_tinfo('', 0, 0, ida_typeinf.PRTYPE_1LINE, func_type_data.rettype, '', '')

        #arguments
        arguments = list()
        
        for funcarg in func_type_data:
            arginfo = {
                'name'              : funcarg.name,
                'type'              : ida_typeinf.print_tinfo('', 0, 0, ida_typeinf.PRTYPE_1LINE, funcarg.type, '', ''),
                'argument_location' : self.__describe_argloc(funcarg.argloc.atype())
            }
            
            arguments.append(arginfo)

        info['arguments'] = arguments
Ejemplo n.º 2
0
    def __process_function_typeinfo(self, info, func):

        tinfo = ida_typeinf.tinfo_t()
        ida_nalt.get_tinfo(tinfo,func.start_ea)
        
        func_type_data = ida_typeinf.func_type_data_t()
        tinfo.get_func_details(func_type_data)

        #calling convention
        info['calling_convention'] = self.__describe_callingconvention(func_type_data.cc)
        info['memory_model_code']  = self.__describe_memorymodel_code(func_type_data.cc)
        info['memory_model_data']  = self.__describe_memorymodel_data(func_type_data.cc)

        #return type
        info['return_type'] = ida_typeinf.print_tinfo('', 0, 0, ida_typeinf.PRTYPE_1LINE, func_type_data.rettype, '', '')

        #arguments
        arguments = list()
        
        for funcarg in func_type_data:
            arginfo = {
                'name'              : funcarg.name,
                'type'              : ida_typeinf.print_tinfo('', 0, 0, ida_typeinf.PRTYPE_1LINE, funcarg.type, '', ''),
                'argument_location' : self.__describe_argloc(funcarg.argloc.atype())
            }
            
            arguments.append(arginfo)

        info['arguments'] = arguments
Ejemplo n.º 3
0
def apply_jni_func_sig():
    """ Apply the standard JNIEnv* and jobject signature to a function.
    """
    print("Function: {}".format(idc.get_func_name(here())))

    func = ida_hexrays.decompile(here())
    func_type = func.type
    funcdata = ida_typeinf.func_type_data_t()
    func_type.get_func_details(funcdata)

    jnienv = ida_typeinf.tinfo_t()
    jnienv.get_named_type(ida_typeinf.get_idati(), "JNIEnv")
    jnienv_ptr = ida_typeinf.tinfo_t()
    jnienv_ptr.create_ptr(jnienv)

    jobject = ida_typeinf.tinfo_t()
    jobject.get_named_type(ida_typeinf.get_idati(), "jobject")

    funcdata[0].type = jnienv_ptr
    funcdata[0].name = "env"
    funcdata[1].type = jobject
    funcdata[1].name = "thiz"
    new_tinfo = ida_typeinf.tinfo_t()
    new_tinfo.create_func(funcdata)
    ida_typeinf.apply_tinfo(here(), new_tinfo, ida_typeinf.TINFO_DEFINITE)
Ejemplo n.º 4
0
    def __get_type_data(self, ea):
        tinfo = ida_typeinf.tinfo_t()
        ida_nalt.get_tinfo(tinfo, ea)
        func_type_data = ida_typeinf.func_type_data_t()
        tinfo.get_func_details(func_type_data)

        return func_type_data
Ejemplo n.º 5
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))
Ejemplo n.º 6
0
    def _ida_func_type_data(self):
        """
            Internal property which allow to get the ``func_type_data_t`` for
            this type function.

            :return: the ``ida_typeinf.func_type_data_t`` for this object.
        """
        ftd = func_type_data_t()
        if not self._tinfo.get_func_details(ftd):
            raise BipError(
                "Unable to get function details for function {}".format(
                    self.name))
        return ftd
Ejemplo n.º 7
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))
Ejemplo n.º 8
0
    def declaration(self, decl):
        """
        Changes the declaration of the function internally.
        """
        # Ensure ends with ';'
        if not decl.endswith(";"):
            decl += ";"

        tif = ida_typeinf.tinfo_t()
        til = ida_typeinf.get_idati()
        func_type_data = ida_typeinf.func_type_data_t()
        ida_typeinf.parse_decl(tif, til, decl, ida_typeinf.PT_SIL)
        tif.get_func_details(func_type_data)
        self._tif = tif
        self._func_type_data = func_type_data
Ejemplo n.º 9
0
def processFunctionTypeinfo(function):

    tinfo = ida_typeinf.tinfo_t()
    func_type_data = ida_typeinf.func_type_data_t()
    tinfo.get_named_type
    ida_typeinf.guess_tinfo(function['start_ea'], tinfo)
    tinfo.get_func_details(func_type_data)

    #calling convention
    function['calling_convention'] = describe_callingconvention(
        func_type_data.cc)
    func_type_data.rettype

    #return tpye
    function['return_type'] = ida_typeinf.print_tinfo('', 0, 0,
                                                      ida_typeinf.PRTYPE_1LINE,
                                                      func_type_data.rettype,
                                                      '', '')

    #arguments
    arguments = list()

    for funcarg in func_type_data:
        arginfo = {
            'name':
            funcarg.name,
            'type':
            ida_typeinf.print_tinfo('', 0, 0, ida_typeinf.PRTYPE_1LINE,
                                    funcarg.type, '', ''),
            'argument_location':
            describe_argloc(funcarg.argloc.atype())
        }

        arguments.append(arginfo)

    function['arguments'] = arguments
Ejemplo n.º 10
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))
Ejemplo n.º 11
0
    def get_function_impl(self, address):
        """Given an address, return a `Function` instance or
        raise an `InvalidFunctionException` exception."""
        arch = self._arch
        os = self._os

        pfn = ida_funcs.get_func(address)
        if not pfn:
            pfn = ida_funcs.get_prev_func(address)

        seg_ref = [None]
        seg = find_segment_containing_ea(address, seg_ref)

        # Check this function.
        if not pfn or not seg:
            raise InvalidFunctionException(
                "No function defined at or containing address {:x}".format(address)
            )

        elif (
            not ida_funcs.func_contains(pfn, address)
            and not _is_extern_seg(seg)
            and not is_imported_table_seg(seg)
        ):
            raise InvalidFunctionException(
                "No function defined at or containing address {:x}".format(address)
            )

        # Reset to the start of the function, and get the type of the function.
        address = pfn.start_ea

        tif = ida_typeinf.tinfo_t()
        if not ida_nalt.get_tinfo(tif, address):
            if ida_typeinf.GUESS_FUNC_OK != ida_typeinf.guess_tinfo(tif, address):
                raise InvalidFunctionException(
                    "Can't guess type information for function at address {:x}".format(
                        address
                    )
                )

        if not tif.is_func():
            raise InvalidFunctionException(
                "Type information at address {:x} is not a function: {}".format(
                    address, tif.dstr()
                )
            )

        ftd = ida_typeinf.func_type_data_t()
        if not tif.get_func_details(ftd):
            raise InvalidFunctionException(
                "Could not get function details for function at address {:x}".format(
                    address
                )
            )

        # Make sure we can handle the basic signature of the function. This might
        # not be the final signature that we go with, but it's a good way to make
        # sure we can handle the relevant types.
        try:
            func_type = _get_type(tif, TYPE_CONTEXT_FUNCTION)
        except UnhandledTypeException as e:
            raise InvalidFunctionException(
                "Could not assign type to function at address {:x}: {}".format(
                    address, str(e)
                )
            )

        # Get the calling convention. The CC might override `is_variadic`, e.g. how
        # old style C functions declared as `foo()` actually imply `foo(...)`.
        cc, is_variadic = _get_calling_convention(arch, os, ftd)
        if is_variadic:
            func_type.set_is_variadic()

        # Go look into each of the parameters and their types. Each parameter may
        # refer to multiple locations, so we want to split each of those locations
        # into unique
        i = 0
        max_i = ftd.size()
        param_list = []
        while i < max_i:
            funcarg = ftd[i]
            i += 1

            arg_type = _get_type(funcarg.type, TYPE_CONTEXT_PARAMETER)
            arg_type_str = arg_type.serialize(arch, {})

            j = len(param_list)
            _expand_locations(arch, pfn, arg_type, funcarg.argloc, param_list)

            # If we have a parameter name, then give a name to each of the expanded
            # locations associated with this parameter.
            if funcarg.name:
                if (j + 1) == len(param_list):
                    param_list[-1].set_name(funcarg.name)
                else:
                    k = j
                    while k < len(param_list):
                        param_list[-1].set_name("{}_{}".format(funcarg.name, k - j))
                        k += 1

        # Build up the list of return values.
        ret_list = []
        ret_type = _get_type(ftd.rettype, TYPE_CONTEXT_RETURN)
        if not isinstance(ret_type, VoidType):
            _expand_locations(arch, pfn, ret_type, ftd.retloc, ret_list)

        func = IDAFunction(
            arch, address, param_list, ret_list, pfn, ftd.is_noret(), func_type, cc
        )
        self.add_symbol(address, _function_name(address))
        return func
Ejemplo n.º 12
0
    def get_function(self, address):
        """Given an address, return a `Function` instance or
    raise an `InvalidFunctionException` exception."""
        arch = self._arch

        pfn = ida_funcs.get_func(address)
        if not pfn:
            pfn = ida_funcs.get_prev_func(address)

        # Check this function.
        if not pfn or not ida_funcs.func_contains(pfn, address):
            raise InvalidFunctionException(
                "No function defined at or containing address {:x}".format(
                    address))

        # Reset to the start of the function, and get the type of the function.
        address = pfn.start_ea
        if address in self._functions:
            return self._functions[address]

        tif = ida_typeinf.tinfo_t()
        if not ida_nalt.get_tinfo(tif, address):
            ida_typeinf.guess_tinfo(tif, address)

        if not tif.is_func():
            raise InvalidFunctionException(
                "Type information at address {:x} is not a function: {}".
                format(address, tif.dstr()))

        ftd = ida_typeinf.func_type_data_t()
        if not tif.get_func_details(ftd):
            raise InvalidFunctionException(
                "Could not get function details for function at address {:x}".
                format(address))

        # Make sure we can handle the basic signature of the function. This might
        # not be the final signature that we go with, but it's a good way to make
        # sure we can handle the relevant types.
        try:
            func_type = get_type(tif)
        except UnhandledTypeException as e:
            raise InvalidFunctionException(
                "Could not assign type to function at address {:x}: {}".format(
                    address, str(e)))

        # Go look into each of the parameters and their types. Each parameter may
        # refer to multiple locations, so we want to split each of those locations
        # into unique
        i = 0
        max_i = ftd.size()
        param_list = []
        while i < max_i:
            funcarg = ftd[i]
            i += 1

            arg_type = get_type(funcarg.type)
            arg_type_str = arg_type.serialize(arch, {})

            j = len(param_list)
            _expand_locations(arch, pfn, arg_type, funcarg.argloc, param_list)

            # If we have a parameter name, then give a name to each of the expanded
            # locations associated with this parameter.
            if funcarg.name:
                if (j + 1) == len(param_list):
                    param_list[-1].set_name(funcarg.name)
                else:
                    k = j
                    while k < len(param_list):
                        param_list[-1].set_name("{}_{}".format(
                            funcarg.name, k - j))
                        k += 1

        # Build up the list of return values.
        ret_list = []
        ret_type = get_type(ftd.rettype)
        if not isinstance(ret_type, VoidType):
            _expand_locations(arch, pfn, ret_type, ftd.retloc, ret_list)

        func = IDAFunction(arch, address, param_list, ret_list, pfn)
        self._functions[address] = func
        return func
    si = None

print "Starting analysis"
current_index = 0
for ea in idautils.Functions():
    flags = idc.GetFunctionFlags(ea)
    func_name = idc.get_func_name(ea)
    if (current_index % 1000 == 0):
        print "Processing function %d" % current_index
    if flags & FUNC_THUNK and not func_name.startswith(
            "sub_") and not func_name.startswith(
                "j__ZdlPv") and not "null" in func_name:
        # Revert weird designations
        # could also use ida_funcs.set_func_name_if_jumpfunc(ea, None)
        func_name = func_name.replace("j_", "")
        funcdata = ida_typeinf.func_type_data_t()
        tinfo = ida_typeinf.tinfo_t()
        ida_nalt.get_tinfo(tinfo, ea)
        tinfo.get_func_details(funcdata)
        if (flags & FUNC_NORET):
            retcode = ''
        else:
            retcode = 'N'
        mcsema_def = ("%s %d C %s" %
                      (func_name, funcdata.size(), retcode)).strip()
        sdk_funcs_file.write(mcsema_def + '\n')

        if func_name.endswith("_1"):
            func_name = func_name.replace("_1", "")
        if func_name.endswith("_0"):
            func_name = func_name.replace("_0", "")
Ejemplo n.º 14
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
Ejemplo n.º 15
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))
Ejemplo n.º 16
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}")