"""
Standalone fake server + DNS redirect.
Stays running until you press Ctrl+C.
Launch Remcos/KeyGen yourself while this is running.

Run as Administrator!
"""
import http.server
import os
import socket
import socketserver
import ssl
import subprocess
import sys
import threading
import time
from datetime import datetime
from urllib.parse import urlparse, parse_qs

VERSION = "7.2.2"
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
CERT_DIR = os.path.join(SCRIPT_DIR, "certs")
CA_CERT = os.path.join(CERT_DIR, "ca.crt")
CA_KEY = os.path.join(CERT_DIR, "ca.key")
SRV_CERT = os.path.join(CERT_DIR, "server.crt")
SRV_KEY = os.path.join(CERT_DIR, "server.key")

NRPT_BASE = r"HKLM:\System\CurrentControlSet\Services\Dnscache\Parameters\DnsPolicyConfig"
NRPT_GUIDS = [
    ("{b1a2c3d4-0001-aaaa-bbbb-000000000001}", [".breakingsec.io", "breakingsec.io"]),
    ("{b1a2c3d4-0001-aaaa-bbbb-000000000002}", [".ip-api.com", "pro.ip-api.com", "ip-api.com"]),
    ("{b1a2c3d4-0001-aaaa-bbbb-000000000003}", [".breakingsecurity.net", "breakingsecurity.net"]),
]

REDIRECT_DOMAINS = {"breakingsec.io", "www.breakingsec.io", "breakingsecurity.net",
                    "www.breakingsecurity.net", "pro.ip-api.com"}
REAL_DNS = "8.8.8.8"

GEO_RESPONSE = "\n".join([
    "success", "US", "United States", "NA", "North America",
    "CA", "California", "Los Angeles", "Los Angeles", "90001",
    "34.0522", "-118.2437", "America/Los_Angeles",
    "ISP", "ISP Corp", "AS0000", "1.2.3.4",
])

# ============================================================
# SSL Certs
# ============================================================
def generate_certs():
    os.makedirs(CERT_DIR, exist_ok=True)
    if os.path.exists(SRV_CERT) and os.path.exists(CA_CERT):
        print("[*] Certs exist, reusing.")
        return True
    print("[*] Generating SSL certificates...")
    try:
        subprocess.run(["openssl", "req", "-x509", "-newkey", "rsa:2048", "-nodes",
            "-keyout", CA_KEY, "-out", CA_CERT, "-days", "3650",
            "-subj", "/CN=Remcos Test CA"], capture_output=True, check=True)

        csr = os.path.join(CERT_DIR, "server.csr")
        ext = os.path.join(CERT_DIR, "ext.cnf")
        with open(ext, "w") as f:
            f.write("[v3_req]\nsubjectAltName=DNS:breakingsec.io,DNS:*.breakingsec.io,"
                    "DNS:breakingsecurity.net,DNS:*.breakingsecurity.net,"
                    "DNS:pro.ip-api.com,DNS:*.ip-api.com,DNS:localhost\n"
                    "basicConstraints=CA:FALSE\nkeyUsage=digitalSignature,keyEncipherment\n"
                    "extendedKeyUsage=serverAuth\n")

        subprocess.run(["openssl", "req", "-newkey", "rsa:2048", "-nodes",
            "-keyout", SRV_KEY, "-out", csr, "-subj", "/CN=breakingsec.io"],
            capture_output=True, check=True)

        subprocess.run(["openssl", "x509", "-req", "-in", csr, "-CA", CA_CERT,
            "-CAkey", CA_KEY, "-CAcreateserial", "-out", SRV_CERT, "-days", "3650",
            "-extfile", ext, "-extensions", "v3_req"], capture_output=True, check=True)

        print("[+] Certificates generated.")
        return True
    except Exception as e:
        print(f"[-] Cert generation failed: {e}")
        return False

def install_ca():
    r = subprocess.run(["certutil", "-addstore", "-f", "Root", CA_CERT],
                       capture_output=True, text=True)
    if r.returncode == 0:
        print("[+] CA cert installed in trust store.")
    else:
        print(f"[-] CA install failed: {r.stderr[:100]}")

def remove_ca():
    subprocess.run(["certutil", "-delstore", "Root", "Remcos Test CA"],
                   capture_output=True)

# ============================================================
# NRPT
# ============================================================
def setup_nrpt():
    print("[*] Setting NRPT rules...")
    for guid, domains in NRPT_GUIDS:
        names_str = ",".join(f"'{d}'" for d in domains)
        ps = f"""
        $p = '{NRPT_BASE}\\{guid}'
        New-Item -Path $p -Force | Out-Null
        Set-ItemProperty -Path $p -Name 'Name' -Value @({names_str}) -Type MultiString
        Set-ItemProperty -Path $p -Name 'GenericDNSServers' -Value '127.0.0.1' -Type String
        Set-ItemProperty -Path $p -Name 'ConfigOptions' -Value 8 -Type DWord
        Set-ItemProperty -Path $p -Name 'Version' -Value 2 -Type DWord
        """
        r = subprocess.run(["powershell", "-Command", ps], capture_output=True, text=True, timeout=10)
        if r.returncode == 0:
            print(f"  [+] {', '.join(domains)} -> 127.0.0.1")
        else:
            print(f"  [-] {domains[0]}: {r.stderr.strip()[:80]}")

    subprocess.run(["ipconfig", "/flushdns"], capture_output=True, timeout=10)
    subprocess.run(["powershell", "-Command", "Clear-DnsClientCache; Register-DnsClient"],
                   capture_output=True, timeout=10)

