#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ EO 全段扫描 v3 (方案 B: 找所有能服务 box.shiroko.one 的 EO 边缘 IP) 两段式: 1) asyncio TCP:443 预筛全段 2) openssl 对 TCP 通的 IP 做 SNI 证书核验 (CN 含 host) 输出: eo-all-hits.json [{ip, cn, tcp_ms}] 用法: python eo-scan-all.py --host box.shiroko.one --nets 43.174.0.0/16 43.175.0.0/16 --workers 400 """ import argparse import asyncio import json import socket import subprocess import time from concurrent.futures import ThreadPoolExecutor def iter_hosts(net: str): net, bits = net.split("/") bits = int(bits) a, b, c, d = map(int, net.split(".")) base = (a << 24) + (b << 16) + (c << 8) + d count = 1 << (32 - bits) for i in range(1, count - 1): v = base + i yield f"{(v >> 24) & 255}.{(v >> 16) & 255}.{(v >> 8) & 255}.{v & 255}" async def tcp_probe(ip: str, port: int, timeout: float, sem: asyncio.Semaphore): async with sem: t0 = time.perf_counter() try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setblocking(False) loop = asyncio.get_running_loop() await asyncio.wait_for(loop.sock_connect(s, (ip, port)), timeout) ms = (time.perf_counter() - t0) * 1000 s.close() return ip, ms except Exception: try: s.close() except Exception: pass return None def tls_cn(ip: str, host: str, timeout: float): try: r = subprocess.run( ["openssl", "s_client", "-connect", f"{ip}:443", "-servername", host, "-tlsextdebug", "-no_ign_eof"], capture_output=True, text=True, timeout=timeout, errors="replace", input="Q") out = r.stdout + r.stderr cn = "" for line in out.splitlines(): ls = line.strip() if ls.startswith("subject="): cn = ls break return {"ip": ip, "cn": cn or "(no-subject)", "ok": host in cn} except Exception as e: return {"ip": ip, "cn": f"ERR:{type(e).__name__}", "ok": False} def main(): ap = argparse.ArgumentParser() ap.add_argument("--host", default="box.shiroko.one") ap.add_argument("--nets", nargs="*", default=["43.174.0.0/16", "43.175.0.0/16"]) ap.add_argument("--workers", type=int, default=300) ap.add_argument("--timeout", type=float, default=2.0) ap.add_argument("--out", default="eo-all-hits.json") args = ap.parse_args() all_ips = [] for n in args.nets: all_ips.extend(iter_hosts(n)) print(f"[*] host={args.host} 网段={args.nets} 总IP={len(all_ips)}") # Stage 1: TCP 预筛 sem = asyncio.Semaphore(args.workers) open_ips = [] async def run_tcp(): CH = args.workers * 6 for i in range(0, len(all_ips), CH): chunk = all_ips[i:i + CH] tasks = [asyncio.create_task(tcp_probe(ip, 443, args.timeout, sem)) for ip in chunk] res = await asyncio.gather(*tasks) for r in res: if r: open_ips.append(r) if (i + CH) % 20000 < CH or i + CH >= len(all_ips): print(f" tcp预筛 [{i+CH}/{len(all_ips)}] open={len(open_ips)}", flush=True) t0 = time.perf_counter() asyncio.run(run_tcp()) print(f"[*] TCP 预筛完成: {len(all_ips)} -> {len(open_ips)} open, {time.perf_counter()-t0:.0f}s") if not open_ips: print("[!] 无开放 IP") return # Stage 2: openssl 证书核验 hits = [] with ThreadPoolExecutor(max_workers=64) as ex: futs = [ex.submit(tls_cn, ip, args.host, args.timeout * 2) for ip, _ in open_ips] done = 0 for fut in futures_as_completed(futs): done += 1 r = fut.result() if r and r["ok"]: hits.append(r) print(f" HIT {r['ip']:16s} {r['cn']}", flush=True) if done % 200 == 0: print(f" 证书核验 [{done}/{len(open_ips)}] hits={len(hits)}", flush=True) print(f"\n[*] 全部完成: hits={len(hits)}") if hits: with open(args.out, "w", encoding="utf-8") as f: json.dump(hits, f, ensure_ascii=False, indent=2) print(f"[*] saved: {args.out}") def futures_as_completed(futs): import concurrent.futures for fut in concurrent.futures.as_completed(futs): yield fut if __name__ == "__main__": main()