首页
社区
课程
招聘
[原创] IDAPython 系列 —— 画出两个函数的交叉引用图
发表于: 2023-4-15 11:47 16229

[原创] IDAPython 系列 —— 画出两个函数的交叉引用图

2023-4-15 11:47
16229

第一次在看雪平台投稿,还是有点忐忑的,一直想分享些 idapython 的内容,网上现在的内容太少,都只是教了一些基础的用法。官方的文档和例子又比较散。希望我的分享能让大家更加快乐的逆向吧,哈哈!

这是这个系列的第一篇,分享一下如何用IDAPython 画出两个函数之间的交叉引用图,可视化一直是我很喜欢的一个东西,逆向的目的就是理解代码嘛,可视化是理解代码的一个很重要的工具。

此外,本教程的实现还依赖了几个 Python 包:

在这里,我们定义了一个名为 find_cross_refs 的递归函数,用于查找两个函数之间的交叉引用关系。该函数包含以下参数:

函数中主要分为以下几个步骤:

下面是 find_cross_refs 函数的详细实现代码:

第二步,我们要向 IDA 中添加 action,通过 action 来执行我们的 find_cross_refs函数,首先我们了解一下 IDA 中 action 的概念:

给一段代码,看一下如何注册一个 Action

然后是将 action 放到 UI 上的代码

了解完这些我们就可以写自己的 Action 了,为了方便我对官方的 Action api 做了一些封装, 下面是我自己的 action.py 文件

然后我们开始定义显示交叉引用图的 Action,我把它叫做 XrefRoadMapAction

具体来说,该类包含以下方法:

可以看出,该类实现的核心是 activate 方法。其主要流程如下:

下面是 XrefRoadMapAction 类的详细实现代码:

代码里面其实还用了很多 sark 库的函数,大家可以自己学习其用法,这个库是专门对 idapython 的 api 做封装的,比较 pythonic,非常好用,我自己也给这个库添加了很多新的功能。

如下所示:
图片描述
图片描述

 
MAX_SEARCH_DEPTH = 10
# 定义递归函数,用于查找两个函数之间的交叉引用关系
def find_cross_refs(func: sark.Function, target_func, G, max_depth, include_data_xref):
    if max_depth == 0:
        return False
    max_depth -= 1
 
    # 获取函数的所有引用
    if include_data_xref:
        refs = list(func.xrefs_from)
    else:
        refs = list(func.calls_from)
    # 遍历函数的每一个引用
    for ref in refs:
        # 获取引用的地址
        ref_addr = ref.to  # type: ignore
 
        # 判断引用是否指向目标函数
        if ref_addr == target_func.start_ea:
            # 如果指向目标函数,则找到了交叉引用关系
            # 在图中添加一条边表示函数之间的引用关系
            # G.add_node(func, name=func.demangled)
            # G.add_node(target_func, name=target_func.demangled)
            G.add_edge(func.ea, target_func.ea)
            return True
 
        # 如果引用指向另一个函数,则递归调用find_cross_refs函数,查找两个函数之间的交叉引用关系
 
        seg = sark.Segment(ea=ref_addr)
        if seg.name != "__text":
            #是 external Function
            continue
        else:
            try:
                ref_func = sark.Function(ea=ref_addr)
            except Exception as e:
                print(e)
                continue
        if find_cross_refs(ref_func, target_func, G, max_depth, include_data_xref):
            # 如果找到了交叉引用关系,则在图中添加一条边表示函数之间的引用关系
            # G.add_node(func, name=func.demangled)
            # G.add_node(ref_func, name=ref_func.demangled)
            G.add_edge(func.ea, ref_func.ea)
            return True
 
    # 如果遍历完所有引用后仍然没有找到交叉引用关系,则返回False
    return False
MAX_SEARCH_DEPTH = 10
# 定义递归函数,用于查找两个函数之间的交叉引用关系
def find_cross_refs(func: sark.Function, target_func, G, max_depth, include_data_xref):
    if max_depth == 0:
        return False
    max_depth -= 1
 
    # 获取函数的所有引用
    if include_data_xref:
        refs = list(func.xrefs_from)
    else:
        refs = list(func.calls_from)
    # 遍历函数的每一个引用
    for ref in refs:
        # 获取引用的地址
        ref_addr = ref.to  # type: ignore
 
        # 判断引用是否指向目标函数
        if ref_addr == target_func.start_ea:
            # 如果指向目标函数,则找到了交叉引用关系
            # 在图中添加一条边表示函数之间的引用关系
            # G.add_node(func, name=func.demangled)
            # G.add_node(target_func, name=target_func.demangled)
            G.add_edge(func.ea, target_func.ea)
            return True
 
        # 如果引用指向另一个函数,则递归调用find_cross_refs函数,查找两个函数之间的交叉引用关系
 
        seg = sark.Segment(ea=ref_addr)
        if seg.name != "__text":
            #是 external Function
            continue
        else:
            try:
                ref_func = sark.Function(ea=ref_addr)
            except Exception as e:
                print(e)
                continue
        if find_cross_refs(ref_func, target_func, G, max_depth, include_data_xref):
            # 如果找到了交叉引用关系,则在图中添加一条边表示函数之间的引用关系
            # G.add_node(func, name=func.demangled)
            # G.add_node(ref_func, name=ref_func.demangled)
            G.add_edge(func.ea, ref_func.ea)
            return True
 
    # 如果遍历完所有引用后仍然没有找到交叉引用关系,则返回False
    return False
