apk-reverse

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

APK Reverse Engineering & Patching

APK逆向工程与补丁制作

Goal: reach a verified, installable, still-working artifact fast — and avoid the whole class of mistakes that destroy an APK while looking completely healthy.
目标:快速得到可验证、可安装、仍能正常运行的产物——避免那些看似完好无损、实则彻底损坏APK的各类错误。

Immediate — do these four things before anything else

立即执行:在做任何其他操作前先完成这四件事

This file is a procedure with gates, not background reading, and it is long enough that its middle gets skimmed. So the four moves come first; everything below is the explanation for them.
1. Classify before choosing a route. Answer the thirteen questions in §Start here. R4 decides whether you are editing the right layer at all, and a wrong branch does not fail loudly — it produces an artifact that builds, runs, and does the wrong thing.
2. Clear the four gates, in order, with their pass criteria (§Gates). G1 names the deliverable form in one testable sentence before any work; G2 establishes what this machine can really do; G3 locates the code; G4 records the baseline and the control. "I understand the idea" is not clearing a gate.
3. A symptom you cannot explain is a stop signal, not a puzzle. Search §Symptom index for the shape before your next attempt and load the file that row names. Those rows exist because each one cost hours — and in several cases the answer was already written down while it was being re-derived.
4. Two strikes on one shape of attempt sends you back to classification, not to a third variant (§Stop conditions). And never report done without the six items in §What "done" means.
Two rules about this file itself. When a failure does not fit your plan, the symptom index is the next action, not more reasoning. And when this file and your own reasoning disagree, this file wins until you have evidence that overrides it: every rule here is the residue of a failure that cost hours, and your current intuition is the intuition of someone who has not hit it yet.
本文档是一份带关卡的操作流程,而非背景读物,内容较长,中间部分容易被略读。因此先列出四个操作步骤;下文是对这些步骤的说明。
1. 选择路线前先分类。 回答§开始这里的13个问题。R4 会判断你是否在正确的层级上进行编辑,错误的分支不会明显报错——它会生成一个能构建、能运行,但功能错误的产物。
2. 按顺序通过四个关卡,满足其通过标准(§关卡)。G1要求在开始任何工作前,用一句可测试的话明确交付物形式;G2确认当前环境的实际能力;G3定位目标代码;G4记录基线和对照组。“我理解思路了”不算通过关卡。
3. 无法解释的故障现象是停止信号,而非待解谜题。 在下次尝试前,先在§故障索引中查找对应的现象,并加载该行指定的文件。这些条目之所以存在,是因为每一个都曾耗费数小时——而且在很多情况下,答案早就被记录下来,却被人重新推导了一遍。
4. 同一类型的尝试失败两次,应回到分类步骤,而非尝试第三种变体(§停止条件)。并且在满足§“完成”的定义中的六项要求前,绝不要宣称任务完成。
关于本文档本身的两条规则。 当故障不符合你的预期时,下一步应该去查故障索引,而非继续推理。当本文档与你自己的推理不一致时,以本文档为准,除非你有证据推翻它:这里的每条规则都是从耗费数小时的故障中总结出来的,而你当前的直觉只是尚未踩过坑的人的直觉。

Four rules that override everything else

最高优先级四条规则

R1 — Write the deliverable as a testable sentence before you touch the target. "It works" is not the goal; "it works under the stated constraint" is. Root-assisted, live- instrumentation, host-proxy and patched-device results frequently do not satisfy a request for an installable artifact that works on a normal phone — and it is easy to present such a result as finished. Write the sentence, re-read it at every checkpoint, and if you cannot meet it, say so plainly and label the privileged workaround a fallback, never the deliverable. →
references/long-task-discipline.md
§the most expensive drift
R2 — Change one variable at a time, and keep a control build. An experiment that flips two things teaches nothing when it fails, and a failure you cannot attribute will be attributed to the wrong cause. Every "the app rejects X" claim needs its own run, and every patch needs a same-pipeline control that still fails the old way. →
references/long-task-discipline.md
§single-variable discipline
R3 — Never ship or claim an unverified artifact. "It assembles" is not "it works"; "the process started" is not "the feature works"; "no error in the log" is not "the check is gone". Install it, launch it, exercise the exact feature you changed, and look at the screen. Prove the device is running the build you made — hash it, do not trust the filename. →
references/verification.md
R4 — Identify the owning layer before patching, and re-classify when reality disagrees. Ads, paywalls, feature gates, integrity checks and update gates live in different layers (Java, dex, native, Dart/Unity, server). Patching the wrong layer either does nothing or breaks the app. If a patch "had no effect", the diagnosis was wrong — go back to classification instead of patching harder. →
references/recon.md
, then the layer-specific file the symptom index points at
R1 — 在接触目标前,用可测试的语句描述交付物。 “能用”不是目标;“在指定约束下能用”才是。Root辅助、实时插桩、主机代理、已修补设备的结果往往不满足“在普通手机上可运行的可安装产物”的要求——而人们很容易把这样的结果当作完成品。写下这句话,在每个检查点重新审视,如果无法满足,就明确说明,并将需要特权的解决方案标记为备选方案,绝不能当作交付物。 →
references/long-task-discipline.md
§代价最高的偏差
R2 — 一次只改变一个变量,并保留对照构建包。 同时改变两个变量的实验如果失败,什么也说明不了,而无法归因的故障会被归到错误的原因上。每一个“应用拒绝X”的结论都需要单独运行验证,每个补丁都需要一个走相同流程但仍按原有方式失败的对照包。 →
references/long-task-discipline.md
§单变量原则
R3 — 绝不要交付或宣称未经验证的产物。 “能打包”不等于“能运行”;“进程启动了”不等于“功能正常”;“日志里没有错误”不等于“校验被移除了”。安装它,启动它,实际操作你修改的那个功能,然后看屏幕显示。证明设备上运行的是你制作的构建包——用哈希校验,不要相信文件名。 →
references/verification.md
R4 — 打补丁前先确定目标所属的层级,当实际结果不符时重新分类。 广告、付费墙、功能开关、完整性校验和更新限制位于不同的层级(Java、dex、native、Dart/Unity、服务端)。在错误的层级打补丁要么毫无效果,要么搞坏应用。如果补丁“没有任何效果”,说明诊断错了——回到分类步骤,而不是更用力地打补丁。 →
references/recon.md
,然后查看故障索引指向的对应层级文件

Tooling — reach for the right instrument, and check before declaring it absent

工具使用——选用正确的工具,在宣称工具缺失前先检查

Most wasted rounds in this domain are not bad reasoning about the target. They are the right question asked of a tool too weak to answer it: an hour of
grep
over a hand-exported smali tree where one indexed query would do, a manual ELF walk where a decompiler was one
pip install
away. The failure is invisible from the inside, because the weak route still produces output.
Five obligations. These are instructions; a violation is a defect, not a preference.
ObligationDo thisCost of ignoring it
Orient with an indexer, not an exportBuild the ability to ask the artifact questions before reading code:
droidasc findrefs
/
ddc findrefs
(string/type/method → every reference site, sub-second). A decompile reads a class you have already located — it is not how you locate it.
jadx
is a readable viewer of last resort, never the source of truth and never the entry point of a recon
hours of
grep
per question asked, over an export you paid for first
Install the missing tool; it is part of the taskA missing arm64 decompiler is the next step, not a constraint to route around. Ask a human only when installation is genuinely impossiblehours re-derived by hand for output a decompiler gives in minutes
Name the gap before spending against itState the capability the blocker requires and whether this machine has it, out loud. This is a G2 itemdesigning around a tool that installs in ten minutes
Reach for a shipped script before writing oneParsing, hashing, alignment and hot-plug probes are already written; a bespoke script in place of
scripts/dex_find_insn.py
is how offsets get guessed instead of computed
a wrong offset that decodes cleanly and behaves wrongly
Pick the instrument for the layer the question lives onA dex in memory wants
dex_mem_scan.py
(find and cut) plus
dex_dump_validate.py
(judge), not a decompiler pointed at a fragment. An algorithm you only have to call wants
emulation-and-rpc.md
. "Does this class ever load at runtime" wants a hook that fires or does not
the heavier tool is not the safer one — it produces a plausible answer, which is worse than none
Run
python skills/apk-reverse/scripts/doctor.py --json
before concluding anything is unavailable.
It reports per-capability closure — what is missing, what to install, and roughly how long that takes — and a capability you have not checked for is not a capability you lack. It is deliberately allowed to report
BLOCKED
:
a machine with a JVM but no
zipalign
/
apksigner
cannot re-sign, and the report says so rather than inferring capability from a tool that is merely present. Detail:
references/toolchain.md
; the registry it reads is
scripts/capabilities.py
.
这个领域里大多数被浪费的时间,都不是因为对目标的推理有误,而是用能力不足的工具去回答正确的问题:比如用一个小时在手动导出的smali树上
grep
,而一次索引查询就能解决;比如手动遍历ELF文件,而反编译器只需要
pip install
一下就能装。这种失败从内部是看不见的,因为低效的方法仍然会产生输出。
五项必须遵守的要求。这些是硬性规定;违反就是缺陷,而非偏好问题。
要求操作方式忽视的代价
用索引器定位,而非导出文件具备在阅读代码前向产物提问的能力:
droidasc findrefs
/
ddc findrefs
(字符串/类型/方法 → 所有引用位置,亚秒级响应)。反编译是用来读取你已经定位到的类的——不是用来定位类的。
jadx
是万不得已才用的可读查看器,绝不是事实来源,也绝不是侦察的入口
每个问题都要花数小时
grep
,而且你还得先花时间导出文件
安装缺失的工具;这是任务的一部分缺少arm64反编译器是下一步要解决的问题,而非需要绕开的限制。只有在确实无法安装时才可以求助他人反编译器几分钟就能出的结果,要花数小时手动推导
在投入时间解决缺口前先明确缺口是什么明确说出阻碍任务所需的能力,以及当前环境是否具备该能力,说出来。这是G2的内容为了绕开一个十分钟就能装好的工具而大费周章设计方案
先找现成的脚本,不要自己写解析、哈希计算、对齐和热插拔探测这些功能都已经有现成脚本了;不用
scripts/dex_find_insn.py
而自己写定制脚本,会导致偏移量是猜出来的而非计算出来的
错误的偏移量解码看起来正常,但运行行为错误
根据问题所属层级选择工具内存中的dex需要用
dex_mem_scan.py
(查找和提取)加上
dex_dump_validate.py
(校验),而不是对着一个片段用反编译器。如果你只需要调用某个算法,参考
emulation-and-rpc.md
。“这个类在运行时会不会加载”这个问题需要用hook来验证是否触发
更重的工具不一定更安全——它会给出一个看似合理的答案,这比没有答案更糟
在断定某个功能不可用之前,先运行
python skills/apk-reverse/scripts/doctor.py --json
。
它会报告各项能力的完备情况——缺少什么、需要安装什么、大概需要多长时间——你没检查过的能力不代表你没有。它被特意设计为可以报告
BLOCKED
(受阻):
一台有JVM但没有
zipalign
/
apksigner
的机器无法进行重签名,报告就会明确说明,而不是仅凭某个工具存在就推断具备该能力。详情见:
references/toolchain.md
;它读取的注册表是
scripts/capabilities.py
。

