Add live scan progress and graceful Ctrl-C handling
This commit is contained in:
+33
-10
@@ -147,11 +147,16 @@ def cmd_run(args):
|
||||
total_hosts = 0
|
||||
total_matches = 0
|
||||
workers = args.workers
|
||||
n_countries = len(countries)
|
||||
|
||||
def progress(msg: str, end="\n"):
|
||||
print(f" {W}{msg}{RST}", end=end, flush=True)
|
||||
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmp = Path(tmpdir)
|
||||
|
||||
for country in countries:
|
||||
for idx, country in enumerate(countries, 1):
|
||||
cc = country["code"].upper()
|
||||
exclude = country.get("exclude_cidrs", [])
|
||||
cidrs = get_cidrs(cc, cidr_index)
|
||||
@@ -164,12 +169,19 @@ def cmd_run(args):
|
||||
if max_hosts:
|
||||
cidrs = cidrs[:max_hosts // 256 + 1]
|
||||
|
||||
info(f"{BOLD}{cc}{RST}: {len(cidrs)} CIDR blocks → masscan...")
|
||||
print(f"\n{C}[{idx}/{n_countries}]{RST} {BOLD}{cc}{RST} — {len(cidrs)} CIDR blocks")
|
||||
info(f"{cc}: running masscan at {rate} pps (Ctrl-C to skip country)...")
|
||||
|
||||
try:
|
||||
open_ports = masscan_sweep(cidrs, all_ports, rate, tmp)
|
||||
except KeyboardInterrupt:
|
||||
print()
|
||||
warn(f"{cc}: scan interrupted — skipping to next country")
|
||||
continue
|
||||
|
||||
hosts_found = len(set(e["ip"] for e in open_ports))
|
||||
total_hosts += hosts_found
|
||||
ok(f"{cc}: {hosts_found} live hosts ({len(open_ports)} open ports)")
|
||||
ok(f"{cc}: {hosts_found} live hosts, {len(open_ports)} open ports found")
|
||||
|
||||
if not open_ports:
|
||||
continue
|
||||
@@ -179,25 +191,33 @@ def cmd_run(args):
|
||||
for entry in open_ports:
|
||||
ip_ports.setdefault(entry["ip"], []).append(entry["port"])
|
||||
|
||||
info(f"{cc}: fingerprinting {hosts_found} hosts with nmap...")
|
||||
total_nmap = len(ip_ports)
|
||||
info(f"{cc}: fingerprinting {total_nmap} hosts with nmap...")
|
||||
nmap_results: dict[str, dict] = {}
|
||||
nmap_done = 0
|
||||
with ThreadPoolExecutor(max_workers=min(workers, 10)) as ex:
|
||||
futs = {ex.submit(nmap_fingerprint, ip, ports, tmp): ip
|
||||
for ip, ports in ip_ports.items()}
|
||||
for fut in as_completed(futs):
|
||||
ip = futs[fut]
|
||||
nmap_done += 1
|
||||
progress(f"nmap: {nmap_done}/{total_nmap}", end="\r")
|
||||
try:
|
||||
nmap_results[ip] = fut.result()
|
||||
except Exception:
|
||||
nmap_results[ip] = {}
|
||||
print()
|
||||
|
||||
info(f"{cc}: probing and matching targets...")
|
||||
total_probes = len(open_ports)
|
||||
info(f"{cc}: probing {total_probes} open ports for target matches...")
|
||||
probe_done = 0
|
||||
with ThreadPoolExecutor(max_workers=workers) as ex:
|
||||
futs = []
|
||||
for entry in open_ports:
|
||||
futs.append(ex.submit(probe_host, entry["ip"], entry["port"], targets))
|
||||
futs = [ex.submit(probe_host, entry["ip"], entry["port"], targets)
|
||||
for entry in open_ports]
|
||||
|
||||
for fut in as_completed(futs):
|
||||
probe_done += 1
|
||||
progress(f"probe: {probe_done}/{total_probes}", end="\r")
|
||||
try:
|
||||
matches = fut.result()
|
||||
except Exception:
|
||||
@@ -215,10 +235,13 @@ def cmd_run(args):
|
||||
db.insert_hit(conn, run_id, hit)
|
||||
total_matches += 1
|
||||
ver = f" [{match['version']}]" if match.get("version") else ""
|
||||
print(f" {G}HIT{RST} {BOLD}{ip}:{port}{RST} {Y}{match['target_name']}{RST}{ver} [{match['confidence']}] {cc}")
|
||||
|
||||
print(f"\r {G}HIT{RST} {BOLD}{ip}:{port}{RST} {Y}{match['target_name']}{RST}{ver} [{match['confidence']}] {cc}")
|
||||
print()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print()
|
||||
warn("Scan aborted by user")
|
||||
|
||||
db.finish_run(conn, run_id, total_hosts, total_matches)
|
||||
conn.close()
|
||||
|
||||
|
||||
+8
-3
@@ -29,14 +29,19 @@ def masscan_sweep(cidrs: list[str], ports: list[int], rate: int, output_dir: Pat
|
||||
f"--ports={port_str}",
|
||||
"-iL", str(cidr_file),
|
||||
"-oJ", str(out_file),
|
||||
"--output-format", "json",
|
||||
"--wait", "3",
|
||||
]
|
||||
|
||||
try:
|
||||
subprocess.run(cmd, capture_output=True, timeout=3600)
|
||||
proc = subprocess.Popen(cmd, stderr=subprocess.DEVNULL)
|
||||
proc.wait(timeout=3600)
|
||||
except subprocess.TimeoutExpired:
|
||||
pass
|
||||
proc.kill()
|
||||
proc.wait()
|
||||
except KeyboardInterrupt:
|
||||
proc.kill()
|
||||
proc.wait()
|
||||
raise
|
||||
except FileNotFoundError:
|
||||
raise RuntimeError("masscan not found — install it first")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user