def cleanup_nrpt():
    for guid, _ in NRPT_GUIDS:
        ps = f"Remove-Item -Path '{NRPT_BASE}\\{guid}' -Recurse -Force -ErrorAction SilentlyContinue"
        subprocess.run(["powershell", "-Command", ps], capture_output=True, timeout=10)
    subprocess.run(["ipconfig", "/flushdns"], capture_output=True, timeout=10)

# ============================================================
# DNS Server
# ============================================================
class DNSHandler(socketserver.BaseRequestHandler):
    def handle(self):
        data = self.request[0]
        sock = self.request[1]
        try:
            domain = self._parse_domain(data).lower().rstrip(".")
            redirect = any(domain == d or domain.endswith("." + d)
                          for d in REDIRECT_DOMAINS)
            if redirect:
                resp = self._build_response(data, "127.0.0.1")
                sock.sendto(resp, self.client_address)
            else:
                fwd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                fwd.settimeout(3)
                fwd.sendto(data, (REAL_DNS, 53))
                try:
                    r, _ = fwd.recvfrom(4096)
                    sock.sendto(r, self.client_address)
                except socket.timeout:
                    pass
                fwd.close()
        except:
            pass

    def _parse_domain(self, data):
        parts, idx = [], 12
        while idx < len(data):
            l = data[idx]
            if l == 0: break
            idx += 1
            parts.append(data[idx:idx+l].decode("ascii", errors="replace"))
            idx += l
        return ".".join(parts)

    def _build_response(self, query, ip):
        resp = bytearray(query[:2]) + b'\x81\x80' + query[4:6] + b'\x00\x01\x00\x00\x00\x00'
        idx = 12
        while idx < len(query):
            if query[idx] == 0:
                idx += 5
                break
            idx += 1 + query[idx]
        resp += query[12:idx]
        resp += b'\xc0\x0c\x00\x01\x00\x01\x00\x00\x0e\x10\x00\x04'
        resp += socket.inet_aton(ip)
        return bytes(resp)

# ============================================================
# HTTP/HTTPS Server
# ============================================================
class FakeHandler(http.server.BaseHTTPRequestHandler):
    def log_message(self, fmt, *args):
        ts = datetime.now().strftime("%H:%M:%S")
        print(f"  [{ts}] {fmt % args}")

    def send_text(self, text, code=200):
        data = text.encode("utf-8")
        self.send_response(code)
        self.send_header("Content-Type", "text/plain")
        self.send_header("Content-Length", str(len(data)))
        self.send_header("Connection", "close")
        self.end_headers()
        self.wfile.write(data)

    def route(self):
        path = urlparse(self.path).path.lower()
        host = self.headers.get("Host", "")
        qs = parse_qs(urlparse(self.path).query)
        print(f"  -> {self.command} {self.path} (Host: {host})")
        # Dump ALL headers for debugging
        for h_name, h_val in self.headers.items():
            print(f"     {h_name}: {h_val}")

        if "whitelist" in path:
            print(f"  <- Whitelist: EMPTY")
            self.send_text("")
            return
        if "signalabuse" in path:
            print(f"  <- Abuse absorbed")
            self.send_text("")
            return
        if "licpost" in path or ("keygen" in path and "lic" in path.lower()):
            lic_raw = qs.get("LIC", [""])[0]
            parts = lic_raw.split("|")
            print(f"  ***** KeyGen LicPost! *****")
            print(f"  Parts: {parts}")
            # Try response matching request format: pipe-delimited after "0\n"
            key = parts[0] if len(parts) > 0 else ""
            email = parts[1] if len(parts) > 1 else ""
            # Format: 0\nkey|email|product|expiry|type|version
            resp = f"0\n{key}|{email}|Remcos|20301231|Pro|7.2.2"
            print(f"  <- Responding: '{resp}'")
            self.send_text(resp)
            return
        if "upd_pro" in path:
            print(f"  <- Version: {VERSION}")
            self.send_text(VERSION + "\n")
            return
        # PeriodicCheck - license validation with hash + expiry
        if "periodiccheck" in path:
            lic = qs.get("LIC", ["?"])[0]
            resp = "49ef9592748fac8986f0d360454dbab0\n20301231\nRemcos"
            print(f"  ***** PeriodicCheck (LIC={lic[:24]}) -> {repr(resp)} *****")
            self.send_text(resp)
            return
        # Auth/license check - sign with our private key
        if "auth" in path or "check" in path or "verify" in path or "validate" in path:
            print(f"  ***** Auth endpoint: {self.path} *****")
            self.send_text("0\n20301231\nPro\n7.2.2")
            return
        if "onlinecheck" in path:
            print(f"  <- OnlineCheck: {VERSION}")
            self.send_text(VERSION + "\n")
            return
        if "/line" in path or "ip-api" in host:
            print(f"  <- GeoIP")
            self.send_text(GEO_RESPONSE + "\n")
            return
        print(f"  *** UNKNOWN ENDPOINT: {self.path} ***")
        print(f"  *** Host: {host} ***")
        # Return "0" instead of empty - empty might cause range check errors
        self.send_text("0")

    def do_GET(self): self.route()
    def do_POST(self):
        cl = int(self.headers.get("Content-Length", 0))
        body = self.rfile.read(cl) if cl > 0 else b""
        if body:
            try: print(f"  POST body: {body[:300].decode('utf-8', errors='replace')}")
            except: print(f"  POST body: {body[:100].hex()}")
        self.route()

