#!/usr/bin/env python3
"""Agent for performing dynamic analysis of Android applications using Frida."""

import json
import argparse
import subprocess

try:
    import frida
    HAS_FRIDA = True
except ImportError:
    HAS_FRIDA = False


def list_packages(device_id=None):
    """List installed packages on Android device/emulator."""
    cmd = ["adb"] + (["-s", device_id] if device_id else []) + ["shell", "pm", "list", "packages", "-3"]
    result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
    packages = [l.replace("package:", "").strip() for l in result.stdout.strip().split("\n") if l.strip()]
    return {"total": len(packages), "packages": packages}


def capture_network_traffic(device_id=None, duration=30, output="capture.pcap"):
    """Capture network traffic from Android device using tcpdump."""
    adb = ["adb"] + (["-s", device_id] if device_id else [])
    subprocess.run(adb + ["shell", "tcpdump", "-i", "any", "-w", f"/sdcard/{output}", "-G", str(duration), "-W", "1"],
                   timeout=duration + 10, capture_output=True)
    subprocess.run(adb + ["pull", f"/sdcard/{output}", output], capture_output=True, timeout=15)
    return {"output": output, "duration": duration}


def check_ssl_pinning(package_name, device_id=None):
    """Check if app implements SSL/TLS certificate pinning using Frida."""
    if not HAS_FRIDA:
        return {"error": "frida not installed"}
    device = frida.get_usb_device() if not device_id else frida.get_device(device_id)
    pid = device.spawn([package_name])
    session = device.attach(pid)
    script = session.create_script("""
    Java.perform(function() {
        var results = {pinning_detected: false, methods: []};
        try {
            var TrustManagerImpl = Java.use('com.android.org.conscrypt.TrustManagerImpl');
            TrustManagerImpl.verifyChain.implementation = function() {
                results.pinning_detected = true;
                results.methods.push('TrustManagerImpl.verifyChain');
                return arguments[0];
            };
        } catch(e) {}
        try {
            var OkHostnameVerifier = Java.use('okhttp3.internal.tls.OkHostnameVerifier');
            OkHostnameVerifier.verify.overload('java.lang.String', 'javax.net.ssl.SSLSession').implementation = function() {
                results.pinning_detected = true;
                results.methods.push('OkHostnameVerifier.verify');
                return true;
            };
        } catch(e) {}
        setTimeout(function() { send(results); }, 5000);
    });
    """)
    results = {}
    def on_message(msg, data):
        nonlocal results
        if msg["type"] == "send":
            results = msg["payload"]
    script.on("message", on_message)
    script.load()
    device.resume(pid)
    import time
    time.sleep(8)
    session.detach()
    return {"package": package_name, "ssl_pinning": results}


def analyze_exported_components(package_name, device_id=None):
    """List exported activities, services, receivers, and providers."""
    adb = ["adb"] + (["-s", device_id] if device_id else [])
    result = subprocess.run(
        adb + ["shell", "dumpsys", "package", package_name],
        capture_output=True, text=True, timeout=15
    )
    output = result.stdout
    components = {"activities": [], "services": [], "receivers": [], "providers": []}
    section = None
    for line in output.split("\n"):
        line = line.strip()
        if "Activity Resolver Table:" in line:
            section = "activities"
        elif "Service Resolver Table:" in line:
            section = "services"
        elif "Receiver Resolver Table:" in line:
            section = "receivers"
        elif "Provider Resolver Table:" in line:
            section = "providers"
        elif section and package_name in line:
            components[section].append(line[:200])
    return {"package": package_name, "components": components}


def check_data_storage(package_name, device_id=None):
    """Check for insecure data storage patterns."""
    adb = ["adb"] + (["-s", device_id] if device_id else [])
    findings = []
    # Check shared preferences for sensitive data
    sp_result = subprocess.run(
        adb + ["shell", "run-as", package_name, "ls", "shared_prefs/"],
        capture_output=True, text=True, timeout=10
    )
    if sp_result.returncode == 0:
        prefs = [f.strip() for f in sp_result.stdout.split("\n") if f.strip()]
        findings.append({"type": "shared_prefs", "files": prefs})
    # Check for world-readable files
    wr_result = subprocess.run(
        adb + ["shell", "run-as", package_name, "find", ".", "-perm", "-o+r", "-type", "f"],
        capture_output=True, text=True, timeout=10
    )
    if wr_result.stdout.strip():
        findings.append({"type": "world_readable", "files": wr_result.stdout.strip().split("\n")[:20]})
    return {"package": package_name, "findings": findings}


def main():
    parser = argparse.ArgumentParser(description="Android Dynamic Analysis Agent")
    parser.add_argument("--device", help="ADB device ID")
    sub = parser.add_subparsers(dest="command")
    sub.add_parser("packages", help="List third-party packages")
    s = sub.add_parser("ssl", help="Check SSL pinning")
    s.add_argument("--package", required=True)
    c = sub.add_parser("components", help="Analyze exported components")
    c.add_argument("--package", required=True)
    d = sub.add_parser("storage", help="Check data storage security")
    d.add_argument("--package", required=True)
    n = sub.add_parser("network", help="Capture network traffic")
    n.add_argument("--duration", type=int, default=30)
    args = parser.parse_args()
    if args.command == "packages":
        result = list_packages(args.device)
    elif args.command == "ssl":
        result = check_ssl_pinning(args.package, args.device)
    elif args.command == "components":
        result = analyze_exported_components(args.package, args.device)
    elif args.command == "storage":
        result = check_data_storage(args.package, args.device)
    elif args.command == "network":
        result = capture_network_traffic(args.device, args.duration)
    else:
        parser.print_help()
        return
    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
