GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

应用安全 --- IDA脚本 之 导出函数调用链

果您希望将之前“递归调用图”中的数据(即函数调用链)导出为文本或文件,而不是显示图形,可以使用下面的脚本。

我为您准备了两种方案:

  1. 方案一:导出当前函数的递归调用链(树状结构) —— 对应您之前的“xref graph from”需求,但是输出为文本文件。
  2. 方案二:导出整个程序的“谁调用了谁”列表(CSV格式) —— 如果您想要整个二进制文件的所有引用关系。

方案一:导出当前函数的递归调用链 (保存为 .txt)

这个脚本会从光标所在的函数开始,递归查找所有调用的子函数,并按层级缩进写入文本文件。

Python
import idaapi
import idautils
import ida_funcs
import ida_kernwin

# 配置:最大递归深度
MAX_DEPTH = 10

def get_callees(func_ea):
    """获取指定函数调用的所有子函数地址"""
    callees = set()
    func = ida_funcs.get_func(func_ea)
    if not func:
        return callees
        
    for head in idautils.FuncItems(func_ea):
        for ref_ea in idautils.CodeRefsFrom(head, 0):
            target_func = ida_funcs.get_func(ref_ea)
            if target_func and target_func.start_ea == ref_ea:
                if target_func.start_ea != func_ea: # 忽略自身递归
                    callees.add(target_func.start_ea)
    return callees

def recursive_export(ea, current_depth, file_handle, visited, path_stack):
    """递归写入文件"""
    if current_depth > MAX_DEPTH:
        file_handle.write("  " * current_depth + "-> [Max Depth Reached]\n")
        return

    func_name = ida_funcs.get_func_name(ea)
    indent = "  " * current_depth
    
    # 写入当前行
    file_handle.write(f"{indent}-> {func_name} (0x{ea:X})\n")

    # 防止循环调用 (A->B->A)
    if ea in path_stack:
        file_handle.write(f"{indent}   [Cyclic Reference Detected]\n")
        return

    # 获取子函数
    callees = get_callees(ea)
    
    # 将当前节点加入路径栈
    path_stack.add(ea)
    visited.add(ea)

    for callee in sorted(list(callees)):
        recursive_export(callee, current_depth + 1, file_handle, visited, path_stack)
    
    # 回溯时移除
    path_stack.remove(ea)

def main():
    # 获取当前光标地址
    ea = ida_kernwin.get_screen_ea()
    func = ida_funcs.get_func(ea)
    
    if not func:
        print("Error: Please cursor inside a function.")
        return

    start_ea = func.start_ea
    root_name = ida_funcs.get_func_name(start_ea)
    
    # 弹出保存文件对话框
    save_path = ida_kernwin.ask_file(1, f"{root_name}_call_chain.txt", "Save Call Chain Tree")
    
    if not save_path:
        return

    print(f"Exporting recursive xrefs for {root_name}...")
    
    try:
        with open(save_path, "w", encoding="utf-8") as f:
            f.write(f"Recursive Call Chain for: {root_name}\n")
            f.write(f"Start Address: 0x{start_ea:X}\n")
            f.write("=" * 50 + "\n")
            
            # 开始递归
            # path_stack 用于检测当前路径的循环
            # visited 用于记录全局已访问(如果想打印完整树结构,通常只用path_stack防死循环即可,不用visited全局去重,否则树会缺枝)
            recursive_export(start_ea, 0, f, set(), set())
            
        print(f"Done! Saved to {save_path}")
    except Exception as e:
        print(f"Error writing file: {e}")

if __name__ == "__main__":
    main()

方案二:导出整个程序的所有调用关系 (保存为 .csv)

如果您不是想看“某一个”函数的链条,而是想把程序里所有的 Caller(调用者) -> Callee(被调用者) 关系导出来做数据分析,用这个脚本。

Python
import idautils
import ida_funcs
import ida_kernwin

def main():
    # 弹出保存文件对话框
    save_path = ida_kernwin.ask_file(1, "global_xrefs.csv", "Save All Xrefs to CSV")
    if not save_path:
        return

    print("Scanning all functions (this may take a while)...")

    try:
        with open(save_path, "w", encoding="utf-8") as f:
            # 写入 CSV 表头
            f.write("Caller_Address,Caller_Name,Callee_Address,Callee_Name\n")
            
            # 遍历 IDA 识别出的所有函数
            for caller_ea in idautils.Functions():
                caller_name = ida_funcs.get_func_name(caller_ea)
                
                # 遍历该函数内的每一条指令
                for head in idautils.FuncItems(caller_ea):
                    # 查找代码引用 (CodeRefsFrom)
                    for ref_ea in idautils.CodeRefsFrom(head, 0):
                        # 检查目标是否是一个函数
                        callee_func = ida_funcs.get_func(ref_ea)
                        
                        # 过滤逻辑:目标必须是函数的起始地址
                        if callee_func and callee_func.start_ea == ref_ea:
                            callee_name = ida_funcs.get_func_name(ref_ea)
                            
                            # 写入一行数据
                            # 格式: 调用者地址, 调用者名, 被调用者地址, 被调用者名
                            line = f"0x{caller_ea:X},{caller_name},0x{ref_ea:X},{callee_name}\n"
                            f.write(line)

        print(f"Successfully exported global xrefs to {save_path}")

    except Exception as e:
        print(f"Error: {e}")

if __name__ == "__main__":
    main()

使用说明

  1. 复制脚本:选择上面的方案一或方案二。
  2. 运行脚本:
    • 在 IDA 中按 Alt+F7 或 File -> Script file 加载脚本。
    • 或者直接粘贴到下方的 Output 窗口 (Python 模式) 并回车。
  3. 方案一 (递归链):
    • 确保光标在某个函数内。
    • 脚本会弹窗询问保存位置。
    • 生成的文本文件长这样:
      text
      -> main (0x401000)
        -> sub_401050 (0x401050)
          -> printf (0x402000)
          -> sub_401080 (0x401080)
        -> sub_401200 (0x401200)
  4. 方案二 (全局 CSV):
    • 直接运行即可。
    • 生成的 CSV 可以用 Excel 打开,方便筛选和搜索。
    • 我在测试函数时间有点问题

posted on 2025-11-21 05:06  GKLBB  阅读(2)  评论(0)    收藏  举报