166 lines
5.3 KiB
Python
166 lines
5.3 KiB
Python
|
|
"""
|
||
|
|
Android App Extraction - Pull APKs, app data, shared preferences
|
||
|
|
"""
|
||
|
|
|
||
|
|
DESCRIPTION = "Android app extraction (APK pull, app data, shared prefs)"
|
||
|
|
AUTHOR = "AUTARCH"
|
||
|
|
VERSION = "1.0"
|
||
|
|
CATEGORY = "hardware"
|
||
|
|
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||
|
|
|
||
|
|
|
||
|
|
class AndroidApps:
|
||
|
|
"""Interactive menu for Android app extraction."""
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
from core.android_exploit import get_exploit_manager
|
||
|
|
from core.hardware import get_hardware_manager
|
||
|
|
self.mgr = get_exploit_manager()
|
||
|
|
self.hw = get_hardware_manager()
|
||
|
|
self.serial = None
|
||
|
|
|
||
|
|
def _select_device(self):
|
||
|
|
devices = self.hw.adb_devices()
|
||
|
|
if not devices:
|
||
|
|
print(" No ADB devices connected.")
|
||
|
|
return
|
||
|
|
if len(devices) == 1:
|
||
|
|
self.serial = devices[0]['serial']
|
||
|
|
print(f" Selected: {self.serial}")
|
||
|
|
return
|
||
|
|
print("\n Select device:")
|
||
|
|
for i, d in enumerate(devices, 1):
|
||
|
|
model = d.get('model', '')
|
||
|
|
print(f" {i}) {d['serial']} {model}")
|
||
|
|
try:
|
||
|
|
choice = int(input(" > ").strip())
|
||
|
|
if 1 <= choice <= len(devices):
|
||
|
|
self.serial = devices[choice - 1]['serial']
|
||
|
|
except (ValueError, EOFError, KeyboardInterrupt):
|
||
|
|
pass
|
||
|
|
|
||
|
|
def _ensure_device(self):
|
||
|
|
if not self.serial:
|
||
|
|
self._select_device()
|
||
|
|
return self.serial is not None
|
||
|
|
|
||
|
|
def show_menu(self):
|
||
|
|
print(f"\n{'='*50}")
|
||
|
|
print(" App Extraction")
|
||
|
|
print(f"{'='*50}")
|
||
|
|
print(f" Device: {self.serial or '(none)'}")
|
||
|
|
print()
|
||
|
|
print(" [1] List Packages")
|
||
|
|
print(" [2] Pull APK")
|
||
|
|
print(" [3] Pull App Data (root/debuggable)")
|
||
|
|
print(" [4] Extract Shared Prefs")
|
||
|
|
print(" [s] Select Device")
|
||
|
|
print(" [0] Back")
|
||
|
|
print()
|
||
|
|
|
||
|
|
def list_packages(self):
|
||
|
|
if not self._ensure_device():
|
||
|
|
return
|
||
|
|
try:
|
||
|
|
inc = input(" Include system apps? [y/N]: ").strip().lower() == 'y'
|
||
|
|
except (EOFError, KeyboardInterrupt):
|
||
|
|
return
|
||
|
|
result = self.mgr.list_packages(self.serial, include_system=inc)
|
||
|
|
if 'error' in result:
|
||
|
|
print(f" Error: {result['error']}")
|
||
|
|
return
|
||
|
|
print(f"\n Found {result['count']} packages:")
|
||
|
|
for pkg in result['packages']:
|
||
|
|
flag = ' [SYS]' if pkg['is_system'] else ''
|
||
|
|
print(f" {pkg['package']}{flag}")
|
||
|
|
print(f" {pkg['path']}")
|
||
|
|
|
||
|
|
def pull_apk(self):
|
||
|
|
if not self._ensure_device():
|
||
|
|
return
|
||
|
|
try:
|
||
|
|
package = input(" Package name: ").strip()
|
||
|
|
except (EOFError, KeyboardInterrupt):
|
||
|
|
return
|
||
|
|
if not package:
|
||
|
|
return
|
||
|
|
print(f" Pulling APK for {package}...")
|
||
|
|
result = self.mgr.pull_apk(self.serial, package)
|
||
|
|
if result['success']:
|
||
|
|
size_mb = result['size'] / (1024 * 1024)
|
||
|
|
print(f" Saved: {result['local_path']} ({size_mb:.1f} MB)")
|
||
|
|
else:
|
||
|
|
print(f" Error: {result['error']}")
|
||
|
|
|
||
|
|
def pull_app_data(self):
|
||
|
|
if not self._ensure_device():
|
||
|
|
return
|
||
|
|
try:
|
||
|
|
package = input(" Package name: ").strip()
|
||
|
|
except (EOFError, KeyboardInterrupt):
|
||
|
|
return
|
||
|
|
if not package:
|
||
|
|
return
|
||
|
|
print(f" Pulling app data for {package}...")
|
||
|
|
result = self.mgr.pull_app_data(self.serial, package)
|
||
|
|
if result['success']:
|
||
|
|
print(f" Output dir: {result['output_dir']}")
|
||
|
|
for f in result['files']:
|
||
|
|
print(f" {f}")
|
||
|
|
else:
|
||
|
|
print(" No data extracted (need debuggable app or root).")
|
||
|
|
|
||
|
|
def extract_prefs(self):
|
||
|
|
if not self._ensure_device():
|
||
|
|
return
|
||
|
|
try:
|
||
|
|
package = input(" Package name: ").strip()
|
||
|
|
except (EOFError, KeyboardInterrupt):
|
||
|
|
return
|
||
|
|
if not package:
|
||
|
|
return
|
||
|
|
print(f" Extracting shared prefs for {package}...")
|
||
|
|
result = self.mgr.extract_shared_prefs(self.serial, package)
|
||
|
|
if result['success']:
|
||
|
|
print(f" Found {result['count']} pref files:")
|
||
|
|
for name, content in result['prefs'].items():
|
||
|
|
print(f"\n --- {name} ---")
|
||
|
|
# Show first 20 lines
|
||
|
|
lines = content.split('\n')[:20]
|
||
|
|
for line in lines:
|
||
|
|
print(f" {line}")
|
||
|
|
if len(content.split('\n')) > 20:
|
||
|
|
print(" ...")
|
||
|
|
else:
|
||
|
|
print(f" Error: {result.get('error', 'Failed')}")
|
||
|
|
|
||
|
|
def run_interactive(self):
|
||
|
|
while True:
|
||
|
|
self.show_menu()
|
||
|
|
try:
|
||
|
|
choice = input(" Select > ").strip().lower()
|
||
|
|
except (EOFError, KeyboardInterrupt):
|
||
|
|
break
|
||
|
|
if choice == '0':
|
||
|
|
break
|
||
|
|
elif choice == '1':
|
||
|
|
self.list_packages()
|
||
|
|
elif choice == '2':
|
||
|
|
self.pull_apk()
|
||
|
|
elif choice == '3':
|
||
|
|
self.pull_app_data()
|
||
|
|
elif choice == '4':
|
||
|
|
self.extract_prefs()
|
||
|
|
elif choice == 's':
|
||
|
|
self._select_device()
|
||
|
|
else:
|
||
|
|
print(" Invalid choice.")
|
||
|
|
|
||
|
|
|
||
|
|
def run():
|
||
|
|
m = AndroidApps()
|
||
|
|
m.run_interactive()
|