initial commit
This commit is contained in:
+383
@@ -0,0 +1,383 @@
|
||||
"""
|
||||
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())
|
||||
Reference in New Issue
Block a user