Files
setec-mitm/README.md
sssnake 20e7eb343d Initial commit — SetecMITM generic IoT MITM framework
Templated from cam-mitm. The camera-specific code (UBox cloud client,
CVE verifiers, OAM HMAC signing, fuzzer wordlists) is removed; what's
left is the generic core: ARP spoof, DNS spoof, HTTP/HTTPS interception
with peek-before-wrap, raw sniffer with conntrack-based original-dst
lookup, protocol fingerprinting, intruder detection, packet injection,
log rotation, PyQt6 GUI on top of a service Controller.

All 'camera' references renamed to 'target' throughout. Configuration
moved into ~/.config/setec-mitm/config.json with the Settings tab as
the primary editor. Plugin system at targets/<name>/plugin.py for
vendor-specific code.

See README.md for full setup, plugin authoring, and troubleshooting.

Co-authored by Setec Labs.
2026-04-09 08:38:59 -07:00

18 KiB

SetecMITM

Generic LAN-side MITM framework for any IoT or cloud-connected device.

A drop-in toolkit for ARP spoofing, DNS hijacking, HTTP/HTTPS interception with auto-generated certs, raw packet sniffing with original-destination lookup and protocol fingerprinting, UDP capture, intruder detection, and packet injection — packaged with a PyQt6 GUI on top of a service supervisor that lets you toggle each component independently.

Built for authorized security research on hardware you own. The framework is target-agnostic; vendor-specific code (cloud API clients, fuzz wordlists, CVE verifiers, custom protocol decoders) lives in targets/<name>/ plugins, so you can re-aim the same toolkit at any device without forking the core.

This is the generic version. The companion repository cam-mitm is the specialised camera-research case study that produced camhak.seteclabs.io — a 20-finding teardown of a UBIA-rebrand IP camera. Both share the same core; cam-mitm just bundles the camera-specific plugin.


Table of Contents

  1. What it does
  2. Architecture
  3. Requirements
  4. Install
  5. Quick start
  6. Configuration
  7. The GUI
  8. Services
  9. Writing a target plugin
  10. Headless / curses mode
  11. REST API
  12. Logs and captures
  13. Troubleshooting
  14. Legal
  15. License

What it does

SetecMITM positions itself as the gateway for one specific device on your LAN, intercepts everything that device sends, and lets you observe / log / fingerprint / replay / mutate the traffic. Specifically:

  • ARP poisoning — tells the target that you are the gateway, with auto-cleanup on exit so you don't strand anything when you're done.
  • Selective DNS spoof — answers DNS queries from the target with your IP. Spoof everything, or whitelist a few domains.
  • HTTP/HTTPS MITM — listens on :80 and :443, accepts the redirected traffic, peeks at the first bytes before wrapping in TLS so non-TLS traffic on :443 doesn't get lost. Auto-generates a cert with the right SAN list (regenerable via regen_cert.sh).
  • Raw packet sniffer — sees every packet on the interface, looks up the original destination via conntrack (so you know what the target was actually trying to reach before iptables redirected it), and labels each packet with a protocol guess from the first 6 bytes.
  • UDP listeners — bind to arbitrary UDP ports to catch P2P / push traffic. Configurable per target.
  • Intruder detection — flags ARP-spoof attempts against your target, unknown LAN hosts contacting it, and outbound destinations not on your "expected cloud" whitelist. Useful for catching a third party already on the device.
  • Packet injection — UDP, ARP, DNS. Used for crafting tests and simulating traffic.
  • Plugin system — drop a targets/<name>/plugin.py to add vendor-specific endpoints, DNS hosts, UDP ports, and CVE verifiers.

Architecture

