自动化逆向框架搭建: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执行层提供隔离环境,内部跑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 | 环境隔离、快速部署 | 镜像打包、卷挂载、资源限制 |