337 lines
18 KiB
Markdown
337 lines
18 KiB
Markdown
|
|
# 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](https://repo.seteclabs.io/SetecLabs/cam-mitm)** is the specialised camera-research case study that produced [camhak.seteclabs.io](https://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](#what-it-does)
|
||
|
|
2. [Architecture](#architecture)
|
||
|
|
3. [Requirements](#requirements)
|
||
|
|
4. [Install](#install)
|
||
|
|
5. [Quick start](#quick-start)
|
||
|
|
6. [Configuration](#configuration)
|
||
|
|
7. [The GUI](#the-gui)
|
||
|
|
8. [Services](#services)
|
||
|
|
9. [Writing a target plugin](#writing-a-target-plugin)
|
||
|
|
10. [Headless / curses mode](#headless--curses-mode)
|
||
|
|
11. [REST API](#rest-api)
|
||
|
|
12. [Logs and captures](#logs-and-captures)
|
||
|
|
13. [Troubleshooting](#troubleshooting)
|
||
|
|
14. [Legal](#legal)
|
||
|
|
15. [License](#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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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:
|
||
|
|
|
||
|
|
```python
|
||
|
|
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:
|
||
|
|
|
||
|
|
```bash
|
||
|
|
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.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Legal
|
||
|
|
|
||
|
|
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](https://camhak.seteclabs.io) research project.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## See also
|
||
|
|
|
||
|
|
- **[cam-mitm](https://repo.seteclabs.io/SetecLabs/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](https://camhak.seteclabs.io)** — the published research report (20 findings, 3 CVEs)
|
||
|
|
- **[seteclabs.io](https://seteclabs.io)** — the lab
|