┌─────────────────────────────────────────────────────────┐
│  PyQt6 GUI  (gui.py)        │  curses TUI  (mitm.py)    │
└──────────┬──────────────────┴──────────┬────────────────┘
           │                             │
           ▼                             ▼
        ┌─────────────────────────────────────┐
        │           Controller                │
        │  (per-service start/stop, iptables) │
        └──┬───────────────────────────────┬──┘
           │                               │
           ▼                               ▼
   ┌───────────────┐               ┌──────────────────┐
   │  services/    │               │  targets/<name>/ │
   │  - arp_spoof  │               │  - plugin.py     │
   │  - dns_spoof  │               │    (optional)    │
   │  - http_server│               └──────────────────┘
   │  - udp_listen │
   │  - sniffer    │               ┌──────────────────┐
   │  - intruder_w │               │  utils/          │
   └───────────────┘               │  - log (1GB rot) │
                                   │  - proto (fp)    │
                                   └──────────────────┘
  • Controller is the only thing that touches iptables. It supervises a fixed set of services (arp, dns, http, https, sniffer, intruder) plus any number of UDP listeners. Each service runs in its own thread and is independently start/stoppable.
  • Services are dumb threads. They read config, do their job, and write to the shared log buffer.
  • Targets are optional plugins. If target_plugin = "foo" is set in the config, the Controller imports targets/foo/plugin.py at startup and gives the plugin an opportunity to register endpoints, DNS rules, UDP ports, and protocol detectors.
  • GUI is a thin layer over the Controller. The same Controller works headless (mitm.py) for unattended deployments.

Requirements

  • Linux. Tested on Ubuntu 22.04 / 24.04 ARM64 and x86_64.
  • Python 3.10+ (the system Python — /usr/bin/python3 on Debian-derivatives).
  • PyQt6 (for the GUI). On Debian/Ubuntu: sudo apt install python3-pyqt6.
  • Standard userland: iptables, openssl, conntrack (recommended for original-destination lookup), arp-scan (optional).
  • Root access. The framework binds raw sockets and modifies the firewall — there is no way around sudo.

No external Python packages required beyond PyQt6 — the rest is standard library.


Install

git clone https://repo.seteclabs.io/SetecLabs/setec-mitm
cd setec-mitm
sudo apt install python3-pyqt6 conntrack openssl iptables

That's it. There is nothing to compile and nothing to pip install.


Quick start

cd /path/to/setec-mitm
sudo /usr/bin/python3 gui.py

Then in the GUI:

  1. Open the Settings tab.
  2. Fill in the target's IP, the target's MAC, the IP of this box, the gateway IP, and the network interface name. Save Config.
  3. Open the Dashboard.
  4. Click ▶ START ALL — or click each service row individually to bring them up one at a time.
  5. Switch to Live Log to watch traffic stream in real time.
  6. Power-cycle the target so its boot-time traffic gets captured.

Stop everything with ⏹ STOP ALL, which also restores ARP and removes the iptables rules.


Configuration

Config lives at ~/.config/setec-mitm/config.json. The Settings tab in the GUI is the easiest way to edit it; the file is plain JSON if you'd rather use an editor.

Key Default Notes
target_ip (empty) REQUIRED. IP of the device under test.
target_mac (empty) REQUIRED. MAC of the device under test.
our_ip (empty) REQUIRED. IP of THIS box. The MITM host.
router_ip (empty) REQUIRED. Gateway IP for the LAN.
iface (empty) REQUIRED. Network interface name (e.g. eth0, wlan0, enP4p65s0).
log_dir ~/setec_mitm_logs Where capture files and the rotating log live.
log_max_bytes 1073741824 1 GiB. Log file rotates above this size.
auto_arp true Start ARP service when "START ALL" is hit.
auto_dns true Same, for DNS spoof.
auto_http true Same, for HTTP.
auto_https true Same, for HTTPS.
auto_sniffer true Same, for sniffer.
auto_intruder true Same, for intruder watch.
auto_udp_ports [] List of UDP ports to listen on (e.g. [10240, 8000]).
dns_spoof_only [] If non-empty, only spoof these hostnames. Empty = spoof all.
intruder_known_nets [] CIDRs the target is expected to talk to. Anything outside flagged.
rest_port 9090 REST API port for external tool integration.
target_plugin "" Plugin name under targets/. Optional.

When run with sudo, paths starting with ~ resolve to /root because the env's HOME is the root user's. Use absolute paths in the config if you want files to land in your normal user's home.


The GUI

Six tabs:

Dashboard

Big START/STOP buttons. The "Services" group has a clickable button per service — click any one to toggle it independently. The "Protocols Seen" panel shows live counts of every protocol the sniffer has fingerprinted (TLS, HTTP, RTSP, IOTC, STUN, DNS, NTP, etc.). The "Target" panel shows the four IPs and the MAC.

