Add Android forensics, IOC threat-intel DB, Compose companion; scrub secrets from configs

This commit is contained in:
SsSnake
2026-07-13 15:45:47 -07:00
parent 925216290f
commit e48d577bd5
387 changed files with 211976 additions and 921 deletions

View File

@@ -0,0 +1,372 @@
"""
Autarch Android Forensics — forensic acquisition and IOC scanning for Android devices.
"""
DESCRIPTION = "Android forensic acquisition and spyware IOC scanning"
AUTHOR = "AUTARCH"
VERSION = "1.0"
CATEGORY = "counter"
class AutarchAndroidModule:
"""Interactive Android forensics CLI menu."""
def __init__(self):
from core.android_forensics import get_android_forensics_manager
from core.hardware import get_hardware_manager
self.mgr = get_android_forensics_manager()
self.hw = get_hardware_manager()
self.serial = None
def _pick_device(self):
devices = self.hw.adb_devices()
if not devices:
print(" No ADB devices connected.")
return None
if len(devices) == 1:
self.serial = devices[0]['serial']
print(f" Selected: {self.serial}")
return self.serial
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']
return self.serial
except (ValueError, EOFError, KeyboardInterrupt):
pass
return None
def _ensure_device(self):
if self.serial:
return self.serial
return self._pick_device()
def _sev_marker(self, sev):
return {'critical': '[!!!]', 'high': '[!! ]', 'medium': '[! ]', 'low': '[ ]'}.get(sev, '[? ]')
def show_menu(self):
status = self.hw.get_status()
serial_str = self.serial or 'None selected'
stats = self.mgr.get_ioc_stats()
counts = stats.get('counts', {})
ioc_str = (f"{counts.get('packages', 0)} pkgs, "
f"{counts.get('domains', 0)} domains, "
f"{counts.get('certs', 0)} certs, "
f"{counts.get('hashes', 0)} hashes")
print(f"\n{'='*60}")
print(" Autarch Android Forensics")
print(f"{'='*60}")
print(f" ADB: {'Available' if status.get('adb') else 'Not found'}")
print(f" Device: {serial_str}")
print(f" IOC DB: {ioc_str}")
print()
print(" -- Acquisition --")
print(" 1) Full Acquisition (all modules)")
print(" 2) Package List")
print(" 3) Running Processes")
print(" 4) System Properties (getprop)")
print(" 5) Settings")
print(" 6) Services")
print(" 7) Dumpsys")
print(" 8) Logcat")
print(" 9) Temp Files")
print()
print(" -- IOC Scanning --")
print(" 20) Scan Apps vs IOC Database")
print(" 21) Scan APK Certificates")
print(" 22) Scan Network Indicators")
print(" 23) Scan File Artifacts")
print(" 24) Scan Running Processes")
print(" 25) Full IOC Scan (all)")
print()
print(" -- Reports --")
print(" 30) List Past Acquisitions")
print(" 31) View Acquisition Report")
print()
print(" -- Database --")
print(" 40) IOC Database Stats")
print(" 41) Reload IOC Database")
print()
print(" [s] Select Device | [0] Back")
print()
# ── Acquisition ─────────────────────────────────────────────────
def do_full_acquisition(self):
if not self._ensure_device():
return
print(f"\n Running full acquisition on {self.serial}...")
print(" This may take several minutes.")
result = self.mgr.full_acquisition(self.serial)
summary = result.get('scan_summary', {})
modules = result.get('modules', {})
print(f"\n Acquisition complete: {result.get('acq_id', '')}")
print(f" Modules collected: {len(modules)}")
print(f" IOC findings: {summary.get('total_findings', 0)}")
print(f" Critical: {summary.get('critical', 0)}")
print(f" High: {summary.get('high', 0)}")
if not summary.get('clean', True):
print("\n Findings:")
for f in result.get('ioc_findings', [])[:20]:
marker = self._sev_marker(f.get('severity', 'high'))
print(f" {marker} [{f.get('type', '?')}] {f.get('threat_name', '?')}")
detail = f.get('package') or f.get('process') or f.get('path') or f.get('remote_ip', '')
if detail:
print(f" {detail}")
else:
print(" Device appears clean.")
def do_acquire_module(self, module_name):
if not self._ensure_device():
return
func_map = {
'packages': self.mgr.acquire_packages,
'processes': self.mgr.acquire_processes,
'getprop': self.mgr.acquire_getprop,
'settings': self.mgr.acquire_settings,
'services': self.mgr.acquire_services,
'dumpsys': self.mgr.acquire_dumpsys,
'logcat': self.mgr.acquire_logcat,
'tmp_files': self.mgr.acquire_tmp_files,
}
func = func_map.get(module_name)
if not func:
print(f" Unknown module: {module_name}")
return
print(f"\n Acquiring {module_name} from {self.serial}...")
result = func(self.serial)
if not result.get('ok'):
print(f" Error: {result.get('error', 'Unknown')}")
return
if module_name == 'packages':
pkgs = result.get('packages', [])
print(f" Found {len(pkgs)} packages ({sum(1 for p in pkgs if p.get('system'))} system, "
f"{sum(1 for p in pkgs if not p.get('system'))} user)")
for p in pkgs[:20]:
marker = "[sys]" if p.get('system') else "[usr]"
print(f" {marker} {p['package']}")
if len(pkgs) > 20:
print(f" ... and {len(pkgs) - 20} more")
elif module_name == 'getprop':
props = result.get('props', {})
print(f" {len(props)} properties")
for k in ('ro.product.model', 'ro.build.version.release', 'ro.build.fingerprint',
'ro.secure', 'ro.debuggable', 'net.dns1', 'net.dns2'):
if k in props:
print(f" {k} = {props[k]}")
else:
out = result.get('output', '')
lines = out.splitlines()
print(f" {len(lines)} lines")
for line in lines[:30]:
print(f" {line}")
if len(lines) > 30:
print(f" ... {len(lines) - 30} more lines")
# ── IOC Scanning ─────────────────────────────────────────────────
def _print_findings(self, result, scan_name):
if not result.get('ok'):
print(f" Error: {result.get('error', 'Unknown')}")
return
findings = result.get('findings', [])
note = result.get('note', '')
if note:
print(f" Note: {note}")
if findings:
print(f"\n {scan_name}: {len(findings)} finding(s)")
for f in findings:
marker = self._sev_marker(f.get('severity', 'high'))
name = f.get('threat_name', '?')
detail = f.get('package') or f.get('process') or f.get('path') or f.get('remote_ip', '')
print(f" {marker} {name}")
if detail:
print(f" {detail}")
print(f" Source: {f.get('source', '?')}")
else:
scanned = result.get('total_scanned', '')
scanned_str = f" ({scanned} scanned)" if scanned else ""
print(f" {scan_name}: Clean{scanned_str}")
def do_scan_packages(self):
if not self._ensure_device():
return
print(f"\n Scanning packages on {self.serial}...")
self._print_findings(self.mgr.scan_packages(self.serial), "Package Scan")
def do_scan_certs(self):
if not self._ensure_device():
return
print(f"\n Scanning APK certificates (may take a while)...")
self._print_findings(self.mgr.scan_certificates(self.serial), "Certificate Scan")
def do_scan_network(self):
if not self._ensure_device():
return
print(f"\n Scanning network indicators on {self.serial}...")
self._print_findings(self.mgr.scan_network_indicators(self.serial), "Network Scan")
def do_scan_files(self):
if not self._ensure_device():
return
print(f"\n Scanning file artifacts on {self.serial}...")
self._print_findings(self.mgr.scan_file_indicators(self.serial), "File Scan")
def do_scan_processes(self):
if not self._ensure_device():
return
print(f"\n Scanning running processes on {self.serial}...")
self._print_findings(self.mgr.scan_process_indicators(self.serial), "Process Scan")
def do_full_scan(self):
if not self._ensure_device():
return
print(f"\n Running full IOC scan on {self.serial}...")
all_findings = []
for scan_name, func in [
("Packages", self.mgr.scan_packages),
("Certs", self.mgr.scan_certificates),
("Network", self.mgr.scan_network_indicators),
("Files", self.mgr.scan_file_indicators),
("Processes", self.mgr.scan_process_indicators),
]:
print(f" Scanning {scan_name}...", end='', flush=True)
result = func(self.serial)
found = len(result.get('findings', []))
print(f" {found} findings")
all_findings.extend(result.get('findings', []))
print(f"\n Total findings: {len(all_findings)}")
if all_findings:
for f in all_findings:
marker = self._sev_marker(f.get('severity', 'high'))
detail = f.get('package') or f.get('process') or f.get('path') or f.get('remote_ip', '')
print(f" {marker} {f.get('threat_name', '?')}{detail}")
else:
print(" Device appears clean.")
# ── Reports ──────────────────────────────────────────────────────
def do_list_acquisitions(self):
if not self._ensure_device():
return
acqs = self.mgr.get_acquisition_list(self.serial)
if not acqs:
print(f" No acquisitions found for {self.serial}")
return
print(f"\n Acquisitions for {self.serial}:")
for i, a in enumerate(acqs, 1):
ts = a.get('timestamp', a['acq_id'])
findings = a.get('findings', 0)
modules = ', '.join(a.get('modules', []))
print(f" {i:2}) {ts} findings={findings} [{modules}]")
def do_view_report(self):
if not self._ensure_device():
return
acqs = self.mgr.get_acquisition_list(self.serial)
if not acqs:
print(f" No acquisitions found for {self.serial}")
return
print(f"\n Select acquisition:")
for i, a in enumerate(acqs, 1):
print(f" {i}) {a.get('timestamp', a['acq_id'])}")
try:
choice = int(input(" > ").strip())
if 1 <= choice <= len(acqs):
result = self.mgr.export_report(self.serial, acqs[choice - 1]['acq_id'])
if result.get('ok'):
report = result['report']
summary = report.get('scan_summary', {})
print(f"\n Report: {report.get('acq_id', '')}")
print(f" Device: {report.get('serial', '')}")
print(f" Time: {report.get('timestamp', '')}")
print(f" Total findings: {summary.get('total_findings', 0)}")
print(f" Critical: {summary.get('critical', 0)}, High: {summary.get('high', 0)}")
for f in report.get('ioc_findings', []):
marker = self._sev_marker(f.get('severity', 'high'))
detail = f.get('package') or f.get('process') or f.get('path') or ''
print(f" {marker} {f.get('threat_name', '?')}: {detail}")
else:
print(f" Error: {result.get('error')}")
except (ValueError, EOFError, KeyboardInterrupt):
pass
# ── Database ─────────────────────────────────────────────────────
def do_ioc_stats(self):
stats = self.mgr.get_ioc_stats()
counts = stats.get('counts', {})
sources = stats.get('sources', {})
print(f"\n IOC Database Statistics:")
print(f" Generated: {stats.get('generated', 'N/A')}")
print(f" Packages: {counts.get('packages', 0)}")
print(f" Domains: {counts.get('domains', 0)}")
print(f" Certificates: {counts.get('certs', 0)}")
print(f" File paths: {counts.get('file_paths', 0)}")
print(f" Process names: {counts.get('processes', 0)}")
print(f" Hashes: {counts.get('hashes', 0)}")
print(f" IPs: {counts.get('ips', 0)}")
if sources:
print(f"\n Sources:")
print(f" Stalkerware YAML: {'Yes' if sources.get('stalkerware_ioc_yaml') else 'No'}")
print(f" Amnesty campaigns: {sources.get('amnesty_investigations', 0)}")
print(f" MVT campaigns: {sources.get('mvt_campaigns', 0)}")
print(f" Citizen Lab: {sources.get('citizen_lab_campaigns', 0)}")
print(f" YARA rules: {sources.get('yara_rules', 0)}")
def do_reload_db(self):
print(" Reloading IOC database from disk...")
db = self.mgr.reload_ioc_database()
print(f" Done — {len(db.get('packages', {}))} packages, "
f"{len(db.get('domains', {}))} domains loaded")
# ── Main Loop ─────────────────────────────────────────────────────
def run_interactive(self):
while True:
self.show_menu()
try:
choice = input(" Select > ").strip()
except (EOFError, KeyboardInterrupt):
break
if choice == '0':
break
actions = {
'1': self.do_full_acquisition,
'2': lambda: self.do_acquire_module('packages'),
'3': lambda: self.do_acquire_module('processes'),
'4': lambda: self.do_acquire_module('getprop'),
'5': lambda: self.do_acquire_module('settings'),
'6': lambda: self.do_acquire_module('services'),
'7': lambda: self.do_acquire_module('dumpsys'),
'8': lambda: self.do_acquire_module('logcat'),
'9': lambda: self.do_acquire_module('tmp_files'),
'20': self.do_scan_packages,
'21': self.do_scan_certs,
'22': self.do_scan_network,
'23': self.do_scan_files,
'24': self.do_scan_processes,
'25': self.do_full_scan,
'30': self.do_list_acquisitions,
'31': self.do_view_report,
'40': self.do_ioc_stats,
'41': self.do_reload_db,
's': self._pick_device,
}
action = actions.get(choice)
if action:
action()
else:
print(" Invalid choice.")
def run():
m = AutarchAndroidModule()
m.run_interactive()