AI 取证实战 · 第 06 期(v2.5 新增批量 + 定时)
一个文件夹丢进去,
APK 批量解析自动出报告:
从「采集仪效果不行」到 3 小时无人值守
批量解析 · 批量出报告 · 自定义下载 · 定时任务 · 一个 Flutter 包教会的教训

前阵子同行在微信里跟我说:「目前就是有想法做个自动解析 apk 的,这样有些 apk 分析起来就简单了,采集仪自动解析效果不怎么样」。
这个「有空」,一拖就拖到了第 06 期。本期把工作台 v2.5 新加的批量 + 定时两条链路拆开讲:一整个文件夹的 APK 丢进去不用管,报告自己一份份出来;每天凌晨到点,把新检材自动跑掉。以及,自动解析最容易在哪儿被打脸,我用一个 Flutter 自绘包,把翻车现场和修复过程原样复盘。
① 批量解析的核心:一个目录 → 扫描去重 → 排队 → 逐包动静态 → 自动出报告
② 去重账本:为什么同一个包不会解析两遍(路径+大小+时间戳三重指纹)
③ 批量状态机:排队/运行/完成/失败看着它走,中断不丢进度
④ 自定义下载:批量每包一键「⬇ 报告」,单包导出共用一条链路
⑤ 定时任务:watchDir 一挂,凌晨自动扫新检材,同批互斥不打架
⑥ 实战翻车现场:一个 Flutter 自绘 App 被判「无法自动化」,三层根因 + v3.4 修复
⑦ 批量解析效果怎么才算「靠谱」:语义树、OCR 兜底、如实归因的分层哲学
⑧ 学费清单 + 三步验收法
1|批量解析:一个文件夹丢进去就行
批量解析的形态很简单:指定一个检材目录(watchDir),点「开始批量解析」,剩下的事工作台自己干。它会按这个流程走:
[去重] 每包算 路径+大小+修改时间 指纹 → 账本比对
[排队] 未解析过的进队列,逐包串行出队
[单包] 每个包完整走一遍动静态分析(同单包解析)
[报告] 每包完成自动落盘 「APK报告_包名_随机串.html」
[汇总] 进度/状态/报告路径实时推给前端面板
关键设计:批量不是"重新写一套分析",而是把单包分析原样复用,外面套一层队列。所以批量解析和单包解析的结论口径完全一致,不会出现"单跑准、批量糊"的落差。这正好回应开头那个痛点:自研批量自动解析和采集仪批量解析最大的区别,就在每一份报告用的都是同一套经过验证的引擎,而不是批量的简化版。

(入口在 APK 面板右上角,一个目录 + 一个按钮)
2|去重账本:同一个包为什么不会解析两遍
定时任务和批量最容易踩的坑是重复解析:同一批检材凌晨扫一遍、中午再扫一遍,两小时就白烧了。工作台用一张本地账本(~/.dsh/fx-batch/parsed.json)记下每个包的身份:
"D:\\case\\9月3日\\com.w2obqw50bipl.apk|105465873|1788444415508"
→ 已解析,报告在 …\APK报告_com.w2obqw50bipl.apk_mtms1ra2.html
三个字段缺一不可:
- 路径
——同名文件在不同目录是两回事,各算各的; - 大小 + 修改时间
——同一个文件名被覆盖更新(比如嫌犯端重新打包过一次),指纹就变了,会重新解析。「文件没动过」和「文件被动过」在账本上分得清清楚楚。
账本只增不改,天然可审计:哪天想查"这批检材几点解析的、报告落在哪",直接看 JSON。这也符合取证习惯,自动化的每一步都要能回查。

3|批量状态机:看着它走,中断不丢
批量跑起来最怕黑盒:到底是卡住了还是在干活?面板上每个包都有独立状态:排队 → 解析中 → 完成 / 失败,加上总进度(完成数/总数)。
真正要命的设计决策是串行还是并行。一开始想过"4 个包一起跑"提速,后来否了:
动态分析要抢同一个模拟器、同一个 adb、同一个 arc 服务,并行只会互相踩; 报告路径、账本写入、日志推送全是共享状态,串行才能保证"账本和报告一一对应"。
于是批量严格单队列串行,一个包完整跑完(动+静+报告落盘)才轮到下一个。批量 3 小时无人值守这种事,宁慢勿乱。自动化的第一原则不是快,是每一份产物都可追溯。

