IDA插件开发:用IDAPython自动化分析

用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都可以。但日常逆向分析中,上面这些脚本已经能省很多时间了。