Fixed scan crash: all tkinter widget access now happens on main thread (Tcl NOT thread-safe). _log() routes GUI updates via root.after(). _scan_worker reads all tkinter vars on main thread. Removed unused _spray_queue.

This commit is contained in:
drjones
2026-05-06 12:35:35 -07:00
parent 399e36f848
commit 7068dbd12d
7 changed files with 242 additions and 104 deletions

64
gui.py
View File

@@ -94,7 +94,6 @@ class FastRDPGUI:
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
# Proxy manager
self.proxy_manager = ProxyManager()
@@ -758,11 +757,8 @@ class FastRDPGUI:
# ── 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
# Schedule GUI update on main thread (tkinter is NOT thread-safe)
self.root.after(0, lambda m=msg, t=tag, ts=ts: self._log_gui(m, t, ts))
tag_colors = {
"hit": "\033[92m",
"info": "\033[94m",
@@ -777,6 +773,14 @@ class FastRDPGUI:
except OSError:
pass
def _log_gui(self, msg: str, tag: str, ts: str):
"""Thread-safe GUI log append (runs on main thread via after())."""
try:
self.log_text.insert("end", f"[{ts}] {msg}\n", tag)
self.log_text.see("end")
except tk.TclError:
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."""
@@ -979,7 +983,22 @@ class FastRDPGUI:
print(_c("\033[96m", " \u2502 ") + _c("\033[91m", "REAPER v2.0 :: RDP Reaper :: No Mercy") + _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)
# Read tkinter vars on MAIN thread (Tcl NOT thread-safe)
scan_speed = self.speed_var.get()
scan_timeout = self.timeout_var.get()
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()
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 ""
thread = threading.Thread(
target=self._scan_worker,
args=(scan_speed, scan_timeout, scan_ports, scan_randomize, spray_enabled,
ranges_path, users_file, pass_file),
daemon=True
)
thread.start()
def _stop_scan(self):
@@ -997,19 +1016,25 @@ class FastRDPGUI:
self.log_text.delete("1.0", "end")
self.stats = ScanStats()
def _load_wordlists(self):
def _load_wordlists(self, users_path="", pass_path=""):
"""Load usernames and passwords from files (or use defaults)."""
usernames = TOP_USERNAMES
passwords = TOP50_PASSWORDS
uf = self.users_entry.get().strip()
if users_path:
uf = users_path
else:
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 pass_path:
pf = pass_path
else:
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()]
@@ -1018,14 +1043,16 @@ class FastRDPGUI:
return usernames, passwords
def _scan_worker(self):
"""Background thread: scan + spray run CONCURRENTLY."""
def _scan_worker(self, speed, timeout, active_ports, randomize, spray_enabled,
ranges_path="", users_path="", pass_path=""):
"""Background thread: scan + spray run CONCURRENTLY.
All tkinter values are passed from main thread (Tcl NOT thread-safe).
"""
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):
if not ranges_path or not os.path.exists(ranges_path):
self._log(f"\u274c Ranges file not found: {ranges_path}", "error")
self._scan_done()
return
@@ -1040,15 +1067,12 @@ class FastRDPGUI:
self._scan_done()
return
if self.randomize_var.get():
if randomize:
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 "
@@ -1059,7 +1083,7 @@ class FastRDPGUI:
print(_c("\033[90m", " [+] " + "\u2500" * 55))
# Load wordlists now (in the background thread, before async loop)
usernames, passwords = self._load_wordlists()
usernames, passwords = self._load_wordlists(users_path, pass_path)
self._log(f"\U0001f4cb Loaded {len(usernames)} username(s) x {len(passwords)} password(s)", "system")
# ── Streaming design ─────────────────────────────
@@ -1078,7 +1102,7 @@ class FastRDPGUI:
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():
if spray_enabled:
# Create an asyncio task for this host's spray
task = asyncio.create_task(
self._spray_host(loop, ip, port, usernames, passwords)