# 1) Create the handler class
    class MyHandler(idaapi.action_handler_t):
        def __init__(self):
            idaapi.action_handler_t.__init__(self)
        # Say hello when invoked.
        def activate(self, ctx):
            print "Hello!"
            return 1
        # This action is always available.
        def update(self, ctx):
            return idaapi.AST_ENABLE_ALWAYS
    # 2) Describe the action
    action_desc = idaapi.action_desc_t(
        'my:action',   # The action name. This acts like an ID and must be unique
        'Say hello!'# The action text.
        MyHandler(),   # The action handler.
        'Ctrl+H',      # Optional: the action shortcut
        'Says hello'# Optional: the action tooltip (available in menus/toolbar)
        199)           # Optional: the action icon (shows when in menus/toolbars)
    # 3) Register the action
    idaapi.register_action(action_desc)
# 1) Create the handler class
    class MyHandler(idaapi.action_handler_t):
        def __init__(self):
            idaapi.action_handler_t.__init__(self)
        # Say hello when invoked.
        def activate(self, ctx):
            print "Hello!"
            return 1
        # This action is always available.
        def update(self, ctx):
            return idaapi.AST_ENABLE_ALWAYS
    # 2) Describe the action
    action_desc = idaapi.action_desc_t(
        'my:action',   # The action name. This acts like an ID and must be unique
        'Say hello!'# The action text.
        MyHandler(),   # The action handler.
        'Ctrl+H',      # Optional: the action shortcut
        'Says hello'# Optional: the action tooltip (available in menus/toolbar)
        199)           # Optional: the action icon (shows when in menus/toolbars)
    # 3) Register the action
    idaapi.register_action(action_desc)
# 将 action 放到菜单中
idaapi.attach_action_to_menu(
        'Edit/Other/Manual instruction...', # The relative path of where to add the action
        'my:action',                        # The action ID (see above)
        idaapi.SETMENU_APP)                 # We want to append the action after the 'Manual instruction...'
# 将 action 放到 toolbar 中
idaapi.attach_action_to_toolbar(
        "AnalysisToolBar"# The toolbar name
        'my:action')        # The action ID
 
# 将 action 永久的放到某个 widget 的上下文菜单中
# Create a widget, or retrieve a pointer to it.
    form = idaapi.get_current_tform()
    idaapi.attach_action_to_popup(form, None, "my:action", None)
# 将 action 放到菜单中
idaapi.attach_action_to_menu(
        'Edit/Other/Manual instruction...', # The relative path of where to add the action
        'my:action',                        # The action ID (see above)
        idaapi.SETMENU_APP)                 # We want to append the action after the 'Manual instruction...'
# 将 action 放到 toolbar 中
idaapi.attach_action_to_toolbar(
        "AnalysisToolBar"# The toolbar name
        'my:action')        # The action ID
 
# 将 action 永久的放到某个 widget 的上下文菜单中
# Create a widget, or retrieve a pointer to it.
    form = idaapi.get_current_tform()
    idaapi.attach_action_to_popup(form, None, "my:action", None)
import idaapi
 
from .hookers import global_hooker_manager
from typing import Optional
 
class ActionManager(object):
    def __init__(self):
        self.__actions = []
 
    def register(self, action):
        self.__actions.append(action)
        idaapi.register_action(
                idaapi.action_desc_t(action.name, action.description, action, action.hotkey)
            )
        if isinstance(action, HexRaysPopupAction):
            global_hooker_manager.register(HexRaysPopupRequestHandler(action))
 
    def initialize(self):
        pass
 
    def finalize(self):
        for action in self.__actions:
            idaapi.unregister_action(action.name)
 
 
action_manager = ActionManager()
 