Live Log

Color-coded scrolling log of every event from every service. Substring filter (live). Toggleable Autoscroll — uncheck it to read history while traffic continues to append silently below.

Intruders

Table of every suspicious event the intruder watcher has flagged. Three kinds:

  • ARP_SPOOF — Someone other than the real target is sending ARP replies claiming to be the target's IP. Either you're being attacked, or another tool is in the way, or you ARP-spoofed yourself.
  • LAN_PEER — A LAN host that isn't you, the gateway, or the target is exchanging traffic with the target. Worth investigating.
  • UNKNOWN_DST — The target reached out to an internet host not in your intruder_known_nets whitelist. Useful for catching the device phoning home to a new C2 / new vendor cloud.

Inject

Forms for crafting and sending raw UDP, ARP, and DNS packets. UDP takes a hex payload. ARP_REPLY takes a spoofed source IP. DNS_QUERY takes a domain name.

Settings

Every config key, in a form. Save Config to persist. Reload From Disk to discard unsaved changes.

Help

Embedded short version of this README.


Services

Service What it does Notes
arp ARP cache poisoning of the target so the target thinks we are the gateway. Auto-cleanup on stop. Required for any other service to actually intercept traffic.
dns Listens on :53/udp. Answers DNS queries from the target with our IP (or only specific hosts if dns_spoof_only is set). Catches the cloud lookups before they leave the LAN.
http Listens on :80/tcp. Logs request line + headers + body. iptables NAT redirect from the target's traffic to us.
https Listens on :443/tcp. Peeks at first bytes before wrapping TLS — so non-TLS traffic on :443 doesn't get lost. Uses ~/setec_mitm_logs/mitm_cert.pem and mitm_key.pem. Regenerate with regen_cert.sh if you need a different SAN list.
udp_listen Listens on configurable UDP ports. Logs every packet with hex dump and basic magic-byte detection. List the ports in auto_udp_ports.
sniffer Raw socket sniffer on the configured interface. For each packet sent by the target, looks up the pre-NAT original destination via conntrack -L --src ... --sport ... -p tcp/udp, fingerprints the protocol from the first 6 bytes of the payload, and logs both. Requires conntrack (Debian: apt install conntrack). Gracefully degrades to "orig=?" if missing.
intruder_watch Same raw socket as the sniffer. Detects ARP_SPOOF / LAN_PEER / UNKNOWN_DST events as defined above. The whitelist for "expected cloud" comes from intruder_known_nets in your config plus the plugin's KNOWN_CLOUD_NETS.

Writing a target plugin

Plugins let you bundle vendor-specific knowledge without forking the core.

The minimal plugin is a single file at targets/<your_name>/plugin.py containing a Plugin class. Look at targets/example/plugin.py for the full template. The fields you'll likely set:

class Plugin:
    NAME = "myvendor"
    DESCRIPTION = "MyVendor IP camera (rebrand of XYZ)"

    KNOWN_CLOUD_NETS = [
        ("203.0.113.0", 24),    # vendor's API
        ("198.51.100.0", 23),   # P2P relay
    ]

    DNS_SPOOF_HOSTS = [
        "api.myvendor.com",
        "p2p.myvendor.com",
    ]

    UDP_PORTS = [10240, 8000]

    KNOWN_API_ENDPOINTS = [
        "/api/v1/login",
        "/api/v1/devices",
        "/api/v1/firmware/check",
    ]

    def __init__(self, cfg):
        self.cfg = cfg

    def on_start(self): pass
    def on_stop(self): pass
    def custom_http_handler(self, request): return None
    def detect_protocol(self, payload_first_bytes): return None

Then in the GUI Settings tab set target_plugin = "myvendor" and restart. The Controller will import it and the plugin's known cloud nets and DNS hosts will be added to the framework's defaults.

For richer plugins (vendor cloud client, CVE verifiers, fuzzer wordlists), add modules alongside plugin.py:

targets/myvendor/
├── __init__.py
├── plugin.py             # required
├── client.py             # optional — wraps the vendor cloud API
├── cve_checks.py         # optional — original PoC verifiers
├── fuzzer_endpoints.py   # optional — KNOWN_ENDPOINTS list for the fuzzer
└── README.md             # what's this device, what works, what doesn't

