Close the byte-patching gap, add the server-config path, fix two script crashes
Worked end-to-end on a real unobfuscated commercial app (single-dex, no packer,
server-driven launch screen) and folded back everything that cost time. Net +17
files, no new dependencies, no sample-specific identifiers.
New references
byte-level-patching.md
The technique that was missing entirely. Equal-length in-place edits beat
method rebuilding (measured on a real sample: method rewrite grew debug_info
924 B -> 22.8 KB and the dex 4.32 MB -> 7.73 MB), but four failure modes are
silent: same-length is not enough, branch polarity, verifier legality, and
stale dex header fields. Covers locating an instruction's exact offset (why
listings and byte-pattern guesses both fail), the instruction width table and
the two traps that desynchronise a decode, neutralise-a-branch vs redirect-it,
and the header's signature-then-checksum order.
server-config-and-updates.md
The most common shape of "ad" in a modern app, and the one usually
mis-diagnosed: the server supplies UI the client renders. The two-layer fetch
(local fallback then remote override) that proves it, finding the config DTOs
through the field names data classes keep, why you patch the decision and not
the data, deciding the scope of "remove", and remote re-enable / cached config
durability.
New scripts
dexutil.py dependency-free dex reader: structural walk, exact
instruction decode, dex header recompute/verify in the
correct order, branch-target and operand helpers
dex_find_insn.py locate an instruction by decoded semantics, print its exact
byte offset with context and BOTH sides of any branch
dex_patch_bytes.py equal-length byte patches from a JSON spec: semantic match,
polarity pin via expect_next, equal-length enforcement,
verifier check, header recompute, re-decode to prove it landed
dex_check_verifier.py does any conditional branch target a move-result (bypassing
its producer)? Compares builds and separates pre-existing
findings from regressions the patch introduced
coldstart.py cold-launch capture: timed screenshot burst + logcat signals
+ installed-build facts + launch timing, and a warning when
the foreground activity is not the app
check_repo.py maintenance check: every script parses and answers --help,
every referenced path resolves, every file is documented
Fixes found while running the above
repack.py wrote archives with a plain zipfile writer, which cannot satisfy
the Android R+ install gate: resources.arsc must be STORED *and*
4-byte aligned, and a zip extra record cannot express a 1-3 byte
pad (a stored filler entry is inserted instead). Also fixes the
central-directory pointer and adds an alignment gate to the output.
usb_net_proxy.py, dex_strpatch.py
crashed on --help with a traceback instead of printing usage.
Updated
toolchain.md ddc added (single-binary dex->Java with query subcommands,
with measured timings and its real limits); "not on PATH is
not not-installed" with the search commands; which signer
to use and why jarsigner breaks alignment
repack-and-sign.md alignment requirement and its arithmetic; dex header
recompute; jarsigner hazard; vendor install interception
([N] with no symbolic constant -> root pm install); the
installer can keep owning the screen after an install
recon.md resolve package identity from a tool, not from hand-read
AXML strings (a wrong package sends all later device queries
nowhere); snapshot the config surface during recon
long-task-discipline.md captures you never looked at are not evidence (incl. the
consistent-but-wrong-window variant); long-context decay and
the record shape that survives it
dynamic-frida.md when live attach cannot work at all: static instrumentation,
static read, and self-recorded evidence, with the control
build caveat each one carries
ad-removal.md server-driven config is usually the EASIEST case, not the
hardest; a three-way zero result is a positive finding;
scope note on local brand splash vs promotional content
pitfalls.md P37 stale dex header checksum, P38 [-124] alignment,
P39 bare-numeric install failure, P40 wrong-window evidence,
P41 inverted branch polarity, P42 decode desynchronisation
SKILL.md, README.md symptom index, reference/script indexes and requirements
updated for all of the above
This commit is contained in:
@@ -226,6 +226,97 @@ When you discover a window was contaminated, **the correct action is to discard
|
||||
not to salvage it. A re-run costs minutes; a wrong conclusion costs the rest of the task, and it will
|
||||
be re-derived from the same bad evidence because the record says it was observed.
|
||||
|
||||
### Captures you never looked at are not evidence
|
||||
|
||||
This is a distinct failure from a contaminated window, and it survives every other guard here. The
|
||||
frames are real, the timestamps are honest, the hashes are right — and nobody looked at them.
|
||||
|
||||
Two shapes it takes, both of which produce a confident wrong answer:
|
||||
|
||||
- **You took the screenshots and moved on.** The images exist; the conclusion was drawn from logcat
|
||||
or from the patch itself. A burst of uninspected frames reads in a report exactly like a burst of
|
||||
inspected ones.
|
||||
- **You did look, but at the wrong thing.** Every frame shows a window that is not your app — a
|
||||
vendor installer confirmation left over from an earlier install, a system dialog, the launcher —
|
||||
and because the frames are consistent with each other, conviction grows. Consistency is not
|
||||
corroboration when all the frames share the same blind spot.
|
||||
|
||||
Guards:
|
||||
|
||||
1. **Check what is on screen before trusting any capture.** One command settles it:
|
||||
`dumpsys activity activities | grep -m1 ResumedActivity`. If the component is not your package,
|
||||
the frames describe something else. Do this at the start of a capture sequence and again at the
|
||||
end — the foreground can change mid-sequence.
|
||||
2. **Inspect immediately, not "later".** Look at the first frame, the middle one, and any frame at a
|
||||
moment you care about (the second the splash would have shown, the moment a dialog would have
|
||||
appeared). If you cannot describe in one sentence what a frame shows, you have not looked at it.
|
||||
3. **Treat identical consecutive frames as a signal.** Byte-identical frames mean the screen is
|
||||
static. That is a finding — a hang, a dialog waiting for input, an activity that never changed —
|
||||
not a capture artefact to be skipped over.
|
||||
4. **Name what you saw, per frame, in the record.** One line each. "f03 at 2.1 s: target main screen,
|
||||
list populated" is evidence; "captured 12 frames" is a file listing.
|
||||
5. **A screenshot of a clean-looking screen is not proof that a dialog is absent** unless you captured
|
||||
continuously across the moment it would have shown. Sampling is not observation: a modal that
|
||||
appears and is then occluded can fall entirely between samples, and every frame you happened to
|
||||
take shows something else.
|
||||
|
||||
`scripts/coldstart.py` exists to make points 1 and 5 automatic: it takes a timed burst, prints the
|
||||
foreground component, and refuses to present the run as meaningful if the foreground is not your
|
||||
app. It still cannot look at the frames for you — that part is on you.
|
||||
|
||||
## Long-context decay: the same mistake, twice
|
||||
|
||||
A long task has a failure mode that has nothing to do with the target: **as the
|
||||
working context fills, settled conclusions lose their force.** Something that was
|
||||
established and verified two hours ago becomes, by the end, just another plausible
|
||||
belief — and the cheapest way to get from a stuck point to a feeling of progress is
|
||||
to re-try the thing that already failed.
|
||||
|
||||
The symptoms, which are recognisable if you watch for them:
|
||||
|
||||
- You are about to run a command whose result you already recorded earlier.
|
||||
- You are about to re-derive a fact (package name, an offset, a version, which
|
||||
patch landed) that is in your own notes.
|
||||
- You are about to re-attempt an approach that failed — and the failure is not
|
||||
obviously connected in your mind to this attempt.
|
||||
- A conclusion you are treating as solid was actually never verified, only
|
||||
assumed early on and repeated since.
|
||||
- You are rewriting a summary of the task from memory rather than reading the
|
||||
record.
|
||||
|
||||
**The antidote is a record written for the second half of the task, not for a
|
||||
human at the end.** Keep it structured so a weakened-context read still gets the
|
||||
decision-relevant content:
|
||||
|
||||
```markdown
|
||||
## Settled (verified — do not re-derive)
|
||||
- <fact> [how it was verified, one clause]
|
||||
## Refuted (do NOT retry — each cost time)
|
||||
- <approach> — fails because <mechanism>
|
||||
## Unverified assumptions currently in play
|
||||
- <assumption> — becomes a problem if <condition>
|
||||
## Next action, and how it will be judged
|
||||
- <one step> — success looks like <observable>
|
||||
```
|
||||
|
||||
Three rules that make the difference:
|
||||
|
||||
1. **Record the mechanism, not just the outcome.** "Traversal via that endpoint
|
||||
broke the home screen because the child request shares the parent load" survives
|
||||
context decay; "tried the endpoint thing, did not work" does not — and the
|
||||
second version invites a third attempt.
|
||||
2. **Re-read the record at every checkpoint, and before starting any new
|
||||
experiment.** Not at the end. The cost is seconds and it is the only thing
|
||||
standing between you and a loop.
|
||||
3. **Two failures of the same shape means the model is wrong, not the
|
||||
parameters.** Do not run a third variation. That is the same rule as the
|
||||
two-strike rule in `SKILL.md`, and long-context decay is exactly what makes
|
||||
people violate it.
|
||||
|
||||
A useful asymmetry: **re-verifying is cheap, re-deciding is not.** Reading a value
|
||||
back out of the artifact takes a second and is fine. Re-running a whole experiment
|
||||
because you forgot its conclusion is what costs the round.
|
||||
|
||||
## Handover
|
||||
|
||||
A handover is the record plus three things, written for someone with **no** memory of the task:
|
||||
|
||||
Reference in New Issue
Block a user