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:
@@ -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())
|
||||
Reference in New Issue
Block a user