1. 为什么要给逆向工程套一层“Agent 工作流”
1.1 逆向工程的真实痛点:不是不会分析,而是流程太碎
做了几年 Android 逆向,我越来越觉得这行真正的门槛不在“会不会反编译”,而在“能不能把一条完整的分析链路跑通”。拿一个 APK 来说,从拿到手到产出结论,你至少得经过解包、看 manifest、查签名、识别加固、反编译 Java/Kotlin 代码、翻 native 库、跑动态 hook、抓网络包,最后再把散落各处的线索汇成一份判断。每一步单独拎出来都不算难,难的是这些步骤之间的衔接。
以前我接一个 App 的授权安全测试,光是把 apktool、jadx、frida 这些工具按顺序跑一遍,再手动记录中间产物,就要搭进去五六个小时。中间稍微有点干扰,比如某个目录权限不对、某个 dex 反编译到一半内存爆了,整条流水线就得停下来重新调整。更头疼的是,这些“过程知识”全在我脑子里,换个同事来接手,基础信息又要重新摸一遍。
后来我试过写纯 shell 脚本把命令串起来。能跑,但非常僵硬——遇到加固壳要换分支,遇到资源混淆要改参数,脚本里全是 if else 的补丁,维护成本比人工跑命令还高。我意识到,逆向工程缺的不是“更快的单点工具”,而是一个能根据现场情况动态调整流程的“总调度”。这正是 AI Agent 擅长的事。
1.2 apk-reverse 想解决的三个问题
于是就有了这个项目:apk-reverse。它不是一个新逆向工具,而是一套把 Android 逆向工程标准化、模块化之后,交给 AI Agent 去编排执行的完整工作流。
这套工作流主要解决三个问题。第一个是流程碎片化的问题。我按逆向工程的经验把分析过程拆成阶段,每个阶段封装成可独立调用的工具步骤,Agent 按需调用,不再需要我在终端里一条条敲命令。
第二个是经验沉淀与复用的问题。以前的经验存在脑子里,现在我把“什么时候该跑 jadx、什么时候该上 frida、看到什么特征该怀疑加固”这些判断逻辑写成了 Agent 的决策提示词和工具描述,换个人来用这套工作流,也能产出接近老手水准的分析过程。
第三个是结果可追溯问题。每次 Agent 执行任务,所有命令、输出、中间文件、最终结论都会落到结构化目录里,随时可以回溯。对于一个需要写报告的安全测试来说,这比“我凭印象觉得这里有问题”要靠谱得多。
1.3 整体架构:Agent 负责决策,工具层负责执行
apk-reverse 的整体架构分三层。最上层是 Agent 大脑,负责理解用户意图、拆解任务、选择下一步动作;中间层是任务编排层,把“分析 AndroidManifest”“反编译代码”“追踪某方法调用”这类逆向动作定义成标准化的任务单元,每个单元有清晰的输入输出说明,Agent 通过 tool calling 的方式调用它们;最底层是执行层,封装 apktool、jadx、frida、objection 等实际工具,同时负责文件路径管理、日志采集、结果格式化。
这个三层设计的好处是把“决策”和“执行”彻底解耦。Agent 不需要懂 apktool 的具体参数,它只需要知道“分析 manifest 信息”这个任务能返回什么;执行层也不关心 Agent 为什么调用自己,它只要把结果按约定格式吐出来就行。这样两边都能独立演进——我换更好的大模型不会影响执行层,升级逆向工具版本也不用改 Agent 的决策逻辑。
2. 工作流阶段拆解与任务编码设计
2.1 静态信息采集:从 APK 文件到 App 画像
一个 APK 落到工作流里,第一个阶段是静态信息采集。这个阶段的产出不是代码,而是一份 App 画像:包名、版本号、最低支持版本、申请的权限列表、四大组件清单、签名信息、是否有多 dex、有没有 native 库、manifest 里有没有可疑的导出组件。
这些信息的获取方式我在工作流里拆成了三个任务单元。第一个是基础元数据读取,直接用 aapt 或 apktool 的解析能力拿 manifest 里最表层的信息;第二个是组件与权限提取,重点记录 activity、service、receiver、provider 的完整名称以及 exported 属性;第三个是签名与加固特征识别,检查 APK 的签名方案、是否存在 libjiagu.so、libshell.so 这类加固特征文件,或者 classes.dex 个数异常的情况。
之所以把静态采集放在最前面,是因为后续所有决策都建立在“这是个什么样的 App”之上。比如发现有多 dex,Agent 就会在反编译阶段自动选择批量处理模式;发现疑似加固,就不再浪费时间走静态反编译主流程,而是直接进入动态分析环节。
2.2 反编译与代码结构分析:让 AI 看懂 smali 之前先看懂工程
反编译阶段是整个工作流里最容易出问题、也最依赖经验的部分。对于无加固的 App,我一般用 jadx 直接反编译成 Java 代码,再让 Agent 基于面向对象的视角去分析逻辑;对于带壳或者需要精细修改的场景,则用 apktool 解包后看 smali,再配合 baksmali 处理多 dex。
在这个阶段,工作流不只是一股脑把代码反编译完丢给 LLM,而是做了两件额外的事。第一件是“工程结构摘要”——反编译完成后,先把代码目录树、关键文件列表、入口 Activity、Application 类这些信息提取出来,让 Agent 在分析具体代码之前先建立宏观认知。第二件是“代码索引切分”——一个大项目的代码量动辄上万行,直接塞给 LLM 必然爆上下文,所以我按“类/方法/字符串引用”为单位做索引和切片,Agent 在分析某个功能时只拉取相关的代码片段,而不是全文阅读。
我还特意加了一个“可疑点预扫描”任务,用正则和特征库先筛一遍代码里的高危模式,比如 WebView 的 setJavaScriptEnabled(true)、明文存储的 SharedPreferences、硬编码的密钥字符串。这些预扫描结果会成为 Agent 后续深入分析的线索清单,相当于把一个有经验的分析师“先扫一眼”的习惯固化进了工作流。
2.3 动态行为分析:模拟器与真机上的采集方案
静态分析只能告诉你“代码长什么样”,动态分析才能告诉你“跑起来到底干了什么”。这个阶段我设计了基于 frida 的脚本注入、HTTP 流量抓取、文件行为监控三类任务单元。
基于 frida 的部分是最核心的。工作流里预置了常见 hook 脚本模板,比如 Hook Activity 生命周期、Hook 加解密函数的入参与出参、Hook 网络库的请求响应。Agent 可以根据静态分析阶段的发现,动态生成一段 frida 脚本,然后通过 USB 或无线 adb 注入到指定设备上。这个过程完全由 Agent 的决策驱动——它在静态分析里看到某个可疑的加密调用,下一步就会自然选择“对加密函数下 hook”这个动作。这也正是普通脚本无法做到的灵活性。
流量抓取和文件监控相对简单,前者通过 iptables 重定向到 mitmproxy,后者通过监控 /storage/emulated/0/Android/data/ 包名下文件的变化来追踪 App 写入行为。这两个任务在分析某些“看起来啥也没干”的 App 时特别有用,很多恶意样本就是靠偷偷写文件、连固定 IP 来干活的。
2.4 报告生成与知识沉淀:把分析结果变成可读文档
最后一个阶段是报告生成。很多搞技术的人不重视这一步,但实际工作中,一份分析报告才是交付物。apk-reverse 的工作流在最后会把所有阶段的产物汇总,按“基础信息、代码分析、动态行为、风险结论、修复建议”的结构生成 Markdown 报告。
这个过程中我最满意的是一个叫“结论溯源”的机制。报告里每一条判断,比如“该 App 存在 WebView 远程代码执行风险”,后面都会附上证据链——哪个文件、哪一行代码、对应的反编译片段、hook 到的调用栈。这样一来,报告里的结论不是模型“编”出来的,而是有实打实的分析依据支撑,可以直接拿去做漏洞评级和整改沟通。
3. 工具选型解析与参数实操细节
3.1 工具链选型:apktool、jadx、frida、objection 怎么组合
很多人在工具选型上容易犯“什么热门用什么”的毛病。我在这套工作流里的选型原则很简单:稳定性优先、命令行友好、能跨平台。最终敲定的是下面这套组合。
| 工具 | 用途 | 选型理由 |
|---|---|---|
| apktool | 资源解包与 smali 解码 | 逆向工程标配,支持多 dex,修改后重打包方便 |
| jadx | Java/Kotlin 反编译 | 输出可读性高,内置反混淆部分手段,批量反编译稳定 |
| baksmali/smali | smali 汇编与回编译 | 配合 apktool 处理代码级修改和精确调试 |
| frida | 动态插桩 | 跨平台且支持 JS/Python 接口,Agent 生成脚本方便 |
| objection | 运行时探索 | 基于 frida,免 root 环境下做内存搜索、类枚举很顺手 |
| mitmproxy | 网络抓包 | 脚本化接口好,能对接 Agent 做流量特征分析 |
这套组合基本覆盖了静态、动态、网络三个维度,而且每个工具都能在无交互的命令行环境下稳定工作——这点对 Agent 执行特别重要,因为 Agent 无法像人一样去点 GUI 按钮。
实操中我会特别提醒一点:apktool 和 jadx 的 Java 版本兼容性是个老坑。apktool 2.x 对 JDK 版本有要求,jadx 对高版本 JDK 也可能有模块访问警告。我的工作流里统一固定了 JDK 17 作为运行环境,避免工具之间互相踩踏。
3.2 任务切换与上下文管理:Agent 不炸内存的关键
AI Agent 跑起来之后最大的敌人不是工具不稳定,而是上下文爆炸。一个大型 App 反编译出来的源码可能有几百 MB,就算再强的大模型也吃不下,更不用说我在动态分析阶段还会持续灌入 frida 的输出和抓包结果。
我的应对策略是“任务切换 + 上下文压缩”双管齐下。任务切换是指每个任务单元执行时,Agent 只接收自己的输入数据,其他阶段的数据全部存放在工作目录文件里,不入对话上下文。比如代码分析任务只接收代码索引文件和预扫描结果,而不是整个反编译目录。
上下文压缩则依赖一套“摘要-索引-按需拉取”机制。每个任务单元执行完后,都会生成一段结构化摘要,写入一个叫 memory.json 的文件;Agent 在做全局决策时只读摘要,需要细节时再去读对应文件。这样一轮分析跑下来,即使实际处理了几百 MB 的数据,Agent 的上下文占用始终保持在可控范围。
关于“AI Agent 怎么扛并发”这个话题,我的经验是:逆向分析这种重 IO 重工具的活儿,短时间并行不如把单条链路跑稳。apk-reverse 目前对同一台设备的操作默认串行,因为 frida 对同一进程的插桩并发很容易互相干扰;跨设备分析我会按设备维度走多实例并行,而不是在同一实例里开一堆 goroutine。
3.3 为什么我把命令行执行器用 Rust 重写了一遍
apk-reverse 最初的任务执行器是用 Python 写的,跑得通,但有几个毛病:Python 子进程管理复杂,信号处理容易留下僵尸进程;打包分发要带一堆依赖;最烦的是,Agent 在极端情况下会生成异常的命令参数,Python 解析起来防御性太差。
后来我把执行层的关键组件用 Rust 重写,主要是看中它的进程管理能力和安全的字符串处理。Rust 的 std::process 对子进程的创建、超时控制、管道读写都比 Python 干净得多,而且它天然防路径遍历和参数注入——对“AI 生成的命令”这种不可信输入,Rust 的严格类型和所有权模型能帮我在编译期挡住一大批问题。
这个选择带来的直接收益是:其一,执行层变成了单一二进制文件,部署只需要一份 Rust 编译产物;其二,Android 10 及以上分区存储导致的路径问题(比如 /storage/emulated/0/Android/data/ 目录访问受限)可以在 Rust 层直接做归一化处理,不用 Agent 去自己猜。
4. 一次完整实跑记录:从 APK 到审计报告
4.1 跑一个 30MB 的 APK 需要多久
为了验证工作流靠谱,我拿一个朋友授权测试的 30MB 电商类 App 完整跑了一遍。这个 App 无加固、单 dex、用了常见的 OkHttp 网络库,算是难度中等的样本。
整个流程从凌晨一点半开始,到凌晨三点十二分结束,一共耗时约 100 分钟。其中静态信息采集约 3 分钟,反编译和代码索引约 28 分钟(主要耗在 jadx 反编译上),动态分析和 hook 约 45 分钟,报告生成不到 2 分钟,剩下时间是各阶段的等待和重试。
说实话这个速度比我想象的快。以前我跟人工做同样的分析,光是梳理组件和权限列表就能磨蹭半小时,而 Agent 在静态阶段几乎没有停顿地按顺序执行完了所有任务单元。动态阶段最慢,因为 frida 脚本注入后需要耐心等待触发场景,Agent 在等待期间会再翻一遍代码找可疑调用点,这个人机协作的节奏还挺自然。
4.2 关键步骤与命令行输出实录
下面是我截取的部分关键执行记录。首先是静态采集阶段,Agent 调用了解包任务单元:
$ apktool d -f -o /tmp/work/1_extract/ sample.apk I: Using Apktool 2.9.0 I: Copying assets and libs... I: Decoding AndroidManifest.xml with resources... I: Decoding file-resources... I: Loading resource table... I: Decoding values/*.xmls... I: Done紧接着 Agent 读取了 AndroidManifest 的关键片段,然后决定进入反编译阶段:
$ jadx -d /tmp/work/2_decompile/ --show-bad-code --no-imports sample.apk INFO - loading ... ERROR - finished with errors, count: 3有意思的是,jadx 报了 3 个错误,但 Agent 的处理方式是记录错误、继续分析成功反编译的部分,并在最终报告中标注“存在解析失败的代码块”。这种“容错继续”的行为模式,恰恰是脚本写死很难实现的——脚本要么直接失败退出,要么忽略错误,而 Agent 能基于上下文判断要不要停下。
动态分析阶段,Agent 根据预扫描结果发现 App 里有一处可疑的字符串加密操作,于是生成了一段 hook 脚本:
$ frida -U -f com.example.shop -l /tmp/work/3_dynamic/hook_decrypt.js [Android Emulator] onLoad: com.example.shop [Android Emulator] Calling decrypt('U2FsdGVkX1/...') => 'platform=android&token=...'这个输出直接成为报告中“密钥硬编码风险”结论的证据链。前后不到一小时,Agent 就把一个需要人工反复尝试才能定位的问题直接挖了出来。
4.3 过程中踩到的路径与权限坑
第一次完整跑下来,我并未一帆风顺。开头就撞上了 Android 分区存储引发的老问题:动态阶段想在真机上读取 App 私有目录下的文件,结果 shell 权限根本访问不了 /data/data/ 下面的路径,只能在 /storage/emulated/0/Android/data/ 的公共目录碰碰运气。
我的解决方式是在工作流里加了一个“设备环境探活”前置任务:每次开始动态分析前,先检测设备的 Android 版本、root 状态、frida-server 是否在运行,然后根据这些信息动态调整后续任务的执行方式。如果没 root 就用 objection 的免 root 模式配合 frida-server 的映射方式进行插桩,有 root 就直接走 /data/local/tmp/ 放工具。这个探活任务解决了我后续 90% 的动态阶段连不上设备问题。
另一个坑是解包时的非法路径字符。某些 App 的资源文件里会带一些特殊字符,在 Windows 上解包会直接失败,Linux/macOS 上则表现为文件名乱码。我在执行层做了一个文件名清洗的通用处理,把不可打印字符和路径分隔符统一替换掉,避免 Agent 因为一个异常命名文件就中断整个流程。
4.4 结果文件组织与二次利用
跑完之后,工作目录里总共生成了约 2.3GB 的中间产物。目录结构是这样的:
/tmp/work/ │── 1_extract/ # apktool 解包结果 │── 2_decompile/ # jadx 反编译代码 │── 3_dynamic/ # frida 脚本和运行日志 │── 4_report/ # 最终 Markdown 报告 │── memory.json # 各阶段摘要信息 └── logs/ # 完整操作日志我最看重的是 logs 目录,里面完整记录了 Agent 每次调用的工具、传入的参数和执行结果。这些日志不仅方便排错,也相当于一份“自动生成的审计追溯文件”。下次如果要对同一个 App 做回归分析,直接读 memory.json 就能快速定位分析到了哪个阶段,不需要重新跑一遍完整流程。
5. 常见问题与排查速查表
5.1 解包与反编译阶段问题
这个阶段我遇到最多的就是“apktool 解包报错”和“jadx 内存溢出”两个问题。
apktool 解包报错时,先看是不是 Java 版本不对。apktool 2.9 需要 JDK 8 以上,但某些加壳样本和较老版本有兼容问题,我固定用 JDK 17 后基本没再踩坑。如果报错信息里有 “Unsupported version” 类似字样,优先检查 Java 版本;如果是资源解码报错,多半是文件损坏或特殊字符导致,可以加 --only-main-classes 或者 --no-src 选项跳过资源解码。
jadx 内存溢出则更常见。我在 4GB 内存的设备上跑稍微大一点的 APK,经常收到 OutOfMemoryError。解决方式是给 jadx 显式设置 JVM 堆大小:
$ JVM_ARGS="-Xmx4g" jadx -d output/ sample.apk如果单 dex 文件过大,我会考虑用 dex2jar 配合 JD-GUI 做局部替代分析,或者直接走 smali 层先用 baksmali 看逻辑。还有一个经验是:jadx 的 --show-bad-code 参数一定得开着,否则反编译失败的代码会被静默跳过,后续分析会漏掉关键逻辑。
5.2 动态分析阶段问题
动态阶段的问题主要集中在 frida 连接不上、hook 脚本注入后不生效、抓包流量不完整这三件事上。
frida 连接不上的排查路径比较固定。第一步确认设备端 frida-server 版本和电脑端 frida 版本一致,版本不匹配是头号问题;第二步检查 adb 是否能看到设备,USB 调试授权弹窗是否被 App 挡住;第三步确认 frida-server 有没有 root 权限,没 root 时要用 --no-pause 配合特殊启动方式。我在工作流里把这三步做成了“设备探活”任务,Agent 会先跑探活再决定具体方案,实测下来连接成功率从不到五成提升到了九成以上。
hook 脚本注入后不生效,最常见的原因是注入时机太晚或者太早。晚了——目标类已经被系统加载,hook 错过了最佳时机;太早了——类还没加载,Java.use 会报找不到类。我在脚本模板里统一使用 Java.perform 配合延迟和重试机制,确保各类加载后再附加 hook。
抓包流量不完整一般不是工具问题,而是目标 App 启用了证书校验。简单场景用 frida 的 SSL pinning bypass 脚本可以绕过去,复杂场景需要先在代码里定位 OkHttp 的证书校验逻辑,再针对性 hook 校验函数。这套流程我会让 Agent 分两步执行:先尝试通用绕过,不管用再从代码层面精准定位,不盲目堆方案。
5.3 AI Agent 编排问题
工具层面的问题排查完,还得面对 AI Agent 自身引发的“故障”。我用下来发现有三类问题最需要留意。
第一类是 Agent 的“幻觉命令”——它可能凭空生成一个不存在的参数。我的应对方式是执行层做严格的白名单校验,Rust 执行器只允许调用预设的工具集合和参数格式,其他统统拒绝并返回错误信息。这样就算模型抽风,也只会反馈一个错误结果,而不会真的执行莫名其妙的系统命令。
第二类是 Agent 陷入死循环。比如某个 hook 一直不触发,Agent 可能反反复复重试同样的操作。我在编排层加了“动作去重与频控”机制,同一个任务单元连续执行超过三次且结果相同,就停止重试并记录一条“疑似无响应”的结论,让用户介入。
第三类是上下文污染。Agent 在处理过程中可能把某个阶段产生的低置信度信息带进下一阶段,在最终报告里写一些没有明确证据支撑的结论。我最后的解法是在报告生成任务前加一道“证据链检查”提示词,要求 Agent 给每个结论附加引用文件路径和行号。这条约束执行起来出奇的管用,明显减少了报告里“可能”“大概”这类模糊表述。
6. 最后说点掏心窝的话
这套 apk-reverse 工作流从最初只有三个命令行脚本加一段 prompt,一路迭代到现在稳定的四阶段架构,前后折腾了小半年。我最大的体会是:AI Agent 能不能真正落地,看的不是单点功能有多强,而是你愿不愿意把行业经验拆碎成“它能理解、能执行、能纠错”的原子步骤。这个过程最累,但也最值钱。
如果让我给想复刻这套工作流的同行一句建议,我会说:先别急着上各种复杂框架,把自己手头的一个分析链路完整走一遍,记录下每一步的输入输出,再问自己“如果我把这一步描述给一个不懂逆向的聪明实习生,他能照做吗”。能,就说明这个步骤够原子化,值得交给 Agent 执行;不能,就继续拆。等到所有步骤都拆得足够细,再回头看,你会发现那些之前困扰你的经验沉淀、上下文管理、流程复用问题,都已经找了一半答案。
后面我还打算给这套工作流加上“跨设备任务分发”和“逆向知识库自动积累”两块能力,让跑过的每次分析都能反哺到后续任务里。不过那是下一个阶段的事了,先把当前这套跑稳,比什么都重要。