The cam-mitm repo is the reference example: it has targets/javiscam_2604/ with client.py (UBox cloud + OAM HMAC signing), cve_checks.py (CVE-2025-12636, CVE-2021-28372, CVE-2023-6322 chain), firmware_fetch.py, and ota_bucket_probe.py. Worth reading.


Headless / curses mode

If you don't want the PyQt6 GUI:

sudo /usr/bin/python3 mitm.py

This runs the Controller directly, starts every service that has auto_* set to true, and blocks until SIGINT/SIGTERM. Useful for unattended deployments.

A full curses TUI is also available in the cam-mitm repo (the camera-specific fork). Drop mitm.py from there into this directory if you want command-line interactive control.


REST API

The Controller exposes a small REST API on 127.0.0.1:9090 (configurable via rest_port). Endpoints:

Method Path Description
GET /status Service status, flags, config snapshot
GET /logs?count=N Recent log entries
GET /config Current configuration
POST /start Start all services
POST /stop Stop all services
POST /config Update config: {"key": "value"}
POST /inject Send packet: {"type": "udp", "dst_ip": "...", ...}

Useful for AI-assisted automated testing or integration with other tools. Note: the REST API binds to 127.0.0.1 only — never expose it to a network you don't control.


Logs and captures

Default log directory: ~/setec_mitm_logs/ (or /root/setec_mitm_logs/ when run via sudo — set log_dir to an absolute path if you want it elsewhere).

File What
setec_mitm.log Main log file. Rotates at 1 GiB to setec_mitm.log.YYYYMMDD_HHMMSS.
mitm_cert.pem / mitm_key.pem Auto-generated TLS cert/key for HTTPS interception. Regen with regen_cert.sh.
raw_443_<ip>_<ts>.bin Raw bytes captured when non-TLS traffic hit the HTTPS listener.
raw_tls_fail_<ip>_<ts>.bin First-bytes capture of any TLS connection that failed handshake (e.g. cert pinning).
sniff_udp<port>_<sport>_<ts>.bin UDP payloads captured by the sniffer.
udp<port>_<addr>_<sport>_<ts>.bin UDP listener captures.

Troubleshooting

"target_ip and our_ip must be set" — Open the Settings tab and fill in the four required network fields. Save. Try START ALL again.

HTTPS shows SSL fail / wrong version number — That's the framework correctly detecting non-TLS traffic on :443. The first 8 bytes are dumped in the log; check what protocol the target is actually speaking. The HTTPS listener already peeks before wrapping, so this shouldn't crash anything — but if you keep getting it, the bytes file at raw_443_*.bin will tell you what the device is doing.

Camera is unreachable after starting MITM — Either you misconfigured our_ip (you set it to a different host on the LAN) or router_ip (so the target's traffic is being routed to a dead end). Stop everything, fix the config, restart. ARP poison is auto-restored on stop.

No traffic in the sniffer at all — Verify ARP is actually working: arp -an | grep <target_ip> should show your MAC, not the router's. If it shows the router, the ARP service isn't running, or iface is wrong.

conntrack not installed — Sniffer logs will show orig=? instead of the pre-NAT destination. Not fatal, but install it: sudo apt install conntrack.

fuser: not found — Install psmisc: sudo apt install psmisc. The framework uses fuser -k to free a stuck listener port.

Custom Python build doesn't have curses or PyQt6 — The framework requires the system Python. Always launch with /usr/bin/python3 gui.py, never with a /usr/local/bin/python3 from a manual build that's missing modules.


This tool is intended for authorized security testing on devices you own. Unauthorized interception of network traffic is illegal in most jurisdictions. Always obtain proper authorization before testing.

The authors take no responsibility for misuse. Don't be an idiot.


License

MIT. See LICENSE. Originally developed by Setec Labs as part of the Camhak research project.


See also

  • cam-mitm — the camera-specific fork with the full Javiscam/UBox plugin, the CVE verifiers, the OAM HMAC client, and the fuzzer with a 146-endpoint wordlist
  • camhak.seteclabs.io — the published research report (20 findings, 3 CVEs)
  • seteclabs.io — the lab