用IDA分析二进制文件的时候,很多操作是重复的——找加密函数、标注关键位置、批量重命名。IDAPython可以把这些操作自动化,写脚本比手动点快十倍。
IDAPython环境
IDA Pro 7.x/8.x内置了Python 3支持(早期版本是Python 2)。三个核心模块:
- idc:IDA命令行函数的Python封装,最基础的API
- idaapi:底层API,功能最全
- idautils:常用工具函数的高层封装
在IDA中按Alt+F7可以运行Python脚本,Shift+F2打开交互式Python控制台。
常用API速查
获取所有函数
import idautils
import idc
import idaapi
# 遍历所有函数
for func_ea in idautils.Functions():
func_name = idc.get_func_name(func_ea)
func_size = idc.get_func_attr(func_ea, idc.FUNCATTR_END) - func_ea
print(f"0x{func_ea:X} {func_name} (size: {func_size})")
获取交叉引用
def get_xrefs_to(addr):
"""获取所有引用了指定地址的位置"""
xrefs = []
for xref in idautils.XrefsTo(addr):
xrefs.append({
'from': xref.frm,
'to': xref.to,
'type': xref.type,
'func': idc.get_func_name(xref.frm),
})
return xrefs
def get_xrefs_from(addr):
"""获取指定地址引用的所有位置"""
return list(idautils.XrefsFrom(addr))
获取字符串
def get_all_strings(min_length=4):
"""获取二进制中的所有字符串"""
strings = []
sc = idautils.Strings()
for s in sc:
if len(str(s)) >= min_length:
strings.append({
'ea': s.ea,
'length': s.length,
'type': s.strtype,
'value': str(s),
})
return strings
# 查找包含特定关键词的字符串
for s in get_all_strings():
if 'password' in s['value'].lower() or 'encrypt' in s['value'].lower():
print(f"0x{s['ea']:X}: {s['value']}")
自动标注加密函数
逆向分析时经常需要找到加密相关的函数。可以通过特征自动识别:
# 加密函数常用的常量
CRYPTO_CONSTANTS = {
# AES S-box首字节
0x637c777b: "可能是AES (S-box)",
# SHA-256初始哈希值
0x6a09e667: "可能是SHA-256",
0xbb67ae85: "可能是SHA-256",
# MD5 T表
0xd76aa478: "可能是MD5",
# DES初始置换
0x3A3B3C3D: "可能是DES相关",
}
def find_crypto_functions():
"""通过特征常量查找加密函数"""
results = []
for func_ea in idautils.Functions():
func_end = idc.get_func_attr(func_ea, idc.FUNCATTR_END)
# 扫描函数内的立即数
ea = func_ea
while ea < func_end:
# 检查每条指令的操作数
for op_idx in range(2):
op_type = idc.get_operand_type(ea, op_idx)
if op_type == idc.o_imm: # 立即数
imm_val = idc.get_operand_value(ea, op_idx) & 0xFFFFFFFF
if imm_val in CRYPTO_CONSTANTS:
func_name = idc.get_func_name(func_ea)
hint = CRYPTO_CONSTANTS[imm_val]
results.append((func_ea, func_name, hint))
# 添加注释
idc.set_cmt(ea, hint, 0)
break
ea = idc.next_head(ea, func_end)
return results
# 运行并标注
for addr, name, hint in find_crypto_functions():
print(f"0x{addr:X} {name}: {hint}")
# 给函数加上前缀标记
if not name.startswith("crypto_"):
idc.set_name(addr, f"crypto_{name}", idc.SN_FORCE)
批量重命名
分析过程中经常需要根据某种规则批量重命名函数:
def rename_by_string_refs():
"""根据函数内引用的字符串来推测函数名"""
renamed_count = 0
for func_ea in idautils.Functions():
func_name = idc.get_func_name(func_ea)
# 只处理IDA自动命名的函数(sub_XXXXX)
if not func_name.startswith("sub_"):
continue
func_end = idc.get_func_attr(func_ea, idc.FUNCATTR_END)
# 收集函数内引用的字符串
ref_strings = []
ea = func_ea
while ea < func_end:
for xref in idautils.XrefsFrom(ea):
str_val = idc.get_strlit_contents(xref.to)
if str_val:
ref_strings.append(str_val.decode('utf-8', errors='ignore'))
ea = idc.next_head(ea, func_end)
if ref_strings:
# 用第一个有意义的字符串作为函数名提示
hint = ref_strings[0][:30].replace(' ', '_').replace('.', '_')
new_name = f"fn_{hint}"
if idc.set_name(func_ea, new_name, idc.SN_NOWARN):
renamed_count += 1
print(f"0x{func_ea:X}: {func_name} -> {new_name}")
print(f"共重命名 {renamed_count} 个函数")
导出分析结果
把分析结果导出成JSON,方便后续处理或生成报告:
import json
def export_analysis(output_path):
"""导出完整的分析结果"""
result = {
'functions': [],
'strings': get_all_strings(),
'imports': [],
'exports': [],
}
# 导出函数信息
for func_ea in idautils.Functions():
result['functions'].append({
'address': f"0x{func_ea:X}",
'name': idc.get_func_name(func_ea),
'size': idc.get_func_attr(func_ea, idc.FUNCATTR_END) - func_ea,
'xrefs_to_count': len(list(idautils.XrefsTo(func_ea))),
})
# 导出导入表
for i in range(idaapi.get_import_module_qty()):
module_name = idaapi.get_import_module_name(i)
entries = []
def imp_cb(ea, name, ordinal):
entries.append({'ea': f"0x{ea:X}", 'name': name, 'ordinal': ordinal})
return True
idaapi.enum_import_names(i, imp_cb)
result['imports'].append({'module': module_name, 'entries': entries})
with open(output_path, 'w') as f:
json.dump(result, f, indent=2)
print(f"分析结果已导出到 {output_path}")
IDAPython能做的事远不止这些——编写loader、processor module、插件UI都可以。但日常逆向分析中,上面这些脚本已经能省很多时间了。