自动化逆向框架搭建:Python脚本批量分析、Frida脚本管理、Docker化部署

说实话,做逆向分析最怕什么?不是遇到强壳,不是遇到混淆,而是——重复劳动。每次拿到一个APK,都要手动解包、查壳、hook关键函数、记录日志……这些事情做一次两次还行,做十次二十次,你肯定会想吐。

所以我一直在琢磨,怎么把这些流程自动化。今天聊的这套自动化逆向框架,说白了就是把三样东西揉在一起:Python做批量分析、Frida做动态插桩、Docker做环境隔离。我个人习惯把这套东西叫“逆向流水线”,你想想看,流水线一跑,你喝杯咖啡的功夫,结果就出来了。

核心思路:用Python调度Frida脚本,在Docker容器里批量跑APK分析任务。一次配置,到处复用。

一、Python脚本批量分析

Python在逆向里的角色,我理解就是“胶水”。它不直接做逆向,但它能把所有工具串起来。我最早写Python批量脚本,是为了解决一个很实际的问题:同时分析50个APK的入口点

手动做?每个APK用jadx打开,找AndroidManifest.xml,复制入口类名……50个下来,手都酸了。用Python呢?几行代码搞定。

import os
import subprocess
import zipfile
from xml.etree import ElementTree

def extract_entry_points(apk_path):
    """从APK中提取所有Activity入口点"""
    with zipfile.ZipFile(apk_path, 'r') as z:
        manifest_data = z.read('AndroidManifest.xml')
    
    # 这里用AAPT2解析二进制XML
    result = subprocess.run(
        ['aapt2', 'dump', 'xmltree', apk_path, '--file', 'AndroidManifest.xml'],
        capture_output=True, text=True
    )
    
    # 解析输出,提取activity标签
    entries = []
    for line in result.stdout.split('\n'):
        if 'activity' in line and 'android:name' in line:
            name = line.split('=')[-1].strip().strip('"')
            entries.append(name)
    
    return entries

# 批量处理
apk_dir = './apks/'
for apk in os.listdir(apk_dir):
    if apk.endswith('.apk'):
        points = extract_entry_points(os.path.join(apk_dir, apk))
        print(f'{apk}: {points}')

这段代码看起来简单,但我在项目里吃过亏——直接读ZIP里的AndroidManifest.xml是二进制的,不是纯文本。所以必须用aapt2或者AXMLPrinter2来解析。嗯,这里要注意,别踩这个坑。

我的经验:Python批量脚本里,最耗时的往往是APK解包和反编译。建议用multiprocessing做并行处理,4-8个进程同时跑,效率能提升3-5倍。

二、Frida脚本管理

Frida脚本多了以后,管理就成了大问题。我见过有人把所有hook逻辑写在一个几百行的JS文件里,想改一个hook点,得翻半天。这显然不行。

我的做法是:按功能模块拆分Frida脚本,然后用一个主调度脚本来加载。

// 目录结构
// frida_scripts/
//   ├── common/
//   │   ├── logger.js        # 日志输出统一管理
//   │   └── utils.js         # 通用工具函数
//   ├── hooks/
//   │   ├── crypto.js        # 加密函数hook
//   │   ├── network.js       # 网络请求hook
//   │   └── storage.js       # 数据存储hook
//   └── loader.js            # 主加载器

// loader.js 示例
function loadScripts() {
    // 加载通用模块
    const common = ['logger', 'utils'];
    common.forEach(mod => {
        const code = readFile(`./common/${mod}.js`);
        eval(code);
    });
    
    // 根据配置加载hook模块
    const config = JSON.parse(readFile('./config.json'));
    config.enabled_hooks.forEach(hook => {
        const code = readFile(`./hooks/${hook}.js`);
        eval(code);
        log(`[Loader] Hook模块 ${hook} 已加载`);
    });
    
    log('[Loader] 所有脚本加载完成');
}

loadScripts();

为什么要这么拆分?因为不同的分析场景,需要的hook点不一样。分析加密算法时,我只想hook crypto相关的函数;分析网络协议时,我只关心socket和http。模块化之后,改配置就行,不用改代码。

我曾经踩过的坑:Frida脚本里用eval()加载模块时,要注意作用域问题。每个模块里的变量默认是全局的,容易冲突。建议用IIFE(立即执行函数)包裹每个模块,或者用Module.export的方式暴露接口。

三、Docker化部署

环境问题,是逆向工程里最让人头疼的事情之一。你写好的脚本,换台机器就跑不起来了——Frida版本不对、Python库缺失、adb连不上……我经历过太多次这种事了。

Docker化部署,就是为了解决这个问题。把整个逆向环境打包成一个镜像,到哪都能跑。

# Dockerfile
FROM ubuntu:20.04