# ============================================================
# Main
# ============================================================
def main():
    print("=" * 60)
    print("  Fake Server (standalone) - stays running")
    print("  Launch Remcos/KeyGen yourself while this runs")
    print("=" * 60)

    if not ctypes.windll.shell32.IsUserAnAdmin():
        print("[-] Need Administrator! Right-click -> Run as admin")
        return 1

    # Setup
    generate_certs()
    install_ca()

    # CA bundle for OpenSSL apps
    ca_bundle = os.path.join(CERT_DIR, "ca_bundle.pem")
    try:
        import certifi
        system_ca = certifi.where()
    except ImportError:
        system_ca = None
    with open(ca_bundle, "w") as out:
        with open(CA_CERT) as f: out.write(f.read() + "\n")
        if system_ca and os.path.exists(system_ca):
            with open(system_ca) as f: out.write(f.read())
    os.environ["SSL_CERT_FILE"] = ca_bundle
    os.environ["SSL_CERT_DIR"] = CERT_DIR

    # Start servers
    dns = socketserver.UDPServer(("127.0.0.1", 53), DNSHandler)
    threading.Thread(target=dns.serve_forever, daemon=True).start()
    print("[+] DNS on 127.0.0.1:53")

    http_srv = http.server.HTTPServer(("0.0.0.0", 80), FakeHandler)
    threading.Thread(target=http_srv.serve_forever, daemon=True).start()
    print("[+] HTTP on 0.0.0.0:80")

    try:
        class LoggingHTTPS(http.server.HTTPServer):
            def handle_error(self, request, client_address):
                print(f"  [SSL ERROR] from {client_address}: {sys.exc_info()[1]}")
        https_srv = LoggingHTTPS(("0.0.0.0", 443), FakeHandler)
        ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
        ctx.load_cert_chain(SRV_CERT, SRV_KEY)
        https_srv.socket = ctx.wrap_socket(https_srv.socket, server_side=True)
        threading.Thread(target=https_srv.serve_forever, daemon=True).start()
        print("[+] HTTPS on 0.0.0.0:443")
    except Exception as e:
        print(f"[-] HTTPS failed: {e}")

    # Port 45000 - Viotto packer license validation (raw TCP protocol!)
    def handle_viotto_client(conn, addr):
        print(f"\n  *** PORT 45000 CONNECTION from {addr} ***")
        conn.settimeout(5)
        try:
            # Read whatever the client sends
            data = conn.recv(4096)
            print(f"  *** RECEIVED ({len(data)} bytes): {data[:200].hex()}")
            try:
                print(f"  *** AS TEXT: {data[:200].decode('ascii', errors='replace')}")
            except:
                pass
            # Echo back for now - we need to see the protocol first
            conn.sendall(data)
            print(f"  *** Echoed {len(data)} bytes back")
        except socket.timeout:
            print(f"  *** PORT 45000: recv timeout (client didn't send data)")
        except Exception as e:
            print(f"  *** PORT 45000 error: {e}")
        finally:
            conn.close()

    def viotto_server():
        srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        srv.bind(("0.0.0.0", 45000))
        srv.listen(5)
        print("[+] Raw TCP on 0.0.0.0:45000 (Viotto license port!)")
        while True:
            conn, addr = srv.accept()
            threading.Thread(target=handle_viotto_client, args=(conn, addr), daemon=True).start()

    threading.Thread(target=viotto_server, daemon=True).start()

    # NRPT
    setup_nrpt()

    # Verify
    time.sleep(1)
    for domain in ["breakingsec.io", "breakingsecurity.net"]:
        try:
            result = socket.getaddrinfo(domain, 443)
            ip = result[0][4][0]
            status = "OK" if ip == "127.0.0.1" else f"WRONG ({ip})"
            print(f"  {domain} -> {ip} [{status}]")
        except Exception as e:
            print(f"  {domain} -> ERROR: {e}")

    print()
    print("=" * 60)
    print("  RUNNING. Now launch KeyGen.exe or Remcos manually.")
    print("  Press Ctrl+C to stop and clean up.")
    print("=" * 60)
    print()

    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        pass
    finally:
        print("\n[*] Cleaning up...")
        cleanup_nrpt()
        print("[+] NRPT removed")
        remove_ca()
        print("[+] CA removed")

import ctypes
if __name__ == "__main__":
    sys.exit(main())