Coverage — what this skill claims, and what it does not

覆盖范围——本技能的能力边界

The failure this section prevents is not ignorance. It is a confident wrong answer produced by applying the nearest available procedure to a target it was never written for. A documented method that almost fits is more dangerous than no method at all, because it arrives with a plan, a vocabulary and a set of reassuring numbers.
How strong these claims are — this is load-bearing, not cosmetic. Every claim in this skill carries one of three labels, and you must label your own results the same way:
LabelMeansMay it justify a decision?
observed
a command was run and its output existsyes
inferred
follows from an observation, but the step is reasonedprovisionally
unverified
assumed, or reported elsewhere and never reproduced hereonly as something to test
The inventory that replaces a guess: the per-route list of what is covered and what is not — by verified mechanism versus by documented-but-mostly-inferred route — is in
references/coverage-and-limits.md
. Load it when the question is "can this skill actually do X".
Partial coverage that is easy to over-read — these are documented routes whose dependency this skill does not ship, and each has a fence around it in the same file: Dart AOT analysis needs a snapshot dump you must obtain elsewhere and naming the front end matters · extraction-shell recovery stops at the VMP boundary · a Stalker trace is not guaranteed on every device · a kernel-side answer requires a module this skill only scaffolds.
Not covered — say so rather than improvise: Unity / IL2CPP · React Native / Hermes / Cordova · iOS · defeating a server-side authority · an off-the-shelf unpacker or an anti-detection arms race · building and shipping a kernel module. Reasons and evidence boundaries are in
references/coverage-and-limits.md
; silence in that record is not support.
The fallback, as an instruction — this is a rule, not advice. If the target does not match the covered list, or no symptom-index row matches, then stop and classify before choosing a branch: answer the thirteen questions (§Start here). If the shape still does not fit — an unknown runtime, a mechanism you cannot name — say exactly that and propose the cheapest experiment that would identify it. Do not take the closest documented route and apply it anyway. A wrong branch here does not fail loudly: it produces an artifact that builds, runs, and does the wrong thing.
本节要避免的失败不是无知,而是把现成的流程用在根本不适用的目标上,从而得出自信但错误的答案。 一个几乎适用的有文档记录的方法,比没有方法更危险,因为它自带一套方案、术语和让人安心的数字。
这些结论的可靠程度——这是核心内容,而非装饰。 本技能中的每一项结论都带有三个标签之一,你也必须用同样的方式标记你自己的结果:
标签含义是否可用于决策?
observed
已运行命令且存在输出是
inferred
由观测结果推导而来,但推导过程是逻辑推理暂定可以
unverified
假设的,或在其他地方有报告但从未在此处复现仅作为测试对象
替代猜测的清单: 按路线划分的覆盖与未覆盖内容列表——按已验证机制和有文档但大多为推断的路线分类——位于
references/coverage-and-limits.md
。当问题是“本技能到底能不能做X”时,查看该文件。
容易被过度解读的部分覆盖——这些是有文档记录的路线,但本技能不提供其依赖项,且在同一文件中每条都有明确的边界说明:Dart AOT分析需要你从其他地方获取快照转储,且前端的命名很重要 · 抽取壳恢复到VMP边界为止 · Stalker trace不保证在所有设备上都能用 · 内核层面的解决方案需要一个本技能仅提供脚手架的模块。
未覆盖内容——直接说明,不要临时凑合: Unity / IL2CPP · React Native / Hermes / Cordova · iOS · 突破服务端权限 · 现成的脱壳工具或反检测军备竞赛 · 构建并发布内核模块。原因和证据边界见
references/coverage-and-limits.md
;该记录中没有提及的,就是不支持的。
备选方案是硬性规定——这是规则,不是建议。 如果目标不符合覆盖列表,或没有匹配的故障索引条目,那么先停下来分类,再选择分支:回答那13个问题(§开始这里)。如果仍然不匹配——未知的运行时、你叫不出名字的机制——明确说明情况,并提出成本最低的识别实验。 不要硬套最接近的有文档记录的路线。这里的错误分支不会明显报错:它会生成一个能构建、能运行,但功能错误的产物。

Hand-off points — where this skill ends and another view begins

交接点——本技能的边界与其他领域的衔接

Four boundaries that are easy to walk into without noticing. Each names what the other side owns rather than restating it, because two copies of the same advice drift apart. The JNI form table and the per-form verification table live in
references/handoff-boundaries.md
.
  1. JNI — a Java
    native
    declaration and its implementation are two views of one function, and this skill reads each with a different tool. A symbol search fails silently on dynamic registration. Authoritative treatment:
    java2c-and-jni-sinking.md
    §The JNI boundary — why a symbol search fails silently.
  2. Hardening — a dex-side packer observation is a native-side implementation question.
    packers.md
    owns the dex-side identification; the deep dive belongs on the other side.
  3. Native anomalies — read them from the APK side, because the judgement is whether your patch caused the death. Restoring a symbol or reversing an OLLVM function is a different activity with a different toolchain: point across instead of extending
    native-and-so.md
    into it.
  4. Deliverable form — when the artifact stops being an APK, the verification question changes with it. G1's other three forms each move the evidence somewhere else, and the failure is quiet: a privileged result reported in the language of a finished build.
有四个容易在不经意间跨越的边界。每条边界都明确了另一方负责的内容,而非重复说明,因为同一建议的两个副本会逐渐产生偏差。JNI形式表和各形式验证表位于
references/handoff-boundaries.md
。
  1. JNI — Java的
    native
    声明和它的实现是同一个函数的两种视图,本技能用不同的工具读取它们。符号搜索在动态注册的情况下会静默失败。权威处理方式:
    java2c-and-jni-sinking.md
    §JNI边界——为什么符号搜索会静默失败。
  2. 加固 — dex层的加壳观测是native层的实现问题。
    packers.md
    负责dex层的识别;深入分析属于native层的范畴。
  3. Native异常 — 从APK侧读取这些异常,因为要判断的是你的补丁是否导致了崩溃。恢复符号或逆向OLLVM函数是另一项工作,需要不同的工具链:指向对应的文档即可,不要把
    native-and-so.md
    扩展到这些内容里。
  4. 交付物形式 — 当产物不再是APK时,验证问题也会随之改变。G1的其他三种形式各有不同的验证依据,且这种失败是静默的:用完成构建的话术来报告需要特权的结果。

Symptom index — a matching row is a stop signal

故障索引——匹配到条目就是停止信号