# 安装基础依赖
RUN apt-get update && apt-get install -y \
    python3 python3-pip \
    adb \
    git \
    wget \
    && rm -rf /var/lib/apt/lists/*

# 安装Frida
RUN pip3 install frida-tools

# 复制脚本
WORKDIR /workspace
COPY ./scripts/ ./scripts/
COPY ./frida_scripts/ ./frida_scripts/

# 设置入口点
ENTRYPOINT ["python3", "./scripts/main.py"]

构建镜像:

docker build -t android-reverse-engineer:1.0 .

运行容器:

docker run --rm -v $(pwd)/apks:/workspace/apks \
    -v $(pwd)/output:/workspace/output \
    android-reverse-engineer:1.0 \
    --apk-dir ./apks --output-dir ./output

你看,通过-v挂载卷,APK文件和输出结果都在宿主机上,但分析环境完全隔离在容器里。换机器?把镜像拉下来,直接跑。

Docker化的三个好处:

  • 环境一致性:开发环境和生产环境完全一样
  • 快速部署:新机器上只需要装Docker,不用装其他任何东西
  • 资源隔离:每个分析任务跑在独立容器里,互不干扰

四、整体架构与流程

这三部分怎么串起来?我画了一张图,你看一眼就明白了。

自动化逆向框架整体架构 输入层 APK文件 / 批量任务 Python调度层 批量分析 / 任务分发 Docker执行层 环境隔离 / 并行执行 Docker容器内部 Frida Server Frida脚本管理 ADB连接 输出层:结构化报告 / Hook日志 / 脱壳文件 / 分析结果 Python调度 → Docker执行 → Frida Hook → 结果输出,全流程自动化

这张图展示的就是整个自动化逆向框架的流程。从输入APK开始,Python调度层负责任务分发,Docker执行层提供隔离环境,内部跑Frida脚本做动态分析,最后输出结构化结果。

五、实战:一键批量脱壳

说了这么多理论,来点实际的。我写过一个一键批量脱壳的脚本,结合了上面说的所有技术。

# batch_unpack.py
import os
import json
import subprocess
from multiprocessing import Pool

def unpack_single(apk_info):
    """单个APK脱壳任务"""
    apk_path, output_dir = apk_info
    apk_name = os.path.basename(apk_path).replace('.apk', '')
    
    # 1. 检测壳类型
    shell_type = detect_shell(apk_path)
    
    # 2. 选择对应的Frida脚本
    script_map = {
        '360': 'unpack_360.js',
        'ali': 'unpack_ali.js',
        'tencent': 'unpack_tencent.js',
        'baidu': 'unpack_baidu.js',
        'common': 'unpack_common.js'
    }
    
    script = script_map.get(shell_type, 'unpack_common.js')
    
    # 3. 在Docker中执行脱壳
    cmd = [
        'docker', 'run', '--rm',
        '-v', f'{apk_path}:/workspace/target.apk',
        '-v', f'{output_dir}:/workspace/output',
        'android-reverse-engineer:1.0',
        '--script', f'./frida_scripts/{script}',
        '--apk', '/workspace/target.apk',
        '--output', '/workspace/output'
    ]
    
    result = subprocess.run(cmd, capture_output=True, text=True)
    
    return {
        'apk': apk_name,
        'shell': shell_type,
        'status': 'success' if result.returncode == 0 else 'failed',
        'output': output_dir
    }

# 批量执行
if __name__ == '__main__':
    apk_dir = './apks/'
    output_base = './output/'
    
    tasks = []
    for apk in os.listdir(apk_dir):
        if apk.endswith('.apk'):
            output_dir = os.path.join(output_base, apk.replace('.apk', ''))
            os.makedirs(output_dir, exist_ok=True)
            tasks.append((os.path.join(apk_dir, apk), output_dir))
    
    # 并行处理
    with Pool(processes=4) as pool:
        results = pool.map(unpack_single, tasks)
    
    # 生成报告
    report = {
        'total': len(results),
        'success': sum(1 for r in results if r['status'] == 'success'),
        'failed': sum(1 for r in results if r['status'] == 'failed'),
        'details': results
    }
    
    with open(os.path.join(output_base, 'report.json'), 'w') as f:
        json.dump(report, f, indent=2)
    
    print(f'批量脱壳完成:成功{report["success"]}个,失败{report["failed"]}个')

这段代码的核心逻辑很简单:遍历APK目录,对每个APK检测壳类型,然后丢进Docker容器里跑对应的脱壳脚本。最后生成一份JSON报告,一目了然。

我的建议:脱壳脚本不要写死,做成可配置的。比如在config.json里维护壳类型和脚本的映射关系,这样新壳出来,加一行配置就行,不用改代码。

六、避坑指南

这套框架我用了两年多,踩过的坑不少。挑几个典型的说说:

  • Docker里跑ADB的问题:容器里要连真机或模拟器,需要把宿主机的USB设备映射进去。用--privileged参数或者-v /dev/bus/usb:/dev/bus/usb挂载USB设备。
  • Frida版本兼容性:不同Android版本对Frida版本有要求。我建议在Docker镜像里装多个版本的Frida,通过环境变量切换。
  • 日志太多怎么办:Frida脚本里如果hook了高频调用的函数,日志量会爆炸。加个采样率控制,比如每100次调用才输出一次日志。
  • 任务超时处理:有些APK脱壳会卡死。在Python调度层加超时控制,超过5分钟自动杀掉容器,标记为失败。

我曾经犯过的错:一开始没做超时控制,结果有个APK脱壳跑了整整8小时,把服务器资源全吃光了。从那以后,我所有批量任务都加了超时和资源限制。

七、总结

自动化逆向框架,说白了就是把重复劳动交给机器,把精力留给真正需要思考的部分。Python做调度、Frida做hook、Docker做环境,这三样东西组合起来,能解决90%的批量分析需求。

你想想看,以前手动分析一个APK要半小时,现在框架一跑,50个APK半小时搞定。这就是自动化的价值。

最后说一句:框架是死的,思路是活的。别照搬我的代码,理解背后的设计思路,然后根据你自己的需求去改造。这才是真正的“框架”。

核心要点回顾:

组件 作用 关键点
Python 批量调度、任务分发 多进程并行、超时控制
Frida 动态插桩、Hook逻辑 模块化管理、配置驱动
Docker 环境隔离、快速部署 镜像打包、卷挂载、资源限制

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