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