You arrive at a symptom, not at a file name. Each row below is a failure that has already been paid for. If any row matches what you are observing, load the file before your next command — not after your next three attempts. Reasoning from first principles at this point is how the same hours get spent twice; more than one entry here is a lesson that was re-derived by hand while the answer sat unread in this repository.
What you observeLoad first
A repackaged/re-signed build dies before your code runs;
SIGSEGV
, all registers zero,
pc=0
,
fault addr
near
0x0
native-tamper-and-suicide.md
(deliberate crash), then
code-virtualization-and-custom-linkers.md
No packer (Application is the app's own, dex readable) and it still dies
code-virtualization-and-custom-linkers.md
§a loader is still a possibility; but if the same build also dies on a second, unrelated device you are looking at an ordinary startup fault, not a hardened one
The app dies at startup on every device, packed or not, with no tombstone while
crash_dump
says
already traced
and logcat says
exited cleanly (0)
a bundled crash reporter has taken the signal handlers, so the platform's own trail is gone. Frida spawn-gating is the recovery route
A
FORTIFY: pthread_mutex_lock called on a destroyed mutex
abort in a Flutter app, on the main thread, before the first frame completes
dart-aot.md
— check
libapp.so
is actually being loaded; Flutter's engine bootstrap is the usual place a native lifecycle fault surfaces
Log says a Java-layer signature/integrity check passed, yet the process dies
code-virtualization-and-custom-linkers.md
§a Java-layer "signature killer" is a decoy
Deleting a library fixes validation but yields
UnsatisfiedLinkError: dlopen failed: library "X" not found
code-virtualization-and-custom-linkers.md
§the deadlock that eats hours
Whole classes appear as bare
native
declarations with no body
java2c-and-jni-sinking.md
— read it before dumping memory: if this is Java2C there is no DEX to find, at any point in the process lifetime. A handful of
native
methods in an otherwise ordinary dex is JNI sinking, not this
A
Java_*
search over a hardened library returns nothing at all
java2c-and-jni-sinking.md
§The JNI boundary — why a symbol search fails silently — dynamic registration, or
-fvisibility=hidden
. The check that works is "exports
JNI_OnLoad
and zero
Java_*
"
You are about to publish an evidence file, a transcript or a README that quotes real work
references/desensitization-and-leak-scans.md
— run
scripts/scan_leaks.py
before it is committed; the hit list is a set of lines to look at, and
--show-exempt
is where the wrong suppressions show
A hooking module appears to have run but its log tag is silent, and you are about to record "it never loaded"
references/precedents/logd-broken-module-never-ran-case-3.md
— a broken
logd
delivers nothing on
logcat
while the module's whole run sits in LSPosed's file log; read both channels
A library's SONAME does not match its filename
code-virtualization-and-custom-linkers.md
,
native-and-so.md
Your edit had no effect at all, with no error
server-config-and-updates.md
§3 (the value may be server-sent), then
packers.md
§map the validation boundary
Process hangs with no crash record, or dies to a
uid 0
killer
native-tamper-and-suicide.md
§the rule (you probably made a terminate path not return)
Death looks like an ordinary null dereference in a hardened library
native-tamper-and-suicide.md
§deliberate-crash stubs
The app dies only while you are attached/rooted
detection-and-anti-analysis.md
; run the unmodified original under identical conditions first
Install fails with
[-124]
and mentions
resources.arsc
/ alignment
repack-and-sign.md
§2a — STORED and 4-byte aligned, both required
Install fails with a bare numeric code (e.g.
[-99]
) and no
INSTALL_FAILED_*
repack-and-sign.md
§vendor install interception — a device-side interceptor, not your build. Use the root
pm install
path
After an install,
am start
does nothing / screenshots show another app /
am start -W
hangs
repack-and-sign.md
§the installer may still own the screen
Log shows
Failure to verify dex file ...: Bad checksum
and a startup
ClassNotFoundException
for an ordinary class
byte-level-patching.md
§the dex header has two integrity fields — order matters
An install "succeeded" but nothing changed, or the version did not move
long-task-discipline.md
§keep the observation window clean
Evidence contradicts itself, or a capture looks like two states mixed
long-task-discipline.md
§keep the observation window clean
You took screenshots but drew the conclusion from logs or from the patch itself
long-task-discipline.md
§captures you never looked at are not evidence
You are about to re-run an experiment whose result you already recorded
long-task-discipline.md
§long-context decay
A script will not start, or a tool "is missing"
scripts/doctor.py
, then
toolchain.md
§"not on PATH" is not "not installed"
A hook or probe reports no events at all, and you are about to call it detection
scripts/anti_detect_probe.js
for the environment self-report first, then
detection-and-anti-analysis.md
§Step 3: locating the check — the order of search from Stage 0
attach
hangs and then fails while the process is still in
ps
detection-and-anti-analysis.md
§Step 3 Stage 0 — check for
D
in
/proc/<pid>/stat
, and attach a different pid as a one-line control before blaming the target
The app exits with no tombstone, no crash and no ANR record
detection-and-anti-analysis.md
§Step 3 — a clean self-exit means the check ran before your hooks existed; the branch conditions there say which Stage
A dump region validates as the wrong thing, or an
r--s
view of
base.apk
looks like a dex
advanced-unpacking.md
§What this route cannot do, and how to tell before you spend the window
Feature-scoped network failure (login/register/pay) while the rest works
tls-and-cert.md
— do not assume your patch caused it
Everything works but every signed request fails after repack
signature-derived-keys.md
A re-signed build runs fine, renders its whole UI and logs no error — but one feature silently never loads, and
dumpsys
/DNS/logcat show no request for it at all (not a rejected request: no request)
code-virtualization-and-custom-linkers.md
§what the native check actually reads — refusing before the request is built. Not the row above: "sent and rejected" and "never sent" have different owners
You cannot tell whether a missing feature is your patch's fault or the target's own behaviour
long-task-discipline.md
§single-variable discipline. Run the zero-change control through the same pipeline, and the decisive variant: the unmodified original with the patch applied in memory only, same device, same network
Under Frida
spawn
, the UI never appears —
mCurrentFocus
stays
null
, screenshots come back blank, the Activity stack never builds
dynamic-frida.md
§spawn keeps the Activity stack down: write the patch into memory, detach, then start the Activity normally
frida-server
keeps disappearing mid-experiment, or the device reboots itself while you are working
dynamic-frida.md
§when the ROM hunts your instrumentation
Ads still appear after a patch that should have killed them
server-config-and-updates.md
§6 (cached config / remote re-enable), then
ad-removal.md
§step 4 (count the SDK's own log lines; n -> 0, not "I did not see it")
A forced-update or "must update" gate blocks the build
updates-and-forced-upgrade.md
§step 6
The dialog is gone but the feature is still locked
membership-and-limits.md
/
account-gates.md
— decide server vs client authority before patching again
You are about to discard a route as "blocked"
packers.md
— re-read it before writing any route off; mis-attributed failures have removed viable routes for hours
The task has run long and you are unsure what is already proven
long-task-discipline.md
§keep a live record
A dumped dex parses in full, the classes are all there, and most method bodies are
return-void
stubs or nop fills
references/advanced-unpacking.md
— an extraction shell: measure the
stub%
with
scripts/dex_dump_validate.py
before trusting any of it, and know that recovering the bodies is a different route
Your
frida
dump dies mid-write (
script has been destroyed
), or the process you are dumping keeps changing pid
advanced-unpacking.md
§dumping when frida is refused — rule out memory pressure first; a reclaim-and-relaunch needs no instrumentation
A repack is refused by several independent checks, or the build has to keep working through store updates
references/lsposed-and-modules.md
— deliver a module instead of an APK; G1's form table says when
A hook module is installed, enabled and scoped, yet its log tag never appears — and you are about to conclude it never ran
lsposed-and-modules.md
§Deploy, enable, and verify — a
logcat
-only verdict has already been wrong here
: on one ROM
logd
is broken and output reaches only
/data/adb/lspd/log/modules_<ts>.log
A module's entry class is missing from its own dex (so it can never load), yet the package installs, enables and looks healthy
references/lsposed-and-modules.md
— check
assets/xposed_init
against the dex's actual classes; installation is not evidence of anything
You only need to call the target's own routine (sign, token, encrypt) rather than change the app
references/emulation-and-rpc.md
— emulate it, or service-ify the live function over Frida RPC
A native function is a many-thousand-line
switch
state machine, or the decompiler's output is meaningless
references/native-dbi-and-deobfuscation.md
— OLLVM shapes, a Stalker trace, and how far a trace actually gets you
Stalker.follow
installs but no events arrive, or following a hot libc export crashes the process
references/native-dbi-and-deobfuscation.md
§6 failure modes — this repository measured both
The traffic is protobuf/gRPC/QUIC, or a proxy sees TLS but requests still fail on a Flutter app
references/protocol-reverse.md
— schema-less protobuf, frame capture, and native-side pinning
Userspace hooks land and the app still dies: the check reads
/proc/self/status
through a raw
svc
, or runs before
JNI_OnLoad
references/kernel-and-environment-hardening.md
— what the next layer up and down can actually do, and when to stop
You must edit, repack, sign or inspect the APK from the phone itself
references/on-device-tooling.md
,
scripts/mt_mcp_probe.py
A captured body decodes to nothing readable, or you cannot tell whether a length-delimited field is a string, a nested message or a packed array
protocol-reverse.md
. Protobuf on the wire (measured) — run
scripts/protobuf_decode_raw.py
; the candidate list and its
tie:
lines are the answer
Method bodies are present but decode as private opcodes, and you need the mapping rather than an explanation of why VMP is hard
vmp-differential-analysis.md
, then
advanced-unpacking.md
for the shape diagnosis
A store build arrives as
base.apk
+
split_config.*.apk
, or a rebuilt build is refused as a set although every file verifies on its own
split-apk.md
— one keystore across every member for
pm install-multiple
, and check that a merge is legal before trusting a merged single APK
你遇到的是故障现象,而不是文件名。下面的每一行都是已经付出过代价的故障。如果有任何一行匹配你观察到的现象,在执行下一条命令前先加载对应的文件——而不是再试三次之后。在这个时候从第一性原理开始推理,只会让同样的时间被浪费两次;这里不止一个条目都是曾经被手动重新推导过,而答案早就躺在这个仓库里没人看。
你观察到的现象优先查看的文件
重打包/重签名的构建包在你的代码运行前就崩溃;出现
SIGSEGV
,所有寄存器为零,
pc=0
,
fault addr
接近
0x0
native-tamper-and-suicide.md
(故意崩溃),然后看
code-virtualization-and-custom-linkers.md
没有加壳(Application是应用自己的,dex可读)但仍然崩溃
code-virtualization-and-custom-linkers.md
§加载器仍然是可能的原因;但如果同一个构建包在另一台无关的设备上也崩溃,那就是普通的启动故障,不是加固导致的
应用在所有设备上启动时都崩溃,不管有没有加壳,没有tombstone,而
crash_dump
显示
already traced
,logcat显示
exited cleanly (0)
内置的崩溃报告器接管了信号处理器,因此平台本身的痕迹消失了。Frida spawn-gating是恢复方法
Flutter应用的主线程在第一帧完成前出现
FORTIFY: pthread_mutex_lock called on a destroyed mutex
中止错误
dart-aot.md
—— 检查
libapp.so
是否真的被加载;Flutter的引擎启动是native生命周期故障通常的暴露位置
日志显示Java层的签名/完整性校验通过了,但进程还是崩溃了
code-virtualization-and-custom-linkers.md
§Java层的“签名杀手”是诱饵
删除某个库修复了验证问题,但出现
UnsatisfiedLinkError: dlopen failed: library "X" not found
code-virtualization-and-custom-linkers.md
§耗费数小时的死锁
整个类都只有空的
native
声明,没有方法体
java2c-and-jni-sinking.md
—— 在转储内存之前先读它:如果是Java2C,那么在进程生命周期的任何时刻都找不到DEX。在普通dex中只有少量
native
方法是JNI下沉,不是这种情况
在加固库上搜索
Java_*
完全没有结果
java2c-and-jni-sinking.md
§JNI边界——为什么符号搜索会静默失败——动态注册,或者
-fvisibility=hidden
。有效的检查方法是“导出
JNI_OnLoad
且没有任何
Java_*
符号”
你即将发布包含实际工作内容的证据文件、记录或README
references/desensitization-and-leak-scans.md
—— 在提交前运行
scripts/scan_leaks.py
;命中列表是需要检查的行,
--show-exempt
会显示错误的豁免项
一个hook模块看起来已经运行了,但它的日志标签没有输出,而你正准备记录“它从未加载”
references/precedents/logd-broken-module-never-ran-case-3.md
—— 损坏的
logd
不会在
logcat
中输出任何内容,但模块的整个运行记录都在LSPosed的文件日志里;两个通道都要读
某个库的SONAME与其文件名不匹配
code-virtualization-and-custom-linkers.md
、
native-and-so.md
你的修改完全没有效果,也没有错误
server-config-and-updates.md
§3(值可能是服务端下发的),然后看
packers.md
§映射验证边界
进程挂起没有崩溃记录,或者被
uid 0
的杀手进程杀死
native-tamper-and-suicide.md
§规则(你可能让终止路径没有返回)
崩溃看起来像是加固库中的普通空指针解引用
native-tamper-and-suicide.md
§故意崩溃存根
应用只在你连接/root的时候崩溃
detection-and-anti-analysis.md
;先在相同条件下运行未修改的原始包
安装失败,错误码
[-124]
,提到
resources.arsc
/ 对齐问题
repack-and-sign.md
§2a —— 必须同时满足STORED存储和4字节对齐
安装失败,只有纯数字错误码(例如
[-99]
),没有
INSTALL_FAILED_*
提示
repack-and-sign.md
§厂商安装拦截——是设备端的拦截器,不是你的构建包有问题。用root的
pm install
路径
安装后,
am start
没有反应 / 截图显示另一个应用 /
am start -W
挂起
repack-and-sign.md
§安装器可能仍然占据屏幕
日志显示
Failure to verify dex file ...: Bad checksum
,并且启动时普通类出现
ClassNotFoundException
byte-level-patching.md
§dex头有两个完整性字段——顺序很重要
安装“成功”了但什么都没变,或者版本号没有变化
long-task-discipline.md
§保持观察窗口干净
证据自相矛盾,或者捕获的内容看起来像两种状态混合
long-task-discipline.md
§保持观察窗口干净
你截了图但结论是从日志或补丁本身得出的
long-task-discipline.md
§你从未查看过的捕获内容不算证据
你正准备重新运行一个已经记录过结果的实验
long-task-discipline.md
§长上下文衰减
某个脚本无法启动,或者某个工具“缺失”先运行
scripts/doctor.py
,然后看
toolchain.md
§“不在PATH上”不等于“没安装”
某个hook或探针完全没有报告事件,而你正准备称之为检测到了先用
scripts/anti_detect_probe.js
获取环境自报告,然后看
detection-and-anti-analysis.md
§步骤3:定位校验——从阶段0开始的搜索顺序
attach
挂起然后失败,而进程仍然在
ps
列表中
detection-and-anti-analysis.md
§步骤3阶段0 —— 检查
/proc/<pid>/stat
中的
D
状态,并且在归咎于目标之前,先附加一个不同的pid作为一行对照实验
应用退出,没有tombstone、没有崩溃、没有ANR记录
detection-and-anti-analysis.md
§步骤3 —— 干净的自退出意味着校验在你的hook生效前就运行了;那里的分支条件说明是哪个阶段
转储的区域验证出来是错误的东西,或者
base.apk
的
r--s
视图看起来像dex
advanced-unpacking.md
§这条路线做不到什么,以及如何在花费时间前判断
特定功能的网络故障(登录/注册/支付),但其他功能正常
tls-and-cert.md
—— 不要假设是你的补丁导致的
一切正常,但重打包后所有签名请求都失败
signature-derived-keys.md
重签名的构建包运行正常、渲染完整UI、日志没有错误——但有一个功能悄无声息地永远加载不出来,而
dumpsys
/DNS/logcat显示根本没有发出请求(不是请求被拒绝:是没有请求)
code-virtualization-and-custom-linkers.md
§native校验实际读取的内容——在请求构建之前就拒绝了。和上面那行不同:“发送后被拒绝”和“从未发送”的负责方不同
你无法判断某个缺失的功能是你的补丁的问题,还是目标本身的行为
long-task-discipline.md
§单变量原则。运行走相同流程的零修改对照包,以及决定性的变体:未修改的原始包,仅在内存中应用补丁,相同设备、相同网络
在Frida
spawn
模式下,UI永远不出现——
mCurrentFocus
保持
null
,截图返回空白,Activity栈永远不构建
dynamic-frida.md
§spawn会保持Activity栈挂起:把补丁写入内存,分离,然后正常启动Activity
frida-server
在实验过程中不断消失,或者你工作的时候设备自己重启
dynamic-frida.md
§当ROM追捕你的插桩工具时
本应杀死广告的补丁之后,广告仍然出现
server-config-and-updates.md
§6(缓存配置 / 远程重新启用),然后看
ad-removal.md
§步骤4(数SDK自己的日志行数;要从n变成0,不是“我没看到”)
强制更新或“必须更新”的门控挡住了构建包
updates-and-forced-upgrade.md
§步骤6
对话框消失了但功能仍然被锁
membership-and-limits.md
/
account-gates.md
—— 在再次打补丁前先判断是服务端还是客户端权限
你正准备把某条路线判定为“走不通”
packers.md
—— 在废弃任何路线前重新读一遍;错误归因的故障已经让可行路线被废弃了数小时
任务已经运行了很久,你不确定哪些已经被证实
long-task-discipline.md
§保持实时记录
转储的dex能完整解析,所有类都在,但大多数方法体都是
return-void
存根或nop填充
references/advanced-unpacking.md
—— 抽取壳:在信任任何内容之前,先用
scripts/dex_dump_validate.py
测量
stub%
,并且要知道恢复方法体是另一条路线
你的
frida
转储在写入中途失败(
script has been destroyed
),或者你正在转储的进程pid一直在变
advanced-unpacking.md
§frida被拒绝时的转储——首先排除内存压力;回收并重新启动不需要插桩
重打包被多个独立校验拒绝,或者构建包必须能经受应用商店更新
references/lsposed-and-modules.md
—— 交付模块而不是APK;G1的形式表说明什么时候用这种方式
一个hook模块已经安装、启用、设置了作用域,但它的日志标签从未出现——而你正准备得出它从未运行的结论
lsposed-and-modules.md
§部署、启用和验证——仅靠
logcat
的结论在这里已经出过错
:在某款ROM上
logd
损坏了,输出只会到
/data/adb/lspd/log/modules_<ts>.log
模块的入口类不在它自己的dex里(所以永远加载不了),但包能安装、能启用、看起来一切正常
references/lsposed-and-modules.md
—— 对照dex的实际类检查
assets/xposed_init
;安装不能证明任何事情
你只需要调用目标自己的例程(签名、令牌、加密),而不是修改应用
references/emulation-and-rpc.md
—— 模拟它,或者通过Frida RPC把实时函数封装成服务
一个native函数是数千行的
switch
状态机,或者反编译器的输出毫无意义
references/native-dbi-and-deobfuscation.md
—— OLLVM形态、Stalker trace,以及trace实际能帮你到什么程度
Stalker.follow
安装了但没有事件到达,或者跟踪一个热门的libc导出函数导致进程崩溃
references/native-dbi-and-deobfuscation.md
§6故障模式——本仓库都实测过
流量是protobuf/gRPC/QUIC,或者代理看到了TLS但Flutter应用的请求仍然失败
references/protocol-reverse.md
—— 无schema的protobuf、帧捕获,以及native层证书锁定
用户态hook生效了但应用仍然崩溃:校验通过原始
svc
指令读取
/proc/self/status
,或者在
JNI_OnLoad
之前就运行了
references/kernel-and-environment-hardening.md
—— 上下层分别能做什么,以及什么时候该停止
你必须从手机上编辑、重打包、签名或检查APK
references/on-device-tooling.md
、
scripts/mt_mcp_probe.py
捕获的响应体解码不出任何可读内容,或者你无法判断一个长度分隔的字段是字符串、嵌套消息还是打包数组
protocol-reverse.md
。线上protobuf(实测)——运行
scripts/protobuf_decode_raw.py
;候选列表及其
tie:
行就是答案
方法体存在但解码为私有操作码,你需要的是映射关系,而不是VMP为什么难的解释
vmp-differential-analysis.md
,然后看
advanced-unpacking.md
做形态诊断
应用商店的构建包以
base.apk
+
split_config.*.apk
的形式提供,或者重建的构建包作为一组被拒绝,尽管每个文件单独验证都通过
split-apk.md
—— 所有成员用同一个密钥库执行
pm install-multiple
,并且在信任合并后的单个APK之前先检查合并是否合法

Gates — clear these before you patch, in order

关卡——打补丁前按顺序通过这些检查

Each gate is an action with a pass criterion. Do not proceed past a gate you have not cleared, and do not treat "I understand the idea" as clearing it. Skipping a gate is not a shortcut; it is how the work gets redone.
G1 · Deliverable form — and the cost ceiling on it. State, in one sentence you could hand to someone else, what artifact must exist at the end and under what constraints (rooted or not, installable on a stock device or not, must survive updates or not, online or offline). Pass: the sentence names a testable constraint, not an activity. Fail: you are solving a problem in an environment the deliverable will never see.
Then name the form that sentence implies. "A rebuilt, self-contained APK" is only one of four, and this is where the choice belongs — not after the repack has already failed:
FormRight answer whenWhat it costs you
Rebuilt, installable APKThe client owns the behaviour; no multi-point integrity check; no extraction shellRepack, re-sign, and a device to verify on
LSPosed / Xposed moduleThe logic is client-side but the app fights repacks (multi-point signature checks, shell self-verification), or the result only has to work on rooted devices you controlA rooted device, module scaffolding, and an app that must not detect the hooking framework →
references/lsposed-and-modules.md
Local RPC / emulation serviceYou do not need to change the app — you need to call it: a signing routine, a token, an encryption functionA live device or an emulated loader plus a call harness →
references/emulation-and-rpc.md
Analysis report with a stated boundaryThe authority is server-side, or the target is a real VMP / extraction shell whose recovery cost exceeds the value of the taskNothing ships — and that is the honest answer, not a failure →
references/advanced-unpacking.md
,
references/server-api.md
Decision trigger for leaving the first column: switch off "rebuilt APK" as soon as the evidence shows (a) more than one independent integrity check that must all pass, (b) an extraction shell whose method bodies exist only at invocation time, or (c) any body that decodes as private opcodes. At that point the repack route is not merely expensive — it is blocked, and the deliverable sentence should say which form replaced it. A form chosen here and re-read at every checkpoint is the guard against the most expensive drift in this skill (
references/long-task-discipline.md
).
G2 · Environment truth and capability inventory. Run
scripts/doctor.py
(and
scripts/preflight.py
if a device is in play). Pass: you know which toolchains and scripts can actually run here, you have seen the environment warnings — clock skew, leftover
adb forward
/proxy, a device-side frida process already running, a tool installed off-PATH — and you have written down the capability this target will demand against the capability this machine has. Name the two or three layers the task will almost certainly reach (for example "arm64 native decompilation", "Dart AOT snapshot dumping", "device-side TLS inspection", "dex-wide cross-referencing") and mark each available / missing-but-installable / genuinely out of reach. Fail: you are about to attribute to the target a failure caused by your own setup — or to spend a day routing around a tool that installs in ten minutes. A layer whose tool is missing is a task item, not a constraint to design around. →
references/toolchain.md
§Closing a capability gap
G3 · Code location. From the manifest and dex, answer: is there a packer, where does the app's own code live (dex / native / Dart / Unity / server), and is any of it virtualized to native. Pass: you can name the class that owns the behaviour you intend to change, or you have an explicit plan to find it. Fail: you are about to patch a layer you have not located. If recon says "no packer", still check the virtualization shape — see the index rows above.
G4 · Baseline and control. Pass: you have a control run — the unmodified original, or a zero-change repack through the same pipeline — and you have recorded the observed failure (including time-to-death, if it dies). Fail: when the patched build misbehaves you will have nothing to compare against, and every later measurement is unfalsifiable.
每个关卡都是一项有通过标准的操作。不要跳过未通过的关卡,也不要把“我理解思路了”当作通过。跳过关卡不是捷径——它只会让工作返工。
G1 · 交付物形式——以及成本上限。 用一句可以交给别人的话,说明最终必须产出什么产物,以及有哪些约束条件(是否需要root、是否能在原装设备上安装、是否必须能经受更新、在线还是离线)。通过标准: 这句话明确了可测试的约束,而非一项活动。未通过: 你在一个交付物永远不会用到的环境里解决问题。
然后确定这句话对应的形式。“重建的自包含APK”只是四种形式之一,而选择应该在这里做出——不是在重打包已经失败之后:
形式适用场景所需成本
重建的可安装APK行为逻辑在客户端;没有多点完整性校验;没有抽取壳重打包、重签名,以及一台用于验证的设备
LSPosed / Xposed模块逻辑在客户端,但应用抗拒重打包(多点签名校验、壳自校验),或者结果只需要在你控制的root设备上运行一台root设备、模块脚手架,以及应用不能检测到hook框架 →
references/lsposed-and-modules.md
本地RPC / 模拟服务你不需要修改应用——你需要调用它的功能:签名例程、令牌、加密函数一台运行中的设备或模拟加载器,加上调用框架 →
references/emulation-and-rpc.md
带明确边界的分析报告权限在服务端,或者目标是真正的VMP / 抽取壳,其恢复成本超过任务价值不交付产物——这是诚实的答案,而非失败 →
references/advanced-unpacking.md
、
references/server-api.md
离开第一列的决策触发条件: 一旦证据显示以下任意一种情况,就放弃“重建APK”的方案:(a) 存在多个必须全部通过的独立完整性校验,(b) 存在抽取壳,其方法体仅在调用时才存在,或**(c)** 任何方法体解码为私有操作码。此时重打包路线不仅是成本高——而是根本走不通,交付物说明中应明确替换为哪种形式。在这里选定形式并在每个检查点重新审视,可以避免本技能中代价最高的目标偏差(
references/long-task-discipline.md
)。
G2 · 环境实际情况与能力清单。 运行
scripts/doctor.py
(如果涉及设备,还要运行
scripts/preflight.py
)。通过标准: 你知道哪些工具链和脚本可以在这里实际运行,你已经看到了环境警告——时钟偏移、残留的
adb forward
/代理、设备端已运行的frida进程、安装在非PATH路径的工具——并且你已经写下了目标所需的能力,以及当前环境具备的能力,进行了对比。 列出任务几乎肯定会涉及的两到三个层级(例如“arm64 native反编译”、“Dart AOT快照转储”、“设备端TLS检测”、“全dex交叉引用”),并标记每个是可用/缺失但可安装/确实无法获得。未通过: 你即将把自己环境配置导致的故障归咎于目标——或者花一天时间绕开一个十分钟就能装好的工具。某个层级的工具缺失是待办事项,而非需要绕开的限制。→
references/toolchain.md
§弥补能力缺口
G3 · 代码定位。 根据清单文件和dex,回答以下问题:是否有壳?应用自己的代码位于哪里(dex / native / Dart / Unity / 服务端)?是否有任何部分被虚拟化到native层?通过标准: 你能说出你要修改的行为属于哪个类,或者你有明确的计划去找到它。未通过: 你即将给一个你还没定位到的层级打补丁。如果侦察显示“没有壳”,仍然要检查虚拟化形态——参见上面的索引条目。
G4 · 基线与对照。 通过标准: 你有一次对照运行——未修改的原始包,或者走相同流程的零修改重打包——并且你已经记录了观测到的故障(包括崩溃时间,如果会崩溃的话)。未通过: 当修补后的构建包出现异常时,你没有可以对比的基准,之后的所有测量都无法证伪。

Start here: classify the target in thirteen questions

从这里开始:用13个问题对目标分类

Answer these before touching a tool — all thirteen, in order. Each one changes the whole plan, and a wrong answer here does not fail loudly: it produces an artifact that builds, runs, and does the wrong thing. The answer column names both the action and the file that owns the detail; load that file before acting on the question.
#QuestionWhat the answer changesLoad
1Is the app packed/hardened? Read the manifest's
application android:name
A third-party shell class rather than the app's own Application means you have a packer and must handle it first
recon.md
2Where does the behaviour actually live? — ad SDK · server-issued config for client-rendered UI · membership/VIP · feature flag or debug switch · anything decided by an API responseClient-side and removable, versus server-supplied data the client decides with, versus server-authoritative where a client patch is cosmetic. Decide this early: hunting an ad SDK that does not exist costs hours, and the server-config shape has no SDK to find
ad-removal.md
,
server-config-and-updates.md
,
membership-and-limits.md
,
server-api.md
3Is the app's own code in plain dex, or moved to native / Flutter / Unity?Which toolchain the whole task uses. Cheap runtime check before committing: hook the obvious Java classes for the UI you care about and reproduce that UI. Hooks that fire mean Java owns it; hooks that fire zero times while the UI is plainly on screen mean the runtime or native code draws it, and a dex-only plan will stall. Do not keep hunting in dex after a zero-hit probe — the most expensive wrong turn in this skill's history
recon.md
§Where does the app's own code live,
framework-runtimes.md
4What must the deliverable be able to do? Write it as a testable sentence and re-read it at every checkpointThe most expensive drift in this skill: a runtime-only result (data edit, live hook, blocked hostname, host proxy) looks like success while failing the requirement. The axes: privilege (unrooted?), modification form (rebuilt artifact vs live instrumentation), ABI/device class, network, persistence, distribution (self-contained?). If the evidence already shows a deep extraction shell, a VMP, or more than one independent integrity check, re-answer against G1's four forms — the repack column may be blocked, not expensive
long-task-discipline.md
§the most expensive drift
5Does the app verify its own signature, or does the server?App-side means you must bypass it; server-side means re-signing silently breaks the app later
repack-and-sign.md
,
server-api.md
6What is your device situation? — rooted real device, rooted emulator, or no deviceWhether Frida is usable at all, and whether the deliverable can be tested where it will run. Run
scripts/preflight.py
before your first experiment
and again whenever a failure surprises you — device state, a dead device server, a leftover proxy and clock drift all masquerade as a broken patch
environment.md
,
pitfalls.md
P9
7Which architecture is actually executing?
getprop
reports what the device claims and
primaryCpuAbi
what the package manager chose — neither is what runs. Only the live mapping is ground truth. An unmapped library, a translator in play, or a different ABI than assumed changes the plan more than any patch will
native-and-so.md
§Cross-architecture,
scripts/lib_map.py
8Is one specific feature failing at runtime (login, registration, payment, an API-backed screen) while the rest works?A feature-scoped network failure is very often a TLS/certificate problem on one code path, not your patch. An app can carry two independent trust chains, so "other requests work" proves nothing. Rule this out in minutes before hunting a signature check
tls-and-cert.md
9Was the input a build you did not produce (a circulating "cracked"/"modded" APK)?Such builds are frequently re-protected — sometimes with more layers than the original — and may carry injected components or endpoints. Never use one as a patching workbench
third-party-builds.md
10Does the client sign its requests with its own signing certificate?Grep for
toCharsString()
/
signatures[0]
/
getPackageInfo(..., 64)
before the first repack. If that value feeds a native HMAC/DES routine, the rebuilt APK must hardcode the original certificate value at every read site, or every signed request fails while the app still launches and looks healthy. The single most expensive silent failure in a repack, and 15 minutes of grep prevents it
signature-derived-keys.md
11Does the app die on its own after a while — no Java stack, or a native crash that looks like a bug?A hardened library rarely calls
kill
; it more often arranges a fault (load a small constant, use it as a pointer) so the death looks like an ordinary defect. Two rules before touching anything: enumerate which mechanism actually fires (signal and tombstone split them apart), and neutralise by returning, never by making it not return — a spinning stub freezes the process and produces a symptom that looks nothing like the cause
native-tamper-and-suicide.md
12Will this build still be usable in a week?Any version check, upgrade prompt or self-update path means an unpatched build can be switched off or replaced remotely. This is one or two edits and it decides whether the work is durable — part of the build, not a follow-up. Also check for a hot-update / remote-config channel, which restores removed behaviour with no version change at all
updates-and-forced-upgrade.md
13Does the request touch sign-in or phone binding ("no login required", "skip binding", "guest ok")?Separating a client-side gate (patchable) from an account-scoped resource (the screen is empty because the server has no account to answer for — not patchable). Classify first; and never fabricate a session to satisfy a gate, which produces a state worse than being signed out
account-gates.md
Long-task rule (a condition, not advice): if this is likely to run long, open
references/long-task-discipline.md
now and keep its record updated as you go. Re-read its refuted-conclusions and dead-routes sections before starting any experiment. Losing earlier findings is the most expensive failure in this skill, and it is entirely preventable.
在接触任何工具前回答这些问题——全部13个,按顺序回答。每个问题都会改变整个计划,而错误的答案不会明显报错:它会生成一个能构建、能运行,但功能错误的产物。 答案列同时说明了操作和对应的详细文档;在根据问题采取行动前,先查看对应的文档。
序号问题答案的影响查看文档
1应用是否加壳/加固? 查看清单文件中的
application android:name
如果是第三方壳类而非应用自己的Application,说明应用有壳,必须首先处理
recon.md
2目标行为实际位于哪里? — 广告SDK · 服务端下发配置、客户端渲染的UI · 会员/VIP · 功能开关或调试开关 · 任何由API响应决定的内容分为三种情况:客户端侧可移除的、客户端根据服务端数据决策的、服务端说了算(客户端补丁只是表面功夫)。尽早确定: 寻找一个根本不存在的广告SDK会耗费数小时,而服务端配置的情况根本没有SDK可找
ad-removal.md
、
server-config-and-updates.md
、
membership-and-limits.md
、
server-api.md
3应用自己的代码是在普通dex里,还是移到了native / Flutter / Unity?决定整个任务使用哪种工具链。在投入前做低成本的运行时检查: hook你关心的UI对应的明显Java类,然后复现那个UI。如果hook触发了,说明是Java层负责的;如果UI明显显示在屏幕上,但hook一次都没触发,说明是运行时或native代码绘制的,纯dex的方案会卡住。零命中探测后不要再继续在dex里找——这是本技能历史上代价最高的弯路
recon.md
§应用自己的代码在哪里,
framework-runtimes.md
4交付物必须具备什么能力? 写成可测试的语句,并在每个检查点重新审视本技能中代价最高的偏差:仅运行时有效的结果(数据修改、实时hook、拦截主机名、主机代理)看起来像是成功,但实际上不满足要求。考量维度:权限(是否不需要root?)、修改形式(重建产物 vs 实时插桩)、ABI/设备类型、网络、持久性、分发方式(是否自包含?)。如果证据已经显示存在深层抽取壳、VMP,或多个独立完整性校验,对照G1的四种形式重新回答——重打包路线可能是走不通,而不是成本高
long-task-discipline.md
§代价最高的偏差
5应用是自己验证签名,还是服务端验证?应用端验证意味着你必须绕过它;服务端验证意味着重签名会在之后悄无声息地搞坏应用
repack-and-sign.md
、
server-api.md
6你的设备情况如何? — root的真机、root的模拟器,还是没有设备决定Frida是否可用,以及交付物是否能在它实际运行的环境中测试。在第一次实验前运行
scripts/preflight.py
,每次遇到意外故障时也再运行一次——设备状态、设备服务进程死亡、残留的代理和时钟偏移,都会伪装成补丁失效
environment.md
、
pitfalls.md
P9
7实际执行的是哪种架构?
getprop
报告的是设备声称的架构,
primaryCpuAbi
是包管理器选择的架构——两者都不是实际运行的架构。只有实时内存映射才是事实。未映射的库、存在转译器,或者实际ABI与假设不同,对计划的影响比任何补丁都大
native-and-so.md
§跨架构,
scripts/lib_map.py
8是否有一个特定功能在运行时失败(登录、注册、支付、依赖API的页面),而其他功能正常?特定功能的网络故障往往是某个代码路径上的TLS/证书问题,而不是你的补丁导致的。一个应用可以有两套独立的信任链,所以“其他请求正常”什么也证明不了。在寻找签名校验之前,花几分钟排除这个问题
tls-and-cert.md
9输入的构建包是不是你自己制作的(流传的“破解版”/“修改版”APK)?这类构建包经常被重新加固——有时比原版的层数还多——而且可能携带注入的组件或端点。绝不要用它作为打补丁的工作台
third-party-builds.md
10客户端是否用自己的签名证书对请求签名?在第一次重打包前,用grep搜索
toCharsString()
/
signatures[0]
/
getPackageInfo(..., 64)
。如果这个值会传入native的HMAC/DES例程,那么重建的APK必须在每个读取位置硬编码原始证书的值,否则所有签名请求都会失败,但应用仍然能启动,看起来一切正常。这是重打包中代价最高的静默失败,15分钟的grep就能避免
signature-derived-keys.md
11应用是否会在运行一段时间后自行崩溃——没有Java栈,或者看起来像bug的native崩溃?加固库很少调用
kill
;它更常主动触发故障(加载一个小常量,把它当指针用),让崩溃看起来像普通缺陷。在做任何操作前记住两条规则:先确定实际触发的是哪种机制(信号和tombstone可以区分),并且通过返回值来中和,绝不要让它不返回——一个空转的存根会冻结进程,产生的症状和原因完全不沾边
native-tamper-and-suicide.md
12这个构建包一周后还能用吗?任何版本检查、升级提示或自更新路径,都意味着未修补的构建包可能被远程停用或替换。这只需要一两个修改,但决定了工作成果是否持久——这是构建的一部分,不是后续工作。还要检查是否有热更新/远程配置通道,它们可以在不改变版本号的情况下恢复被移除的功能
updates-and-forced-upgrade.md
13需求是否涉及登录或手机绑定(“无需登录”、“跳过绑定”、“游客可用”)?区分客户端侧的门控(可修补)和账号范围的资源(页面为空是因为服务端没有对应的账号可以返回——不可修补)。先分类;并且绝不要伪造会话来通过门控,这会导致比未登录更糟的状态
account-gates.md
长任务规则(这是要求,不是建议): 如果任务可能耗时较长,现在就打开
references/long-task-discipline.md
,并在过程中持续更新记录。在开始任何实验前,重新阅读其中的“已推翻结论”和“已废弃路线”部分。丢失之前的发现是本技能中代价最高的失败,而这是完全可以避免的。

The workflow, end to end

端到端工作流程

Steps are ordered. Skip a step only when its stated skip condition is met — "it seems unnecessary" is not a condition, and it is the reason most of the failures in
pitfalls.md
happened.
Two-strike rule. If the same kind of attempt fails twice, stop and go back to classification. Do not run a third variation of a hypothesis that has already failed twice. Two failures of one shape means the model is wrong, not that the parameters need tuning — and the third attempt is where an entire round gets spent confirming what the first two already said. Re-read the symptom index at that point; it exists for exactly this moment.
  1. Preflight, then Recon —
    scripts/doctor.py
    is the cheapest possible first command: it reports which toolchains and scripts can actually run here, and surfaces the environment facts that poison experiments (clock skew, leftover
    adb forward
    /proxy, a device-side frida process already running, a tool installed off-PATH). Then
    scripts/preflight.py
    before anything else if a device is involved (it takes seconds and prevents a whole class of false conclusions), then
    references/recon.md
    . Manifest, package name, version, ABI, dex count, packer, embedded SDKs, where the app's own code lives. Ten minutes here saves hours. If it is packed, unpack before anything else (
    references/recon.md
    §unpacking): you cannot patch code you cannot read, the encrypted payload lengths tell you which dumped dex is the original, and a memory dump must be de-duplicated by hash and structurally validated before any of it is trusted. If recon says there is no packer but a re-signed build still dies, you are in the layer
    references/code-virtualization-and-custom-linkers.md
    covers — do not proceed on the assumption that "no packer" means "editable". If the app already dies on its own — especially at a roughly constant time after launch, or with a native crash — locate the mechanism before planning any patch (
    references/native-tamper-and-suicide.md
    ,
    scripts/native_crash.py
    ). Record the observed time-to-death: it is the baseline every later attempt is measured against, and without it a surviving run cannot be told from a changed schedule. Skip condition: never skipped. G2/G3 in §Gates are cleared here or not at all.
  2. Extract strings and endpoints — build a picture of the app's API surface and SDK inventory from the dex string tables. No decompiler needed for this, and it is fast. Scripts:
    scripts/dex_strings.py
    .
  3. Trace to the owning class — find the class that wraps the behavior (the app almost always wraps third-party SDKs in one helper). Reverse-lookup instructions:
    references/dex-patching.md
    §finding-the-call-site.
  4. Decide the patch layer — client SDK call / client rendering / client data consumption / server contract. See the table in
    references/ad-removal.md
    .
  5. Patch surgically —
    references/dex-patching.md
    and
    references/byte-level-patching.md
    . Two techniques, and picking the right one is a decision, not a preference: equal-length byte edits (
    scripts/dex_patch_bytes.py
    , located with
    scripts/dex_find_insn.py
    ) when the change fits in an existing instruction slot or constant — nothing moves, so no offset, try/catch block or debug pointer can be invalidated. dexlib2 method rewriting (
    scripts/dexpatch/
    ) only when the change genuinely needs new instructions. Whole-tree smali round-trip damages R8-optimized dex in ways that only show up at runtime; a method rebuild also inflates the file (measured:
    debug_info
    924 B -> 22.8 KB, dex 4.32 MB -> 7.73 MB on one sample). Whichever you use, recompute the dex header integrity fields (signature first, checksum last) —
    references/byte-level-patching.md
    §the dex header has two integrity fields.
  6. Repack and sign —
    references/repack-and-sign.md
    . Do not strip the whole
    META-INF/
    .
    This single mistake destroys otherwise-correct builds. 6b. Neutralise the update path — before you call the build done. If the app checks for updates at all, add the two-layer patch (
    references/updates-and-forced-upgrade.md
    ): no-op the update routine's entry, and force the version comparison to its "no update" side. A build that can be switched off or replaced remotely is not a deliverable, and this costs minutes here versus a rebuild later. Do the same for any remote-config or hot-update channel that could restore the behaviour you removed. 6c. Handle account gates only after classifying them — if the request mentions sign-in or binding, apply
    references/account-gates.md
    and state plainly which guarded screens become usable and which stay empty because their content is account-scoped.
  7. Verify on device —
    references/environment.md
    +
    references/verification.md
    . Check: launches, the changed behavior actually changed, nothing unrelated broke, and the app reaches its normal UI with no blocking dialog. First prove the artifact actually changed on the device -- a package manager reporting success does not prove an interposed confirmation was accepted (P18). Capture continuously for the first ~20 seconds after launch, and look at the captures — sampling gaps are how a blocking modal goes unseen (P20), and a burst of images that were never inspected is not evidence. If the accessibility tree is empty, the image is the primary evidence rather than a fallback.
  8. Log what you learned — if a failure cost you more than thirty minutes, add it to
    references/pitfalls.md
    . That file is the most valuable artifact in this skill.
步骤是按顺序排列的。只有当步骤说明的跳过条件满足时才能跳过——“看起来没必要”不是跳过条件,这也是
pitfalls.md
中大多数故障发生的原因。
两次失败规则。 如果同一类型的尝试失败了两次,停下来回到分类步骤。不要对一个已经失败两次的假设尝试第三种变体。同一形态的两次失败意味着模型是错的,而不是参数需要调整——第三次尝试只会把一整轮时间花在确认前两次已经表明的结论上。这时候重新去看故障索引;它就是为这种时刻准备的。
  1. 预检,然后侦察 —
    scripts/doctor.py
    是成本最低的第一条命令:它会报告哪些工具链和脚本可以在这里实际运行,并暴露会破坏实验的环境问题(时钟偏移、残留的
    adb forward
    /代理、设备端已运行的frida进程、安装在非PATH路径的工具)。如果涉及设备,在做任何其他操作前先运行
    scripts/preflight.py
    (只需要几秒钟,能避免一整类错误结论),然后查看
    references/recon.md
    。检查清单文件、包名、版本、ABI、dex数量、壳、嵌入的SDK、应用自己的代码位置。在这里花十分钟能节省数小时。如果有壳,先脱壳再做其他操作(
    references/recon.md
    §脱壳):你无法给读不到的代码打补丁,加密payload的长度能告诉你哪个转储的dex是原始的,而内存转储必须通过哈希去重并进行结构验证,才能被信任。 如果侦察显示没有壳,但重签名的构建包仍然崩溃,说明你遇到的是
    references/code-virtualization-and-custom-linkers.md
    覆盖的层级——不要假设“没有壳”就等于“可编辑”。 如果应用本身就会自行崩溃——尤其是在启动后大致固定的时间崩溃,或者是native崩溃——在计划任何补丁之前先定位机制(
    references/native-tamper-and-suicide.md
    、
    scripts/native_crash.py
    )。记录观测到的崩溃时间:这是之后所有尝试的衡量基线,没有它,你无法区分是运行成功了,还是崩溃时间变了。 跳过条件: 永远不能跳过。§关卡中的G2/G3要么在这里通过,要么永远通不过。
  2. 提取字符串和端点 — 从dex字符串表中构建应用的API表面和SDK清单。这一步不需要反编译器,速度很快。脚本:
    scripts/dex_strings.py
    。
  3. 追溯到所属类 — 找到封装目标行为的类(应用几乎总是把第三方SDK封装在一个辅助类里)。反向查找说明:
    references/dex-patching.md
    §查找调用点。
  4. 确定补丁层级 — 客户端SDK调用 / 客户端渲染 / 客户端数据消费 / 服务端约定。参见
    references/ad-removal.md
    中的表格。
  5. 精准打补丁 — 参考
    references/dex-patching.md
    和
    references/byte-level-patching.md
    。 有两种技术,选择正确的一种是决策问题,而非偏好问题: 当修改可以放进现有的指令槽或常量中时,使用等长字节修改(
    scripts/dex_patch_bytes.py
    ,用
    scripts/dex_find_insn.py
    定位)——没有任何内容移动,因此不会导致偏移量、try/catch块或调试指针失效。只有当修改确实需要新指令时,才使用dexlib2方法重写(
    scripts/dexpatch/
    )。全树smali往返转换会损坏R8优化过的dex,这种损坏只有在运行时才会显现;方法重建也会增大文件体积(实测:一个样本中
    debug_info
    从924 B变为22.8 KB,dex从4.32 MB变为7.73 MB)。无论使用哪种方法,都要重新计算dex头的完整性字段(先算signature,后算checksum)——参考
    references/byte-level-patching.md
    §dex头有两个完整性字段。
  6. 重打包与签名 — 参考
    references/repack-and-sign.md
    。不要删除整个
    META-INF/
    目录。
    仅仅这一个错误就会毁掉原本正确的构建包。 6b. 在宣布构建完成前,屏蔽更新路径。 如果应用会检查更新,添加两层补丁(
    references/updates-and-forced-upgrade.md
    ):将更新例程的入口置为空操作,并强制版本比较结果为“无需更新”。一个可以被远程停用或替换的构建包不是合格的交付物,在这里花几分钟就能避免之后的重建。对任何可能恢复你移除的功能的远程配置或热更新通道,也要做同样的处理。 6c. 仅在分类后再处理账号门控 — 如果需求涉及登录或绑定,参考
    references/account-gates.md
    ,并明确说明哪些受保护的页面变得可用,哪些仍然为空,因为它们的内容是账号范围的。
  7. 在设备上验证 — 参考
    references/environment.md
    +
    references/verification.md
    。检查项:能启动、修改的行为确实改变了、没有无关功能损坏,以及应用能进入正常UI,没有阻塞性对话框。首先要证明设备上的产物确实被更改了——包管理器报告成功并不证明插入的确认被接受了(P18)。在启动后的前20秒左右持续截图,并且一定要看这些截图——采样间隙就是阻塞性弹窗被忽略的原因(P20),而一堆从未被检查的截图不能作为证据。如果可访问性树为空,那么图像就是主要证据,而非备选。
  8. 记录你的收获 — 如果某个故障耗费了你超过30分钟的时间,把它添加到
    references/pitfalls.md
    中。这个文件是本技能中最有价值的产物。

What "done" means — do not claim it earlier

“完成”的定义——不要提前宣称完成

Every item below must be true before you report completion. Anything less is a checkpoint and must be labelled as one, out loud, with what remains. Premature "done" is the most damaging thing you can report, because it ends the investigation while the user believes the problem is solved.
  1. The artifact exists and its identity is recorded — path plus hash, not a filename.
  2. It was installed and launched on the environment the deliverable sentence names (G1/R1). If that environment was not available to you, say so and label the result accordingly.
  3. The behaviour you changed is verified changed — by direct observation of the feature, not by the absence of an error message. "The log is clean" is not evidence; "the screen shows X" is.
  4. The features it touches still work. You exercised them. A build that starts but whose affected feature is dead is not a result.
  5. The original limitation is stated if any survives — with the coupling that causes it, so the next person can decide whether to accept it.
  6. Nothing you did leaves the target or the device in a broken state unless that was the goal, and any privileged workaround is labelled a fallback rather than the deliverable.
If items 1–4 hold but the environment was wrong, you have a prototype, not a deliverable. Say "prototype" and name the gap.
在报告完成之前,下面的每一项都必须为真。达不到的话就是一个检查点,必须明确标记为检查点,并说明剩余工作。过早宣称“完成”是你能报告的最具破坏性的事情,因为它会终止调查,而用户还以为问题已经解决了。
  1. 产物存在且其标识已记录 — 路径加哈希,而不是文件名。
  2. 它已经在交付物说明指定的环境中安装并启动(G1/R1)。如果你没有该环境,明确说明,并相应地标记结果。
  3. 你修改的行为已被验证确实改变了 — 通过直接观察功能,而非通过没有错误消息。“日志很干净”不是证据;“屏幕显示X”才是。
  4. 涉及的功能仍然正常。 你已经测试过它们。一个能启动但受影响的功能失效的构建包不算成果。
  5. 如果还有残留的原始限制,要说明清楚 — 包括导致限制的耦合关系,这样下一个人可以决定是否接受。
  6. 你的操作不会让目标或设备处于损坏状态,除非这就是目标,并且任何需要特权的解决方案都被标记为备选方案,而非交付物。
如果1-4项都满足,但环境不对,那你得到的是一个原型,而非交付物。要说“原型”,并说明差距在哪里。

Stop conditions — halt and re-classify, do not retry

停止条件——停下来重新分类,不要重试

These are moments where continuing to push forward is the wrong move. Each has cost hours somewhere.
  • The same shape of attempt failed twice. See the two-strike rule above.
  • A patch had no effect and you were about to try a third variant of it. No effect means the diagnosis was wrong, not that the patch was unlucky. Re-classify the layer.
  • A new failure has no place in your current model. That is the symptom index's trigger condition.
  • You are about to write off a route as "blocked" without a control build proving the block is the app's doing rather than your pipeline's. Mis-attributed blocks have removed viable routes.
  • You are about to claim success on absence of errors. See §What "done" means.
  • A measurement disagrees with a conclusion you already recorded as settled. Re-open the conclusion; do not explain the measurement away.
在这些情况下继续推进是错误的。每一种情况都曾在某个时候耗费过数小时。
  • 同一形态的尝试失败了两次。 参见上面的两次失败规则。
  • 补丁没有任何效果,而你正准备尝试第三种变体。 没有效果意味着诊断错了,不是补丁运气不好。重新对层级分类。
  • 出现了一个你当前模型无法解释的新故障。 这就是故障索引的触发条件。
  • 你正准备把某条路线判定为“走不通”,但没有对照构建包证明阻碍是应用导致的,而非你的流程导致的。错误归因的阻碍已经让很多可行路线被废弃了。
  • 你正准备以没有错误为由宣称成功。 参见§“完成”的定义。
  • 一项测量结果与你已经记录为定论的结论不一致。 重新打开该结论;不要把测量结果解释为无关紧要。

Non-negotiable constraints

不可妥协的约束条件

These are defects when violated, not preferences. Each row states the check that catches it, so "did I comply" is a command you can run rather than a judgement you make about yourself.
ConstraintWhy it is absoluteThe check
Inputs are read-only. Work on copies; keep a known-good baselinean edited original destroys the only reference you can diff against
git
/hash the original before the first edit;
scripts/apk_diff.py
for entry-level proof
One variable at a time, with a control build (same pipeline, zero patches)a compound change that fails teaches nothing, and the failure will be attributed to the wrong causea control run exists and its result is recorded
Verify structure after every dex edita patch that assembles and dies at load looks like a patch that worked
scripts/dex_classdiff.py
: zero differences in class set and access flags for classes you did not intend to change
Never patch a widely shared method. Count callers firsta
Long.valueOf
-shaped helper with 30 callers is not an ad-specific hook
scripts/find_refs.py
on the method you are about to touch
Never make an API fail to suppress a UI elementa 404/400 an endpoint's other features depend on takes the whole screen with it — measured as a build that never leaves the launch screensuppress at the data-consumption or render layer instead; if a request's path changes, that is the bug
Neutralise a native terminate path by returning, never by making it not returna spin stub freezes the caller holding its lock; unrelated threads wedge and the symptom (hang, external kill, restart loop) looks nothing like the cause, with no crash recordreturn a benign value, prefer success (
0
) over failure (
-1
), never touch
pthread_exit
/
exit
/
abort
/
snprintf
/
closedir
.
native-tamper-and-suicide.md
Look before you conclude, and look while you waita screen that is actually looked at answers in one step what coordinate-guessing cannot answer in fivecapture and inspect; byte-identical samples mean nothing will change.
scripts/snap.py
,
environment.md
§look at the screen
Bound every command, and calibrate the bound from a measurementan unbounded call turns a stall into "still working", which is indistinguishable from progresstime the operation once, record it, derive the deadline from it.
long-task-discipline.md
§Bound every wait
Every claim carries a label —
observed
,
inferred
or
unverified
"probably", "should be" and "in theory" are not findings, and an unlabelled guess propagates as a fact
observed
requires a command and its output; nothing else may be written as established
"Done" means the user-visible outcomea blocking dialog still on screen means the task is not done, however clean the log is§What "done" means, all six items; absence of a log line is never evidence of success
Never discard a route on compound evidenceif a failure followed two simultaneous changes, the attribution is a hypothesis — mis-attributed failures have removed viable routes for hoursre-run the abandonment single-variable before writing it down
Prove the device changed before measuring anythinginstall success describes the request, not the app on diskhash the on-device artifact against your build; otherwise every later observation describes the previous build
Attribute a failure to the right layer before patching againa feature-scoped network failure is frequently the app's own TLS/certificate problem, and chasing a signature check that does not exist burns hoursrun the unmodified original on the same device and network first (
tls-and-cert.md
)
违反这些就是缺陷,而非偏好问题。 每一行都说明了对应的检查方法,因此“我是否遵守了”是一个你可以运行的命令,而非你对自己的主观判断。
约束条件为什么是绝对的检查方法
输入文件是只读的。 处理副本;保留已知良好的基线被修改的原始文件会毁掉你唯一可以对比的参照第一次编辑前用
git
/哈希保存原始文件;用
scripts/apk_diff.py
做入门级验证
一次只改变一个变量,保留对照构建包(相同流程,零补丁)失败的复合修改什么也说明不了,而且故障会被归到错误的原因上存在对照运行且结果已记录
每次dex编辑后都验证结构一个能打包但加载时崩溃的补丁看起来和成功的补丁一样
scripts/dex_classdiff.py
:你无意修改的类的集合和访问标志零差异
绝不要给被广泛共享的方法打补丁。 先数一下调用者数量一个有30个调用者的
Long.valueOf
之类的辅助方法不是广告专用的hook点
对你要修改的方法运行
scripts/find_refs.py
绝不要通过让API失败来隐藏UI元素一个端点的其他功能依赖的404/400错误会导致整个页面无法使用——表现为构建包永远停留在启动页改为在数据消费或渲染层隐藏;如果请求的路径变了,那就是bug
通过返回值来中和native终止路径,绝不要让它不返回空转存根会冻结调用者,并且持有它的锁;不相关的线程会卡住,症状(卡顿、外部杀死、重启循环)看起来和原因完全不沾边,也没有崩溃记录返回一个良性值,优先返回成功(
0
)而非失败(
-1
),绝不要碰
pthread_exit
/
exit
/
abort
/
snprintf
/
closedir
。参考
native-tamper-and-suicide.md
下结论前先看,等待的时候也要看真正看一眼屏幕就能一步得出的结论,坐标猜测五步也得不出截图并且检查;字节完全相同的样本意味着什么都不会改变。参考
scripts/snap.py
、
environment.md
§看屏幕
给每个命令设时限,并根据测量结果校准时限无限制的调用会把卡顿变成“还在运行”,这和进展无法区分给操作计时一次,记录下来,据此得出截止时间。参考
long-task-discipline.md
§给每个等待设时限
每一项结论都带有标签——
observed
、
inferred
或
unverified
“可能”、“应该是”和“理论上”不是结论,未标记的猜测会被当作事实传播
observed
需要有命令及其输出;其他任何内容都不能被写为已确立的结论
“完成”意味着用户可见的结果屏幕上还有阻塞性对话框就说明任务没完成,不管日志有多干净§“完成”的定义,全部六项;缺少某行日志绝不是成功的证据
绝不要基于复合证据废弃一条路线如果一次失败发生在两个同时进行的修改之后,那么归因只是假设——错误归因的失败已经让可行路线被废弃了数小时在记录废弃之前,用单变量方式重新验证
在测量任何东西之前,先证明设备上的内容已经改变安装成功描述的是请求,而非磁盘上的应用将设备上的产物与你的构建包做哈希对比;否则之后的所有观测都是针对之前的构建包的
再次打补丁前,把故障归因到正确的层级特定功能的网络故障往往是应用自己的TLS/证书问题,追查一个不存在的签名校验会耗费数小时先在同一设备和网络上运行未修改的原始包(
tls-and-cert.md
)

