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_hosts = 0
|
||||||
total_matches = 0
|
total_matches = 0
|
||||||
workers = args.workers
|
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:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
tmp = Path(tmpdir)
|
tmp = Path(tmpdir)
|
||||||
|
|
||||||
for country in countries:
|
for idx, country in enumerate(countries, 1):
|
||||||
cc = country["code"].upper()
|
cc = country["code"].upper()
|
||||||
exclude = country.get("exclude_cidrs", [])
|
exclude = country.get("exclude_cidrs", [])
|
||||||
cidrs = get_cidrs(cc, cidr_index)
|
cidrs = get_cidrs(cc, cidr_index)
|
||||||
@@ -164,12 +169,19 @@ def cmd_run(args):
|
|||||||
if max_hosts:
|
if max_hosts:
|
||||||
cidrs = cidrs[:max_hosts // 256 + 1]
|
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)
|
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))
|
hosts_found = len(set(e["ip"] for e in open_ports))
|
||||||
total_hosts += hosts_found
|
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:
|
if not open_ports:
|
||||||
continue
|
continue
|
||||||
@@ -179,25 +191,33 @@ def cmd_run(args):
|
|||||||
for entry in open_ports:
|
for entry in open_ports:
|
||||||
ip_ports.setdefault(entry["ip"], []).append(entry["port"])
|
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_results: dict[str, dict] = {}
|
||||||
|
nmap_done = 0
|
||||||
with ThreadPoolExecutor(max_workers=min(workers, 10)) as ex:
|
with ThreadPoolExecutor(max_workers=min(workers, 10)) as ex:
|
||||||
futs = {ex.submit(nmap_fingerprint, ip, ports, tmp): ip
|
futs = {ex.submit(nmap_fingerprint, ip, ports, tmp): ip
|
||||||
for ip, ports in ip_ports.items()}
|
for ip, ports in ip_ports.items()}
|
||||||
for fut in as_completed(futs):
|
for fut in as_completed(futs):
|
||||||
ip = futs[fut]
|
ip = futs[fut]
|
||||||
|
nmap_done += 1
|
||||||
|
progress(f"nmap: {nmap_done}/{total_nmap}", end="\r")
|
||||||
try:
|
try:
|
||||||
nmap_results[ip] = fut.result()
|
nmap_results[ip] = fut.result()
|
||||||
except Exception:
|
except Exception:
|
||||||
nmap_results[ip] = {}
|
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:
|
with ThreadPoolExecutor(max_workers=workers) as ex:
|
||||||
futs = []
|
futs = [ex.submit(probe_host, entry["ip"], entry["port"], targets)
|
||||||
for entry in open_ports:
|
for entry in open_ports]
|
||||||
futs.append(ex.submit(probe_host, entry["ip"], entry["port"], targets))
|
|
||||||
|
|
||||||
for fut in as_completed(futs):
|
for fut in as_completed(futs):
|
||||||
|
probe_done += 1
|
||||||
|
progress(f"probe: {probe_done}/{total_probes}", end="\r")
|
||||||
try:
|
try:
|
||||||
matches = fut.result()
|
matches = fut.result()
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -215,10 +235,13 @@ def cmd_run(args):
|
|||||||
db.insert_hit(conn, run_id, hit)
|
db.insert_hit(conn, run_id, hit)
|
||||||
total_matches += 1
|
total_matches += 1
|
||||||
ver = f" [{match['version']}]" if match.get("version") else ""
|
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()
|
print()
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print()
|
||||||
|
warn("Scan aborted by user")
|
||||||
|
|
||||||
db.finish_run(conn, run_id, total_hosts, total_matches)
|
db.finish_run(conn, run_id, total_hosts, total_matches)
|
||||||
conn.close()
|
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}",
|
f"--ports={port_str}",
|
||||||
"-iL", str(cidr_file),
|
"-iL", str(cidr_file),
|
||||||
"-oJ", str(out_file),
|
"-oJ", str(out_file),
|
||||||
"--output-format", "json",
|
|
||||||
"--wait", "3",
|
"--wait", "3",
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
subprocess.run(cmd, capture_output=True, timeout=3600)
|
proc = subprocess.Popen(cmd, stderr=subprocess.DEVNULL)
|
||||||
|
proc.wait(timeout=3600)
|
||||||
except subprocess.TimeoutExpired:
|
except subprocess.TimeoutExpired:
|
||||||
pass
|
proc.kill()
|
||||||
|
proc.wait()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
proc.kill()
|
||||||
|
proc.wait()
|
||||||
|
raise
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise RuntimeError("masscan not found — install it first")
|
raise RuntimeError("masscan not found — install it first")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user