English · 简体中文
能力 · 结构 · 安装 · 环境要求 · 失败目录 · 范围 · 维护 · 免责声明
# apk-reverse 面向 Android APK 逆向工程、去臃肿、去广告、外科式 dex 补丁、重打包,以及运行时/服务端分析的 Agent Skill。 它是一个 **skill**,不是教程:它是写给 agent(Claude Code、Codex,或任何支持 Agent Skills 格式的 harness)在工作过程中加载的,因此按 progressive disclosure(渐进披露)来组织——一份简短、面向决策的 `SKILL.md`,只在某一步真正需要时才加载的详细 references,以及可直接运行的参数化脚本。 ## Agent 应当如何使用它 `SKILL.md` 刻意写成一份**带 gate 的流程**,而不是建议,因为观察到的失败模式并不是无知——而是模型把整份文档读完、表示认同,然后照样从第一性原理重新推理一遍。 所以正文里有四样东西是用来**执行**的,不是用来读的: - **四条覆盖规则(R1–R4)。** 当它们与当前计划冲突时,在证据推翻它们之前,以它们为准。 - **一份症状索引。** 每一行都是已经付过代价的失败。**命中一行就是停止信号**:在你跑下一条命令之前加载那个文件,而不是在又试了几次之后。绕过已经记录在案的已知症状继续推理,就是同一批小时被花掉两次的方式。 - **四个 gate(G1–G4)**,每个都是一个动作加一条通过标准。「我理解这个思路」并不能通过 gate。它们存在的意义是让分类、环境真相和对照构建发生在第一次打补丁**之前**,而不是在第三次失败之后。 - **一条两击规则和若干停止条件。** 同一形态的失败出现两次,说明错的是模型,不是参数。一个已经失败过两次的假设再出第三个变体,就是回合葬身之处。 另外还有一样在结尾、应当被**扣住**的东西:**「done」**有定义(六项)。一份干净的日志不在其中。少于六项全部达成,就只是一个检查点,应当作为检查点汇报,并说明还剩什么。 如果你是正在读这段的 agent:成本最低的第一条命令是 `python skills/apk-reverse/scripts/doctor.py`。它会告诉你这里存在哪些工具、哪些脚本真的能跑,以及环境里是否已经有什么东西在污染你的测量结果。 ## 它擅长什么 - **快速**判断一个需求在客户端是否本来就做不到,而不是把几小时烧在一个由服务端强制执行的付费墙(paywall)上。 - 在任何工作开始之前先确定**交付物必须是什么形态**——一个免 root、自包含的产物与「让它在你这台机器上能跑」是两个不同的问题,混淆这两者是这个领域里最昂贵的漂移。 - 为给定改动选择**最安全的补丁层**,并避开那些会把 app 弄坏的层。 - 抓住那种「看起来成功」的重打包失败:app 安装、启动、渲染都完美,但**每一个签名请求都被拒绝**,因为客户端是从自己的签名证书推导请求签名密钥的。 - 把**你自己的错误与 app 或服务端的问题区分开**——限定在某个功能上的失败(登录、注册、支付)常常是某一条代码路径上的 TLS/证书问题,而不是你刚做出来的补丁的后果。设备状态、失联的 device server 和时钟漂移会以同样的方式伪装成同类问题。 - 确认**实际执行的是哪个架构、哪个库**,而不是相信 manifest 里带了什么或设备自称什么。 - 处理**加壳/加固目标**:识别壳、脱壳,并把内存 dump 还原成一个打过补丁、可安装的 APK。 - 处理一个**故意终止进程的加固库**——包括那种刻意崩溃的形态(`fault addr 0x4`),它看起来和普通的空指针解引用 bug 一模一样;以及「把它失效化,但绝不要让它*不返回*」这条规则,它决定修法是生效,还是以完全看不出与原因有关的方式冻住整个 app。 - 知道**该伸手去拿哪些工具,以及每个工具在哪里说谎**——包括那些只以 GUI 形式存在的工具,此时应当请求人类介入,而不是悄悄换用一个更弱的方法。 - 让打过补丁的构建**保持**打过补丁:失效化版本检查、强制升级弹窗和自更新安装器,使这份成果无法被远端一键关掉——同时认出那个能在版本号毫无变化的情况下悄悄把它撤销的热更新/远程配置通道。 - 把**客户端侧的登录闸门**(可打补丁)与**账号维度的资源**(为空,因为服务端没有东西可答)区分开;并且知道伪造一个 session 所产生的状态比保持未登录更糟。 - 让**长时间任务保持诚实**:一份实时记录、分级结论、校准过的超时和有界等待,使进展不会丢失、同一个错误不会被犯两次。 - 避开那些会产出「构建完美、运行即死」的 APK 的具体错误。 - 当 APK 不是可选项时,决定**交付物应该是什么**——被多个独立检查拒绝的重打包是*受阻*,不是*昂贵*;回落阶梯是系统级模块、本地 RPC 服务,或一份明确写出边界的诚实报告。 - 分辨真实内存 dump 与**抽取壳骨架**,并知道哪条恢复路线适用——包括当 `frida` 本身被拒绝时走 root 侧 dump。这次测量能看见什么、看不见什么,写在 `skills/apk-reverse/references/advanced-unpacking.md`。 - 当逆向的成本高于调用时,**调用例程而不是逆向它**:在宿主上做模拟执行,或把一个活函数通过 Frida RPC 服务化。 - 当一个 native 函数被 OLLVM 碾平成状态机时,去读**指令级执行证据**——包括在真实设备上实测到的 Stalker 反咬的两种方式。 - 认出**用户态 hooking 根本够不到那个检查**的情况(裸 `svc` 系统调用、`init_array` 早期检测)、上下相邻层实际能做什么,以及什么时候继续升级是错误答案。 - 处理**非 REST 的协议**——无 schema 的 protobuf、gRPC、QUIC/HTTP3——以及无视系统信任库的 native 侧证书固定(pinning)。 - **直接在手机上工作**:MT Manager 的编辑/重打包/签名流程及其 APK MCP 接口、LSPosed Manager、设备上的数据检查,与 PC 工具链并用,而不是用它来替代 PC 工具链。 - 在花几小时去追一个在进程生命周期内任何时刻都不存在的「解密 DEX」之前,先把 **Java2C 与抽取壳区分开**——代码已经被编译进 `.so`。 - 处理以 **split APK / App Bundle 集合**形式到来的构建:从设备上取出该集合、用一个 keystore 为每个成员签名以便 `pm install-multiple`,或在合法时把 code/native 成员合并成一个独立 APK。 - 用已知明文差分处理一个**真实的 Dex VMP**——哪些环节可以自动化、哪些不能,一个编译出来的 fixture 能触达什么、不能触达什么,以及如何*证明*一张推导出的私有 opcode 表,而不是断言它。 - **在发表所学的同时不发表目标**——一个带上下文报告目标身份形态的扫描器、一份明确列出*不得*脱敏内容(工具、库、协议字段、CVE、加固产品、公开 crackme)的清单,因为把这些脱敏会毁掉可复用的那部分;以及能 gate 住一次提交的退出码。 - 在重复本仓库已经收敛过的劳动之前先读**先例**:记录中正向的那一半,含路线及其死路、对每条断言的分级,以及该案例指明要回写的文件。 ## 结构 `SKILL.md`、`references/` 与 `scripts/` 都在 skill 目录 `skills/apk-reverse/` 内。仓库根目录下的一切是跨 skill 共享的维护工具,不属于已安装的 skill。 ``` SKILL.md 带 gate 的流程,不是背景阅读材料: how-to-use -> 四条覆盖规则(R1-R4) 症状索引(命中一行即为停止信号) 四个 gate(G1-G4,带通过标准的动作) 十三个分类问题 工作流,带每一步的跳过条件和一条两击规则 「done」的含义 -> 停止条件 -> 约束 -> 索引 references/ 按需加载,每个文件一个主题 recon.md 识别壳、SDK、代码位置、篡改检查;脱壳 server-config-and-updates.md 「广告」最常见的那种形态,也是通常被误诊的那种: 服务端提供 UI,客户端负责渲染(启动屏、弹窗、 公告、tab 集合)。证明它的两层抓取、如何按 data class 保留的字段名找到配置 DTO、为什么 你要补的是决策而不是数据、「移除」的范围如何界定, 以及远端重新启用 / 配置缓存持久性 byte-level-patching.md 等长字节编辑:为什么它优于方法重建(实测)、 如何在不刮 listing 的情况下定位一条指令的精确偏移、 会让解码失步的指令宽度陷阱、把分支失效化还是重定向它、 dex 头完整性字段的顺序,以及校验器的 move-result 规则 packers.md 加固目标:拒绝信号、用单变量测试测量校验边界、 选择 native 宿主 code-virtualization-and-custom-linkers.md 「已加壳」与「干净」之间的那一层:整个类被变成 `native` 声明、SONAME 与文件名不匹配的私有加载器、 内嵌的自解密 payload、一个 Java 层「签名杀手」在日志里 报告成功而 native 检查却在杀你。留还是删的死锁、 如何把*检查器*与*实现*分开,以及那条不做任何 失效化就终结它的字符串重定向技术 framework-runtimes.md Flutter / React Native / Unity:UI 归哪一层所有, 以及在没有符号时怎么找逻辑(字符串编码陷阱) dart-aot.md Dart AOT 深入:版本钉定与构建一个匹配的反编译器、 对象池与引用索引、寄存器/布尔约定、 识别业务逻辑的三种签名、定位、打补丁。 开篇先讲它所依赖的 snapshot 解码前端(aotopsy 或 blutter),因为 pool 列表是输入,不是本 skill 自己产出的东西 native-and-so.md .so 宿主、DT_NEEDED vs JNI_OnLoad、重定位限制、 免重定位的 bootstrap、用 native 替换 Java 方法, 以及*实际加载并执行*的是哪个 ABI/库 native-tamper-and-suicide.md 加固库如何杀掉自己的进程:可见的机制、 如何判断实际触发的是哪一个、如何定位现场、 伪造的 section header、由 PT_GNU_EH_FRAME 推出的 函数边界、扫描器陷阱,以及安全地失效化 detection-and-anti-analysis.md 当 app 反击,或工具在这里跑不起来时:把检测与 环境损坏区分开、按成本而不是按升级来决策、认出 动态分析根本无效的环境,并把「挡住我的分析」这个问题 与「挡住交付物」分开 toolchain.md 该装什么、如何非交互地调用它、哪些工具只有 GUI、 版本对齐陷阱、离线工作、**「不在 PATH 上」不等于「没装」**, 以及该用哪个签名器 long-task-discipline.md 实时记录、结论分级、漂移控制、超时与等待校准、 交付物形态漂移、你从未看过的截图、 长上下文衰减、交接 ad-removal.md 广告分类学、包装层映射、回调陷阱、全局闸门、验证 updates-and-forced-upgrade.md 让打过补丁的构建活下去:定位版本检查、 两层补丁(把例程 no-op、把比较失效化)、不能碰的东西 (manifest 版本、安装器权限、host 阻断)、 自更新与热更新/远程配置通道、验证根本没有发出任何版本请求 account-gates.md 登录墙、强制手机绑定、游客模式:把客户端侧闸门 (可补)与账号维度资源(不可补)区分开、为什么 伪造 session 比保持未登录更糟,以及重装后 无法避免的登录态丢失 signature-derived-keys.md 当 app 自己的签名证书被当作密钥材料时: 检测用 grep、为什么离线提取不可靠、 先硬编码再验证的流程 membership-and-limits.md 服务端 vs 客户端权威;什么可补、什么不可补 server-api.md 探测 app 的 API;证明闸门归谁所有 tls-and-cert.md 功能维度的网络失败:过期证书、双信任链 third-party-builds.md 在信任一个「破解版」/「魔改版」APK 之前审计它 dex-patching.md 补丁层对照表 + dexlib2 技术深入 patch-audit.md 证明一个补丁*落地*了并且*合法*:长度 vs 字节 比较、等长替换的盲区、校验器层面的合法性 (move-result 相邻性)静态检查、文本匹配式 补丁陷阱,以及如何报告一个缺失的补丁 repack-and-sign.md 重打包规则、解包与重打包、签名、安装后隐患 runtime-data.md DataStore / SharedPreferences / SQLite / protobuf;app 何时 会改写你的编辑,以及解码一个看起来像加密的值 dynamic-frida.md Frida 配置、版本钉定、四层探测、hook 策略 environment.md 设备/模拟器配置、root、ADB、离线设备、日志信号、 模拟器 console 控制与恢复、preflight、看一眼屏幕 verification.md 主张阶梯;「done」的含义 desensitization-and-leak-scans.md 发表纪律:什么必须脱敏、什么必须保留、 不得匿名化的清单、泄漏扫描器及其退出状态,以及 入口文件本身就是一处提示词暴露面 precedents/ 正向案例库:含死路的路线、每条断言的分级、 实测到的坑,以及回写清单 routing.md 按需清单:每个 reference 及其加载时机、每个 script 及其用途,以及症状索引的镜像 rasc-and-droidsaw.md ASC 索引器的 Rust 重实现:实测加速比与 完全一致的类集合、它会静默丢掉方法体的 enum 形态, 以及如何构建与验证它 evidence-summary.md 随 skill 一起发布的浓缩版:能力、一行结论、 强度,以及你在已安装副本里实际能打开的证据 ../evals/ 也不是 spec 目录,而是 Agent Skills 指南推荐的 位置:`evals.json` 存放本 skill **尚未**运行过的 带 skill / 不带 skill 用例,运行方法写在文件里 ../evidence/ 不是 spec 目录:上面 evidence summary reference 的 机器可读配套文件 —— `capability-matrix.json`(同样的行、 更多字段)、`tested-tool-versions.json`(版本及每个版本背后的 探测)、`known-limitations.md`(面向安装者的限制清单)。随 skill 一起发布,使已安装的副本不需要仓库就能回答「这一条验证过吗、 强度如何」 pitfalls.md 失败目录 —— 构建之前先读 advanced-unpacking.md dump 拿到了但方法体是空的:按平凡方法体比例做 抽取壳诊断、FART 式主动调用以及为什么它的经典 hook 在 Android 12-16 上失效、code_item 拼接、当 frida 本身被拒绝时的 root 侧 dump,以及诚实的 VMP 边界 lsposed-and-modules.md 重打包被拒绝,那就交付一个系统级 hook 模块:模块解剖、 无 gradle 的构建链、作用域配置与如何验证注入, 以及 Java 模块够不到的那一层 emulation-and-rpc.md 调用例程而不是读它:Unidbg/Unicorn 模拟及其 环境填充成本,对比把活函数通过 Frida RPC 服务化 native-dbi-and-deobfuscation.md OLLVM 形态、Frida-Stalker trace、trace 到 CFG 的路线、 Stalker/QBDI/模拟的取舍,以及两条实测边界(一次 不产生任何事件的 follow,和一次因 follow 热 libc 导出而崩溃) protocol-reverse.md 无 schema 的 protobuf、从反编译代码恢复 schema、gRPC 帧 抓取、QUIC/HTTP3 的限制,以及 native 侧证书固定 kernel-and-environment-hardening.md 用户态 hooking 可证明够不到检查:裸 `svc`、init_array 早期 检测、每种 root 方案隐藏了什么、带版本闸门的 内核路线图,以及什么时候停止升级 on-device-tooling.md 直接在手机上工作:MT Manager 的编辑/重打包/签名及其 APK MCP、 LSPosed Manager、Termux+frida、设备上数据检查 java2c-and-jni-sinking.md Java2C 与 JNI sinking,最容易与抽取壳混淆的两种加固 形态:区分落地壳 / 抽取壳 / VMP / Java2C / JNI sinking 的那张表、为什么代码在 `.so` 里而*永远*不在 dump 出的 dex 里, 以及为什么 `Java_*` 符号搜索会一无所获(动态注册、 `-fvisibility=hidden`) split-apk.md App Bundle / split APK 集合:集合是什么、如何从设备拉取、 合并成一个 APK 还是把整个集合作为一个单元签名、安装拒绝 及其各自含义,以及如何从拉取的集合做出一个可安装的 fixture vmp-differential-analysis.md 针对真实 Dex VMP 的已知明文差分:哪些环节可以 自动化、哪些不能(上传是瓶颈)、一个编译出的 fixture 能覆盖到什么程度、如何*证明*一张推导出的私有 opcode 表、smali 生成,以及这条路线何时关闭 coverage-and-limits.md 把主张阶梯用在 skill 自己身上:每个已覆盖条目背后的 证据、本 skill 不随附的依赖,以及从未实际演练过的东西 handoff-boundaries.md 本 skill 在哪里结束、另一门学科在哪里开始:JNI 形态 对照表、壳与加载器的分界,以及「已核实」对四种 交付物形态各自意味着什么 scripts/ 参数化、与路径无关 doctor.py 先跑这个:能力报告 + 每个脚本的可运行性,发现 安装在 PATH 之外或作为可运行 jar 存在的工具,并暴露 会污染实验的环境事实(时钟偏移、残留的 adb forward / proxy、设备侧已在运行的 frida 进程) dexutil.py 无依赖的 dex 读取器:结构遍历 + 精确指令 解码、dex 头重算/校验(正确的 checksum/signature 顺序)、分支目标与操作数辅助函数。被 dex 脚本共享的库,也可独立运行以带偏移 dump 单个方法 dex_find_insn.py 按解码语义定位一条指令,并打印其精确字节 偏移,附上下文与任一分支的两侧 —— 你靠这个找到 补丁点,而不是猜偏移 dex_patch_bytes.py 按 JSON 规格做等长字节补丁:语义匹配、通过 expect_next 钉定极性、等长强制、校验器检查、dex 头重算、重新解码以证明它已落地(先 --dry-run) dex_check_verifier.py 三级检查:是否有任何条件分支以 move-result 为目标 (绕过它的生产者)?比较两个构建,并把 既存发现与你补丁引入的回归分开 coldstart.py 冷启动捕获:定时截图连拍 + logcat 信号 + 已安装构建事实 + 启动计时,并在前台 activity 不是你的 app 时发出警告 so_constpatch.py 对一个孤立的字符串常量做等长原地改写,用于 重定向一次库加载,而不是击败一个检查 smtool.py 带可配置 classpath 的 baksmali/smali 包装器 dexpatch/ dexlib2 方法级重写器(用于需要新指令的改动) patch_smali.py smali 树中的方法体替换 dex_strpatch.py 带 string_ids 顺序守卫的字节级字符串补丁 dex_classdiff.py 证明一次 dex 编辑是外科式的 dex_strings.py 不需要反编译器的 strings/URL/SDK 标记提取 dart_pool_strings.py 从 Dart AOT snapshot 恢复字面量(带帧条目、 单字节 vs UTF-16 的分岔、文件偏移、游程噪声过滤) dart_pprefs.py 为 Dart snapshot 构建/查询 对象池 -> 代码位置 索引 dart_disasm.py Dart AOT 代码的带注释窗口式反汇编 + B/BL 调用者索引 find_refs.py 在给一个方法打补丁之前统计它的调用者 repack.py 重建 APK、只剥离签名、保留 META-INF/services/、写出 4 字节对齐的归档(resources.arsc STORED+对齐)、签名、校验; 也处理 split APK / App Bundle 集合:清点、用一个 keystore 为每个成员签名,或把 code/native 成员合并成独立 APK devsh.py 引号安全的 ADB shell 辅助工具 usb_net_proxy.py 通过 USB 给离线设备提供网络 datastore_inject.py 安全地编码/注入 AndroidX DataStore preferences probe_api.py 带正确请求头探测一个 HTTP API grab_crash.py 恢复被崩溃上报 SDK 藏起来的栈 install_test.py 安装 + 启动健康检查,带 logcat 信号扫描 frida_probe.js 四层运行时探测(app 网络层 + OkHttp + java.net + 异常) run_probe.py 注入探测、把它流式写入日志文件、保持常驻 tls_check.py 对一个或多个 host 做严格证书检查 preflight.py 每个实验块之前的环境检查(设备、root、 ABI/翻译层、时钟偏移、残留 proxy/forward、失联的 server) lib_map.py 一个活进程里*实际映射*了什么:每库路径、 基址、架构,以及它来自 APK 还是 运行时物化出来的 elf_plt.py 从重定位表把 PLT stub 解析到它导入的符号(x86_64 + aarch64); 列出某个符号的调用者;对两个 库做字节级 diff,并指出每个变更 stub 属于哪个符号 apk_diff.py 两个构建的条目级 diff:变更 / 新增 / 移除,按 内容哈希,使等长替换也能被抓住 native_crash.py 从日志或 tombstone 定位一次 native 死亡:signal、故障 地址、寄存器、把帧拆成 app 与 system、故障 指令本身,以及当故障看起来是*被安排好的*时给出标记 blob_decode.py 搜索、而不是猜测一个存储值的分帧 (base64/hex x 旋转 x deflate);把编辑后的 payload 重新编码 snap.py 有界连拍截图 + 带停滞检测的控件树捕获, 并对这棵控件树是否可用给出结论 sig_probe.py 找到确切的 signatures[0].toCharsString() 值 —— 从 APK 得到的 离线候选,或从设备读取的权威值 spawn_patch_detach.py 在 Frida 探测下 spawn、detach,然后启动并捕获:在 spawn 模式下 Activity 栈常常起不来,而内存 写入能在 detach 后存活,hook 不能 hook_patch_only.js spawn_patch_detach.py 的最小探测 —— 按偏移把一个 native 死亡点失效化并报告 PATCHED dex_dump_validate.py 对一目录的 dump dex 镜像去重、校验并排序:sha256 分组、头完整性、区分真实 dump 与抽取壳骨架的 平凡方法体比例,以及一个最可能原件的排序 (对页对齐的 /proc/