first init

This commit is contained in:
nouri_a
2026-04-15 11:11:44 +03:30
parent f41bc57b71
commit 1bad6394d1
20 changed files with 2844 additions and 0 deletions
+495
View File
@@ -0,0 +1,495 @@
"""
SNISPF - Cross-platform SNI spoofing and DPI bypass tool.
Works on Windows, macOS, and Linux without requiring kernel drivers.
On Linux with root, enables raw packet injection for the seq_id trick.
Usage:
snispf --config config.json
snispf --listen 0.0.0.0:40443 --connect 188.114.98.0:443 --sni auth.vercel.com
"""
import argparse
import asyncio
import json
import logging
import os
import platform
import signal
import sys
from pathlib import Path
# Add parent to path for direct script execution
if __name__ == "__main__":
sys.path.insert(0, str(Path(__file__).parent.parent))
from sni_spoofing import __version__
from sni_spoofing.bypass import (
BypassStrategy,
CombinedBypass,
FakeSNIBypass,
FragmentBypass,
RawInjector,
is_raw_available,
)
from sni_spoofing.forwarder import start_server
from sni_spoofing.utils import (
check_platform_capabilities,
get_default_interface_ipv4,
is_valid_ip,
is_valid_port,
resolve_host,
)
# ─── Banner ──────────────────────────────────────────────────────────────────
BANNER = r"""
███████╗███╗ ██╗██╗███████╗██████╗ ███████╗
██╔════╝████╗ ██║██║██╔════╝██╔══██╗██╔════╝
███████╗██╔██╗ ██║██║███████╗██████╔╝█████╗
╚════██║██║╚██╗██║██║╚════██║██╔═══╝ ██╔══╝
███████║██║ ╚████║██║███████║██║ ██║
╚══════╝╚═╝ ╚═══╝╚═╝╚══════╝╚═╝ ╚═╝
┌──────────────────────────────────────────────────────────────────┐
│ SNISPF - Cross-Platform DPI Bypass Tool │
│ SNI Spoofing + TLS Fragmentation │
│ Works on Windows / macOS / Linux │
│ https://github.com/Rainman69/SNISPF │
└──────────────────────────────────────────────────────────────────┘
"""
# ─── Logging ─────────────────────────────────────────────────────────────────
def setup_logging(verbose: bool = False, quiet: bool = False):
"""Configure logging."""
if quiet:
level = logging.WARNING
elif verbose:
level = logging.DEBUG
else:
level = logging.INFO
formatter = logging.Formatter(
"%(asctime)s%(levelname)-7s%(message)s",
datefmt="%H:%M:%S",
)
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
logger = logging.getLogger("snispf")
logger.setLevel(level)
logger.addHandler(handler)
return logger
# ─── Config ──────────────────────────────────────────────────────────────────
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",
"BYPASS_METHOD": "fragment",
"FRAGMENT_STRATEGY": "sni_split",
"FRAGMENT_DELAY": 0.1,
"USE_TTL_TRICK": False,
"FAKE_SNI_METHOD": "prefix_fake",
}
def load_config(config_path: str) -> dict:
"""Load configuration from JSON file."""
try:
with open(config_path, "r") as f:
user_config = json.load(f)
# Merge with defaults
config = DEFAULT_CONFIG.copy()
config.update(user_config)
return config
except FileNotFoundError:
print(f"Error: Config file not found: {config_path}")
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON in config file: {e}")
sys.exit(1)
def generate_config(output_path: str):
"""Generate a default configuration file."""
config = {
"LISTEN_HOST": "0.0.0.0",
"LISTEN_PORT": 40443,
"CONNECT_IP": "188.114.98.0",
"CONNECT_PORT": 443,
"FAKE_SNI": "auth.vercel.com",
"BYPASS_METHOD": "fragment",
"FRAGMENT_STRATEGY": "sni_split",
"FRAGMENT_DELAY": 0.1,
"USE_TTL_TRICK": False,
"FAKE_SNI_METHOD": "prefix_fake",
}
with open(output_path, "w") as f:
json.dump(config, f, indent=2)
print(f"Generated default config: {output_path}")
print(json.dumps(config, indent=2))
# ─── Strategy Builder ────────────────────────────────────────────────────────
def build_strategy(config: dict, raw_injector=None) -> BypassStrategy:
"""Build the appropriate bypass strategy from config.
Available methods:
- "fragment": Fragment TLS ClientHello at SNI boundary
- "fake_sni": Send fake ClientHello with spoofed SNI (needs raw sockets
for the seq_id trick; falls back to fragmentation without them)
- "combined": Both fragmentation and fake SNI (recommended)
"""
method = config.get("BYPASS_METHOD", "fragment").lower()
if method == "fragment":
return FragmentBypass(
strategy=config.get("FRAGMENT_STRATEGY", "sni_split"),
fragment_delay=config.get("FRAGMENT_DELAY", 0.1),
)
elif method == "fake_sni":
return FakeSNIBypass(
method=config.get("FAKE_SNI_METHOD", "prefix_fake"),
raw_injector=raw_injector,
)
elif method == "combined":
return CombinedBypass(
fragment_strategy=config.get("FRAGMENT_STRATEGY", "sni_split"),
use_ttl_trick=config.get("USE_TTL_TRICK", False),
fragment_delay=config.get("FRAGMENT_DELAY", 0.1),
raw_injector=raw_injector,
)
else:
print(f"Warning: Unknown bypass method '{method}', using 'fragment'")
return FragmentBypass()
# ─── CLI ─────────────────────────────────────────────────────────────────────
def parse_args():
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
prog="snispf",
description=(
"SNISPF - Cross-platform DPI bypass tool.\n\n"
"This tool forwards TCP connections while applying DPI bypass\n"
"techniques (SNI spoofing, TLS fragmentation) to circumvent\n"
"internet censorship."
),
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=(
"Examples:\n"
" %(prog)s --config config.json\n"
" %(prog)s -l 0.0.0.0:40443 -c 188.114.98.0:443 -s auth.vercel.com\n"
" %(prog)s -l :40443 -c 188.114.98.0:443 -s dl.google.com -m combined\n"
" %(prog)s --generate-config my_config.json\n"
"\nBypass Methods:\n"
" fragment - Fragment TLS ClientHello at SNI boundary (default)\n"
" fake_sni - Inject fake ClientHello (needs root for seq_id trick)\n"
" combined - Both fragmentation and fake SNI (most effective)\n"
"\nFragment Strategies (for fragment/combined methods):\n"
" sni_split - Split in middle of SNI value (default)\n"
" half - Split record in half\n"
" multi - Split into many small fragments\n"
" tls_record_frag - Use TLS-level record fragmentation\n"
"\nhttps://github.com/Rainman69/SNISPF"
),
)
# Config file
parser.add_argument(
"--config", "-C",
help="Path to JSON config file",
)
parser.add_argument(
"--generate-config",
metavar="PATH",
help="Generate a default config file and exit",
)
# Connection settings
parser.add_argument(
"--listen", "-l",
metavar="HOST:PORT",
help="Listen address (default: 0.0.0.0:40443)",
)
parser.add_argument(
"--connect", "-c",
metavar="IP:PORT",
help="Target server address (default: 188.114.98.0:443)",
)
parser.add_argument(
"--sni", "-s",
metavar="HOSTNAME",
help="Fake SNI hostname (default: auth.vercel.com)",
)
# Bypass settings
parser.add_argument(
"--method", "-m",
choices=["fragment", "fake_sni", "combined"],
help="Bypass method (default: fragment)",
)
parser.add_argument(
"--fragment-strategy",
choices=["sni_split", "half", "multi", "tls_record_frag"],
help="Fragment strategy (default: sni_split)",
)
parser.add_argument(
"--fragment-delay",
type=float,
metavar="SECONDS",
help="Delay between fragments in seconds (default: 0.1)",
)
parser.add_argument(
"--ttl-trick",
action="store_true",
help="Use IP TTL trick for fake packets (may need privileges)",
)
parser.add_argument(
"--no-raw",
action="store_true",
help="Disable raw socket injection even if available",
)
# Output settings
parser.add_argument(
"--verbose", "-v",
action="store_true",
help="Verbose output (debug logging)",
)
parser.add_argument(
"--quiet", "-q",
action="store_true",
help="Quiet output (warnings only)",
)
parser.add_argument(
"--version", "-V",
action="version",
version=f"SNISPF {__version__}",
)
parser.add_argument(
"--info",
action="store_true",
help="Show platform capabilities and exit",
)
return parser.parse_args()
def parse_host_port(addr: str, default_host: str = "0.0.0.0", default_port: int = 443) -> tuple:
"""Parse HOST:PORT string."""
if not addr:
return default_host, default_port
if addr.startswith(":"):
return default_host, int(addr[1:])
parts = addr.rsplit(":", 1)
if len(parts) == 2:
host = parts[0] or default_host
port = int(parts[1])
return host, port
else:
return parts[0], default_port
def show_platform_info():
"""Display platform capability information."""
caps = check_platform_capabilities()
# Also check raw injection availability
caps["raw_injection"] = is_raw_available()
print("\n╔══════════════════════════════════════════╗")
print("║ Platform Capabilities ║")
print("╠══════════════════════════════════════════╣")
for key, value in caps.items():
status = "" if value is True else ("" if value is False else str(value))
print(f"{key:<28} {status:>8}")
print("╚══════════════════════════════════════════╝")
print("\nRecommended bypass methods for your platform:")
if caps["raw_injection"]:
print(" ✓ Raw packet injection available (running as root)")
print(" ★ Recommended: combined (uses seq_id trick + fragmentation)")
print(" ★ Also good: fake_sni (uses seq_id trick)")
elif caps["raw_socket"]:
print(" ✓ All methods available (running with sufficient privileges)")
print(" ★ Recommended: combined --ttl-trick")
else:
print(" ✓ fragment - TLS ClientHello fragmentation")
print(" ✓ combined - Fragmentation (fake_sni needs root for seq_id)")
print(" ★ Recommended: fragment or combined")
if platform.system() != "Windows":
print(" Run with sudo/root for raw injection (seq_id trick)")
# ─── Main ────────────────────────────────────────────────────────────────────
def main():
"""Main entry point."""
args = parse_args()
# Handle special commands
if args.generate_config:
generate_config(args.generate_config)
return
if args.info:
print(BANNER)
show_platform_info()
return
# Print banner
print(BANNER)
# Setup logging
logger = setup_logging(verbose=args.verbose, quiet=args.quiet)
# Load configuration
if args.config:
config = load_config(args.config)
else:
config = DEFAULT_CONFIG.copy()
# Override with CLI arguments
if args.listen:
host, port = parse_host_port(args.listen, "0.0.0.0", 40443)
config["LISTEN_HOST"] = host
config["LISTEN_PORT"] = port
if args.connect:
host, port = parse_host_port(args.connect, "188.114.98.0", 443)
config["CONNECT_IP"] = host
config["CONNECT_PORT"] = port
if args.sni:
config["FAKE_SNI"] = args.sni
if args.method:
config["BYPASS_METHOD"] = args.method
if args.fragment_strategy:
config["FRAGMENT_STRATEGY"] = args.fragment_strategy
if args.fragment_delay is not None:
config["FRAGMENT_DELAY"] = args.fragment_delay
if args.ttl_trick:
config["USE_TTL_TRICK"] = True
# Validate configuration
if not is_valid_port(config["LISTEN_PORT"]):
print(f"Error: Invalid listen port: {config['LISTEN_PORT']}")
sys.exit(1)
if not is_valid_port(config["CONNECT_PORT"]):
print(f"Error: Invalid connect port: {config['CONNECT_PORT']}")
sys.exit(1)
# Resolve target host if needed
config["CONNECT_IP"] = resolve_host(config["CONNECT_IP"])
# Detect interface IP
interface_ip = get_default_interface_ipv4(config["CONNECT_IP"])
logger.info(f"Default interface: {interface_ip or 'auto'}")
# Try to start raw injector (Linux + root only)
raw_injector = None
use_raw = not getattr(args, 'no_raw', False)
method = config.get("BYPASS_METHOD", "fragment").lower()
if use_raw and method in ("fake_sni", "combined") and interface_ip:
if is_raw_available():
from sni_spoofing.bypass.raw_injector import RawInjector
raw_injector = RawInjector(
local_ip=interface_ip,
remote_ip=config["CONNECT_IP"],
remote_port=config["CONNECT_PORT"],
fake_sni_builder=None,
)
if not raw_injector.start():
logger.warning(
"Raw injector failed to start. "
"Falling back to fragmentation."
)
raw_injector = None
else:
if method == "fake_sni":
logger.warning(
"Raw sockets not available (need root/CAP_NET_RAW). "
"fake_sni will fall back to fragmentation."
)
elif method == "combined":
logger.info(
"Raw sockets not available. "
"Using fragmentation-only bypass."
)
# Build bypass strategy
strategy = build_strategy(config, raw_injector=raw_injector)
# Show configuration summary
logger.info(f"Platform: {platform.system()} {platform.machine()}")
logger.info(f"Python: {platform.python_version()}")
# Setup signal handlers for graceful shutdown
def signal_handler(sig, frame):
print("\n\nShutting down...")
if raw_injector:
raw_injector.stop()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
if hasattr(signal, "SIGTERM"):
signal.signal(signal.SIGTERM, signal_handler)
# Run the server
try:
asyncio.run(
start_server(
listen_host=config["LISTEN_HOST"],
listen_port=config["LISTEN_PORT"],
connect_ip=config["CONNECT_IP"],
connect_port=config["CONNECT_PORT"],
fake_sni=config["FAKE_SNI"],
bypass_strategy=strategy,
interface_ip=interface_ip,
raw_injector=raw_injector,
)
)
except KeyboardInterrupt:
print("\nShutting down...")
except PermissionError:
print(f"\nError: Permission denied on port {config['LISTEN_PORT']}.")
if config["LISTEN_PORT"] < 1024:
print("Ports below 1024 require root/administrator privileges.")
print(f"Try: sudo {sys.argv[0]} ... or use a port >= 1024")
sys.exit(1)
except OSError as e:
if "address already in use" in str(e).lower():
print(f"\nError: Port {config['LISTEN_PORT']} is already in use.")
print("Use --listen :PORT to specify a different port.")
else:
print(f"\nError: {e}")
sys.exit(1)
finally:
if raw_injector:
raw_injector.stop()
if __name__ == "__main__":
main()