feat: apk-reverse Agent Skill

Progressive-disclosure skill for Android APK reverse engineering:
recon, ad removal, membership limits, dex surgical patching, repack/sign,
runtime data, Frida, environment setup, and a hard-won failure catalogue.
This commit is contained in:
inliver233
2026-09-19 21:25:35 +08:00
commit c66658b6b3
33 changed files with 3713 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""baksmali / smali wrapper.
Assembling and disassembling dex requires smali + baksmali + several runtime jars on
the classpath. Getting the set wrong produces a bare `ClassNotFoundException` that
looks like a broken tool rather than a missing jar, so this wrapper centralizes it.
Two ways to configure the classpath, in priority order:
1. --cp "jar1;jar2" (explicit, per invocation)
2. APK_REVERSE_SMALI_CP env var
3. a `smali_cp.txt` next to this script (one jar path per line, '#' comments ok)
Required jars: smali, baksmali, dexlib2, util, antlr-runtime, stringtemplate,
jcommander, guava.
Usage
-----
python smtool.py d <in.dex> <out_dir> # disassemble to a smali tree
python smtool.py a <in_tree> <out.dex> # assemble a smali tree back to dex
python smtool.py check # print the resolved classpath and exit
Notes
-----
* Always pass the tree directory as its own argument; do not rely on the shell.
* Call java from Python rather than from a shell: PowerShell in particular mangles
arguments containing ';' or '$', which silently drops jars from the classpath.
"""
import os
import subprocess
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
CP_FILE = os.path.join(HERE, 'smali_cp.txt')
JAR_NAMES = [
'smali-2.5.2.jar',
'antlr-runtime-3.5.2.jar',
'stringtemplate-3.2.1.jar',
'baksmali-2.5.2.jar',
'util-2.5.2.jar',
'jcommander-1.64.jar',
'guava-27.1-android.jar',
'dexlib2-2.5.2.jar',
]
def resolve_cp(explicit=None):
if explicit:
return explicit
env = os.environ.get('APK_REVERSE_SMALI_CP')
if env:
return env
if os.path.isfile(CP_FILE):
parts = []
for line in open(CP_FILE, encoding='utf-8'):
line = line.strip()
if line and not line.startswith('#'):
parts.append(line)
if parts:
return os.pathsep.join(parts)
# last resort: look for the jars in a 'tools' dir next to this script
cand = [os.path.join(HERE, 'tools', n) for n in JAR_NAMES]
if all(os.path.isfile(c) for c in cand):
return os.pathsep.join(cand)
return None
def run(cp, main_class, args):
cmd = ['java', '-cp', cp, main_class] + args
r = subprocess.run(cmd, capture_output=True, text=True, errors='replace')
out = (r.stdout or '') + (('\n[stderr]\n' + r.stderr) if (r.stderr or '').strip() else '')
return r.returncode, out
def main():
if len(sys.argv) < 2:
print(__doc__)
return 2
explicit = None
argv = sys.argv[1:]
if '--cp' in argv:
i = argv.index('--cp')
explicit = argv[i + 1]
del argv[i:i + 2]
cp = resolve_cp(explicit)
if not cp:
print('[FAIL] no classpath. Provide --cp, set APK_REVERSE_SMALI_CP, '
'or create smali_cp.txt next to this script.')
return 1
cmd = argv[0]
if cmd == 'check':
print('classpath: %s' % cp)
for j in cp.split(os.pathsep):
print(' %s %s' % ('OK ' if os.path.isfile(j) else 'MISSING', j))
return 0
if cmd in ('d', 'dis', 'disassemble'):
if len(argv) < 3:
print('usage: smtool.py d <in.dex> <out_dir>')
return 2
rc, out = run(cp, 'org.jf.baksmali.Main', ['d', argv[1], '-o', argv[2]])
elif cmd in ('a', 'asm', 'assemble'):
if len(argv) < 3:
print('usage: smtool.py a <in_tree> <out.dex>')
return 2
rc, out = run(cp, 'org.jf.smali.Main', ['a', argv[1], '-o', argv[2]])
else:
print(__doc__)
return 2
print(out.strip()[-4000:])
print('rc=%d' % rc)
return 0 if rc == 0 else 1
if __name__ == '__main__':
sys.exit(main())