用过IDA的朋友,是否用过IDA自带的函数调用跟踪功能,右击一个函数的,点击"List Cross References From"菜单,会弹出一个窗口,列出了这个程序中调用这个函数的所有引用,这个功能确实不错,但是有点太简单。比如,我在调试一个QT5的程序时,遇到了这样一种情况,软件使用时,明显感到点击一个按钮时,会有一个10s的延时,如果我们想修改这个延时时长,我们势必要找到延时函数的地址。具体来说,假定我们通过字符串定位到了某个主函数,但是这个主函数极其复杂,我们单单使用上面的"List Cross References From"功能,我们根本很难定位到延时函数,因为引用实在太多了,一个个人工查找太麻烦,而且你也不知道主函数中到底时哪层子函数调用了延时函数api,这时,我想到了自己编一个python版的ida脚本。
使用方法也很简单,首先定位到主函数起始地址,然后按快捷键ALT + F7
选择我的python脚本

在弹出的对话框中,输入需要查找的StartTimer函数(_ZN13QElapsedTimer5startEv)

可以看到左下角的IDA消息窗口显示找到两个StartTimer函数(_ZN13QElapsedTimer5startEv)
在调试的程序(QT5)目录中,我们也可以详细看到查找到的函数地址信息和函数的调用堆栈情况。


因为这两个记录文件都是追加写入,如需重新记录,请删除这两个文件。
完整代码如下,请保存为.py文件,并用上面的方法打开(ALT + F7):
import idautils
import idaapi
import idc
import ida_kernwin
import sys
import json
from datetime import datetime
target_api = ""
FunLevel = 0
FunLinkStrArr = []
def find_calls_to_api(func_addr,FunLevel,SourceFunName,FunLinkStr):
#函数所在层级+1
FunLevel += 1
# 获取当前函数的名称
func_name = idaapi.get_func_name(func_addr)
# 打印当前函数名称
print(f"正在分析第{FunLevel}层函数: {func_name}({func_addr})")
#赋值函数链
FunLinkStr = FunLinkStr + "\n" + "====>" + "第" + str(FunLevel) + "层函数:" + func_name + "(" + str(func_addr) + ")"
# 遍历函数中的所有指令
for ea in idautils.FuncItems(func_addr):
# 获取当前指令的Mnem(操作码)字符串
mnem = idaapi.print_insn_mnem(ea)
# 检查是否调用了目标API函数
if mnem == "call":
# 获取指令的操作数
operand = idc.print_operand(ea,0)
# 获取调用目标的操作数
target_operand = idc.get_operand_value(ea, 0)
# 获取目标操作数所引用的函数名称
target_function_name = idaapi.get_name(target_operand)
# 出问题时请取消下一句的代码注释,以方便调试
#print("funname: " + target_function_name)
global target_api
if target_function_name == target_api:
print(f"=========已找到目标函数{target_api}=========")
print(f"******在第{FunLevel}层函数({func_name}): 0x{func_addr:X} 中调用了目标API函数 {target_api},地址:0x{ea:X}******")
print("=" * (len("=========已找到目标函数{target_api}=========") + 25))
with open(target_function_name + ".txt","a+")as f:
f.write("在第" + str(FunLevel) + "层函数(" + func_name + "): " + hex(func_addr) + " 中调用了目标API函数 " + target_api + ",地址:" + hex(ea) + "\n")
f.close()
#赋值函数链"
FunLogOut = FunLinkStr + "\n" + "====>" + "第" + str(FunLevel) + "层函数" + "(" + func_name + ")中调用了目标API函数" + target_function_name + " (address:" + hex(ea) + ")为API函数,不进入分析!"
FunLogOut = FunLogOut + "\n" + "============================================================================================================================="
if FunLogOut not in FunLinkStrArr:
FunLinkStrArr.append(FunLogOut)
with open(target_function_name + datetime.now().strftime("%Y%m%d")+ ".txt","a+")as f:
f.write(FunLogOut)
f.close()
FunLogOut = ""
func_ea = idaapi.get_name_ea(idaapi.NT_NONE, target_function_name)
if func_ea != None:
if operand.find("sub") == 0:
#出问题时请取消下一句的代码注释,以方便调试
#print("进入子函数: " + target_function_name)
find_calls_to_api(func_ea,FunLevel,SourceFunName,FunLinkStr)
else:
print("动态操作码,无法静态调试!请使用动态调试如X64")
print(f"第{FunLevel}层函数{func_name}:分析结束!")
#分析函数结束,所在层级-1
FunLevel -= 1
FunLinkStr = ""
def main():
# 目标API函数的名称
global target_api
target_api = ida_kernwin.ask_str("",0,"输入call后的函数名")#"sub_45DFC0"
if target_api == "":
print("未输入任何数据,已退出!")
else:
print(f"输入的函数是:{target_api}")
# 获取当前屏幕地址
current_ea = idaapi.get_screen_ea()
# 获取当前屏幕地址所在的函数的起始地址
func_addr = idaapi.get_func(current_ea).start_ea
# 查找当前函数以及其所有子函数是否调用了目标API函数
SourceFunName = ""
FunLinkStr = ""
find_calls_to_api(func_addr,FunLevel,SourceFunName,FunLinkStr)
print("程序运行完毕!")
if __name__=="__main__":
main()
本文含有隐藏内容,请 开通VIP 后查看