Reference index and script index

参考索引与脚本索引

Both tables live in
references/routing.md
— one load gets you every reference file with when to load it, and every script with what it does. They are deliberately not duplicated here: this file is loaded in full every time the skill activates, and those two tables are about 140 rows of lookup data that nobody needs until they have already decided what to do.
The symptom index above stays, because symptom to file has to be one hop: when something fails you are not choosing a file, you are recognising a failure, and a two-hop lookup at that moment is exactly how a stop signal gets skipped.
references/evidence-summary.md
is the one reference that answers the claim-strength question from inside an installed copy: capability → one-line conclusion →
observed
/
inferred
/
unverified
→ the evidence that ships with the skill. Load it when a claim's strength decides whether you trust it and the run record is not in front of you.
python check_routing.py
checks that this file and
references/routing.md
still agree, that every reference file is named there, and that every script is named there. CI runs it.
两个表都在
references/routing.md
中
—— 加载一次就能看到所有参考文件及其适用场景,以及所有脚本及其功能。特意没有在这里重复它们:每次激活本技能时都会完整加载本文档,而这两个表有大约140行查找数据,在决定好做什么之前没人需要它们。
上面的故障索引保留在这里,因为故障到文件必须是一跳直达:当出现故障时,你不是在选择文件,而是在识别故障,而此时的两跳查找正是停止信号被忽略的原因。
references/evidence-summary.md
是唯一一个可以从已安装副本中回答结论强度问题的参考文档:能力 → 单行结论 →
observed
/
inferred
/
unverified
→ 本技能附带的证据。当你需要根据结论的强度决定是否信任它,而运行记录又不在手边时,查看该文件。
python check_routing.py
用于检查本文档和
references/routing.md
是否一致,检查所有参考文件是否都在其中列出,以及所有脚本是否都在其中列出。CI会运行这个脚本。