🕵️ Make Every App on Your Windows PC Anonymous Through Tor — One Click, No VPN, No Setup, Completely Free

:locked: Hide your real IP in every Windows app at once — free tool routes browsers, messengers, and games through Tor

Route any Windows app through Tor — system-wide or app-by-app, one click.
Scans the Tor Browser SOCKS5 port automatically. No config files. No manual setup.

░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

🔍 How it works

Install Tor Browser normally. Run this tool. It handles the rest:

1 — Port scan
Finds Tor Browser’s active SOCKS5 port by scanning the process PID. If that fails, brute-forces ports 9000–9500 with an actual SOCKS5 handshake (\x05\x01\x00) to verify — not just a TCP ping.

2 — System-wide mode
Writes socks5=127.0.0.1:PORT directly into the Windows Registry (HKCU\...\Internet Settings), then calls InternetSetOptionW to refresh WinInet instantly — no restart required. One click to enable, one click to revert.

3 — Per-app mode (Ctrl+click or Shift+click to select multiple)

  • Chrome / Brave / Chromium → relaunches with --proxy-server=socks5://127.0.0.1:PORT and opens check.torproject.org to confirm
  • Firefox → creates an isolated temp profile with -no-remote, routes via env proxy vars
  • Everything else (Telegram, Discord, etc.) → use system-wide mode, then open the app

:warning: Known limit: Chrome/Telegram per-app routing can fail on some setups — enable system-wide mode before launching those if that happens. Will be fixed in a future update. Exit country switching is also coming.

For Telegram, Discord, games: these apps bypass WinInet entirely. The packet-level fix is Netch — it routes by PID using WinDivert at the kernel packet layer, no system-wide proxy needed.

:warning: Warning: Don’t enable system-wide mode on a machine with sensitive data — some apps bypass SOCKS or leak DNS. Use it on a clean/throwaway session.

💻 The code
import sys
import os
import subprocess
import socket
import threading
import time
import re
import psutil
import winreg
import ctypes
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import QFont


class TorPortScanner:
    def __init__(self):
        self.found_ports = []
        self.active_port = None

    def find_tor_process_ports(self):
        ports = []
        try:
            tor_pids = []
            for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
                try:
                    name = proc.info['name'].lower() if proc.info['name'] else ''
                    cmdline = ' '.join(proc.info['cmdline'] or []).lower()
                    if ('tor' in name or 'firefox' in name) and ('tor' in cmdline or 'browser' in cmdline or 'firefox' in name.lower()):
                        tor_pids.append(proc.info['pid'])
                        print(f"[DEBUG] Found Tor process: PID={proc.info['pid']}, name={name}")
                except:
                    continue
            if not tor_pids:
                for proc in psutil.process_iter(['pid', 'name']):
                    try:
                        tor_pids.append(proc.info['pid'])
                    except:
                        continue
            for pid in tor_pids:
                if sys.platform == "win32":
                    cmd = f"netstat -ano | findstr {pid}"
                    try:
                        output = subprocess.check_output(cmd, shell=True, text=True)
                        for line in output.splitlines():
                            if "LISTENING" in line or "LISTEN" in line:
                                match = re.search(r":(\d{4,5})", line)
                                if match:
                                    port = int(match.group(1))
                                    if 9000 <= port <= 9500:
                                        ports.append(port)
                                        print(f"[DEBUG] Found port {port} via PID {pid}")
                    except:
                        pass
        except Exception as e:
            print(f"[DEBUG] Process search error: {e}")
        return list(set(ports))

    def brute_force_scan(self):
        found = []
        print("[DEBUG] Starting brute force scan...")
        for port in range(9000, 9501):
            try:
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.settimeout(0.1)
                result = sock.connect_ex(('127.0.0.1', port))
                if result == 0:
                    try:
                        sock.send(b'\x05\x01\x00')
                        data = sock.recv(2)
                        if data and data[0] == 0x05:
                            found.append(port)
                            print(f"[DEBUG] Found SOCKS5 port: {port}")
                    except:
                        pass
                sock.close()
            except:
                continue
        return found

    def test_socks_proxy(self, port, timeout=3):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(timeout)
            result = sock.connect_ex(('127.0.0.1', port))
            if result == 0:
                sock.send(b'\x05\x01\x00')
                data = sock.recv(2)
                sock.close()
                if data and data[0] == 0x05:
                    return True
            sock.close()
        except Exception as e:
            print(f"[DEBUG] Error checking port {port}: {e}")
        return False

    def full_scan(self, progress_callback=None):
        self.found_ports = []
        if progress_callback:
            progress_callback("Searching for Tor process...")
        ports_from_process = self.find_tor_process_ports()
        if ports_from_process:
            self.found_ports.extend(ports_from_process)
            if progress_callback:
                progress_callback(f"Found via process: {ports_from_process}")
        if not self.found_ports:
            if progress_callback:
                progress_callback("Extended port scanning (9000-9500)...")
            ports_from_bruteforce = self.brute_force_scan()
            if ports_from_bruteforce:
                self.found_ports.extend(ports_from_bruteforce)
                if progress_callback:
                    progress_callback(f"Found via brute force: {ports_from_bruteforce}")
        self.found_ports = list(set(self.found_ports))
        valid_ports = []
        if self.found_ports:
            if progress_callback:
                progress_callback(f"Checking {len(self.found_ports)} ports for SOCKS5...")
            for i, p in enumerate(self.found_ports):
                if progress_callback:
                    progress_callback(f"Checking port {p} ({i+1}/{len(self.found_ports)})...")
                if self.test_socks_proxy(p):
                    valid_ports.append(p)
                    if progress_callback:
                        progress_callback(f"Port {p} ✓ WORKING (SOCKS5)")
                else:
                    if progress_callback:
                        progress_callback(f"Port {p} ✗ Not responding to SOCKS5")
        self.found_ports = valid_ports
        return self.found_ports