(失败包保留原因,不会静默跳过)
4|批量自动出报告 + 自定义下载
每个包解析完,报告自动落盘到指定目录(默认 ~/.dsh/fx-reports/),文件名带包名和随机串,互不覆盖。落盘之后,面板上每个包旁边都有一个「⬇ 报告」按钮:
点一下直接下载这份 HTML 报告,文件名自动取包名( com.w2obqw50bipl.html);下载链路走 RPC 端点 fx/downloadApkReport,在浏览器里用 Blob + a[download] 触发保存。不做成 file:// 直开,因为浏览器会拦本地文件,这是单包导出踩过的坑,批量直接复用同一套;报告目录也可以自定(面板输入框),批量全部写进去,归档即整理。
所以「自动归档」实际上是一句话:报告输出目录 = 你的归档目录,解析完文件已经躺好,剩下的就是下载或拷走。要按案件归档,就把报告目录指到案件文件夹,一份报告一个包名文件,取证卷宗该有的都有。

(报告文件名 = 包名,自动归档不用改文件名)
5|定时任务:新检材丢进目录,到点自己跑
批量是"点一下跑一次",定时是"挂一个目录,到点自己扫"。定时任务表存在 ~/.dsh/fx-batch/schedules.json,每条任务记录:
{ id, name, watchDir, reportDir, dynamic,
cron: "0 3 * * *" // 每天凌晨 3 点 }
三个必须交代的工程细节:
- 到点只跑"新"的
:触发时重新扫描目录,但账本去重保证已经解析过的包绝不再跑——凌晨扫到的都是新丢进来的检材; - 全局互斥
:同一时刻只允许一个批次。定时器触发时若已有批次在跑,直接跳过本次并记日志——防止半夜两点手动批量、三点定时又抢同一个模拟器; - 下次运行时间可见
:每条任务算出 nextRunAt,面板能看见"下次跑在几点",不是黑盒。
定时+去重+互斥三件套合起来,就是一个检材到了就自动进流水线的看守目录:办案过程中同事不断往共享检材目录里丢 APK,工作台每天凌晨自动把新到的全部解析出报告。第二天早上过来,报告已经整整齐齐躺在归档目录里。

(凌晨自动跑完,早上报告已归档)
6|实战翻车现场:一个 Flutter 包,差点让"自动解析"翻车
开头同行说"采集仪自动解析效果不怎么样"。这话我信,因为9 月 3 日批量解析真就翻过一次车。检材目录里有个 com.w2obqw50bipl.apk,批量列表里它那行显示的是:
这是自动解析最难受的失败方式:不是报错,是"跳过",它连挣扎都没挣扎就放弃了。我决定把这个包揪出来查清楚,结果查出三层根因,每一层都值得讲:
根因一:Flutter 的语义不在 text,在 content-desc
这个 App 确实是 Flutter 自绘(libflutter.so + libapp.so + flutter_assets)。但查语义树发现它其实有语义内容,只是全放在 contentDescription 里,text 是空的。而引擎当时只统计 text 字段 → 判定"语义节点少于 5 个" → 误判自绘。uiautomator dump 的证据清清楚楚:text="" 但 content-desc="请检查您的网络是否通畅…ERROR_CODE:V_1"。
根因二:OCR 兜底存在,但从没生效过
引擎里其实早就写了"语义树拿不到 → 截屏 OCR → 按坐标识别表单注册"的兜底代码,但日志里的一行 兜底识别失败:Unexpected end of JSON input 出卖了它。查到最后,问题出在截图用的 adb shell cat 把二进制损坏了:PNG 魔数从 89504e470d0a1a0a 被改成 89504e470d0d0a1a(CRLF/0x1A 转换),图片识别直接失败、OCR 返回空,兜底永远落空,最后降级"自绘"。兜底通道从没生效过,却一直显示"自绘"这个看似合理的结果,这就是自动化里最坑的静默失效。
根因三:App 压根没到注册页,它卡在网络错误页
把 OCR 修好后,截图识别出 App 的真实状态:「请检查您的网络是否通畅或切换网络重试 / 点击屏幕重新连接 / ERROR_CODE:V_1」。这个包的服务端已经不可达(配置资源 404),它连登录页都没到。所谓"无法自动化",不是界面结构问题,而是服务端已经死了。这在取证上反而是一条更关键的结论:这个 App 的后台已停止服务,比"自绘界面"有价值得多。
修复后同一份报告从「自绘界面无法自动化」变成:

(同一个包、同一套引擎,结论从"跳过"变成"服务端不可达")
7|批量自动解析怎么才算"靠谱":分层兜底 + 如实归因
一个 Flutter 包教会的最大一课:自动解析的"效果不怎么样",九成是兜底链断了,而不是自动化本身不行。靠谱的批量解析必须分三层递进,任何一层都不许静默放弃:
同理适用于整个批量链路:失败要留下失败的原因,跳过要留下跳过的理由,归因要比结论诚实。批量列表里"跳过:自绘界面"和"跳过:需要短信验证码"和"网络错误页:App 服务端不可达"是三种完全不同的办案线索,绝不能混成一个"没搞定"。
OCR 还能顺手干一件事:把那些弹窗、协议页、验证码页的文案认出来,很多 App 的真正状态在语义树上根本看不见,但屏幕上写着。自动化的边界,就是"把看不见的东西想办法看见"。
8|学费清单:这期新增的坑
9|怎么知道自己做对了:三步验收
批量+定时这种"无人值守"功能,验收标准比单包更严:
- 一次性验收
:准备 N 个混合样本(普通包、加固包、Flutter 包、伪装包)丢进目录跑一遍,报告 N 份全出、账本 N 条全记、再跑一遍 0 份重复——去重正确; - 中断恢复验收
:跑到第 3 个的时候把工作台杀了重启,继续跑——账本不丢、从第 4 个接着来,前面的报告不受影响; - 定时验收
:把 cron 改成"每分钟"临时跑一次,确认到点触发、目录里新丢的包被扫到、已解析的没动,然后改回正式时间。
10|写在最后
回到开头那段对话。同行说"采集仪自动解析效果不怎么样"——我的回答是:自研批量解析的价值不在"快",而在每一份报告都经过同一套可解释的引擎,每一行结论都能回查到证据。采集仪可能是黑盒,自己搭的工作台不是。
这期工作台从"单包解析"长到"批量 + 定时 + 自动归档"。下一步可能就是在报告里直接给出待办线索清单,或者按案件自动归档目录结构。路径还是那句话:先解决自己手里最烦的那一步,工具就会自己长。
你的检材目录里,还有多少个 APK 在等一份报告?把它们丢进去试试。
写这期的时候,我顺手把工作台底层的 dsh 引擎升了个级(0.1.0 → 0.1.2),结果踩了一串坑:装包 404、启动崩溃、打开页面连环报错,前前后后 12 个。经验已写成避坑记录,并做了一个一键自愈脚本 dsh-selfheal.mjs:
node dsh-selfheal.mjs --check(体检 7 项)→ node dsh-selfheal.mjs --fix(自动修复)
给你提个醒:如果工作台现在用得好好的,非必要不需要升级——升完并没有优化到哪里去,界面几乎没变化,却要付一整晚排雷的账。真到必须升级那天,记得先备份工作台,再跑一遍自愈脚本,能避开大部分坑。
v2.5 发布包(批量解析 · 自动出报告 · 自定义下载 · 定时任务,全包版 175.8MB / 瘦身版 63.1MB)领取:一键三连本文章且关注下方视频号并一键三连视频(点赞、关注、在看)回复 6 一并获取;第 05 期发布包回复 5,第 04 期发布包回复 4。
— 小谢 · AI 取证实战 · 第 06 期 · 完 —
敬请各位大佬关注:小谢取证


敬请各位大佬关注:小谢取证


扫取二维码获取
更多精彩
小谢取证
