diff --git a/gui.py b/gui.py new file mode 100644 index 0000000..3ebcfc8 --- /dev/null +++ b/gui.py @@ -0,0 +1,243 @@ +import os +import sys +import json +import ctypes +import threading +import subprocess +import customtkinter as ctk +import tkinter.messagebox as messagebox + +def is_admin(): + try: + return ctypes.windll.shell32.IsUserAnAdmin() + except: + return False + +if not is_admin(): + exe = sys.executable.replace("python.exe", "pythonw.exe") if "python.exe" in sys.executable else sys.executable + script_path = f'"{os.path.abspath(__file__)}"' + ctypes.windll.shell32.ShellExecuteW(None, "runas", exe, script_path, None, 1) + sys.exit() + +ctk.set_appearance_mode("Dark") +ctk.set_default_color_theme("blue") + +CONFIG_FILE = "config.json" +DEFAULT_CONFIG = { + "LISTEN_HOST": "0.0.0.0", + "LISTEN_PORT": 40443, + "CONNECT_IP": "188.114.98.0", + "CONNECT_PORT": 443, + "FAKE_SNI": "auth.vercel.com" +} + +class SNISpooferGUI(ctk.CTk): + def __init__(self): + super().__init__() + + self.title("SNI-Spoofing Controller") + self.geometry("600x700") + self.minsize(500, 600) + + self.process = None + self.is_running = False + + self.frame_top = ctk.CTkFrame(self, fg_color="transparent") + self.frame_top.pack(pady=20, padx=20, fill="x") + + self.btn_toggle = ctk.CTkButton( + self.frame_top, + text="START SPOOFING", + font=("Arial", 20, "bold"), + height=60, + fg_color="#1f6aa5", + hover_color="#144870", + command=self.toggle_process + ) + self.btn_toggle.pack(fill="x") + + self.frame_config = ctk.CTkFrame(self) + self.frame_config.pack(pady=10, padx=20, fill="x") + + self.entries = {} + row = 0 + for key in DEFAULT_CONFIG.keys(): + lbl = ctk.CTkLabel(self.frame_config, text=key + ":", font=("Arial", 12, "bold")) + lbl.grid(row=row, column=0, padx=10, pady=5, sticky="w") + + ent = ctk.CTkEntry(self.frame_config, width=250) + ent.grid(row=row, column=1, padx=10, pady=5, sticky="ew") + self.entries[key] = ent + row += 1 + + self.frame_config.columnconfigure(1, weight=1) + + self.frame_config_btns = ctk.CTkFrame(self, fg_color="transparent") + self.frame_config_btns.pack(pady=5, padx=20, fill="x") + + self.btn_save = ctk.CTkButton(self.frame_config_btns, text="Save", command=self.save_config, width=100) + self.btn_save.pack(side="left", padx=5) + + self.btn_clear = ctk.CTkButton(self.frame_config_btns, text="Clear", command=self.clear_config, width=100, fg_color="#8B0000", hover_color="#5C0000") + self.btn_clear.pack(side="left", padx=5) + + self.btn_import = ctk.CTkButton(self.frame_config_btns, text="Import JSON", command=self.import_clipboard) + self.btn_import.pack(side="right", padx=5) + + self.btn_export = ctk.CTkButton(self.frame_config_btns, text="Export JSON", command=self.export_clipboard) + self.btn_export.pack(side="right", padx=5) + + self.frame_log = ctk.CTkFrame(self) + self.frame_log.pack(pady=10, padx=20, fill="both", expand=True) + + lbl_log = ctk.CTkLabel(self.frame_log, text="Logs Output:", font=("Arial", 12, "bold")) + lbl_log.pack(anchor="w", padx=10, pady=(10,0)) + + self.textbox_log = ctk.CTkTextbox(self.frame_log, state="disabled", font=("Consolas", 12)) + self.textbox_log.pack(padx=10, pady=10, fill="both", expand=True) + + self.load_config() + + def load_config(self): + config_data = DEFAULT_CONFIG.copy() + if os.path.exists(CONFIG_FILE): + try: + with open(CONFIG_FILE, "r") as f: + data = json.load(f) + config_data.update(data) + except Exception as e: + self.log(f"[!] Error loading config: {e}") + + self.clear_config() + for key, value in config_data.items(): + if key in self.entries: + self.entries[key].insert(0, str(value)) + + def save_config(self): + new_config = {} + for key, ent in self.entries.items(): + val = ent.get().strip() + if "PORT" in key: + val = int(val) if val.isdigit() else val + new_config[key] = val + + try: + with open(CONFIG_FILE, "w") as f: + json.dump(new_config, f, indent=2) + self.log("[+] Config saved successfully.") + except Exception as e: + self.log(f"[!] Error saving config: {e}") + + def clear_config(self): + for ent in self.entries.values(): + ent.delete(0, "end") + + def import_clipboard(self): + try: + clipboard_data = self.clipboard_get() + data = json.loads(clipboard_data) + self.clear_config() + for key, value in data.items(): + if key in self.entries: + self.entries[key].insert(0, str(value)) + self.log("[+] Config imported from clipboard.") + except Exception as e: + messagebox.showerror("Import Error", f"Invalid JSON format in clipboard.\n{e}") + + def export_clipboard(self): + new_config = {} + for key, ent in self.entries.items(): + val = ent.get().strip() + if "PORT" in key: + val = int(val) if val.isdigit() else val + new_config[key] = val + + self.clipboard_clear() + self.clipboard_append(json.dumps(new_config, indent=2)) + self.log("[+] Config exported to clipboard.") + + def log(self, message): + self.textbox_log.configure(state="normal") + self.textbox_log.insert("end", message + "\n") + self.textbox_log.see("end") + self.textbox_log.configure(state="disabled") + + def toggle_process(self): + if not self.is_running: + self.start_process() + else: + self.stop_process() + + def start_process(self): + self.save_config() + self.textbox_log.configure(state="normal") + self.textbox_log.delete("1.0", "end") + self.textbox_log.configure(state="disabled") + + self.log("[*] Starting main.py...") + + custom_env = os.environ.copy() + custom_env["PYTHONIOENCODING"] = "utf-8" + custom_env["PYTHONUTF8"] = "1" + + startup_info = None + if os.name == 'nt': + startup_info = subprocess.STARTUPINFO() + startup_info.dwFlags |= subprocess.STARTF_USESHOWWINDOW + startup_info.wShowWindow = 0 + + try: + python_exe = sys.executable.replace("python.exe", "pythonw.exe") if "python.exe" in sys.executable else sys.executable + + self.process = subprocess.Popen( + [python_exe, "main.py"], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + encoding="utf-8", + errors="replace", + bufsize=1, + creationflags=subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0, + startupinfo=startup_info, + env=custom_env + ) + + self.is_running = True + self.btn_toggle.configure(text="STOP SPOOFING", fg_color="#8B0000", hover_color="#5C0000") + + threading.Thread(target=self.read_process_output, daemon=True).start() + + except Exception as e: + self.log(f"[!] Failed to start process: {e}") + + def stop_process(self): + if self.process: + self.log("[*] Stopping process...") + self.process.terminate() + self.process = None + + self.is_running = False + self.btn_toggle.configure(text="START SPOOFING", fg_color="#1f6aa5", hover_color="#144870") + self.log("[+] Process stopped.") + + def read_process_output(self): + if self.process: + try: + for line in iter(self.process.stdout.readline, ''): + if line: + self.after(0, self.log, line.strip()) + except Exception as e: + self.after(0, self.log, f"[!] Log reading error: {e}") + finally: + if self.process and self.process.stdout: + self.process.stdout.close() + if self.is_running: + self.after(0, self.stop_process) + + def destroy(self): + self.stop_process() + super().destroy() + +if __name__ == "__main__": + app = SNISpooferGUI() + app.mainloop() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index dc01971..47c3308 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ -pydivert>=3.1.0 \ No newline at end of file +pydivert>=3.1.0 +customtkinter>=5.2.2 \ No newline at end of file