Broaden skill with unpacking, TLS triage, third-party build auditing, runtime probe

references: recon gains packer identification + unpacking workflow (payload/plaintext size mapping, dump validation, hash de-duplication, business-dex heuristic); repack-and-sign gains the de-shelled repack pipeline, why apktool over binary AXML, and the apksigner scheme-selection caveat; new tls-and-cert documents feature-scoped network failures, dual trust chains and the fix template; new third-party-builds covers auditing a cracked/modded APK before trusting it; dynamic-frida gains version pinning, remote-device path, the four-layer probe and hook-never-fires triage; environment gains UI automation pitfalls; ad-removal gains the single convergence point, dynamic-plugin SDKs and the global timestamp gate; pitfalls gains P13-P17; verification gains rung 7 and the three-signal ad check.

scripts: new frida_probe.js (four-layer runtime probe), run_probe.py and tls_check.py; repack.py fixes two crash-on-default bugs and a misleading META-INF note; datastore_inject.py, PatchMethodExample.java and build_scripts.py lose hardcoded values - the example class name did not match its file and could not compile.
This commit is contained in:
inliver233
2026-09-19 21:44:00 +08:00
parent c66658b6b3
commit 1a5cb6245f
18 changed files with 3089 additions and 304 deletions
+167 -63
View File
@@ -1,84 +1,188 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""One-shot builder: copy the battle-tested tools from the project workspace into
the skill, stripping machine-specific hardcoding.
"""Sync a script directory to a publish location, and audit it for machine-specific leftovers.
Run from anywhere: python build_scripts.py <project_work_dir> <skill_scripts_dir>
WHY THIS EXISTS
---------------
An Agent Skill gets copied across machines and across runtimes (Claude Code, Codex,
DeepSeek Harness). Anything machine-specific that survives the copy becomes a
landmine: a baked-in JDK path, a keystore password, a home directory that only
exists on the author's box. This tool does two things:
1. AUDIT (default, read-only): report every absolute-path literal and every
credential-looking constant, with file and line number.
2. SYNC (--apply): copy matching files to the destination, optionally after
neutralising the absolute-path literals it found (--scrub).
The audit is the default on purpose: a silent copy is exactly how the leftovers
travel from the author's machine into someone else's first run.
USAGE
-----
python build_scripts.py scripts2 out # audit only, nothing written
python build_scripts.py scripts out --apply # copy
python build_scripts.py scripts out --apply --scrub # copy, neutralising abs paths
python build_scripts.py scripts out --pattern '*.py' --pattern '*.js'
Exit codes: 0 = clean, 2 = findings reported (audit mode), 1 = usage or IO error.
NOTES
-----
* A finding is a heuristic, not a verdict: `/data/local/tmp/...` is a legitimate
Android device path and is deliberately NOT reported; a drive-letter path is.
* --scrub rewrites `'<abs path>'` literals into `''`, which keeps the file
syntactically valid but leaves the decision (and the fix) to a human. It never
runs unless --apply is also given.
"""
import argparse
import fnmatch
import os
import re
import shutil
import sys
SRC = sys.argv[1] if len(sys.argv) > 1 else r'E:\ciyuan\work'
DST = sys.argv[2] if len(sys.argv) > 2 else r'E:\ciyuan\apk-reverse\scripts'
DEFAULT_PATTERNS = ('*.py', '*.js', '*.java', '*.md', '*.txt')
# file -> (new name, list of (pattern, replacement))
COPY = {
r'tools\dex_strpatch.py': ('dex_strpatch.py', []),
r'tools\dex_classdiff.py': ('dex_classdiff.py', []),
r'tools\patch_smali.py': ('patch_smali.py', []),
r'tools\repack.py': ('repack.py', [
# strip machine-specific defaults; make them explicit args
(re.compile(r'KS_PATH\s*=\s*r?["\'][^"\']*["\']'), 'KS_PATH = None # pass --ks on the command line'),
(re.compile(r"workdir'?,?\s*default=r?['\"][^'\"]*['\"]"), "workdir', default=None"),
]),
r'tools\cyc_proxy.py': ('usb_net_proxy.py', []),
r'tools\mk_video_prefs.py': ('datastore_inject.py', []),
r'tools\probe_adverts.py': ('probe_api.py', []),
r'tools\grab_crash.py': ('grab_crash.py', []),
}
# Absolute-path literals that belong to one particular machine.
# Deliberately excluded: /data/... (an Android device path, not a host path).
WINDOWS_LITERAL = re.compile(r"(?P<prefix>(?:r|u|b)?r?)(?P<quote>['\"])"
r"(?P<path>[A-Za-z]:[\\/][^'\"]*)(?P=quote)")
POSIX_LITERAL = re.compile(r"(?P<prefix>(?:r|u|b)?r?)(?P<quote>['\"])"
r"(?P<path>/(?:home|Users|mnt)/[^'\"]*)(?P=quote)")
# Any drive-letter or foreign-home path outside a quoted literal as well.
# Tuned to avoid the two false-positive families that made an earlier version of
# this audit useless: a URL scheme ("https://" contains "s:" + "/") and a Python
# escape sequence ("is:\n" contains "s:" + "\"). Hence: the previous character must
# not be alphanumeric, a drive letter must be followed by a real backslash (not an
# escape letter, not a second separator), and the POSIX form must start a path.
BARE_ABS = re.compile(
r"(?<![A-Za-z0-9])(?:"
r"[A-Za-z]:\\(?![ntr0bxu'\"\\])\S{2,}"
r"|/(?:home|Users|mnt)/[A-Za-z0-9_.\-]{2,}"
r")")
CREDENTIAL_HINT = re.compile(
r"(?i)(BEGIN [A-Z ]*PRIVATE KEY|storepass\s*=\s*['\"][^'\"]|"
r"keypass\s*=\s*['\"][^'\"]|password\s*=\s*['\"][^'\"]|"
r"api[_-]?key\s*=\s*['\"][^'\"]|secret\s*=\s*['\"][^'\"]{6,})")
def clean(text):
"""Remove absolute paths that point at one particular machine."""
text = re.sub(r"r?'[A-Za-z]:\\\\[^'\"]*'", "''", text)
text = re.sub(r'"[A-Za-z]:\\\\[^"]*"', '""', text)
return text
def collect(src, patterns):
"""Return [(absolute_path, relative_path)] for every matching file."""
out = []
for root, _dirs, files in os.walk(src):
for name in sorted(files):
if any(fnmatch.fnmatch(name, p) for p in patterns):
full = os.path.join(root, name)
out.append((full, os.path.relpath(full, src)))
return out
def audit(path, rel):
"""Return [(kind, lineno, detail)] for one file."""
findings = []
try:
with open(path, encoding='utf-8', errors='replace') as fh:
lines = fh.readlines()
except OSError as exc:
return [('io-error', 0, str(exc))]
for lineno, line in enumerate(lines, 1):
for match in BARE_ABS.finditer(line):
findings.append(('abs-path', lineno, match.group(0)[:90]))
if CREDENTIAL_HINT.search(line):
findings.append(('credential', lineno, line.strip()[:110]))
return findings
def scrub(text):
"""Neutralise absolute-path literals; returns (new_text, count)."""
total = 0
def repl(_match):
return "''"
for pattern in (WINDOWS_LITERAL, POSIX_LITERAL):
text, count = pattern.subn(repl, text)
total += count
return text, total
def main():
os.makedirs(DST, exist_ok=True)
os.makedirs(os.path.join(DST, 'dexpatch'), exist_ok=True)
for rel, (new, subs) in COPY.items():
src = os.path.join(SRC, rel)
if not os.path.isfile(src):
print('[skip missing] %s' % rel)
ap = argparse.ArgumentParser(
description='Sync a script directory to a publish location and audit it for '
'machine-specific leftovers (absolute paths, hardcoded credentials).')
ap.add_argument('src', help='source directory to publish from')
ap.add_argument('dst', help='destination directory to publish into')
ap.add_argument('--pattern', action='append', default=list(DEFAULT_PATTERNS),
help='glob to include, repeatable (default: %s)'
% ' '.join(DEFAULT_PATTERNS))
ap.add_argument('--apply', action='store_true',
help='actually copy files (default is audit only)')
ap.add_argument('--scrub', action='store_true',
help='with --apply, neutralise absolute-path literals in the copies')
args = ap.parse_args()
if not os.path.isdir(args.src):
print('error: source directory not found: %s' % args.src)
return 1
files = collect(args.src, args.pattern)
if not files:
print('error: no files matched %s in %s'
% (', '.join(args.pattern), args.src))
return 1
total_findings = 0
scrubbed = 0
copied = 0
for full, rel in files:
findings = audit(full, rel)
abs_findings = [f for f in findings if f[0] == 'abs-path']
cred_findings = [f for f in findings if f[0] == 'credential']
other = [f for f in findings if f[0] not in ('abs-path', 'credential')]
if findings:
print('-- %s' % rel)
for kind, lineno, detail in abs_findings + cred_findings + other:
print(' [%s] line %d: %s' % (kind, lineno, detail))
total_findings += len(findings)
if not args.apply:
continue
t = open(src, encoding='utf-8', errors='replace').read()
for pat, rep in subs:
t = pat.sub(rep, t)
out = os.path.join(DST, new)
with open(out, 'w', encoding='utf-8', newline='\n') as f:
f.write(t)
print('[copied] %-24s -> %s' % (rel, new))
# dexlib2 patcher sources
for rel, new in [(r'tools\dexpatch\PatchMethod.java', 'PatchMethod.java'),
(r'tools\dexpatch\PatchClasses7v2.java', 'PatchMethodExample.java')]:
src = os.path.join(SRC, rel)
if os.path.isfile(src):
t = open(src, encoding='utf-8', errors='replace').read()
with open(os.path.join(DST, 'dexpatch', new), 'w',
encoding='utf-8', newline='\n') as f:
f.write(t)
print('[copied] %-24s -> dexpatch/%s' % (rel, new))
else:
print('[skip missing] %s' % rel)
target = os.path.join(args.dst, rel)
os.makedirs(os.path.dirname(target) or '.', exist_ok=True)
# cp.txt content -> smali_cp.txt template
cp = os.path.join(SRC, 'tools', 'dexpatch', 'cp.txt')
if os.path.isfile(cp):
raw = open(cp, encoding='utf-8').read().strip()
jars = [j for j in raw.split(';') if j.strip()]
with open(os.path.join(DST, 'smali_cp.txt'), 'w', encoding='utf-8', newline='\n') as f:
f.write('# One jar path per line. Adjust the directory to your own tools.\n')
f.write('# Needed: smali baksmali dexlib2 util antlr-runtime stringtemplate '
'jcommander guava\n')
for j in jars:
f.write(os.path.basename(j) + '\n')
print('[wrote] smali_cp.txt (%d jars)' % len(jars))
if args.scrub and (abs_findings or cred_findings):
with open(full, encoding='utf-8', errors='replace') as fh:
text = fh.read()
new_text, count = scrub(text)
if count:
with open(target, 'w', encoding='utf-8', newline='\n') as fh:
fh.write(new_text)
scrubbed += count
print(' -> copied with %d absolute-path literal(s) neutralised' % count)
copied += 1
continue
shutil.copy2(full, target)
copied += 1
print('')
if not args.apply:
print('== audit: %d file(s) checked, %d finding(s). Nothing was written.'
% (len(files), total_findings))
print(' re-run with --apply to copy, and with --scrub to neutralise '
'absolute-path literals.')
return 2 if total_findings else 0
print('== sync: %d file(s) copied to %s (%d literal(s) scrubbed), %d finding(s) reported.'
% (copied, args.dst, scrubbed, total_findings))
return 0
if __name__ == '__main__':
main()
sys.exit(main())