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
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Quoting-safe ADB helper.
Why this exists: if you build device commands inside a host shell, the host expands
`$`, `|`, `>`, and quotes *before* adb sees them. Symptoms are host-side errors that
look like device failures -- "Could not find a part of the path", "no closing quote",
"Missing type name after '['", unexpanded variables. Windows PowerShell is the worst
offender (it also eats `$var:`, `[^"...`, `$(...)`).
Calling adb from Python with an argument list avoids all of it.
Configuration (first match wins):
1. CLI flags: --serial / --adb
2. env: ADB_SERIAL / ADB_PATH
3. defaults below
Usage
-----
python devsh.py sh "pm list packages | grep myapp" # as the shell user
python devsh.py su "pm grant <pkg> android.permission.X"
python devsh.py sh "dumpsys window | grep mCurrentFocus"
python devsh.py pull /data/local/tmp/x.apk ./x.apk
python devsh.py push ./x.apk /data/local/tmp/x.apk
python devsh.py dev # list devices
`su` wraps the command in `su -c "..."`; internal double quotes are escaped.
"""
import argparse
import os
import subprocess
import sys
DEFAULT_ADB = 'adb'
DEFAULT_SERIAL = None
def resolve():
ap = argparse.ArgumentParser(add_help=False)
ap.add_argument('--adb')
ap.add_argument('--serial')
known, _ = ap.parse_known_args()
adb = known.adb or os.environ.get('ADB_PATH') or DEFAULT_ADB
serial = known.serial or os.environ.get('ADB_SERIAL') or DEFAULT_SERIAL
return adb, serial
def run(adb, serial, args, timeout=300):
cmd = [adb]
if serial:
cmd += ['-s', serial]
cmd += args
r = subprocess.run(cmd, capture_output=True, text=True, errors='replace', timeout=timeout)
out = r.stdout or ''
if (r.stderr or '').strip():
out += '\n[stderr]\n' + r.stderr
return r.returncode, out
def main():
if len(sys.argv) < 2:
print(__doc__)
return 2
adb, serial = resolve()
argv = [a for a in sys.argv[1:] if not a.startswith('--adb=') and not a.startswith('--serial=')]
# strip flag/value pairs
cleaned = []
skip = False
for a in argv:
if skip:
skip = False
continue
if a in ('--adb', '--serial'):
skip = True
continue
cleaned.append(a)
argv = cleaned
if len(argv) < 2 and argv and argv[0] not in ('dev',):
print(__doc__)
return 2
mode = argv[0]
if mode == 'dev':
rc, out = run(adb, None, ['devices', '-l'])
elif mode in ('sh', 'shell'):
rc, out = run(adb, serial, ['shell', argv[1]])
elif mode == 'su':
cmd = argv[1].replace('"', '\\"')
rc, out = run(adb, serial, ['shell', 'su -c "%s"' % cmd])
elif mode == 'suf': # read a device text file
cmd = argv[1].replace('"', '\\"')
size = argv[2] if len(argv) > 2 else '6000'
rc, out = run(adb, serial, ['shell', 'su -c "head -c %s %s"' % (size, cmd)])
elif mode == 'pull':
rc, out = run(adb, serial, ['pull', argv[1], argv[2]])
elif mode == 'push':
rc, out = run(adb, serial, ['push', argv[1], argv[2]])
else:
print(__doc__)
return 2
sys.stdout.write(out)
return 0 if rc == 0 else 1
if __name__ == '__main__':
sys.exit(main())