
拿到一个 APK第一反应是什么大部分人先扔进 jadx 里看反编译代码再打开 apktool 解资源中途穿插几个 Google 关键词碰运气。这套流程我跑了很久熟练工也能一天处理几个样本但问题是大量时间花在重复劳动上翻日志、找入口、试 hook 点、清洗输出。真正耗精力的不是看懂一段代码而是把一堆工具按顺序串起来并且不断回到上一步调整条件。后来我开始尝试把 Android 逆向工程拆成一个 AI Agent 可执行的完整工作流把 aapt、apktool、jadx、frida 这些工具全部封装成 Agent 能调用的 Tool让大模型负责任务拆解、代码语义分析和结果汇总。整个思路跑通之后单样本分析效率提升非常明显更重要的是中间折腾和返工的过程变干净了。这篇文章把整套做法、架构设计和踩过的坑都写出来适合正在做 Android 安全分析、样本审计或者准备把 AI Agent 落地到具体技术域的工程师看。1. 我为什么要给逆向工程加一层Agent 大脑1.1 传统逆向最耗时的其实是工具链编排很多教程教的是单个工具怎么用但真实分析场景从来不是单工具完成的。拿到一个未知 APK常规动作是这样的先aapt dump badging看包名、权限、启动 Activity然后apktool d解出资源和 smali再用 jadx 把 dex 转成可读 Java 代码。好不容易定位到一个可疑的字符串又要切到 frida 去动态验证验证结果不对还得回到代码里继续找调用链。这个过程中真正消耗注意力的地方在于上下文切换。你在 jadx 里看代码时得记住刚才 aapt 输出里的包名特征切到 frida 时得重新组织脚本参数验证失败后又得回想刚才看过的疑似封装函数列表。人脑在这种来回切换中容易出错而 Agent 最大的优势恰好是不怕这种频繁切换——它可以严谨地把每一步的输出记录下来然后基于输出做下一步决策。用 Agent 之后我的体感是它把我启动工具看结果变成了我告诉 Agent 目标它自己决定先看什么、再跑什么。核心不是让 AI 替我思考而是让 AI 承担工具链编排的脏活累活。1.2 Agent 的价值不是替代分析而是任务编排大模型本身并不擅长精确执行终端命令也不内置 Android 逆向知识库。但你如果把逆向流程拆细会发现里面有很多环节非常适合 Agent 介入。比如根据aapt的输出判断应用有没有可疑权限根据 jadx 反编译出来的类名列表筛选可能包含核心逻辑的文件根据一段 smali 或 Java 代码解释某个回调函数的用途根据 frida 的 hook 日志生成下一步验证方案。这些动作的共同特征是什么它们都建立在看一段文本输出、理解语义、决定下一步之上。这正好是 LLM 的强项。Agent 循环就像一个总调度员它理解你设定的目标把目标拆成若干子任务每个子任务对应一个工具调用拿到结果后继续修正自己的策略。传统脚本也能做类似事情但脚本写死了路径一旦中间出现意外分支就断。Agent 则可以在分支中自行判断。1.3 什么场景最适合跑 Agent 化的逆向工作流我自己的实践集中在三类场景。第一类是合规样本分析比如确认某个 APK 有没有在用户不知情的情况下上传通讯录第二类是批量应用审计一个应用商店里几十个应用需要快速筛查是否存在已知风险 SDK第三类是 SDK 行为核查确认某个第三方 SDK 是否在后台启动链路里执行了敏感调用。这三类共同点是样本结构有相似性、分析步骤高度重复、最终要输出结构化结论。这类任务的利润点不在奇技淫巧而在稳定可复现。Agent 化之后同一个 APK 跑三遍应该得到同样的结论链这比凭感觉找线索靠谱得多。2. 一次典型分析要过哪些关卡手动流程的阶段划分不要指望 Agent 能直接从一个 APK 跳到结论。核心教训是必须把分析过程拆成若干个有明确输入输出的阶段。我按实际需求把流程分成五个阶段。2.1 阶段一静态信息收集这个阶段的目标是搞清楚这个 APK 表面看起来是什么。基本工具是 aapt 或者 apktool 的框架信息。需要拿到包名、versionName、targetSdk、minSdk、声明的权限、启动 Activity、Service 和 Receiver 列表。注意不要只给 Agent 原始输出要让 Agent 提取出它认为与目标相关的字段。比如目标关注隐私行为就优先标记读取电话状态、定位、读写外部存储这些权限。实际操作里我通常让 Agent 先用aapt dump permissions和aapt dump badging获取基础信息再根据权限列表判断可疑程度。这一步不用太深但它决定了后面检索代码时的侧重点。2.2 阶段二资源与配置解析Android 应用的很多行为线索藏在资源文件和 manifest 里。比如android:networkSecurityConfig有没有允许明文流量Backup 是否被禁用FileProvider 配置里暴露了哪些路径。这些信息用 jadx 或者 apktool 解包之后直接看 XML 就能拿到。Agent 在这个阶段应该产出的是配置风险清单而不是把一堆 XML 原样贴给后续步骤。我常用的做法是让 Agent 逐个检查 manifest 的关键安全配置项然后输出一张简表。这样后面动态验证阶段可以带着问题去 hook。2.3 阶段三核心代码定位这是传统逆向里最吃经验的部分也是 Agent 最容易被喂太大上下文导致效果变差的环节。处理思路是先让 jadx 输出反编译后的类文件树再让 Agent 基于类名和方法名做一个粗筛。比如目标涉及设备指纹采集就先找包含 DeviceId、IMEI、OAID、MacAddress 等关键词的类。粗筛只是第一步下一步要把命中的类单独提取出来让 Agent 阅读类内核心方法并输出摘要。这段摘要包含调用关系、入参来源和出参去往。经过这两层筛选才真正进入理解业务逻辑的环节。2.4 阶段四动态行为验证静态代码存在大量误判和混淆陷阱。比如一个方法看起来调用了getImei但实际可能因为厂商适配走了别的分支。要确认真实行为最可靠的方式是动起来看。这里用 frida 做 hook常规手段包括hookjava.lang.Runtime.exec看它执行了哪些命令hookTelephonyManager.getImei看调用栈hook 网络库的OkHttpClient或Request.Builder看实际请求的 URL。难点在脚本本身。Agent 生成 frida 脚本相对容易但脚本能否在当前进程里顺利 attach是否被 root 检测拒绝需要一遍遍试。我把这一步设计成Agent 生成脚本 沙箱执行 结果回传校验的循环失败就换一个 hook 点而不是一次到位。2.5 阶段五结构化结论输出最后一个阶段把人工作业里的本能判断变成明确证据链。Agent 汇总各阶段产出输出包含行为描述、对应代码位置、动态验证结果、风险等级的结论表。这一步相对容易但非常依赖前几个阶段产出的质量。如果前几步都是流水账到这里就会东拼西凑。划分清楚这五个阶段等于给 Agent 画了一张地图。剩下要做的是把这张地图变成它能跑的轨道。3. apk-reverse 工作流的核心设计把阶段变成可执行状态机3.1 整体框架五层结构一个都不能少我最终跑通的架构包括五层任务解析层、上下文管理层、工具执行层、记忆与回放层、验证与迭代层。任务解析层负责把确认这个 APK 是否包含设备指纹采集行为这种目标拆成阶段性子任务。上下文管理层负责管理各阶段产出的数据防止把海量反编译代码一次塞给 LLM。工具执行层维护已封装的工具集让 Agent 只能通过预设 Tool 和外界交互。记忆与回放层记录每一步的决策理由和输出摘要。验证与迭代层负责在动态 hook 失败或结果不一致时触发重试。整个框架最核心的认知是Agent 不是一辆自动驾驶车而是一列按轨道行驶但是每站自己决定详细操作的列车。任务解析层定轨道工具执行层提供操作空间。3.2 为什么不要让 Agent 自由乱跑如果你的 Agent 拿到一个大目标就开始无限自由规划实际操作里会快速跑偏。LLM 会凭空假设一些实际上并不存在的线索或者忽略 manifest 里的关键配置。我早期踩过这个坑让 Agent 直接分析这个 APK 是否存在隐私风险结果它在 jadx 的类目录里漫游了十几轮也没找到核心调用链。正确的做法是严格按阶段收敛行为。在静态信息收集阶段不允许调用 frida 相关工具在核心代码定位阶段不允许直接下结论说某行为一定存在。阶段之间设置输出校验点只有产出符合格式要求才允许进入下一阶段。这样做确实部分牺牲了智能感但换来了稳定性和可排查性这对工程化落地比什么都重要。3.3 技术选型LangGraph、自研循环还是低代码平台这部分我做过对比用一个表方便大家参考方案优点缺点我的建议LangGraph状态图模型和逆向阶段流转天然匹配支持持久化检查点适合复杂流程学习成本高需要自己处理大量的状态序列化如果你本身在做后端服务选它没错自研 Agent 循环完全控制工具协议和上下文策略逻辑透明容易退化成 if-else 大杂烩并发和重试要自己处理适合只有一两个工具、流程简单的小场景低代码平台扣子/Coze 等上手快界面可视化内置调试工具工具封装能力受限复杂的本地命令调用很难实现适合做概念验证和快速 Demo我最后选择了基于 FastAPI LangGraph 的自研服务层底层工具用 Python 子进程调用 aapt、apktool、jadx、frida。原因是逆向工具本身都是本机命令行程序低代码平台管不到我的模拟器和文件系统而 LangGraph 的状态管理刚好匹配多阶段流转。如果你只是想在个人电脑上做实验自研循环改起来更自由。4. 核心机的部分把每款逆向工具封装成 Agent 的 Tool工具封装决定了 Agent 的性能上限。能调用 frida不等于能把 frida 用好关键在协议设计。4.1 统一的工具输出协议我定义的每个 Tool 输出格式都是一样的{ tool_name: jadx_search, status: success, summary: 在 3 个类中找到与 getImei 相关的调用详见下方 artifacts, artifacts: [ { type: code_snippet, path: com/example/MainActivity.dex, content: TelephonyManager tm ...; tm.getImei(); } ], error_hint: null }summary是给 LLM 看的摘要artifacts是结构化详情error_hint是错误说明。这样设计有几层用意第一LLM 默认只需要读 summary减少 token 消耗第二后续需要细节时才展开 artifacts第三状态机可以根据 status 直接判断是否需要重试。4.2 aapt/apktool 工具信息收集器的封装aapt 这类工具非常简单直接跑子进程然后把 stdout 清洗成结构化字段。唯一要注意的是超时和 APK 损坏的情况。有的 APK 加过壳aapt 也能正常读 manifest但 dex 已经加密jadx 反编译出来是空的。这种情况下工具输出必须包含error_hint: dex 已加密或内容为空疑似加固Agent 看到这个提示会转入加固判断分支。apktool 的封装更危险一点。一个 APK 解包后可能产生几千个文件不能直接把文件树全部反馈给 LLM。我的做法是只返回 smali 目录下的顶层包名列表和文件数量统计让 Agent 只对包名做粗筛。4.3 jadx 工具代码检索器的协议细节jadx 反编译整包很慢但按需输出单个类却很快。我封装了两个工具jadx_decompile_class和jadx_search_code。前者接收一个类路径返回该类完整反编译代码后者接收一个关键词返回所有包含该关键词的类文件和方法签名。这样设计的好处是Agent 先搜索再决定反编译哪个类避免一次性解析数百兆代码。搜索结果的排序很重要。同一个关键词getImei可能在系统框架、第三方 SDK、应用自身代码中命中几百次。我会在工具封装里把android.*、androidx.*、kotlin.*等系统库包名排除优先返回应用自身和第三方 SDK 的命中结果。4.4 frida 工具动态验证器的沙箱封装frida 的封装是所有工具里最繁琐的。因为 Agent 不仅需要调用一个命令还需要编写脚本、attach 到进程、执行、回收日志、结束会话。我的工具协议里内置了脚本租约机制Agent 提交一个任务描述比如hook getImei 并打印调用栈自动化模块负责从模板生成 frida 代码在沙箱模拟器执行然后把日志返回。这里绝不建议让 LLM 生成完整 frida 代码直接执行风险太高。我的做法是预置一个脚本模板库LLM 只需要选择 hook 类型和目标方法。模板库覆盖常见敏感 APItelephony、location、network、file io、exec。如果 Agent 提交的任务没有匹配模板执行层会拒绝并提示换一种描述方式。4.5 上下文记忆摘要器永远是最后一层无论哪个工具原始输出进入上下文之前都要经过一层摘要器。摘要器的职责不是压缩而是提取与当前目标相关的证据。比如 frida 打印了几百行调用日志摘要器把包含getImei和android/telephony的行提取出来重组成结构化调用链。这个设计基本消除了 LLM 在长文本里丢失关键信息的问题。早期我把原始日志直接塞给 LLM它经常漏看关键行加摘要器后效果好得多。5. 一个端到端实测案例审计 APK 是否采集设备指纹下面用一个我复盘过的案例完整展示 Agent 在各阶段的决策过程。假设目标是审计一个名为 com.example.sample 的 APK确认它是否在启动时采集 IMEI 和 OAID。5.1 阶段一Agent 读了 manifest锁定了权限Agent 调用aapt_dump_permissions后的输出摘要请求了 READ_PHONE_STATE、READ_PHONE_NUMBERS、ACCESS_FINE_LOCATION 等权限。Agent 的记录是存在与设备标识采集相关的权限声明进入代码定位前重点追踪 TelephonyManager 和 OAID SDK。这一步没有任何高深逻辑但 Agent 不乱跑。5.2 阶段二jadx 搜索命中可疑调用链Agent 调用jadx_search_code关键词为getImei和OAID。命中结果中排除了系统包名后剩下 2 个类com/example/sample/MainActivity和com/example/sample/DeviceHelper。Agent 先反编译 DeviceHelper确认它包装了getImei和获取 OAID 的逻辑再反编译 MainActivity在onCreate中看到调用了DeviceHelper.initAndReport(this)。这里的重要点是 Agent 没有自嗨它的输出明确引用了两个类的代码片段状态机校验了引用的代码路径确实存在才允许它跳到动态验证。5.3 阶段三frida 模板带调用栈的 hook 验证Agent 提交动态验证任务hookTelephonyManager.getImei和com.example.sample.DeviceHelper.initAndReport要求在initAndReport返回时打印调用栈。沙箱模板库匹配到了hook_method_with_stacktrace模板执行后日志结果显示DeviceHelper.initAndReport在MainActivity.onCreate中被调用随后触发TelephonyManager.getImei。Agent 还注意到日志里的一个细节getImei 返回了实际设备 ID 但形式异常后续看到一个对getMeid的调用未被 hook。于是 Agent 追加了一个验证任务确认该 SDK 在部分分支里也会采集 MEID。5.4 阶段四输出了带证据链的结论最终报告不需要人再整理Agent 直接生成结构化结论风险行为是启动时采集 IMEI/MEID/OAID证据链包括 manifest 权限声明、DeviceHelper 与 MainActivity 的调用关系、frida 动态调用栈风险等级标注为高。整个流程从输入 APK 到产出报告约 12 分钟大部分时间花在 jadx 反编译和 frida 沙箱执行上。这不是什么特殊案例它验证了这条工作流对常规样本分析完全可行。对比我手动做同类审计通常需要 1 到 2 个小时中间还容易遗漏调用分支。6. 落地半年后仍然要面对的问题并发、超时与幻觉6.1 Agent 并发与任务隔离当你同时分析 10 个 APK 时并行 Agent 之间会互相踩踏。尤其是 frida多个脚本争抢同一个模拟器进程直接导致 hook 失败率飙升。我的解决方案是给每个 Agent 分配独立的模拟器实例或者说轻量容器。如果机器资源有限改用队列串行执行 frida 相关阶段只对静态分析阶段做并行实测下来能显著降低环境崩溃概率。这部分的教训是并发设计要在架构早期就考虑。等到 Agent 框架跑通再回头加资源隔离改动成本会很高。6.2 工具超时和重试策略jadx 反编译一个 100MB 的 APK 可能跑 10 分钟frida attach 在某些系统版本上会直接卡住。所以每个工具调用必须设置超时。我把工具分成快速调用和慢速调用两类快速调用 30 秒超时慢速调用 10 分钟超时。超时后不是盲目重试而是让 Agent 选择继续等、换方法、还是放弃该分支。状态机里我会配置最大重试次数防止 Agent 陷入无限循环。6.3 幻觉控制证据优先原则LLM 最大问题是会脑补并不存在的代码位置。对抗思路是强制要求 Agent 输出的每个断言都附带 artifact 引用。如果一个结论没有对应工具的路径和片段状态机直接拒绝。这个机制在 LangGraph 里实现起来不难在输出节点加一个校验器就行。实测下来强制证据引用之后误报率下降了约六成。6.4 加固与混淆样本下 Agent 的能力退化遇到加壳 APKjadx 反编译结果往往是空壳或只有壳公司的类。Agent 在静态阶段就会拿不到有效线索这是工作流最明显的短板。我的处理方式是静态阶段检测到加固立即标记跳过普通 jadx 流程进入专门的动态分析通道。先在沙箱里跑起来内存 DEX 转储再对转储结果重新走代码定位阶段。这个通道目前很难全自动跑通需要人工介入准备系统环境但半自动已能覆盖相当一部分加固样本。7. 哪些环节必须保留人工不要硬交给 Agent第一个必须人工把关的是授权边界。无论工作流自动化到什么程度分析样本前确认来源和授权都是不可省的一步。Agent 无法替团队判断一个 APK 是否来自合法授权渠道这个责任必须落在人身上。第二个是高难语义理解。Agent 能告诉你调用链怎么走但很难理解一个调用背后涉及的商业逻辑或业务协议。比如某些 SDK 采集设备 ID 是为了广告归因这在合规上可能合理可能不合理判断需要结合上下文和行业规则这不是提示词调整能解决的。第三个是异常样本的最终结论复核。工作流适合处理常见样本的常见问题一旦遇到反自动化的对抗逻辑、完整 VMP 保护或者特殊 ROM 兼容问题Agent 很可能给出看似合理但实际错误的结论。我的习惯是Agent 报告的风险等级为高危时必须人工复核。最后分享一个自我感觉最值的建议不要试图第一天就做端到端全自动。先把每个工具封装成一个可以被独立调用的 Tool再用历史样本做回归测试确认每个阶段的输出质量都稳定后才让 Agent 把整条流水线串起来。先让工具被 Agent 调用再让 Agent 主导流程顺序不能反。这套 apk-reverse 工作流真正成熟起来靠的不是大模型变聪明而是工程侧把一件件小事做扎实。