class TorStarter(QThread):
    log_signal = pyqtSignal(str)
    finished_signal = pyqtSignal(bool)

    def __init__(self, scanner=None):
        super().__init__()
        self.scanner = scanner

    def run(self):
        paths = []
        if sys.platform == "win32":
            user = os.getlogin()
            paths = [
                r"C:\Program Files\Tor Browser\Browser\firefox.exe",
                rf"C:\Users\{user}\Desktop\Tor Browser\Browser\firefox.exe",
                rf"C:\Users\{user}\Downloads\Tor Browser\Browser\firefox.exe"
            ]
        elif sys.platform == "darwin":
            paths = ["/Applications/Tor Browser.app/Contents/MacOS/firefox"]
        else:
            paths = ["/usr/bin/tor", "/usr/local/bin/tor"]
        for path in paths:
            if os.path.exists(path):
                try:
                    self.log_signal.emit(f"Starting Tor: {path}")
                    if sys.platform == "win32":
                        subprocess.Popen([path], shell=True)
                    else:
                        subprocess.Popen([path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
                    self.log_signal.emit("Waiting for Tor to start (up to 30 seconds)...")
                    for i in range(30):
                        time.sleep(1)
                        self.log_signal.emit(f"Waiting {i+1}/30 sec...")
                        if self.scanner:
                            ports = self.scanner.full_scan()
                            if ports:
                                self.log_signal.emit(f"Tor started! Found ports: {ports}")
                                self.finished_signal.emit(True)
                                return
                    self.log_signal.emit("Tor started, but ports not found. Try manual scan.")
                    self.finished_signal.emit(True)
                    return
                except Exception as e:
                    self.log_signal.emit(f"Start error: {e}")
                    continue
        self.log_signal.emit("Tor Browser not found. Please install it.")
        self.finished_signal.emit(False)


class ProcessUpdater(QThread):
    processes_signal = pyqtSignal(list)

    def run(self):
        while True:
            processes = []
            try:
                for proc in psutil.process_iter(['pid', 'name', 'exe']):
                    try:
                        name = proc.info['name'] or "Unknown"
                        exe = proc.info['exe'] or ""
                        if exe and not exe.lower().endswith('.exe'):
                            continue
                        if name.lower() in ['system', 'idle']:
                            continue
                        processes.append({'pid': proc.info['pid'], 'name': name, 'exe': exe})
                    except:
                        continue
                processes.sort(key=lambda x: x['name'].lower())
                self.processes_signal.emit(processes)
            except:
                pass
            time.sleep(5)


class TorAppSelector(QMainWindow):
    def __init__(self):
        super().__init__()
        self.scanner = TorPortScanner()
        self.selected_port = None
        self.processes = []
        self.selected_processes = []
        self.system_proxy_active = False
        self.scanning_in_progress = False
        self.updating_processes = False
        self.initUI()
        self.update_status("Waiting...")
        self.process_updater = ProcessUpdater()
        self.process_updater.processes_signal.connect(self.update_process_list)
        self.process_updater.start()

    def initUI(self):
        self.setWindowTitle("Tor App Selector v6.3")
        self.setGeometry(200, 200, 900, 700)
        self.setStyleSheet("""
            QMainWindow { background-color: #1e1e2e; }
            QLabel { color: #cdd6f4; font-size: 13px; font-weight: bold; }
            QPushButton { 
                background-color: #313244; color: #cdd6f4; 
                border: 1px solid #45475a; padding: 8px 15px; 
                border-radius: 6px; font-weight: bold;
            }
            QPushButton:hover { background-color: #45475a; }
            QPushButton#launch_btn {
                background-color: #a6e3a1; color: #1e1e2e; 
                font-size: 14px; padding: 12px;
            }
            QPushButton#launch_btn:hover { background-color: #89d48a; }
            QPushButton#proxy_btn_on {
                background-color: #f9e2af; color: #1e1e2e;
            }
            QPushButton#proxy_btn_on:hover { background-color: #f2d08a; }
            QPushButton#proxy_btn_off {
                background-color: #f38ba8; color: #1e1e2e;
            }
            QPushButton#proxy_btn_off:hover { background-color: #e06b87; }
            QListWidget { 
                background-color: #181825; color: #cdd6f4; 
                border: 1px solid #45475a; border-radius: 4px;
                font-family: 'Courier New'; font-size: 12px;
            }
            QListWidget::item:selected { 
                background-color: #45475a; color: #a6e3a1;
            }
            QTextEdit { 
                background-color: #11111b; color: #89b4fa; 
                border: 1px solid #45475a; border-radius: 4px;
                font-family: 'Courier New';
            }
            QStatusBar { color: #a6e3a1; }
            QProgressBar {
                background-color: #181825;
                border: 1px solid #45475a;
                border-radius: 4px;
                color: #cdd6f4;
                text-align: center;
            }
            QProgressBar::chunk {
                background-color: #89b4fa;
                border-radius: 4px;
            }
        """)
        central = QWidget()
        self.setCentralWidget(central)
        layout = QVBoxLayout(central)
        title = QLabel("Tor App Selector v6.3")
        title.setFont(QFont("Arial", 14, QFont.Bold))
        title.setStyleSheet("color: #89b4fa; padding: 10px;")
        layout.addWidget(title)
        top_layout = QHBoxLayout()
        self.scan_btn = QPushButton("Scan Tor Ports")
        self.scan_btn.clicked.connect(self.start_scan)
        self.start_tor_btn = QPushButton("Start Tor")
        self.start_tor_btn.clicked.connect(self.launch_tor_with_scan)
        self.proxy_btn = QPushButton("Enable System Proxy")
        self.proxy_btn.setObjectName("proxy_btn_off")
        self.proxy_btn.clicked.connect(self.toggle_system_proxy)
        top_layout.addWidget(self.scan_btn)
        top_layout.addWidget(self.start_tor_btn)
        top_layout.addWidget(self.proxy_btn)
        top_layout.addStretch()
        layout.addLayout(top_layout)
        self.progress_bar = QProgressBar()
        self.progress_bar.setVisible(False)
        layout.addWidget(self.progress_bar)
        info_label = QLabel("Select multiple apps: Ctrl+click or Shift+click")
        info_label.setStyleSheet("color: #a6e3a1; font-size: 11px;")
        layout.addWidget(info_label)
        layout.addWidget(QLabel("Available Tor Ports (select one):"))
        self.port_list = QListWidget()
        self.port_list.setMaximumHeight(80)
        self.port_list.itemClicked.connect(self.on_port_selected)
        layout.addWidget(self.port_list)
        layout.addWidget(QLabel("Running Applications (select one or more):"))
        self.process_list = QListWidget()
        self.process_list.setSelectionMode(QListWidget.ExtendedSelection)
        self.process_list.setMaximumHeight(350)
        self.process_list.itemClicked.connect(self.on_process_selected)
        layout.addWidget(self.process_list)
        self.launch_btn = QPushButton("LAUNCH SELECTED APPS VIA TOR")
        self.launch_btn.setObjectName("launch_btn")
        self.launch_btn.clicked.connect(self.launch_selected_apps)
        layout.addWidget(self.launch_btn)
        layout.addWidget(QLabel("Log:"))
        self.log_area = QTextEdit()
        self.log_area.setMaximumHeight(120)
        self.log_area.setReadOnly(True)
        layout.addWidget(self.log_area)
        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)

    def update_status(self, text):
        self.statusBar.showMessage(text)

    def log(self, text):
        self.log_area.append(f"[{time.strftime('%H:%M:%S')}] {text}")

    def update_process_list(self, processes):
        if self.updating_processes:
            return
        self.updating_processes = True
        selected_pids = []
        for item in self.process_list.selectedItems():
            pid_match = re.search(r"PID:\s*(\d+)", item.text())
            if pid_match:
                selected_pids.append(int(pid_match.group(1)))
        self.process_list.clear()
        self.processes = processes
        unique_names = {}
        for proc in processes:
            name = proc['name']
            exe = proc['exe']
            pid = proc['pid']
            skip_names = ['svchost.exe', 'csrss.exe', 'wininit.exe', 'lsass.exe',
                         'winlogon.exe', 'services.exe', 'smss.exe', 'spoolsv.exe',
                         'conhost.exe', 'dwm.exe', 'taskhostw.exe', 'explorer.exe',
                         'python.exe', 'pythonw.exe', 'cmd.exe', 'powershell.exe']
            if name.lower() in skip_names:
                continue
            if name in unique_names:
                unique_names[name] += 1
                display_name = f"{name} (PID: {pid})"
            else:
                unique_names[name] = 1
                display_name = f"{name} (PID: {pid})"
            if 'chrome' in name.lower() or 'firefox' in name.lower() or 'brave' in name.lower():
                display_name = "[Browser] " + display_name
            elif 'telegram' in name.lower():
                display_name = "[Messenger] " + display_name
            elif 'discord' in name.lower():
                display_name = "[Game] " + display_name
            self.process_list.addItem(display_name)
        for i in range(self.process_list.count()):
            item = self.process_list.item(i)
            pid_match = re.search(r"PID:\s*(\d+)", item.text())
            if pid_match:
                pid = int(pid_match.group(1))
                if pid in selected_pids:
                    item.setSelected(True)
        self.updating_processes = False

    def on_process_selected(self, item):
        self.selected_processes = []
        for i in range(self.process_list.count()):
            if self.process_list.item(i).isSelected():
                item_text = self.process_list.item(i).text()
                pid_match = re.search(r"PID:\s*(\d+)", item_text)
                if pid_match:
                    pid = int(pid_match.group(1))
                    for proc in self.processes:
                        if proc['pid'] == pid:
                            self.selected_processes.append(proc)
                            break
        if self.selected_processes:
            names = ", ".join([p['name'] for p in self.selected_processes])
            self.log(f"Selected apps: {len(self.selected_processes)} ({names})")

    def start_scan(self):
        if self.scanning_in_progress:
            return
        self.log("Starting Tor port scan...")
        self.port_list.clear()
        self.update_status("Scanning...")
        self.scanning_in_progress = True
        self.progress_bar.setVisible(True)
        self.progress_bar.setRange(0, 0)
        self.scan_btn.setEnabled(False)

        def update_progress(text):
            self.log(text)
            QApplication.processEvents()

        def scan_thread():
            try:
                ports = self.scanner.full_scan(progress_callback=update_progress)
                self.update_ports_signal.emit(ports)
            except Exception as e:
                self.log(f"Scan error: {e}")
                self.update_ports_signal.emit([])

        class UpdateSignal(QObject):
            update_signal = pyqtSignal(list)

        self.update_signal = UpdateSignal()
        self.update_signal.update_signal.connect(self.update_port_list)
        self.update_ports_signal = self.update_signal.update_signal
        threading.Thread(target=scan_thread, daemon=True).start()

    def update_port_list(self, ports):
        self.port_list.clear()
        if ports:
            for p in ports:
                self.port_list.addItem(f"Port {p} (SOCKS5) ✓")
            self.log(f"✅ Active ports found: {len(ports)}")
            self.update_status(f"✅ Found: {len(ports)} ports")
            self.selected_port = ports[0]
            self.port_list.setCurrentRow(0)
            self.log(f"Auto-selected port: {self.selected_port}")
        else:
            self.port_list.addItem("No active ports found")
            self.log("❌ Ports not found. Start Tor Browser and try again.")
            self.update_status("❌ No ports")
            self.selected_port = None
        self.scanning_in_progress = False
        self.progress_bar.setVisible(False)
        self.scan_btn.setEnabled(True)

    def launch_tor_with_scan(self):
        self.log("Starting Tor Browser...")
        self.scan_btn.setEnabled(False)
        self.start_tor_btn.setEnabled(False)
        self.tor_thread = TorStarter(self.scanner)
        self.tor_thread.log_signal.connect(self.log)
        self.tor_thread.finished_signal.connect(self.on_tor_started)
        self.tor_thread.start()
        self.update_status("Starting Tor...")

    def on_tor_started(self, success):
        self.scan_btn.setEnabled(True)
        self.start_tor_btn.setEnabled(True)
        if success:
            self.log("Tor Browser started, running scan...")
            time.sleep(2)
            self.start_scan()
        else:
            self.log("❌ Failed to start Tor Browser. Check installation.")
            self.update_status("❌ Tor not started")

    def on_port_selected(self, item):
        match = re.search(r"\d+", item.text())
        if match:
            self.selected_port = int(match.group())
            self.log(f"Selected port: {self.selected_port}")

    def toggle_system_proxy(self):
        if not self.selected_port:
            self.log("❌ Select a Tor port first!")
            QMessageBox.warning(self, "Error", "Select a Tor port!")
            return
        if self.system_proxy_active:
            self.disable_system_proxy()
        else:
            self.enable_system_proxy()

    def enable_system_proxy(self):
        try:
            key = winreg.OpenKey(
                winreg.HKEY_CURRENT_USER,
                r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
                0, winreg.KEY_SET_VALUE
            )
            winreg.SetValueEx(key, "ProxyEnable", 0, winreg.REG_DWORD, 1)
            winreg.SetValueEx(key, "ProxyServer", 0, winreg.REG_SZ, f"socks5=127.0.0.1:{self.selected_port}")
            winreg.SetValueEx(key, "ProxyOverride", 0, winreg.REG_SZ, "localhost;127.0.0.1;::1")
            winreg.CloseKey(key)
            ctypes.windll.wininet.InternetSetOptionW(0, 39, 0, 0)
            ctypes.windll.wininet.InternetSetOptionW(0, 37, 0, 0)
            self.system_proxy_active = True
            self.proxy_btn.setText(f"Disable System Proxy (port {self.selected_port})")
            self.proxy_btn.setObjectName("proxy_btn_on")
            self.proxy_btn.setStyleSheet(self.styleSheet())
            self.log(f"✅ SYSTEM PROXY ENABLED! All traffic via Tor (port {self.selected_port})")
            self.update_status(f"System proxy active (port {self.selected_port})")
            QMessageBox.information(
                self, "System Proxy Enabled!",
                f"ALL applications now go through Tor (port {self.selected_port})\n\n"
                "WARNING:\n- Internet speed will decrease\n- Some apps may not work\n"
                "- Click 'Disable System Proxy' to revert"
            )
        except Exception as e:
            self.log(f"❌ Error enabling proxy: {e}")

    def disable_system_proxy(self):
        try:
            key = winreg.OpenKey(
                winreg.HKEY_CURRENT_USER,
                r"Software\Microsoft\Windows\CurrentVersion\Internet Settings",
                0, winreg.KEY_SET_VALUE
            )
            winreg.SetValueEx(key, "ProxyEnable", 0, winreg.REG_DWORD, 0)
            winreg.CloseKey(key)
            ctypes.windll.wininet.InternetSetOptionW(0, 39, 0, 0)
            ctypes.windll.wininet.InternetSetOptionW(0, 37, 0, 0)
            self.system_proxy_active = False
            self.proxy_btn.setText("Enable System Proxy")
            self.proxy_btn.setObjectName("proxy_btn_off")
            self.proxy_btn.setStyleSheet(self.styleSheet())
            self.log("✅ SYSTEM PROXY DISABLED")
            self.update_status("System proxy disabled")
        except Exception as e:
            self.log(f"❌ Error disabling proxy: {e}")

    def launch_selected_apps(self):
        if not self.selected_port:
            self.log("❌ Select a port from the list first!")
            QMessageBox.warning(self, "Error", "Select a Tor port!")
            return
        if not self.selected_processes:
            self.log("❌ Select at least one application from the list!")
            QMessageBox.warning(self, "Error", "Select application(s) from the list!")
            return
        reply = QMessageBox.question(
            self, "Close Old Processes?",
            f"Found {len(self.selected_processes)} application(s).\n\n"
            "Close their current instances before launching via Tor?",
            QMessageBox.Yes | QMessageBox.No
        )
        close_old = (reply == QMessageBox.Yes)
        launched = 0
        errors = []
        for proc_data in self.selected_processes:
            app_name = proc_data['name']
            app_path = proc_data['exe']
            pid = proc_data['pid']
            if not app_path or not os.path.exists(app_path):
                errors.append(f"{app_name}: file not found")
                continue
            self.log(f"Launching {app_name} via Tor (port {self.selected_port})...")
            if close_old:
                try:
                    proc = psutil.Process(pid)
                    proc.terminate()
                    proc.wait(timeout=3)
                    self.log(f"Closed old process {app_name} (PID: {pid})")
                except:
                    pass
            cmd = [app_path]
            env = os.environ.copy()
            if any(x in app_path.lower() for x in ['chrome', 'brave', 'chromium']):
                cmd.extend([
                    f"--proxy-server=socks5://127.0.0.1:{self.selected_port}",
                    "--host-resolver-rules=MAP * ~NOTFOUND , EXCLUDE localhost",
                    "--new-window",
                    "https://check.torproject.org"
                ])
            elif 'firefox' in app_path.lower():
                profile_dir = os.path.join(os.environ['TEMP'], f"tor_profile_{int(time.time())}_{app_name}")
                cmd.extend([
                    "-CreateProfile", f"TorProfile_{app_name} {profile_dir}",
                    "-P", f"TorProfile_{app_name}",
                    "-no-remote", "-new-window",
                    "https://check.torproject.org"
                ])
                env['http_proxy'] = f'socks5://127.0.0.1:{self.selected_port}'
                env['https_proxy'] = f'socks5://127.0.0.1:{self.selected_port}'
            else:
                self.log(f"   {app_name} is not a browser. Use system proxy for it.")
            try:
                if 'firefox' in app_path.lower():
                    subprocess.Popen(cmd, env=env, shell=False)
                else:
                    subprocess.Popen(cmd, shell=False)
                launched += 1
                self.log(f"✅ {app_name} launched")
            except Exception as e:
                errors.append(f"{app_name}: {e}")
        if launched > 0:
            self.update_status(f"Launched apps: {launched} via Tor")
            self.log(f"✅ Successfully launched {launched} of {len(self.selected_processes)} apps")
        if errors:
            for err in errors:
                self.log(f"❌ {err}")
            QMessageBox.warning(self, "Partial Success",
                f"Launched: {launched}\nErrors: {len(errors)}\n\n" + "\n".join(errors[:5]))
        elif launched > 0:
            QMessageBox.information(self, "Success!",
                f"✅ All {launched} app(s) launched via Tor (port {self.selected_port})")


if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.setStyle('Fusion')
    window = TorAppSelector()
    window.show()
    sys.exit(app.exec_())
📦 Requirements + how to run

Install Python deps:

pip install psutil PyQt5

Also required: Tor Browser installed (the standard free version — that’s all)

Lighter alternative: don’t want the browser running in the background? Use the Tor Expert Bundle — just tor.exe, no GUI. Pair with NSSM to install it as a Windows service that starts on boot.

Run:

python tor_app_selector.py

Steps:

  1. Open Tor Browser and let it connect to the network
  2. Run the script → click Scan Tor Ports (auto-finds port 9150)
  3. Choose your mode:
    • System-wide: click Enable System Proxy → all traffic routes through Tor
    • Per-app: Ctrl+click apps in the list → Launch Selected Apps via Tor
  4. Verify at check.torproject.org :onion:
✅ Proof it works


check.torproject.org confirms the connection — and doesn’t expose that a proxy is in use.


Speed through Tor is surprisingly usable. Not the crawl most people expect.

🔗 Go deeper — fixes, tools & the full rabbit hole

:white_check_mark: Verify your connection first
Before trusting any proxy setup — check for leaks:

  • DNS Leak Test — are your DNS queries bypassing Tor?
  • IPLeak.net — IP address, DNS, and WebRTC in one test
  • BrowserLeaks WebRTC — Chrome/Firefox can leak your real IP via WebRTC even through SOCKS5
  • BrowserLeaks DNS — resolves 50 random domains to show which DNS server actually answers

· · · · · · · · · · · · · · · ·

:video_game: Route games, Telegram, Discord (they ignore WinInet)
These apps use raw TCP/UDP that bypasses the Windows system proxy entirely. Two solutions:

  • Netch / releases — per-process proxy using WinDivert; routes any EXE through SOCKS5 at the packet level, no system-wide setting
  • tun2socks — creates a virtual TUN adapter and routes ALL traffic through Tor’s SOCKS5; apps can’t bypass it
  • proxychains-windows — Winsock hook approach; injects into the target process and redirects connect() calls; no kernel driver needed
  • ProxiFyre / releases — per-app SOCKS5 proxifier using NDISAPI; routes TCP + UDP

· · · · · · · · · · · · · · · ·

:gear: Run Tor without the browser (lighter setup)

· · · · · · · · · · · · · · · ·

:globe_showing_europe_africa: Add exit country switching right now (don’t wait for v7)
The exit country feature is already built into Tor — you just need Python’s stem library to control it:

· · · · · · · · · · · · · · · ·

:microscope: The tech inside this tool

  • basil00/Tallow — transparent Tor for Windows via WinDivert; the packet-level reference implementation of what this tool does at the registry level
  • basil00/WinDivert — the kernel driver powering Netch, Tallow, GoodbyeDPI, and most Windows transparent proxifiers
  • ValdikSS/GoodbyeDPI — shows WinDivert NETWORK_LAYER per-packet filtering and manipulation patterns
  • WinDivert API docs — full programming reference for WinDivertOpen, filters, WINDIVERT_LAYER

· · · · · · · · · · · · · · · ·

:dashing_away: Heavier alternatives (if you need everything routed, reliably)

  • sing-box / releases — universal proxy platform; TUN inbound + SOCKS5 outbound; routes games, Discord, everything
  • Clash Verge Rev / releases — GUI for mihomo; TUN mode toggle; Windows installer; point SOCKS5 outbound at port 9150
  • sing-box TUN docsauto_route, strict_route, stack options

· · · · · · · · · · · · · · · ·

:onion: Bypass DPI — when Tor itself is blocked
Standard Tor traffic gets fingerprinted and killed by corporate/ISP DPI. Pluggable transports make it look like HTTPS:

  • Expert Bundle dist index — the bundle already includes lyrebird.exe (obfs4), snowflake.exe, meek_client.exe as standalone binaries
  • obfs4 source repo — obfuscated Tor transport; add ClientTransportPlugin obfs4 exec /path/lyrebird.exe to torrc
  • Snowflake — WebRTC-based Tor bridge; bypasses DPI that targets Tor’s TLS fingerprint
  • Pluggable Transports spec — the IPC protocol any PT binary implements

· · · · · · · · · · · · · · · ·

:globe_with_meridians: Other anonymity networks — same SOCKS5 interface, different network
This tool’s SOCKS5 port scan works with any anonymity daemon, not just Tor:

  • i2pd / releases — C++ I2P router; SOCKS5 proxy on port 4447, HTTP proxy on 4444; runs as Windows service
  • geti2p.net — Java I2P router; Windows installer with built-in browser config
  • Lokinet / releases — creates a virtual network adapter on Windows; onion-routed at the IP layer
  • Yggdrasil — end-to-end encrypted mesh network; Windows MSI installer

░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░

Tor was already running on your machine. This just makes everything else use it.

you don’t need nssm to install the tor service bundle as a service
you can just run tor.exe --service install