class Action(idaapi.action_handler_t):
    """
    Convenience wrapper with name property allowing to be registered in IDA using ActionManager
    """
    description: Optional[str] = None
    hotkey: Optional[str] = None
 
    def __init__(self):
        super(Action, self).__init__()
 
    @property
    def name(self):
        return "HexRaysPyTools:" + type(self).__name__
 
    def activate(self, ctx: idaapi.action_ctx_base_t):
        raise NotImplementedError
 
    def update(self, ctx: idaapi.action_ctx_base_t):
        # return idaapi.AST_XXX
        # 通常会根据  ctx.widget_type == idaapi.BWN_XXX 来判断当前是在哪个窗口
        raise NotImplementedError
 
 
class HexRaysPopupAction(Action):
    """
    Wrapper around Action. Represents Action which can be added to menu after right-clicking in Decompile window.
    Has `check` method that should tell whether Action should be added to popup menu when different items
    are right-clicked.
    Children of this class can also be fired by hot-key without right-clicking if one provided in `hotkey`
    static member.
    """
 
    def __init__(self):
        super(HexRaysPopupAction, self).__init__()
 
    def activate(self, ctx: idaapi.action_ctx_base_t):
        raise NotImplementedError
 
    def check(self, hx_view):
        # type: (idaapi.vdui_t) -> bool
        raise NotImplementedError
 
    def update(self, ctx: idaapi.action_ctx_base_t):
        if ctx.widget_type == idaapi.BWN_PSEUDOCODE:
            return idaapi.AST_ENABLE_FOR_WIDGET
        return idaapi.AST_DISABLE_FOR_WIDGET
 
 
class HexRaysPopupRequestHandler(idaapi.Hexrays_Hooks):
    def __init__(self, action):
        super().__init__()
        self.__action = action
 
    def populating_popup(self, widget, popup_handle, vu):
        if self.__action.check(vu):
            idaapi.attach_action_to_popup(widget, popup_handle, self.__action.name, None)
        return 0
import idaapi
 
from .hookers import global_hooker_manager
from typing import Optional
 
class ActionManager(object):
    def __init__(self):
        self.__actions = []
 
    def register(self, action):
        self.__actions.append(action)
        idaapi.register_action(
                idaapi.action_desc_t(action.name, action.description, action, action.hotkey)
            )
        if isinstance(action, HexRaysPopupAction):
            global_hooker_manager.register(HexRaysPopupRequestHandler(action))
 
    def initialize(self):
        pass
 
    def finalize(self):
        for action in self.__actions:
            idaapi.unregister_action(action.name)
 
 
action_manager = ActionManager()
 
class Action(idaapi.action_handler_t):
    """
    Convenience wrapper with name property allowing to be registered in IDA using ActionManager
    """
    description: Optional[str] = None
    hotkey: Optional[str] = None
 
    def __init__(self):
        super(Action, self).__init__()
 
    @property
    def name(self):
        return "HexRaysPyTools:" + type(self).__name__
 
    def activate(self, ctx: idaapi.action_ctx_base_t):
        raise NotImplementedError
 
    def update(self, ctx: idaapi.action_ctx_base_t):
        # return idaapi.AST_XXX
        # 通常会根据  ctx.widget_type == idaapi.BWN_XXX 来判断当前是在哪个窗口
        raise NotImplementedError
 
 
class HexRaysPopupAction(Action):
    """
    Wrapper around Action. Represents Action which can be added to menu after right-clicking in Decompile window.
    Has `check` method that should tell whether Action should be added to popup menu when different items
    are right-clicked.
    Children of this class can also be fired by hot-key without right-clicking if one provided in `hotkey`
    static member.
    """
 

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 10
支持
分享
最新回复 (6)
雪    币: 2575
活跃值: (502)
能力值: ( LV2,RANK:85 )
在线值:
发帖
回帖
粉丝
2
IDA自带的 View-》Graph-》user xref charts 是不是类似效果?
2023-4-15 11:56
0
雪    币: 263
活跃值: (370)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
3
wyfe IDA自带的 View-》Graph-》user xref charts 是不是类似效果?
确实效果类似。不过 ida 的那个画图的功能我一直觉得不太好用,而且很丑。并且如果你的函数比较复杂,调用路径很深的话,ida 的图很难去看。
2023-4-15 12:05
0
雪    币: 5870
活跃值: (1796)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
或者参考下这个 https://github.com/herosi/CTO
2023-4-16 14:57
0
雪    币: 3059
活跃值: (30876)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2023-4-16 16:51
1
雪    币: 263
活跃值: (370)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
6
哇,这个很不错,感谢分享
2023-4-16 23:10
0
雪    币: 4460
活跃值: (6706)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
赞,谢谢分享
2023-4-17 00:28
0
游客
登录 | 注册 方可回帖
返回
//