Add code-virtualization/custom-linker reference, doctor and so_constpatch tools

New reference: references/code-virtualization-and-custom-linkers.md
  Covers the layer between "packed" and "clean" - manifests whose Application is the
  app's own and whose dex is readable, yet a re-signed build dies before user code runs.
  Contents: three cheap identification tells (whole classes as native declarations, the
  registerNativesForClass/special_clinit pair, SONAME-vs-filename mismatch); the
  keep-the-library / delete-the-library deadlock and why both routes fail; the
  checker-vs-implementation question that resolves it; the string-redirect technique
  (equal-length rewrite of an isolated constant so a loadLibrary call reaches an
  already-mapped library, so no offsets move and no death path needs neutralizing);
  expected private-loader log shapes; a single-variable experiment to determine whether
  the native check reads in-APK certificate material or the PMS-reported signature; and
  why a Java-layer signature killer logging success proves nothing.

New tool: scripts/doctor.py
  Cheapest first command. Capability report and per-script runnability, with
  APKREV_TOOLS support so tools installed off-PATH or as runnable jars are found rather
  than reported missing. Surfaces the environment facts that silently poison
  experiments: clock skew, leftover adb forward entries, a leftover device proxy, and a
  frida process already running on the device (which makes the target look like it
  detects instrumentation when nothing has been attached yet).

New tool: scripts/so_constpatch.py
  Implements the string-redirect technique. Enforces equal length, refuses to patch a
  substring of a longer identifier, reports whether each hit sits in an ELF constant
  pool, and patches inside an APK or a bare .so. Field-tested on a real protected
  library: find, equal-length replace, unequal-length refusal, multi-hit guard, --force
  and absent-name paths all verified.

Updated:
  SKILL.md            fix question count (ten -> thirteen); index the new reference and
                      both new scripts; make doctor.py the first step of the workflow;
                      add the "no packer but still dying" pointer during recon
  packers.md          cross-reference the new layer so "no packer" is not read as
                      "editable"
  ad-removal.md       make log evidence a count rather than an impression (n -> 0 is a
                      measurement; a tag that goes quiet without reaching zero is a
                      weaker result)
  repack-and-sign.md  warn that a full apktool rebuild rewrites obfuscated resource
                      paths (hundreds of rename pairs) - a tool artifact that must not be
                      read as breakage - and prefer a dex-only swap when that is the only
                      edit
  pitfalls.md         generalize the ServiceLoader example so it is not tied to one
                      sample's third-party library
  README.md           document the new files and scripts; document APKREV_TOOLS and the
                      cross-platform intent
This commit is contained in:
inliver233
2026-09-20 00:51:58 +08:00
parent 02c832f847
commit 0fc2a30408
9 changed files with 993 additions and 6 deletions
+20
View File
@@ -28,6 +28,26 @@ Recompressing them produces builds that fail to install or misbehave.
**4. Changing any dex changes nothing about resources.** If you only edited dex, do not touch `res/`, `assets/`, or `lib/`.
**4a. A full `apktool b` rebuild rewrites resource paths even when you edited none.** Decoding with
`apktool d` (without `-s`) and rebuilding re-encodes resources, and obfuscated short names are
expanded back to readable ones — an entry originally shipped as `res/-B.png` comes back as
`res/drawable-hdpi/<real-name>.png`. Expect an entry-level diff against the original to show on the
order of a thousand "removed + added" pairs that are **pure renames**, not content changes.
This is expected and usually harmless, but two consequences matter:
- **Never read that diff as "I broke something".** Compare by identity (rename-aware, or by content
hash grouped by size) before drawing a conclusion. A toy diff that reports 1100 changes when you
edited one method is a **tool** artifact and will send you hunting a bug that does not exist.
- **It changes the byte layout of the whole archive.** On a target that fingerprints its own file
against a stored hash, or whose protection binds offsets into a container, a full rebuild is a much
larger change surface than a dex-only swap. If your only edit is dex, prefer replacing the
`classes*.dex` entries inside the original zip (`compress_type` preserved) over a full rebuild.
Keep that dex-only path as a fallback for exactly this reason.
If you must avoid resource churn entirely, decode with `-s` (do not decode resources) and only
rebuild what you changed.
**5. Signing creates new `MANIFEST.MF`/`*.SF`/`*.RSA`.** That is expected — the check is that **no signature artifact from the ORIGINAL** survives, and **no non-signature entry was lost**.
## Repacking an unpacked (de-shelled) app