自动化逆向(一):IDAPython脚本编写、批量分析

说实话,干了这么多年逆向,我最怕的就是重复劳动。一个样本分析完了,又来一批类似的——手动点来点去,眼睛都快瞎了。后来我学乖了,开始用 IDAPython 写脚本。嗯,这玩意儿是真的香。

今天我们就聊聊怎么用 IDAPython 把那些机械化的操作自动化掉。说白了,就是让机器替我们干活,我们只负责动脑子。

为什么需要自动化逆向?

你想想看,一个恶意软件家族可能有几十个变种。每个变种都要手动分析一遍?那不得累死。我在项目中遇到过这样的情况:一个勒索软件家族,每天冒出十几个新样本。手动分析?不存在的。

自动化的核心价值就三点:

  • 效率提升——脚本跑一遍,顶你手动点半小时
  • 一致性——机器不会累,不会漏掉关键点
  • 可复现——换个样本,同样的脚本再来一遍

核心思路:把分析经验固化成代码,让脚本替你执行那些「一看就知道该怎么做」的操作。

IDAPython 基础:你得先会这些

IDAPython 其实就是 IDA Pro 的 Python 接口。它把 IDA 内部的各种功能都暴露出来了。我个人习惯把常用操作记在脑子里,写脚本时信手拈来。

获取当前地址和选中内容

import idc
import idautils
import idaapi

# 获取当前光标所在地址
ea = idc.get_screen_ea()
print(f"当前地址: {hex(ea)}")

# 获取选中的地址范围
sel_start, sel_end = idc.read_selection_start(), idc.read_selection_end()
if sel_start != idc.BADADDR and sel_end != idc.BADADDR:
    print(f"选中范围: {hex(sel_start)} - {hex(sel_end)}")

遍历函数和指令

# 遍历所有函数
for func_ea in idautils.Functions():
    func_name = idc.get_func_name(func_ea)
    print(f"函数: {func_name} @ {hex(func_ea)}")
    
    # 遍历函数内的每条指令
    for head in idautils.Heads(func_ea, idc.get_func_attr(func_ea, idc.FUNCATTR_END)):
        mnem = idc.print_insn_mnem(head)
        if mnem == "call":
            # 这里可以记录所有 call 指令
            pass

小技巧:用 idautils.Heads()idautils.FuncItems() 更灵活,因为它可以处理非连续地址范围。

实战:批量提取字符串引用

我记得有一次分析一个木马样本,它用了大量加密字符串。手动提取?那得搞到猴年马月。于是我写了个脚本,一键提取所有字符串引用。

def extract_string_refs():
    """提取所有被引用的字符串及其调用位置"""
    results = []
    
    for seg_ea in idautils.Segments():
        seg_name = idc.get_segm_name(seg_ea)
        if seg_name in [".rodata", ".rdata", ".data"]:
            for head in idautils.Heads(seg_ea, idc.get_segm_end(seg_ea)):
                if idc.get_str_type(head):
                    # 找到字符串,查找交叉引用
                    for ref in idautils.XrefsTo(head):
                        caller_func = idc.get_func_name(ref.frm)
                        results.append({
                            "string": idc.get_strlit_contents(head),
                            "address": head,
                            "caller": caller_func,
                            "caller_addr": ref.frm
                        })
    return results

# 执行并输出
strings = extract_string_refs()
for s in strings[:10]:  # 只显示前10个
    print(f"[{hex(s['address'])}] {s['string']} -> 被 {s['caller']} 调用")

注意:有些字符串是动态解密的,静态提取不到。这时候需要结合动态分析或者模拟执行。我曾经踩过这个坑,以为提取完了,结果漏掉了一半。

批量分析:一次跑完所有样本

自动化逆向的终极形态,就是批量分析。你扔进去一堆样本,脚本一个个打开、分析、生成报告。我建议用以下流程:

  1. 准备样本列表——把要分析的样本路径写到一个文本文件里
  2. 编写主控脚本——循环打开每个样本,执行分析函数
  3. 生成报告——把结果保存成 JSON 或 CSV,方便后续处理
import subprocess
import json
import os

