应用安全 --- IDA脚本 之 导出函数调用链
果您希望将之前“递归调用图”中的数据(即函数调用链)导出为文本或文件,而不是显示图形,可以使用下面的脚本。
我为您准备了两种方案:
- 方案一:导出当前函数的递归调用链(树状结构) —— 对应您之前的“xref graph from”需求,但是输出为文本文件。
- 方案二:导出整个程序的“谁调用了谁”列表(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()
使用说明
- 复制脚本:选择上面的方案一或方案二。
- 运行脚本:
- 在 IDA 中按
Alt+F7或File -> Script file加载脚本。 - 或者直接粘贴到下方的 Output 窗口 (Python 模式) 并回车。
- 在 IDA 中按
- 方案一 (递归链):
- 确保光标在某个函数内。
- 脚本会弹窗询问保存位置。
- 生成的文本文件长这样:
text
-> main (0x401000) -> sub_401050 (0x401050) -> printf (0x402000) -> sub_401080 (0x401080) -> sub_401200 (0x401200)
- 方案二 (全局 CSV):
- 直接运行即可。
- 生成的 CSV 可以用 Excel 打开,方便筛选和搜索。
- 我在测试函数时间有点问题
浙公网安备 33010602011771号