#!/usr/bin/env python3 """Android malware reverse engineering agent using jadx and androguard subprocess wrappers.""" import subprocess import os import sys import re import hashlib from xml.etree import ElementTree def compute_apk_hashes(apk_path): """Compute hashes for APK identification.""" with open(apk_path, "rb") as f: data = f.read() return { "md5": hashlib.md5(data).hexdigest(), "sha256": hashlib.sha256(data).hexdigest(), "size": len(data), } def extract_manifest(apk_path, output_dir): """Extract and parse AndroidManifest.xml using apktool.""" subprocess.run( ["apktool", "d", apk_path, "-o", output_dir, "-f"], capture_output=True, text=True, timeout=120 ) manifest_path = os.path.join(output_dir, "AndroidManifest.xml") if not os.path.exists(manifest_path): return {"error": "Manifest extraction failed"} tree = ElementTree.parse(manifest_path) root = tree.getroot() ns = {"android": "http://schemas.android.com/apk/res/android"} permissions = [] for perm in root.findall(".//uses-permission"): name = perm.get(f"{{{ns['android']}}}name", "") permissions.append(name) activities = [] for act in root.findall(".//activity"): name = act.get(f"{{{ns['android']}}}name", "") exported = act.get(f"{{{ns['android']}}}exported", "false") activities.append({"name": name, "exported": exported}) services = [] for svc in root.findall(".//service"): name = svc.get(f"{{{ns['android']}}}name", "") services.append(name) receivers = [] for rcv in root.findall(".//receiver"): name = rcv.get(f"{{{ns['android']}}}name", "") intents = [] for intent in rcv.findall(".//intent-filter/action"): intents.append(intent.get(f"{{{ns['android']}}}name", "")) receivers.append({"name": name, "intents": intents}) package = root.get("package", "") return { "package": package, "permissions": permissions, "activities": activities, "services": services, "receivers": receivers, } DANGEROUS_PERMISSIONS = [ "android.permission.READ_SMS", "android.permission.SEND_SMS", "android.permission.RECEIVE_SMS", "android.permission.READ_CONTACTS", "android.permission.CAMERA", "android.permission.RECORD_AUDIO", "android.permission.ACCESS_FINE_LOCATION", "android.permission.READ_PHONE_STATE", "android.permission.CALL_PHONE", "android.permission.READ_CALL_LOG", "android.permission.WRITE_EXTERNAL_STORAGE", "android.permission.READ_EXTERNAL_STORAGE", "android.permission.SYSTEM_ALERT_WINDOW", "android.permission.BIND_ACCESSIBILITY_SERVICE", "android.permission.REQUEST_INSTALL_PACKAGES", "android.permission.BIND_DEVICE_ADMIN", ] def analyze_permissions(permissions): """Classify permissions by risk level.""" dangerous = [p for p in permissions if p in DANGEROUS_PERMISSIONS] sms_related = [p for p in permissions if "SMS" in p] accessibility = [p for p in permissions if "ACCESSIBILITY" in p] admin = [p for p in permissions if "DEVICE_ADMIN" in p or "BIND_ADMIN" in p] risk = "LOW" if len(dangerous) > 5: risk = "HIGH" if sms_related or accessibility or admin: risk = "CRITICAL" return { "total": len(permissions), "dangerous": dangerous, "sms_related": sms_related, "accessibility": accessibility, "device_admin": admin, "risk": risk, } def decompile_with_jadx(apk_path, output_dir): """Decompile APK to Java source using JADX.""" result = subprocess.run( ["jadx", "-d", output_dir, "--deobf", apk_path], capture_output=True, text=True, timeout=300 ) return { "output_dir": output_dir, "returncode": result.returncode, "stdout": result.stdout[-500:] if result.stdout else "", } def search_source_code(source_dir, patterns=None): """Search decompiled source for suspicious patterns.""" if patterns is None: patterns = { "urls": r'https?://[^\s"\'<>]+', "ips": r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b', "crypto_keys": r'(?:AES|DES|RSA|key|secret|encrypt).*?["\']([^"\']{8,})["\']', "base64_strings": r'[A-Za-z0-9+/]{40,}={0,2}', "exec_commands": r'Runtime\.getRuntime\(\)\.exec|ProcessBuilder', "reflection": r'Class\.forName|getMethod|getDeclaredMethod', "dex_loading": r'DexClassLoader|PathClassLoader|InMemoryDexClassLoader', "overlay_attack": r'TYPE_APPLICATION_OVERLAY|SYSTEM_ALERT_WINDOW', "accessibility_abuse": r'AccessibilityService|onAccessibilityEvent', "sms_intercept": r'SmsReceiver|SMS_RECEIVED|sendTextMessage', } findings = {p: [] for p in patterns} for root, dirs, files in os.walk(source_dir): for filename in files: if not filename.endswith(".java"): continue filepath = os.path.join(root, filename) try: with open(filepath, "r", errors="ignore") as f: content = f.read() for pattern_name, regex in patterns.items(): matches = re.findall(regex, content) if matches: findings[pattern_name].extend([ {"file": filepath, "match": m[:100]} for m in matches[:5] ]) except (OSError, UnicodeDecodeError): pass for key in findings: findings[key] = findings[key][:20] return findings def analyze_apk(apk_path, output_base="/tmp/apk_analysis"): """Full APK analysis pipeline.""" os.makedirs(output_base, exist_ok=True) report = {"apk": apk_path} report["hashes"] = compute_apk_hashes(apk_path) apktool_dir = os.path.join(output_base, "apktool") report["manifest"] = extract_manifest(apk_path, apktool_dir) if "permissions" in report["manifest"]: report["permission_analysis"] = analyze_permissions(report["manifest"]["permissions"]) jadx_dir = os.path.join(output_base, "jadx_output") report["decompilation"] = decompile_with_jadx(apk_path, jadx_dir) if os.path.exists(jadx_dir): source_dir = os.path.join(jadx_dir, "sources") if os.path.exists(source_dir): report["code_analysis"] = search_source_code(source_dir) return report def print_report(report): print("Android Malware Analysis Report") print("=" * 50) print(f"APK: {report['apk']}") print(f"SHA-256: {report['hashes']['sha256']}") print(f"Size: {report['hashes']['size']} bytes") manifest = report.get("manifest", {}) print(f"\nPackage: {manifest.get('package', 'N/A')}") perm = report.get("permission_analysis", {}) print(f"Permissions: {perm.get('total', 0)} (Risk: {perm.get('risk', 'N/A')})") if perm.get("dangerous"): print(f" Dangerous: {', '.join(p.split('.')[-1] for p in perm['dangerous'][:8])}") print(f"Activities: {len(manifest.get('activities', []))}") print(f"Services: {len(manifest.get('services', []))}") print(f"Receivers: {len(manifest.get('receivers', []))}") code = report.get("code_analysis", {}) if code: print("\nCode Analysis Findings:") for pattern, matches in code.items(): if matches: print(f" {pattern}: {len(matches)} match(es)") for m in matches[:3]: print(f" -> {m['match'][:80]}") if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python agent.py ") sys.exit(1) result = analyze_apk(sys.argv[1]) print_report(result)