Initial commit — SetecSuite Camera MITM Framework
Original tooling from the Camhak research project (camera teardown of a
rebranded UBIA / Javiscam IP camera). PyQt6 GUI on top of a curses TUI on
top of a service controller; per-service start/stop, intruder detection,
protocol fingerprinting, OAM HMAC signing, CVE verifiers, OTA bucket
probe, firmware fetcher, fuzzer, packet injection.
Tabs: Dashboard, Live Log, Intruders, Cloud API, Fuzzer, Inject, CVEs,
Config, Help. Real-time per-packet protocol detection, conntrack-based
original-destination lookup, log rotation at 1 GiB.
See SECURITY_PAPER.md for the full writeup, site/index.html for the
public report, README.md for usage. Run with:
sudo /usr/bin/python3 gui.py
Co-authored by Setec Labs.
This commit is contained in:
531
mitm.py
Normal file
531
mitm.py
Normal file
@@ -0,0 +1,531 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
SetecSuite — Camera MITM Tool
|
||||
All-in-one IoT camera pentesting framework with TUI.
|
||||
|
||||
Usage: sudo python3 mitm.py
|
||||
"""
|
||||
|
||||
import curses
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import signal
|
||||
import threading
|
||||
import time
|
||||
from collections import deque
|
||||
|
||||
# Add project root to path
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from config import Config
|
||||
from utils.log import log, log_lines, init_logfile, close_logfile, lock
|
||||
from utils.log import C_NONE, C_ERROR, C_SUCCESS, C_INFO, C_TRAFFIC, C_IMPORTANT
|
||||
from services import arp_spoof, dns_spoof, http_server, udp_listener, sniffer, intruder_watch
|
||||
from api import ubox_client, server as rest_server, fuzzer
|
||||
from inject import packet
|
||||
|
||||
|
||||
SERVICE_DEFS = [
|
||||
# (name, flag_key, runner_factory)
|
||||
("arp", "arp", lambda cfg, flags, ck: arp_spoof.run(cfg, flags, ck)),
|
||||
("dns", "dns", lambda cfg, flags, ck: dns_spoof.run(cfg, flags, ck)),
|
||||
("http", "http", lambda cfg, flags, ck: http_server.run_http(cfg, flags, ck)),
|
||||
("https", "https", lambda cfg, flags, ck: http_server.run_https(cfg, flags, ck)),
|
||||
("udp10240", "udp10240", lambda cfg, flags, ck: udp_listener.run(10240, cfg, flags, ck)),
|
||||
("udp20001", "udp20001", lambda cfg, flags, ck: udp_listener.run(20001, cfg, flags, ck)),
|
||||
("sniffer", "sniffer", lambda cfg, flags, ck: sniffer.run(cfg, flags, ck)),
|
||||
("intruder", "intruder", lambda cfg, flags, ck: intruder_watch.run(cfg, flags, ck)),
|
||||
]
|
||||
|
||||
SERVICE_NAMES = [s[0] for s in SERVICE_DEFS]
|
||||
SERVICE_BY_NAME = {s[0]: s for s in SERVICE_DEFS}
|
||||
|
||||
|
||||
class Controller:
|
||||
def __init__(self):
|
||||
self.cfg = Config()
|
||||
self.flags = {}
|
||||
self.running = True
|
||||
self.services_running = False
|
||||
self.fuzzer = None
|
||||
self._devices = []
|
||||
# per-service running state for individual on/off
|
||||
self._svc_running = {n: False for n in SERVICE_NAMES}
|
||||
self._iptables_up = False
|
||||
|
||||
def get_devices(self):
|
||||
return self._devices
|
||||
|
||||
# ─── Service Control ──────────────────────────────────
|
||||
def _ensure_iptables(self):
|
||||
if not self._iptables_up:
|
||||
os.system("pkill -f arpspoof 2>/dev/null")
|
||||
os.makedirs(self.cfg["log_dir"], exist_ok=True)
|
||||
self._setup_iptables()
|
||||
self._iptables_up = True
|
||||
|
||||
def start_service(self, name):
|
||||
if name not in SERVICE_BY_NAME:
|
||||
log(f"unknown service: {name}", C_ERROR)
|
||||
return
|
||||
if self._svc_running.get(name):
|
||||
log(f"{name} already running", C_ERROR)
|
||||
return
|
||||
self._ensure_iptables()
|
||||
# Free ports if needed
|
||||
if name == "http":
|
||||
os.system("fuser -k 80/tcp 2>/dev/null")
|
||||
elif name == "https":
|
||||
os.system("fuser -k 443/tcp 2>/dev/null")
|
||||
elif name == "dns":
|
||||
os.system("fuser -k 53/udp 2>/dev/null")
|
||||
elif name == "udp10240":
|
||||
os.system("fuser -k 10240/udp 2>/dev/null")
|
||||
elif name == "udp20001":
|
||||
os.system("fuser -k 20001/udp 2>/dev/null")
|
||||
time.sleep(0.2)
|
||||
|
||||
self._svc_running[name] = True
|
||||
check = lambda n=name: self.running and self._svc_running.get(n, False)
|
||||
runner = SERVICE_BY_NAME[name][2]
|
||||
threading.Thread(
|
||||
target=lambda: runner(self.cfg, self.flags, check),
|
||||
daemon=True,
|
||||
name=f"svc-{name}",
|
||||
).start()
|
||||
self.services_running = any(self._svc_running.values())
|
||||
log(f"started: {name}", C_SUCCESS)
|
||||
|
||||
def stop_service(self, name):
|
||||
if name not in SERVICE_BY_NAME:
|
||||
log(f"unknown service: {name}", C_ERROR)
|
||||
return
|
||||
if not self._svc_running.get(name):
|
||||
log(f"{name} not running", C_ERROR)
|
||||
return
|
||||
self._svc_running[name] = False
|
||||
log(f"stopping: {name}…", C_INFO)
|
||||
# Force-close listening sockets so accept() unblocks
|
||||
if name == "http":
|
||||
os.system("fuser -k 80/tcp 2>/dev/null")
|
||||
elif name == "https":
|
||||
os.system("fuser -k 443/tcp 2>/dev/null")
|
||||
elif name == "dns":
|
||||
os.system("fuser -k 53/udp 2>/dev/null")
|
||||
elif name == "udp10240":
|
||||
os.system("fuser -k 10240/udp 2>/dev/null")
|
||||
elif name == "udp20001":
|
||||
os.system("fuser -k 20001/udp 2>/dev/null")
|
||||
time.sleep(0.5)
|
||||
self.flags[name] = False
|
||||
self.services_running = any(self._svc_running.values())
|
||||
|
||||
def toggle_service(self, name):
|
||||
if self._svc_running.get(name):
|
||||
self.stop_service(name)
|
||||
else:
|
||||
self.start_service(name)
|
||||
|
||||
def start_services(self):
|
||||
if self.services_running:
|
||||
log("Services already running", C_ERROR)
|
||||
return
|
||||
self._ensure_iptables()
|
||||
for name in SERVICE_NAMES:
|
||||
self.start_service(name)
|
||||
time.sleep(0.3)
|
||||
log("All MITM services started", C_SUCCESS)
|
||||
|
||||
def stop_services(self):
|
||||
if not self.services_running:
|
||||
log("Services not running", C_ERROR)
|
||||
return
|
||||
log("Stopping all services...", C_INFO)
|
||||
for name in SERVICE_NAMES:
|
||||
if self._svc_running.get(name):
|
||||
self.stop_service(name)
|
||||
time.sleep(1)
|
||||
self._cleanup_iptables()
|
||||
self._iptables_up = False
|
||||
self.flags.clear()
|
||||
self.services_running = False
|
||||
log("Services stopped", C_INFO)
|
||||
|
||||
def _setup_iptables(self):
|
||||
cam = self.cfg["camera_ip"]
|
||||
us = self.cfg["our_ip"]
|
||||
for cmd in [
|
||||
"sysctl -w net.ipv4.ip_forward=1",
|
||||
"iptables -A OUTPUT -p icmp --icmp-type redirect -j DROP",
|
||||
f"iptables -t nat -A PREROUTING -s {cam} -p udp --dport 53 -j DNAT --to-destination {us}:53",
|
||||
f"iptables -t nat -A PREROUTING -s {cam} -p tcp --dport 80 -j DNAT --to-destination {us}:80",
|
||||
f"iptables -t nat -A PREROUTING -s {cam} -p tcp --dport 443 -j DNAT --to-destination {us}:443",
|
||||
]:
|
||||
os.system(cmd + " >/dev/null 2>&1")
|
||||
log("iptables rules applied", C_INFO)
|
||||
|
||||
def _cleanup_iptables(self):
|
||||
cam = self.cfg["camera_ip"]
|
||||
us = self.cfg["our_ip"]
|
||||
for cmd in [
|
||||
"iptables -D OUTPUT -p icmp --icmp-type redirect -j DROP",
|
||||
f"iptables -t nat -D PREROUTING -s {cam} -p udp --dport 53 -j DNAT --to-destination {us}:53",
|
||||
f"iptables -t nat -D PREROUTING -s {cam} -p tcp --dport 80 -j DNAT --to-destination {us}:80",
|
||||
f"iptables -t nat -D PREROUTING -s {cam} -p tcp --dport 443 -j DNAT --to-destination {us}:443",
|
||||
]:
|
||||
os.system(cmd + " >/dev/null 2>&1")
|
||||
|
||||
# ─── Fuzzer ───────────────────────────────────────────
|
||||
def run_fuzz_endpoints(self):
|
||||
self.fuzzer = fuzzer.Fuzzer(self.cfg)
|
||||
self.fuzzer.fuzz_endpoints()
|
||||
self.fuzzer.save_results()
|
||||
|
||||
def run_fuzz_params(self, endpoint):
|
||||
self.fuzzer = fuzzer.Fuzzer(self.cfg)
|
||||
self.fuzzer.fuzz_params(endpoint)
|
||||
self.fuzzer.save_results()
|
||||
|
||||
def run_fuzz_auth(self):
|
||||
self.fuzzer = fuzzer.Fuzzer(self.cfg)
|
||||
self.fuzzer.fuzz_auth()
|
||||
self.fuzzer.save_results()
|
||||
|
||||
# ─── Packet Injection ─────────────────────────────────
|
||||
def inject_packet(self, params):
|
||||
return packet.inject(self.cfg, params)
|
||||
|
||||
# ─── Command Processing ───────────────────────────────
|
||||
def process_command(self, cmd):
|
||||
parts = cmd.strip().split()
|
||||
if not parts:
|
||||
return
|
||||
c = parts[0].lower()
|
||||
|
||||
if c == "help":
|
||||
for line in HELP.split("\n"):
|
||||
log(line, C_INFO)
|
||||
|
||||
elif c == "start":
|
||||
threading.Thread(target=self.start_services, daemon=True).start()
|
||||
elif c == "stop":
|
||||
threading.Thread(target=self.stop_services, daemon=True).start()
|
||||
elif c == "status":
|
||||
flags = ", ".join(f"{k}:{'ON' if v else 'off'}" for k, v in self.flags.items())
|
||||
log(f"MITM: {'RUNNING' if self.services_running else 'STOPPED'} {flags}", C_INFO)
|
||||
log(f"REST API: :{self.cfg['rest_port']} Token: {'yes' if self.cfg['api_token'] else 'no'}", C_INFO)
|
||||
|
||||
elif c == "config":
|
||||
for k, v in self.cfg.safe_dict().items():
|
||||
log(f" {k}: {v}", C_INFO)
|
||||
elif c == "set" and len(parts) >= 3:
|
||||
key = parts[1]
|
||||
val = " ".join(parts[2:])
|
||||
if key in self.cfg.keys():
|
||||
# Type coerce
|
||||
old = self.cfg[key]
|
||||
if isinstance(old, int):
|
||||
val = int(val)
|
||||
elif isinstance(old, float):
|
||||
val = float(val)
|
||||
self.cfg[key] = val
|
||||
self.cfg.save()
|
||||
log(f"Set {key} = {val if 'password' not in key else '***'}", C_SUCCESS)
|
||||
else:
|
||||
log(f"Unknown key. Valid: {', '.join(self.cfg.keys())}", C_ERROR)
|
||||
elif c == "save":
|
||||
self.cfg.save()
|
||||
log("Config saved", C_SUCCESS)
|
||||
|
||||
elif c == "login":
|
||||
threading.Thread(target=ubox_client.login, args=(self.cfg,), daemon=True).start()
|
||||
elif c == "devices":
|
||||
def _get():
|
||||
self._devices = ubox_client.devices(self.cfg)
|
||||
threading.Thread(target=_get, daemon=True).start()
|
||||
elif c == "firmware":
|
||||
threading.Thread(target=ubox_client.check_firmware, args=(self.cfg,), daemon=True).start()
|
||||
elif c == "services":
|
||||
threading.Thread(target=ubox_client.device_services, args=(self.cfg,), daemon=True).start()
|
||||
elif c == "families":
|
||||
threading.Thread(target=ubox_client.families, args=(self.cfg,), daemon=True).start()
|
||||
elif c == "api" and len(parts) >= 2:
|
||||
ep = " ".join(parts[1:])
|
||||
threading.Thread(target=ubox_client.raw_request, args=(self.cfg, ep), daemon=True).start()
|
||||
|
||||
elif c == "fuzz":
|
||||
if len(parts) < 2:
|
||||
log("Usage: fuzz endpoints|params <ep>|auth|stop|results", C_ERROR)
|
||||
elif parts[1] == "endpoints":
|
||||
threading.Thread(target=self.run_fuzz_endpoints, daemon=True).start()
|
||||
elif parts[1] == "params" and len(parts) >= 3:
|
||||
threading.Thread(target=self.run_fuzz_params, args=(parts[2],), daemon=True).start()
|
||||
elif parts[1] == "auth":
|
||||
threading.Thread(target=self.run_fuzz_auth, daemon=True).start()
|
||||
elif parts[1] == "stop":
|
||||
if self.fuzzer:
|
||||
self.fuzzer.stop()
|
||||
log("Fuzzer stopped", C_INFO)
|
||||
elif parts[1] == "results":
|
||||
if self.fuzzer:
|
||||
self.fuzzer.save_results()
|
||||
else:
|
||||
log("No fuzzer results", C_ERROR)
|
||||
else:
|
||||
log("Usage: fuzz endpoints|params <ep>|auth|stop|results", C_ERROR)
|
||||
|
||||
elif c == "inject":
|
||||
if len(parts) < 3:
|
||||
log("Usage: inject udp <ip> <port> <hex_payload>", C_ERROR)
|
||||
log(" inject arp_reply <src_ip> <dst_ip>", C_ERROR)
|
||||
log(" inject dns_query <domain>", C_ERROR)
|
||||
elif parts[1] == "udp" and len(parts) >= 5:
|
||||
packet.inject(self.cfg, {
|
||||
"type": "udp", "dst_ip": parts[2],
|
||||
"dst_port": int(parts[3]),
|
||||
"payload": " ".join(parts[4:]),
|
||||
"payload_hex": True,
|
||||
})
|
||||
elif parts[1] == "arp_reply" and len(parts) >= 4:
|
||||
packet.inject(self.cfg, {
|
||||
"type": "arp_reply",
|
||||
"src_ip": parts[2], "dst_ip": parts[3],
|
||||
})
|
||||
elif parts[1] == "dns_query" and len(parts) >= 3:
|
||||
packet.inject(self.cfg, {"type": "dns_query", "domain": parts[2]})
|
||||
else:
|
||||
log("inject: invalid args", C_ERROR)
|
||||
|
||||
elif c == "clear":
|
||||
with lock:
|
||||
log_lines.clear()
|
||||
|
||||
elif c in ("quit", "q", "exit"):
|
||||
if self.services_running:
|
||||
self.stop_services()
|
||||
self.running = False
|
||||
|
||||
else:
|
||||
log(f"Unknown: {cmd}. Type 'help'.", C_ERROR)
|
||||
|
||||
|
||||
HELP = """
|
||||
── MITM Services ──────────────────────────────────────────
|
||||
start Start all MITM services
|
||||
stop Stop all services, restore ARP
|
||||
status Show running services
|
||||
|
||||
── Configuration ──────────────────────────────────────────
|
||||
config Show all settings
|
||||
set <key> <value> Set config (camera_ip, our_ip, router_ip, iface,
|
||||
camera_mac, api_email, api_password, rest_port,
|
||||
fuzzer_threads, fuzzer_delay)
|
||||
save Save config to disk
|
||||
|
||||
── UBox Cloud API ─────────────────────────────────────────
|
||||
login Authenticate to UBox cloud
|
||||
devices List devices (leaks creds!)
|
||||
firmware Check firmware version
|
||||
services Query device services
|
||||
families List account families
|
||||
api <endpoint> Raw POST to any API endpoint
|
||||
|
||||
── Fuzzer ─────────────────────────────────────────────────
|
||||
fuzz endpoints Discover hidden API endpoints
|
||||
fuzz params <ep> Fuzz parameters on endpoint
|
||||
fuzz auth Test authentication bypass
|
||||
fuzz stop Stop running fuzzer
|
||||
fuzz results Save fuzzer results to file
|
||||
|
||||
── Packet Injection ───────────────────────────────────────
|
||||
inject udp <ip> <port> <hex> Send UDP packet
|
||||
inject arp_reply <src> <dst> Send spoofed ARP reply
|
||||
inject dns_query <domain> Send DNS query
|
||||
|
||||
── General ────────────────────────────────────────────────
|
||||
clear Clear log
|
||||
help Show this help
|
||||
quit Exit
|
||||
|
||||
── REST API (for external tools) ──────────────────────────
|
||||
Runs on port 9090 (configurable via 'set rest_port')
|
||||
GET /status, /logs, /devices, /config, /fuzz/results
|
||||
POST /start, /stop, /config, /command, /api,
|
||||
/fuzz/endpoints, /fuzz/params, /fuzz/auth, /inject
|
||||
""".strip()
|
||||
|
||||
|
||||
def curses_main(stdscr, ctrl):
|
||||
curses.curs_set(1)
|
||||
curses.start_color()
|
||||
curses.use_default_colors()
|
||||
curses.init_pair(1, curses.COLOR_RED, -1)
|
||||
curses.init_pair(2, curses.COLOR_GREEN, -1)
|
||||
curses.init_pair(3, curses.COLOR_CYAN, -1)
|
||||
curses.init_pair(4, curses.COLOR_YELLOW, -1)
|
||||
curses.init_pair(5, curses.COLOR_MAGENTA, -1)
|
||||
curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_WHITE)
|
||||
curses.init_pair(7, curses.COLOR_BLACK, curses.COLOR_GREEN)
|
||||
|
||||
stdscr.nodelay(True)
|
||||
stdscr.keypad(True)
|
||||
input_buf = ""
|
||||
scroll_offset = 0
|
||||
cmd_history = deque(maxlen=50)
|
||||
hist_idx = -1
|
||||
|
||||
log("SetecSuite — Camera MITM Tool", C_INFO)
|
||||
log(f"Target: {ctrl.cfg['camera_ip']} Us: {ctrl.cfg['our_ip']} Router: {ctrl.cfg['router_ip']}", C_INFO)
|
||||
log(f"REST API on :{ctrl.cfg['rest_port']} | Type 'help' for commands", C_INFO)
|
||||
log("", C_NONE)
|
||||
|
||||
# Start REST API server
|
||||
threading.Thread(target=rest_server.start_server,
|
||||
args=(ctrl, ctrl.cfg["rest_port"]), daemon=True).start()
|
||||
|
||||
while ctrl.running:
|
||||
try:
|
||||
h, w = stdscr.getmaxyx()
|
||||
if h < 5 or w < 40:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
stdscr.erase()
|
||||
|
||||
# ─── Title bar ────────────────────────────────
|
||||
title = " SetecSuite MITM "
|
||||
parts = []
|
||||
if ctrl.services_running:
|
||||
parts.append("MITM:ON")
|
||||
else:
|
||||
parts.append("MITM:OFF")
|
||||
for k, v in ctrl.flags.items():
|
||||
parts.append(f"{k}:{'✓' if v else '✗'}")
|
||||
if ctrl.cfg["api_token"]:
|
||||
parts.append("API:✓")
|
||||
if ctrl.cfg["device_uid"]:
|
||||
parts.append(f"UID:{ctrl.cfg['device_uid'][:8]}")
|
||||
|
||||
bar = f"{title}| {' | '.join(parts)} "
|
||||
pair = 7 if ctrl.services_running else 6
|
||||
try:
|
||||
stdscr.addstr(0, 0, bar.ljust(w)[:w], curses.color_pair(pair) | curses.A_BOLD)
|
||||
except curses.error:
|
||||
pass
|
||||
|
||||
# ─── Log area ─────────────────────────────────
|
||||
log_h = h - 3
|
||||
with lock:
|
||||
visible = list(log_lines)
|
||||
total = len(visible)
|
||||
if scroll_offset > max(0, total - log_h):
|
||||
scroll_offset = max(0, total - log_h)
|
||||
|
||||
start_idx = max(0, total - log_h - scroll_offset)
|
||||
end_idx = start_idx + log_h
|
||||
|
||||
for i, idx in enumerate(range(start_idx, min(end_idx, total))):
|
||||
line, color = visible[idx]
|
||||
y = i + 1
|
||||
if y >= h - 2:
|
||||
break
|
||||
try:
|
||||
display = line[:w - 1]
|
||||
attr = curses.color_pair(color) if color else 0
|
||||
stdscr.addstr(y, 0, display, attr)
|
||||
except curses.error:
|
||||
pass
|
||||
|
||||
# ─── Separator ────────────────────────────────
|
||||
try:
|
||||
stdscr.addstr(h - 2, 0, "─" * (w - 1), curses.color_pair(3))
|
||||
except curses.error:
|
||||
pass
|
||||
|
||||
# ─── Input ────────────────────────────────────
|
||||
prompt = "❯ "
|
||||
try:
|
||||
stdscr.addstr(h - 1, 0, prompt, curses.color_pair(2) | curses.A_BOLD)
|
||||
stdscr.addstr(h - 1, len(prompt), input_buf[:w - len(prompt) - 1])
|
||||
stdscr.move(h - 1, min(len(prompt) + len(input_buf), w - 1))
|
||||
except curses.error:
|
||||
pass
|
||||
|
||||
stdscr.refresh()
|
||||
|
||||
# ─── Key handling ─────────────────────────────
|
||||
try:
|
||||
ch = stdscr.getch()
|
||||
except:
|
||||
ch = -1
|
||||
|
||||
if ch == -1:
|
||||
time.sleep(0.04)
|
||||
continue
|
||||
elif ch in (10, 13): # Enter
|
||||
if input_buf.strip():
|
||||
cmd_history.appendleft(input_buf)
|
||||
hist_idx = -1
|
||||
ctrl.process_command(input_buf)
|
||||
input_buf = ""
|
||||
scroll_offset = 0
|
||||
elif ch == 27: # Escape
|
||||
input_buf = ""
|
||||
hist_idx = -1
|
||||
elif ch in (curses.KEY_BACKSPACE, 127, 8):
|
||||
input_buf = input_buf[:-1]
|
||||
elif ch == curses.KEY_UP:
|
||||
if cmd_history:
|
||||
hist_idx = min(hist_idx + 1, len(cmd_history) - 1)
|
||||
input_buf = cmd_history[hist_idx]
|
||||
elif ch == curses.KEY_DOWN:
|
||||
if hist_idx > 0:
|
||||
hist_idx -= 1
|
||||
input_buf = cmd_history[hist_idx]
|
||||
elif hist_idx == 0:
|
||||
hist_idx = -1
|
||||
input_buf = ""
|
||||
elif ch == curses.KEY_PPAGE:
|
||||
scroll_offset = min(scroll_offset + log_h, max(0, total - log_h))
|
||||
elif ch == curses.KEY_NPAGE:
|
||||
scroll_offset = max(0, scroll_offset - log_h)
|
||||
elif ch == curses.KEY_HOME:
|
||||
scroll_offset = max(0, total - log_h)
|
||||
elif ch == curses.KEY_END:
|
||||
scroll_offset = 0
|
||||
elif 32 <= ch < 127:
|
||||
input_buf += chr(ch)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
if ctrl.services_running:
|
||||
ctrl.stop_services()
|
||||
ctrl.running = False
|
||||
except Exception as e:
|
||||
log(f"UI: {e}", C_ERROR)
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
def main():
|
||||
if os.geteuid() != 0:
|
||||
print("Run with: sudo python3 mitm.py")
|
||||
sys.exit(1)
|
||||
|
||||
ctrl = Controller()
|
||||
os.makedirs(ctrl.cfg["log_dir"], exist_ok=True)
|
||||
init_logfile(f"{ctrl.cfg['log_dir']}/mitm.log")
|
||||
|
||||
signal.signal(signal.SIGINT, lambda s, f: None) # Let curses handle it
|
||||
|
||||
try:
|
||||
curses.wrapper(lambda stdscr: curses_main(stdscr, ctrl))
|
||||
finally:
|
||||
if ctrl.services_running:
|
||||
ctrl.stop_services()
|
||||
close_logfile()
|
||||
print(f"\nLogs: {ctrl.cfg['log_dir']}/")
|
||||
print(f"Config: {Config.CONFIG_FILE if hasattr(Config, 'CONFIG_FILE') else 'config.json'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user