def batch_analyze(sample_list_path, output_dir):
    """批量分析样本"""
    with open(sample_list_path, "r") as f:
        samples = [line.strip() for line in f if line.strip()]
    
    for sample in samples:
        print(f"正在分析: {sample}")
        
        # 调用 IDA 命令行模式执行脚本
        cmd = [
            "idat64",  # 或者 idat32
            "-A",      # 自动模式,不弹对话框
            f"-Sanalyze_single.py",  # 分析单个样本的脚本
            sample
        ]
        subprocess.run(cmd, capture_output=True)
        
        # 读取分析结果
        result_file = os.path.join(output_dir, 
                                   os.path.basename(sample) + ".json")
        if os.path.exists(result_file):
            with open(result_file, "r") as rf:
                data = json.load(rf)
                print(f"  发现 {len(data.get('functions', []))} 个函数")
    
    print("批量分析完成!")

关键点:IDA 命令行模式用 -A 参数可以跳过所有交互对话框。配合 -S 参数指定脚本,就能实现全自动分析。

知识体系:自动化逆向的核心逻辑

下面这张图是我自己总结的自动化逆向流程。说白了,就是三步走:获取信息、分析处理、输出结果。

自动化逆向核心流程 获取信息 遍历函数、指令、字符串 获取交叉引用、类型信息 分析处理 模式匹配、解密、去混淆 识别关键函数、API调用 输出结果 生成JSON/CSV报告 可视化、告警 迭代优化:根据结果调整分析逻辑 IDAPython API idc, idautils, idaapi 命令行批量执行 idat -A -Sscript.py 结果持久化 JSON / SQLite / CSV 避坑指南 我曾经在批量分析时忘记处理异常,结果一个样本崩溃导致整个流程中断。 建议每个样本用 try/except 包裹,并记录错误日志。

脚本模板:一个完整的分析脚本

下面是我常用的脚本模板。你直接拿去改改就能用。

import idc
import idautils
import idaapi
import json
import sys

def analyze_single():
    """分析单个样本的主函数"""
    result = {
        "file": idc.get_input_file_path(),
        "functions": [],
        "strings": [],
        "imports": []
    }
    
    # 1. 收集函数信息
    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
        result["functions"].append({
            "name": func_name,
            "address": func_ea,
            "size": func_size
        })
    
    # 2. 收集字符串
    for seg_ea in idautils.Segments():
        seg_name = idc.get_segm_name(seg_ea)
        if seg_name in [".rodata", ".rdata", ".data"]:
            for head in idautils.Heads(seg_ea, idc.get_segm_end(seg_ea)):
                str_type = idc.get_str_type(head)
                if str_type:
                    result["strings"].append({
                        "address": head,
                        "value": idc.get_strlit_contents(head)
                    })
    
    # 3. 收集导入表
    import_idx = idaapi.get_import_module_qty()
    for i in range(import_idx):
        name = idaapi.get_import_module_name(i)
        result["imports"].append(name)
    
    # 输出结果
    output_path = idc.get_input_file_path() + ".json"
    with open(output_path, "w") as f:
        json.dump(result, f, indent=2)
    
    print(f"分析完成,结果保存到: {output_path}")

if __name__ == "__main__":
    analyze_single()
    idc.qexit(0)  # 退出 IDA

使用方式:把这个脚本保存为 analyze_single.py,然后执行:idat64 -A -Sanalyze_single.py sample.exe

常见问题与解决方案

问题 原因 解决方案
脚本运行到一半卡住 IDA 弹出了交互对话框 确保使用了 -A 参数,并在脚本开头设置 idaapi.set_script_timeout(30)
获取字符串为空 字符串被加密或混淆 先运行解密脚本,或者使用动态分析辅助
批量分析时内存溢出 样本太大或脚本有内存泄漏 每个样本分析完后调用 idc.qexit(0) 彻底退出 IDA 进程
结果文件被覆盖 多个样本输出到同一路径 用样本的 MD5 或文件名作为输出文件名的一部分

嗯,自动化逆向这东西,说白了就是「把经验写成代码」。刚开始可能觉得写脚本比手动分析还慢,但一旦积累起来,后面就爽了。我现在的习惯是:任何重复超过三次的操作,一定写脚本。

记住,自动化不是目的,解放你的时间去做更有价值的事情才是。那些需要动脑子的事情——比如理解攻击者的意图、设计对抗方案——才是我们真正该花时间的地方。

一句话总结:IDAPython 脚本 = 你的逆向经验 × 代码执行力。写一次,用一辈子。


公众号:蓝海资料掘金营,微信deep3321