diff --git a/intercept.py b/intercept.py
index bcf49f3..df617e4 100755
--- a/intercept.py
+++ b/intercept.py
@@ -18,15 +18,101 @@ app = Flask(__name__)
# Global process management
current_process = None
sensor_process = None
+wifi_process = None
+kismet_process = None
+bt_process = None
output_queue = queue.Queue()
sensor_queue = queue.Queue()
+wifi_queue = queue.Queue()
+bt_queue = queue.Queue()
process_lock = threading.Lock()
sensor_lock = threading.Lock()
+wifi_lock = threading.Lock()
+bt_lock = threading.Lock()
# Logging settings
logging_enabled = False
log_file_path = 'pager_messages.log'
+# WiFi state
+wifi_monitor_interface = None
+wifi_networks = {} # BSSID -> network info
+wifi_clients = {} # Client MAC -> client info
+wifi_handshakes = [] # Captured handshakes
+
+# Bluetooth state
+bt_interface = None
+bt_devices = {} # MAC -> device info
+bt_beacons = {} # MAC -> beacon info (AirTags, Tiles, iBeacons)
+bt_services = {} # MAC -> list of services
+
+# Known beacon prefixes for detection
+AIRTAG_PREFIXES = ['4C:00'] # Apple continuity
+TILE_PREFIXES = ['C4:E7', 'DC:54', 'E4:B0', 'F8:8A']
+SAMSUNG_TRACKER = ['58:4D', 'A0:75']
+
+# OUI Database for manufacturer lookup (common ones)
+OUI_DATABASE = {
+ '00:00:0A': 'Omron',
+ '00:1A:7D': 'Cyber-Blue',
+ '00:1E:3D': 'Alps Electric',
+ '00:1F:20': 'Logitech',
+ '00:25:DB': 'Apple',
+ '04:52:F3': 'Apple',
+ '0C:3E:9F': 'Apple',
+ '10:94:BB': 'Apple',
+ '14:99:E2': 'Apple',
+ '20:78:F0': 'Apple',
+ '28:6A:BA': 'Apple',
+ '3C:22:FB': 'Apple',
+ '40:98:AD': 'Apple',
+ '48:D7:05': 'Apple',
+ '4C:57:CA': 'Apple',
+ '54:4E:90': 'Apple',
+ '5C:97:F3': 'Apple',
+ '60:F8:1D': 'Apple',
+ '68:DB:CA': 'Apple',
+ '70:56:81': 'Apple',
+ '78:7B:8A': 'Apple',
+ '7C:D1:C3': 'Apple',
+ '84:FC:FE': 'Apple',
+ '8C:2D:AA': 'Apple',
+ '90:B0:ED': 'Apple',
+ '98:01:A7': 'Apple',
+ '98:D6:BB': 'Apple',
+ 'A4:D1:D2': 'Apple',
+ 'AC:BC:32': 'Apple',
+ 'B0:34:95': 'Apple',
+ 'B8:C1:11': 'Apple',
+ 'C8:69:CD': 'Apple',
+ 'D0:03:4B': 'Apple',
+ 'DC:A9:04': 'Apple',
+ 'E0:C7:67': 'Apple',
+ 'F0:18:98': 'Apple',
+ 'F4:5C:89': 'Apple',
+ '00:1B:66': 'Samsung',
+ '00:21:19': 'Samsung',
+ '00:26:37': 'Samsung',
+ '5C:0A:5B': 'Samsung',
+ '8C:71:F8': 'Samsung',
+ 'C4:73:1E': 'Samsung',
+ '38:2C:4A': 'Samsung',
+ '00:1E:4C': 'Samsung',
+ '64:B5:C6': 'Liteon/Google',
+ '54:60:09': 'Google',
+ '00:1A:11': 'Google',
+ 'F4:F5:D8': 'Google',
+ '94:EB:2C': 'Google',
+ '78:4F:43': 'Apple',
+ 'F8:E4:E3': 'Tile',
+ 'C4:E7:BE': 'Tile',
+ 'E0:E5:CF': 'Raspberry Pi',
+ 'B8:27:EB': 'Raspberry Pi',
+ 'DC:A6:32': 'Raspberry Pi',
+ '00:0B:57': 'Silicon Wave', # BT Chips
+ '00:02:72': 'CC&C', # BT dongles
+}
+
HTML_TEMPLATE = '''
@@ -734,6 +820,386 @@ HTML_TEMPLATE = '''
color: var(--accent-cyan);
}
+ /* Recon Dashboard - Prominent Device Intelligence */
+ .recon-panel {
+ background: var(--bg-card);
+ border: 1px solid var(--border-color);
+ margin: 15px;
+ margin-bottom: 10px;
+ position: relative;
+ }
+
+ .recon-panel::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 2px;
+ background: linear-gradient(90deg, var(--accent-orange), var(--accent-cyan), transparent);
+ }
+
+ .recon-panel.collapsed .recon-content {
+ display: none;
+ }
+
+ .recon-header {
+ padding: 12px 15px;
+ background: var(--bg-secondary);
+ border-bottom: 1px solid var(--border-color);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .recon-header h4 {
+ color: var(--accent-orange);
+ font-size: 11px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ margin: 0;
+ }
+
+ .recon-stats {
+ display: flex;
+ gap: 15px;
+ font-size: 10px;
+ font-family: 'JetBrains Mono', monospace;
+ }
+
+ .recon-stats span {
+ color: var(--accent-cyan);
+ }
+
+ .recon-content {
+ max-height: 300px;
+ overflow-y: auto;
+ }
+
+ .device-row {
+ display: grid;
+ grid-template-columns: 1fr auto auto auto;
+ gap: 10px;
+ padding: 10px 15px;
+ border-bottom: 1px solid var(--border-color);
+ font-size: 11px;
+ align-items: center;
+ transition: background 0.2s ease;
+ }
+
+ .device-row:hover {
+ background: var(--bg-secondary);
+ }
+
+ .device-row.anomaly {
+ border-left: 3px solid var(--accent-red);
+ background: rgba(255, 51, 102, 0.05);
+ }
+
+ .device-row.new-device {
+ border-left: 3px solid var(--accent-green);
+ background: rgba(0, 255, 136, 0.05);
+ }
+
+ .device-info {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ }
+
+ .device-name-row {
+ color: var(--text-primary);
+ font-weight: 500;
+ }
+
+ .device-id {
+ color: var(--text-dim);
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 10px;
+ }
+
+ .device-meta {
+ text-align: right;
+ color: var(--text-secondary);
+ font-family: 'JetBrains Mono', monospace;
+ }
+
+ .device-meta.encrypted {
+ color: var(--accent-green);
+ }
+
+ .device-meta.plaintext {
+ color: var(--accent-red);
+ }
+
+ .transmission-bar {
+ width: 60px;
+ height: 4px;
+ background: var(--border-color);
+ position: relative;
+ }
+
+ .transmission-bar-fill {
+ height: 100%;
+ background: var(--accent-cyan);
+ transition: width 0.3s ease;
+ }
+
+ .badge {
+ display: inline-block;
+ padding: 2px 6px;
+ font-size: 9px;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ border: 1px solid;
+ }
+
+ .badge.proto-pocsag { border-color: var(--accent-cyan); color: var(--accent-cyan); }
+ .badge.proto-flex { border-color: var(--accent-orange); color: var(--accent-orange); }
+ .badge.proto-433 { border-color: var(--accent-green); color: var(--accent-green); }
+ .badge.proto-unknown { border-color: var(--text-dim); color: var(--text-dim); }
+
+ .recon-toggle {
+ padding: 4px 8px;
+ background: transparent;
+ border: 1px solid var(--border-color);
+ color: var(--text-secondary);
+ cursor: pointer;
+ font-size: 9px;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ }
+
+ .recon-toggle:hover {
+ border-color: var(--accent-orange);
+ color: var(--accent-orange);
+ }
+
+ .recon-toggle.active {
+ border-color: var(--accent-orange);
+ color: var(--accent-orange);
+ background: rgba(255, 136, 0, 0.1);
+ }
+
+ .hex-dump {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 10px;
+ color: var(--text-dim);
+ background: var(--bg-primary);
+ padding: 8px;
+ margin-top: 8px;
+ border: 1px solid var(--border-color);
+ word-break: break-all;
+ }
+
+ .timeline-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: var(--accent-cyan);
+ display: inline-block;
+ margin-right: 5px;
+ }
+
+ .timeline-dot.recent { background: var(--accent-green); }
+ .timeline-dot.stale { background: var(--accent-orange); }
+ .timeline-dot.old { background: var(--text-dim); }
+
+ /* WiFi Visualizations */
+ .wifi-visuals {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 10px;
+ padding: 15px;
+ background: var(--bg-secondary);
+ margin: 0 15px 10px 15px;
+ border: 1px solid var(--border-color);
+ }
+
+ @media (max-width: 1200px) {
+ .wifi-visuals { grid-template-columns: 1fr; }
+ }
+
+ .wifi-visual-panel {
+ background: var(--bg-primary);
+ border: 1px solid var(--border-color);
+ padding: 10px;
+ position: relative;
+ }
+
+ .wifi-visual-panel h5 {
+ color: var(--accent-cyan);
+ font-size: 10px;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+ margin-bottom: 10px;
+ padding-bottom: 5px;
+ border-bottom: 1px solid var(--border-color);
+ }
+
+ /* Radar Display */
+ .radar-container {
+ position: relative;
+ width: 150px;
+ height: 150px;
+ margin: 0 auto;
+ }
+
+ #radarCanvas, #btRadarCanvas {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ background: radial-gradient(circle, #001515 0%, #000a0a 100%);
+ border: 1px solid var(--accent-cyan-dim);
+ }
+
+ #btRadarCanvas {
+ background: radial-gradient(circle, #150015 0%, #0a000a 100%);
+ border: 1px solid rgba(138, 43, 226, 0.3);
+ }
+
+ /* Channel Graph */
+ .channel-graph {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-around;
+ height: 60px;
+ padding: 5px 0;
+ border-bottom: 1px solid var(--border-color);
+ }
+
+ .channel-bar-wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ flex: 1;
+ }
+
+ .channel-bar {
+ width: 80%;
+ background: var(--border-color);
+ min-height: 2px;
+ transition: height 0.3s ease, background 0.3s ease;
+ }
+
+ .channel-bar.active {
+ background: var(--accent-cyan);
+ box-shadow: 0 0 5px var(--accent-cyan);
+ }
+
+ .channel-bar.congested {
+ background: var(--accent-orange);
+ }
+
+ .channel-bar.very-congested {
+ background: var(--accent-red);
+ }
+
+ .channel-label {
+ font-size: 8px;
+ color: var(--text-dim);
+ margin-top: 3px;
+ }
+
+ /* Security Donut */
+ .security-container {
+ display: flex;
+ align-items: center;
+ gap: 15px;
+ }
+
+ .security-donut {
+ width: 80px;
+ height: 80px;
+ flex-shrink: 0;
+ }
+
+ #securityCanvas {
+ width: 100%;
+ height: 100%;
+ }
+
+ .security-legend {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ font-size: 10px;
+ font-family: 'JetBrains Mono', monospace;
+ }
+
+ .security-legend-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ }
+
+ .security-legend-dot {
+ width: 10px;
+ height: 10px;
+ border-radius: 2px;
+ }
+
+ .security-legend-dot.wpa3 { background: var(--accent-green); }
+ .security-legend-dot.wpa2 { background: var(--accent-orange); }
+ .security-legend-dot.wep { background: var(--accent-red); }
+ .security-legend-dot.open { background: var(--accent-cyan); }
+
+ /* Signal Strength Meter */
+ .signal-strength-display {
+ text-align: center;
+ padding: 5px;
+ }
+
+ .target-ssid {
+ font-size: 11px;
+ color: var(--text-secondary);
+ margin-bottom: 5px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .signal-value {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 28px;
+ color: var(--accent-cyan);
+ text-shadow: 0 0 10px var(--accent-cyan-dim);
+ }
+
+ .signal-value.weak { color: var(--accent-red); text-shadow: 0 0 10px rgba(255,51,102,0.4); }
+ .signal-value.medium { color: var(--accent-orange); text-shadow: 0 0 10px rgba(255,136,0,0.4); }
+ .signal-value.strong { color: var(--accent-green); text-shadow: 0 0 10px rgba(0,255,136,0.4); }
+
+ .signal-bars-large {
+ display: flex;
+ justify-content: center;
+ align-items: flex-end;
+ gap: 3px;
+ height: 30px;
+ margin-top: 8px;
+ }
+
+ .signal-bar-large {
+ width: 8px;
+ background: var(--border-color);
+ transition: all 0.2s ease;
+ }
+
+ .signal-bar-large.active {
+ box-shadow: 0 0 5px currentColor;
+ }
+
+ .signal-bar-large.weak { background: var(--accent-red); }
+ .signal-bar-large.medium { background: var(--accent-orange); }
+ .signal-bar-large.strong { background: var(--accent-green); }
+
+ .signal-bar-large:nth-child(1) { height: 20%; }
+ .signal-bar-large:nth-child(2) { height: 40%; }
+ .signal-bar-large:nth-child(3) { height: 60%; }
+ .signal-bar-large:nth-child(4) { height: 80%; }
+ .signal-bar-large:nth-child(5) { height: 100%; }
+
/* Scanline effect overlay */
body::before {
content: '';
@@ -782,6 +1248,8 @@ HTML_TEMPLATE = '''
+
+
@@ -924,6 +1392,171 @@ HTML_TEMPLATE = '''
+
+
+
+
WiFi Interface
+
+
+
+
+
+ airmon-ng:Checking...
+ airodump-ng:Checking...
+ kismet:Checking...
+
+
+
+
+
Monitor Mode
+
+
+
+
+
+ Monitor mode: Inactive
+
+
+
+
+
+
+
Attack Options
+
+ ā Only use on authorized networks
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Bluetooth Interface
+
+
+
+
+
+ hcitool:Checking...
+ bluetoothctl:Checking...
+ ubertooth:Checking...
+ bettercap:Checking...
+
+
+
+
+
+
+
Device Actions
+
+
+
+
+
+
+
+
+
+
+
+
Attack Options
+
+ ā Authorized testing only
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -949,6 +1582,128 @@ HTML_TEMPLATE = '''
SENSORS: 0
DEVICES: 0
+
+
APs: 0
+
CLIENTS: 0
+
HANDSHAKES: 0
+
+
+
DEVICES: 0
+
BEACONS: 0
+
TRACKERS: 0
+
+
+
+
+
+
+
+
+
Channel Utilization (2.4 GHz)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Target Signal
+
+
No target selected
+
-- dBm
+
+
+
+
+
+
+
+
+
Bluetooth Proximity Radar
+
+
+
+
+
+
+
Manufacturer Breakdown
+
+
Scanning for devices...
+
+
+
+
Tracker Detection
+
+
+ Monitoring for AirTags, Tiles, and other trackers...
+
+
+
+
+
+
+
+
+
+
+ Device intelligence data will appear here as signals are intercepted.
+
@@ -968,10 +1723,12 @@ HTML_TEMPLATE = '''
Idle
+
+
@@ -994,12 +1751,32 @@ HTML_TEMPLATE = '''
function switchMode(mode) {
currentMode = mode;
document.querySelectorAll('.mode-tab').forEach(tab => {
- tab.classList.toggle('active', tab.textContent.toLowerCase().includes(mode === 'pager' ? 'pager' : '433'));
+ const tabText = tab.textContent.toLowerCase();
+ const isActive = (mode === 'pager' && tabText.includes('pager')) ||
+ (mode === 'sensor' && tabText.includes('433')) ||
+ (mode === 'wifi' && tabText.includes('wifi')) ||
+ (mode === 'bluetooth' && tabText === 'bt');
+ tab.classList.toggle('active', isActive);
});
document.getElementById('pagerMode').classList.toggle('active', mode === 'pager');
document.getElementById('sensorMode').classList.toggle('active', mode === 'sensor');
+ document.getElementById('wifiMode').classList.toggle('active', mode === 'wifi');
+ document.getElementById('bluetoothMode').classList.toggle('active', mode === 'bluetooth');
document.getElementById('pagerStats').style.display = mode === 'pager' ? 'flex' : 'none';
document.getElementById('sensorStats').style.display = mode === 'sensor' ? 'flex' : 'none';
+ document.getElementById('wifiStats').style.display = mode === 'wifi' ? 'flex' : 'none';
+ document.getElementById('btStats').style.display = mode === 'bluetooth' ? 'flex' : 'none';
+ document.getElementById('wifiVisuals').style.display = mode === 'wifi' ? 'grid' : 'none';
+ document.getElementById('btVisuals').style.display = mode === 'bluetooth' ? 'grid' : 'none';
+
+ // Load interfaces when switching modes
+ if (mode === 'wifi') {
+ refreshWifiInterfaces();
+ initRadar();
+ } else if (mode === 'bluetooth') {
+ refreshBtInterfaces();
+ initBtRadar();
+ }
}
// Track unique sensor devices
@@ -1699,6 +2476,1484 @@ HTML_TEMPLATE = '''
document.getElementById('flexCount').textContent = '0';
document.getElementById('sensorCount').textContent = '0';
document.getElementById('deviceCount').textContent = '0';
+
+ // Reset recon data
+ deviceDatabase.clear();
+ newDeviceAlerts = 0;
+ anomalyAlerts = 0;
+ document.getElementById('trackedCount').textContent = '0';
+ document.getElementById('newDeviceCount').textContent = '0';
+ document.getElementById('anomalyCount').textContent = '0';
+ document.getElementById('reconContent').innerHTML = 'Device intelligence data will appear here as signals are intercepted.
';
+ }
+
+ // ============== DEVICE INTELLIGENCE & RECONNAISSANCE ==============
+
+ // Device tracking database
+ const deviceDatabase = new Map(); // key: deviceId, value: device profile
+ let reconEnabled = localStorage.getItem('reconEnabled') === 'true';
+ let newDeviceAlerts = 0;
+ let anomalyAlerts = 0;
+
+ // Device profile structure
+ function createDeviceProfile(deviceId, protocol, firstSeen) {
+ return {
+ id: deviceId,
+ protocol: protocol,
+ firstSeen: firstSeen,
+ lastSeen: firstSeen,
+ transmissionCount: 1,
+ transmissions: [firstSeen], // timestamps of recent transmissions
+ avgInterval: null, // average time between transmissions
+ addresses: new Set(),
+ models: new Set(),
+ messages: [],
+ isNew: true,
+ anomalies: [],
+ signalStrength: [],
+ encrypted: null // null = unknown, true/false
+ };
+ }
+
+ // Analyze transmission patterns for anomalies
+ function analyzeTransmissions(profile) {
+ const anomalies = [];
+ const now = Date.now();
+
+ // Need at least 3 transmissions to analyze patterns
+ if (profile.transmissions.length < 3) {
+ return anomalies;
+ }
+
+ // Calculate intervals between transmissions
+ const intervals = [];
+ for (let i = 1; i < profile.transmissions.length; i++) {
+ intervals.push(profile.transmissions[i] - profile.transmissions[i-1]);
+ }
+
+ // Calculate average and standard deviation
+ const avg = intervals.reduce((a, b) => a + b, 0) / intervals.length;
+ profile.avgInterval = avg;
+
+ const variance = intervals.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / intervals.length;
+ const stdDev = Math.sqrt(variance);
+
+ // Check for burst transmission (sudden increase in frequency)
+ const lastInterval = intervals[intervals.length - 1];
+ if (avg > 0 && lastInterval < avg * 0.2) {
+ anomalies.push({
+ type: 'burst',
+ severity: 'medium',
+ message: 'Burst transmission detected - interval ' + Math.round(lastInterval/1000) + 's vs avg ' + Math.round(avg/1000) + 's'
+ });
+ }
+
+ // Check for silence break (device was quiet, now transmitting again)
+ if (avg > 0 && lastInterval > avg * 5) {
+ anomalies.push({
+ type: 'silence_break',
+ severity: 'low',
+ message: 'Device resumed after ' + Math.round(lastInterval/60000) + ' min silence'
+ });
+ }
+
+ return anomalies;
+ }
+
+ // Check for encryption indicators
+ function detectEncryption(message) {
+ if (!message || message === '[No Message]' || message === '[Tone Only]') {
+ return null; // Can't determine
+ }
+
+ // Check for high entropy (random-looking data)
+ const printableRatio = (message.match(/[a-zA-Z0-9\s.,!?-]/g) || []).length / message.length;
+
+ // Check for common encrypted patterns (hex strings, base64-like)
+ const hexPattern = /^[0-9A-Fa-f\s]+$/;
+ const hasNonPrintable = /[^\x20-\x7E]/.test(message);
+
+ if (printableRatio > 0.8 && !hasNonPrintable) {
+ return false; // Likely plaintext
+ } else if (hexPattern.test(message.replace(/\s/g, '')) || hasNonPrintable) {
+ return true; // Likely encrypted or encoded
+ }
+
+ return null; // Unknown
+ }
+
+ // Generate device fingerprint
+ function generateDeviceId(data) {
+ if (data.protocol && data.protocol.includes('POCSAG')) {
+ return 'PAGER_' + (data.address || 'UNK');
+ } else if (data.protocol === 'FLEX') {
+ return 'FLEX_' + (data.address || 'UNK');
+ } else if (data.model) {
+ // 433MHz sensor
+ const id = data.id || data.channel || data.unit || '0';
+ return 'SENSOR_' + data.model.replace(/\s+/g, '_') + '_' + id;
+ }
+ return 'UNKNOWN_' + Date.now();
+ }
+
+ // Track a device transmission
+ function trackDevice(data) {
+ const now = Date.now();
+ const deviceId = generateDeviceId(data);
+ const protocol = data.protocol || data.model || 'Unknown';
+
+ let profile = deviceDatabase.get(deviceId);
+ let isNewDevice = false;
+
+ if (!profile) {
+ // New device discovered
+ profile = createDeviceProfile(deviceId, protocol, now);
+ isNewDevice = true;
+ newDeviceAlerts++;
+ document.getElementById('newDeviceCount').textContent = newDeviceAlerts;
+ } else {
+ // Update existing profile
+ profile.lastSeen = now;
+ profile.transmissionCount++;
+ profile.transmissions.push(now);
+ profile.isNew = false;
+
+ // Keep only last 100 transmissions for analysis
+ if (profile.transmissions.length > 100) {
+ profile.transmissions = profile.transmissions.slice(-100);
+ }
+ }
+
+ // Track addresses
+ if (data.address) profile.addresses.add(data.address);
+ if (data.model) profile.models.add(data.model);
+
+ // Store recent messages (keep last 10)
+ if (data.message) {
+ profile.messages.unshift({
+ text: data.message,
+ time: now
+ });
+ if (profile.messages.length > 10) profile.messages.pop();
+
+ // Detect encryption
+ const encrypted = detectEncryption(data.message);
+ if (encrypted !== null) profile.encrypted = encrypted;
+ }
+
+ // Analyze for anomalies
+ const newAnomalies = analyzeTransmissions(profile);
+ if (newAnomalies.length > 0) {
+ profile.anomalies = profile.anomalies.concat(newAnomalies);
+ anomalyAlerts += newAnomalies.length;
+ document.getElementById('anomalyCount').textContent = anomalyAlerts;
+ }
+
+ deviceDatabase.set(deviceId, profile);
+ document.getElementById('trackedCount').textContent = deviceDatabase.size;
+
+ // Update recon display
+ if (reconEnabled) {
+ updateReconDisplay(deviceId, profile, isNewDevice, newAnomalies);
+ }
+
+ return { deviceId, profile, isNewDevice, anomalies: newAnomalies };
+ }
+
+ // Update reconnaissance display
+ function updateReconDisplay(deviceId, profile, isNewDevice, anomalies) {
+ const content = document.getElementById('reconContent');
+
+ // Remove placeholder if present
+ const placeholder = content.querySelector('div[style*="text-align: center"]');
+ if (placeholder) placeholder.remove();
+
+ // Check if device row already exists
+ let row = document.getElementById('device_' + deviceId.replace(/[^a-zA-Z0-9]/g, '_'));
+
+ if (!row) {
+ // Create new row
+ row = document.createElement('div');
+ row.id = 'device_' + deviceId.replace(/[^a-zA-Z0-9]/g, '_');
+ row.className = 'device-row' + (isNewDevice ? ' new-device' : '');
+ content.insertBefore(row, content.firstChild);
+ }
+
+ // Determine protocol badge class
+ let badgeClass = 'proto-unknown';
+ if (profile.protocol.includes('POCSAG')) badgeClass = 'proto-pocsag';
+ else if (profile.protocol === 'FLEX') badgeClass = 'proto-flex';
+ else if (profile.protocol.includes('SENSOR') || profile.models.size > 0) badgeClass = 'proto-433';
+
+ // Calculate transmission rate bar width
+ const maxRate = 100; // Max expected transmissions
+ const rateWidth = Math.min(100, (profile.transmissionCount / maxRate) * 100);
+
+ // Determine timeline status
+ const timeSinceLast = Date.now() - profile.lastSeen;
+ let timelineDot = 'recent';
+ if (timeSinceLast > 300000) timelineDot = 'old'; // > 5 min
+ else if (timeSinceLast > 60000) timelineDot = 'stale'; // > 1 min
+
+ // Build encryption indicator
+ let encStatus = 'Unknown';
+ let encClass = '';
+ if (profile.encrypted === true) { encStatus = 'Encrypted'; encClass = 'encrypted'; }
+ else if (profile.encrypted === false) { encStatus = 'Plaintext'; encClass = 'plaintext'; }
+
+ // Format time
+ const lastSeenStr = getRelativeTime(new Date(profile.lastSeen).toTimeString().split(' ')[0]);
+ const firstSeenStr = new Date(profile.firstSeen).toLocaleTimeString();
+
+ // Update row content
+ row.className = 'device-row' + (isNewDevice ? ' new-device' : '') + (anomalies.length > 0 ? ' anomaly' : '');
+ row.innerHTML = `
+
+
+
+ ${profile.protocol.substring(0, 8)}
+ ${deviceId.substring(0, 30)}
+
+
+ First: ${firstSeenStr} | Last: ${lastSeenStr} | TX: ${profile.transmissionCount}
+ ${profile.avgInterval ? ' | Interval: ' + Math.round(profile.avgInterval/1000) + 's' : ''}
+
+
+ ${encStatus}
+
+ ${Array.from(profile.addresses).slice(0, 2).join(', ')}
+ `;
+
+ // Show anomaly alerts
+ if (anomalies.length > 0) {
+ anomalies.forEach(a => {
+ const alertEl = document.createElement('div');
+ alertEl.style.cssText = 'padding: 5px 15px; background: rgba(255,51,102,0.1); border-left: 2px solid var(--accent-red); font-size: 10px; color: var(--accent-red);';
+ alertEl.textContent = 'ā ' + a.message;
+ row.appendChild(alertEl);
+ });
+ }
+
+ // Limit displayed devices
+ while (content.children.length > 50) {
+ content.removeChild(content.lastChild);
+ }
+ }
+
+ // Toggle recon panel visibility
+ function toggleRecon() {
+ reconEnabled = !reconEnabled;
+ localStorage.setItem('reconEnabled', reconEnabled);
+ document.getElementById('reconPanel').style.display = reconEnabled ? 'block' : 'none';
+ document.getElementById('reconBtn').classList.toggle('active', reconEnabled);
+
+ // Populate recon display if enabled and we have data
+ if (reconEnabled && deviceDatabase.size > 0) {
+ deviceDatabase.forEach((profile, deviceId) => {
+ updateReconDisplay(deviceId, profile, false, []);
+ });
+ }
+ }
+
+ // Initialize recon state
+ if (reconEnabled) {
+ document.getElementById('reconPanel').style.display = 'block';
+ document.getElementById('reconBtn').classList.add('active');
+ }
+
+ // Hook into existing message handlers to track devices
+ const originalAddMessage = addMessage;
+ addMessage = function(msg) {
+ originalAddMessage(msg);
+ trackDevice(msg);
+ };
+
+ const originalAddSensorReading = addSensorReading;
+ addSensorReading = function(data) {
+ originalAddSensorReading(data);
+ trackDevice(data);
+ };
+
+ // Export device database
+ function exportDeviceDB() {
+ const data = [];
+ deviceDatabase.forEach((profile, id) => {
+ data.push({
+ id: id,
+ protocol: profile.protocol,
+ firstSeen: new Date(profile.firstSeen).toISOString(),
+ lastSeen: new Date(profile.lastSeen).toISOString(),
+ transmissionCount: profile.transmissionCount,
+ avgIntervalSeconds: profile.avgInterval ? Math.round(profile.avgInterval / 1000) : null,
+ addresses: Array.from(profile.addresses),
+ models: Array.from(profile.models),
+ encrypted: profile.encrypted,
+ anomalyCount: profile.anomalies.length,
+ recentMessages: profile.messages.slice(0, 5).map(m => m.text)
+ });
+ });
+ downloadFile(JSON.stringify(data, null, 2), 'intercept_device_intelligence.json', 'application/json');
+ }
+
+ // Toggle recon panel collapse
+ function toggleReconCollapse() {
+ const panel = document.getElementById('reconPanel');
+ const icon = document.getElementById('reconCollapseIcon');
+ panel.classList.toggle('collapsed');
+ icon.textContent = panel.classList.contains('collapsed') ? 'ā¶' : 'ā¼';
+ }
+
+ // ============== WIFI RECONNAISSANCE ==============
+
+ let wifiEventSource = null;
+ let isWifiRunning = false;
+ let monitorInterface = null;
+ let wifiNetworks = {};
+ let wifiClients = {};
+ let apCount = 0;
+ let clientCount = 0;
+ let handshakeCount = 0;
+
+ // Refresh WiFi interfaces
+ function refreshWifiInterfaces() {
+ fetch('/wifi/interfaces')
+ .then(r => r.json())
+ .then(data => {
+ const select = document.getElementById('wifiInterfaceSelect');
+ if (data.interfaces.length === 0) {
+ select.innerHTML = '';
+ } else {
+ select.innerHTML = data.interfaces.map(i =>
+ ``
+ ).join('');
+ }
+
+ // Update tool status
+ const statusDiv = document.getElementById('wifiToolStatus');
+ statusDiv.innerHTML = `
+ airmon-ng:${data.tools.airmon ? 'OK' : 'Missing'}
+ airodump-ng:${data.tools.airodump ? 'OK' : 'Missing'}
+ kismet:${data.tools.kismet ? 'OK' : 'Missing'}
+ `;
+
+ // Update monitor status
+ if (data.monitor_interface) {
+ monitorInterface = data.monitor_interface;
+ updateMonitorStatus(true);
+ }
+ });
+ }
+
+ // Enable monitor mode
+ function enableMonitorMode() {
+ const iface = document.getElementById('wifiInterfaceSelect').value;
+ if (!iface) {
+ alert('Please select an interface');
+ return;
+ }
+
+ fetch('/wifi/monitor', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({interface: iface, action: 'start'})
+ }).then(r => r.json())
+ .then(data => {
+ if (data.status === 'success') {
+ monitorInterface = data.monitor_interface;
+ updateMonitorStatus(true);
+ showInfo('Monitor mode enabled on ' + monitorInterface);
+ } else {
+ alert('Error: ' + data.message);
+ }
+ });
+ }
+
+ // Disable monitor mode
+ function disableMonitorMode() {
+ const iface = monitorInterface || document.getElementById('wifiInterfaceSelect').value;
+
+ fetch('/wifi/monitor', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({interface: iface, action: 'stop'})
+ }).then(r => r.json())
+ .then(data => {
+ if (data.status === 'success') {
+ monitorInterface = null;
+ updateMonitorStatus(false);
+ showInfo('Monitor mode disabled');
+ } else {
+ alert('Error: ' + data.message);
+ }
+ });
+ }
+
+ function updateMonitorStatus(enabled) {
+ document.getElementById('monitorStartBtn').style.display = enabled ? 'none' : 'block';
+ document.getElementById('monitorStopBtn').style.display = enabled ? 'block' : 'none';
+ document.getElementById('monitorStatus').innerHTML = enabled
+ ? 'Monitor mode: Active (' + monitorInterface + ')'
+ : 'Monitor mode: Inactive';
+ }
+
+ // Start WiFi scan
+ function startWifiScan() {
+ const scanMode = document.querySelector('input[name="wifiScanMode"]:checked').value;
+ const band = document.getElementById('wifiBand').value;
+ const channel = document.getElementById('wifiChannel').value;
+
+ if (!monitorInterface) {
+ alert('Enable monitor mode first');
+ return;
+ }
+
+ const endpoint = scanMode === 'kismet' ? '/wifi/kismet/start' : '/wifi/scan/start';
+
+ fetch(endpoint, {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({
+ interface: monitorInterface,
+ band: band,
+ channel: channel || null
+ })
+ }).then(r => r.json())
+ .then(data => {
+ if (data.status === 'started') {
+ setWifiRunning(true);
+ startWifiStream();
+ } else {
+ alert('Error: ' + data.message);
+ }
+ });
+ }
+
+ // Stop WiFi scan
+ function stopWifiScan() {
+ const scanMode = document.querySelector('input[name="wifiScanMode"]:checked').value;
+ const endpoint = scanMode === 'kismet' ? '/wifi/kismet/stop' : '/wifi/scan/stop';
+
+ fetch(endpoint, {method: 'POST'})
+ .then(r => r.json())
+ .then(data => {
+ setWifiRunning(false);
+ if (wifiEventSource) {
+ wifiEventSource.close();
+ wifiEventSource = null;
+ }
+ });
+ }
+
+ function setWifiRunning(running) {
+ isWifiRunning = running;
+ document.getElementById('statusDot').classList.toggle('running', running);
+ document.getElementById('statusText').textContent = running ? 'Scanning...' : 'Idle';
+ document.getElementById('startWifiBtn').style.display = running ? 'none' : 'block';
+ document.getElementById('stopWifiBtn').style.display = running ? 'block' : 'none';
+ }
+
+ // Start WiFi event stream
+ function startWifiStream() {
+ if (wifiEventSource) {
+ wifiEventSource.close();
+ }
+
+ wifiEventSource = new EventSource('/wifi/stream');
+
+ wifiEventSource.onmessage = function(e) {
+ const data = JSON.parse(e.data);
+
+ if (data.type === 'network') {
+ handleWifiNetwork(data);
+ } else if (data.type === 'client') {
+ handleWifiClient(data);
+ } else if (data.type === 'info' || data.type === 'raw') {
+ showInfo(data.text);
+ } else if (data.type === 'status') {
+ if (data.text === 'stopped') {
+ setWifiRunning(false);
+ }
+ }
+ };
+
+ wifiEventSource.onerror = function() {
+ console.error('WiFi stream error');
+ };
+ }
+
+ // Handle discovered WiFi network
+ function handleWifiNetwork(net) {
+ const isNew = !wifiNetworks[net.bssid];
+ wifiNetworks[net.bssid] = net;
+
+ if (isNew) {
+ apCount++;
+ document.getElementById('apCount').textContent = apCount;
+ playAlert();
+ pulseSignal();
+ }
+
+ // Update recon display
+ trackDevice({
+ protocol: 'WiFi-AP',
+ address: net.bssid,
+ message: net.essid || '[Hidden SSID]',
+ model: net.essid,
+ channel: net.channel,
+ privacy: net.privacy
+ });
+
+ // Add to output
+ addWifiNetworkCard(net, isNew);
+ }
+
+ // Handle discovered WiFi client
+ function handleWifiClient(client) {
+ const isNew = !wifiClients[client.mac];
+ wifiClients[client.mac] = client;
+
+ if (isNew) {
+ clientCount++;
+ document.getElementById('clientCount').textContent = clientCount;
+ }
+
+ // Track in device intelligence
+ trackDevice({
+ protocol: 'WiFi-Client',
+ address: client.mac,
+ message: client.probes || '[No probes]',
+ bssid: client.bssid
+ });
+ }
+
+ // Add WiFi network card to output
+ function addWifiNetworkCard(net, isNew) {
+ const output = document.getElementById('output');
+ const placeholder = output.querySelector('.placeholder');
+ if (placeholder) placeholder.remove();
+
+ // Check if card already exists
+ let card = document.getElementById('wifi_' + net.bssid.replace(/:/g, ''));
+
+ if (!card) {
+ card = document.createElement('div');
+ card.id = 'wifi_' + net.bssid.replace(/:/g, '');
+ card.className = 'sensor-card';
+ card.style.borderLeftColor = net.privacy.includes('WPA') ? 'var(--accent-orange)' :
+ net.privacy.includes('WEP') ? 'var(--accent-red)' :
+ 'var(--accent-green)';
+ output.insertBefore(card, output.firstChild);
+ }
+
+ const signalStrength = parseInt(net.power) || -100;
+ const signalBars = Math.max(0, Math.min(5, Math.floor((signalStrength + 100) / 15)));
+
+ card.innerHTML = `
+
+
+
+
+
Security
+
${net.privacy}
+
+
+
Signal
+
${net.power} dBm ${'ā'.repeat(signalBars)}${'ā'.repeat(5-signalBars)}
+
+
+
Beacons
+
${net.beacons}
+
+
+
+
+
+
+ `;
+
+ if (autoScroll) output.scrollTop = 0;
+ }
+
+ // Target a network for attack
+ function targetNetwork(bssid, channel) {
+ document.getElementById('targetBssid').value = bssid;
+ document.getElementById('wifiChannel').value = channel;
+ showInfo('Targeted: ' + bssid + ' on channel ' + channel);
+ }
+
+ // Start handshake capture
+ function captureHandshake(bssid, channel) {
+ if (!confirm('Start handshake capture for ' + bssid + '? This will stop the current scan.')) {
+ return;
+ }
+
+ fetch('/wifi/handshake/capture', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({bssid: bssid, channel: channel})
+ }).then(r => r.json())
+ .then(data => {
+ if (data.status === 'started') {
+ showInfo('Capturing handshakes for ' + bssid + '. File: ' + data.capture_file);
+ setWifiRunning(true);
+ } else {
+ alert('Error: ' + data.message);
+ }
+ });
+ }
+
+ // Send deauth
+ function sendDeauth() {
+ const bssid = document.getElementById('targetBssid').value;
+ const client = document.getElementById('targetClient').value || 'FF:FF:FF:FF:FF:FF';
+ const count = document.getElementById('deauthCount').value || '5';
+
+ if (!bssid) {
+ alert('Enter target BSSID');
+ return;
+ }
+
+ if (!confirm('Send ' + count + ' deauth packets to ' + bssid + '?\\n\\nā Only use on networks you own or have authorization to test!')) {
+ return;
+ }
+
+ fetch('/wifi/deauth', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({bssid: bssid, client: client, count: parseInt(count)})
+ }).then(r => r.json())
+ .then(data => {
+ if (data.status === 'success') {
+ showInfo(data.message);
+ } else {
+ alert('Error: ' + data.message);
+ }
+ });
+ }
+
+ // ============== WIFI VISUALIZATIONS ==============
+
+ let radarCtx = null;
+ let radarAngle = 0;
+ let radarAnimFrame = null;
+ let radarNetworks = []; // {x, y, strength, ssid, bssid}
+ let targetBssidForSignal = null;
+
+ // Initialize radar canvas
+ function initRadar() {
+ const canvas = document.getElementById('radarCanvas');
+ if (!canvas) return;
+
+ radarCtx = canvas.getContext('2d');
+ canvas.width = 150;
+ canvas.height = 150;
+
+ // Start animation
+ if (!radarAnimFrame) {
+ animateRadar();
+ }
+ }
+
+ // Animate radar sweep
+ function animateRadar() {
+ if (!radarCtx) {
+ radarAnimFrame = null;
+ return;
+ }
+
+ const canvas = radarCtx.canvas;
+ const cx = canvas.width / 2;
+ const cy = canvas.height / 2;
+ const radius = Math.min(cx, cy) - 5;
+
+ // Clear canvas
+ radarCtx.fillStyle = 'rgba(0, 10, 10, 0.1)';
+ radarCtx.fillRect(0, 0, canvas.width, canvas.height);
+
+ // Draw grid circles
+ radarCtx.strokeStyle = 'rgba(0, 212, 255, 0.2)';
+ radarCtx.lineWidth = 1;
+ for (let r = radius / 4; r <= radius; r += radius / 4) {
+ radarCtx.beginPath();
+ radarCtx.arc(cx, cy, r, 0, Math.PI * 2);
+ radarCtx.stroke();
+ }
+
+ // Draw crosshairs
+ radarCtx.beginPath();
+ radarCtx.moveTo(cx, cy - radius);
+ radarCtx.lineTo(cx, cy + radius);
+ radarCtx.moveTo(cx - radius, cy);
+ radarCtx.lineTo(cx + radius, cy);
+ radarCtx.stroke();
+
+ // Draw sweep line
+ radarCtx.strokeStyle = 'rgba(0, 255, 136, 0.8)';
+ radarCtx.lineWidth = 2;
+ radarCtx.beginPath();
+ radarCtx.moveTo(cx, cy);
+ radarCtx.lineTo(
+ cx + Math.cos(radarAngle) * radius,
+ cy + Math.sin(radarAngle) * radius
+ );
+ radarCtx.stroke();
+
+ // Draw sweep gradient
+ const gradient = radarCtx.createConicalGradient ?
+ null : // Not supported in all browsers
+ radarCtx.createRadialGradient(cx, cy, 0, cx, cy, radius);
+
+ radarCtx.fillStyle = 'rgba(0, 255, 136, 0.05)';
+ radarCtx.beginPath();
+ radarCtx.moveTo(cx, cy);
+ radarCtx.arc(cx, cy, radius, radarAngle - 0.5, radarAngle);
+ radarCtx.closePath();
+ radarCtx.fill();
+
+ // Draw network blips
+ radarNetworks.forEach(net => {
+ const age = Date.now() - net.timestamp;
+ const alpha = Math.max(0.1, 1 - age / 10000);
+
+ radarCtx.fillStyle = `rgba(0, 255, 136, ${alpha})`;
+ radarCtx.beginPath();
+ radarCtx.arc(net.x, net.y, 4 + (1 - alpha) * 3, 0, Math.PI * 2);
+ radarCtx.fill();
+
+ // Glow effect
+ radarCtx.fillStyle = `rgba(0, 255, 136, ${alpha * 0.3})`;
+ radarCtx.beginPath();
+ radarCtx.arc(net.x, net.y, 8 + (1 - alpha) * 5, 0, Math.PI * 2);
+ radarCtx.fill();
+ });
+
+ // Update angle
+ radarAngle += 0.03;
+ if (radarAngle > Math.PI * 2) radarAngle = 0;
+
+ radarAnimFrame = requestAnimationFrame(animateRadar);
+ }
+
+ // Add network to radar
+ function addNetworkToRadar(net) {
+ const canvas = document.getElementById('radarCanvas');
+ if (!canvas) return;
+
+ const cx = canvas.width / 2;
+ const cy = canvas.height / 2;
+ const radius = Math.min(cx, cy) - 10;
+
+ // Convert signal strength to distance (stronger = closer)
+ const power = parseInt(net.power) || -80;
+ const distance = Math.max(0.1, Math.min(1, (power + 100) / 60));
+ const r = radius * (1 - distance);
+
+ // Random angle based on BSSID hash
+ let angle = 0;
+ for (let i = 0; i < net.bssid.length; i++) {
+ angle += net.bssid.charCodeAt(i);
+ }
+ angle = (angle % 360) * Math.PI / 180;
+
+ const x = cx + Math.cos(angle) * r;
+ const y = cy + Math.sin(angle) * r;
+
+ // Update or add
+ const existing = radarNetworks.find(n => n.bssid === net.bssid);
+ if (existing) {
+ existing.x = x;
+ existing.y = y;
+ existing.timestamp = Date.now();
+ } else {
+ radarNetworks.push({
+ x, y,
+ bssid: net.bssid,
+ ssid: net.essid,
+ timestamp: Date.now()
+ });
+ }
+
+ // Limit to 50 networks
+ if (radarNetworks.length > 50) {
+ radarNetworks.shift();
+ }
+ }
+
+ // Update channel graph
+ function updateChannelGraph() {
+ const channels = {};
+ for (let i = 1; i <= 13; i++) channels[i] = 0;
+
+ // Count networks per channel
+ Object.values(wifiNetworks).forEach(net => {
+ const ch = parseInt(net.channel);
+ if (ch >= 1 && ch <= 13) {
+ channels[ch]++;
+ }
+ });
+
+ // Find max for scaling
+ const maxCount = Math.max(1, ...Object.values(channels));
+
+ // Update bars
+ const bars = document.querySelectorAll('#channelGraph .channel-bar');
+ bars.forEach((bar, i) => {
+ const ch = i + 1;
+ const count = channels[ch] || 0;
+ const height = Math.max(2, (count / maxCount) * 55);
+ bar.style.height = height + 'px';
+
+ bar.classList.remove('active', 'congested', 'very-congested');
+ if (count > 0) bar.classList.add('active');
+ if (count >= 3) bar.classList.add('congested');
+ if (count >= 5) bar.classList.add('very-congested');
+ });
+ }
+
+ // Update security donut chart
+ function updateSecurityDonut() {
+ const canvas = document.getElementById('securityCanvas');
+ if (!canvas) return;
+
+ const ctx = canvas.getContext('2d');
+ const cx = canvas.width / 2;
+ const cy = canvas.height / 2;
+ const radius = Math.min(cx, cy) - 2;
+ const innerRadius = radius * 0.6;
+
+ // Count security types
+ let wpa3 = 0, wpa2 = 0, wep = 0, open = 0;
+ Object.values(wifiNetworks).forEach(net => {
+ const priv = (net.privacy || '').toUpperCase();
+ if (priv.includes('WPA3')) wpa3++;
+ else if (priv.includes('WPA')) wpa2++;
+ else if (priv.includes('WEP')) wep++;
+ else if (priv === 'OPN' || priv === '' || priv === 'OPEN') open++;
+ else wpa2++; // Default to WPA2
+ });
+
+ const total = wpa3 + wpa2 + wep + open;
+
+ // Update legend
+ document.getElementById('wpa3Count').textContent = wpa3;
+ document.getElementById('wpa2Count').textContent = wpa2;
+ document.getElementById('wepCount').textContent = wep;
+ document.getElementById('openCount').textContent = open;
+
+ // Clear canvas
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ if (total === 0) {
+ // Draw empty circle
+ ctx.strokeStyle = '#1a1a1a';
+ ctx.lineWidth = radius - innerRadius;
+ ctx.beginPath();
+ ctx.arc(cx, cy, (radius + innerRadius) / 2, 0, Math.PI * 2);
+ ctx.stroke();
+ return;
+ }
+
+ // Draw segments
+ const colors = {
+ wpa3: '#00ff88',
+ wpa2: '#ff8800',
+ wep: '#ff3366',
+ open: '#00d4ff'
+ };
+
+ const data = [
+ { value: wpa3, color: colors.wpa3 },
+ { value: wpa2, color: colors.wpa2 },
+ { value: wep, color: colors.wep },
+ { value: open, color: colors.open }
+ ];
+
+ let startAngle = -Math.PI / 2;
+
+ data.forEach(segment => {
+ if (segment.value === 0) return;
+
+ const sliceAngle = (segment.value / total) * Math.PI * 2;
+
+ ctx.fillStyle = segment.color;
+ ctx.beginPath();
+ ctx.moveTo(cx, cy);
+ ctx.arc(cx, cy, radius, startAngle, startAngle + sliceAngle);
+ ctx.closePath();
+ ctx.fill();
+
+ startAngle += sliceAngle;
+ });
+
+ // Draw inner circle (donut hole)
+ ctx.fillStyle = '#000';
+ ctx.beginPath();
+ ctx.arc(cx, cy, innerRadius, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Draw total in center
+ ctx.fillStyle = '#fff';
+ ctx.font = 'bold 16px JetBrains Mono';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(total, cx, cy);
+ }
+
+ // Update signal strength meter for targeted network
+ function updateSignalMeter(net) {
+ if (!net) return;
+
+ targetBssidForSignal = net.bssid;
+
+ const ssidEl = document.getElementById('targetSsid');
+ const valueEl = document.getElementById('signalValue');
+ const barsEl = document.querySelectorAll('.signal-bar-large');
+
+ ssidEl.textContent = net.essid || net.bssid;
+
+ const power = parseInt(net.power) || -100;
+ valueEl.textContent = power + ' dBm';
+
+ // Determine signal quality
+ let quality = 'weak';
+ let activeBars = 1;
+
+ if (power >= -50) { quality = 'strong'; activeBars = 5; }
+ else if (power >= -60) { quality = 'strong'; activeBars = 4; }
+ else if (power >= -70) { quality = 'medium'; activeBars = 3; }
+ else if (power >= -80) { quality = 'medium'; activeBars = 2; }
+ else { quality = 'weak'; activeBars = 1; }
+
+ valueEl.className = 'signal-value ' + quality;
+
+ barsEl.forEach((bar, i) => {
+ bar.className = 'signal-bar-large';
+ if (i < activeBars) {
+ bar.classList.add('active', quality);
+ }
+ });
+ }
+
+ // Hook into handleWifiNetwork to update visualizations
+ const originalHandleWifiNetwork = handleWifiNetwork;
+ handleWifiNetwork = function(net) {
+ originalHandleWifiNetwork(net);
+
+ // Update radar
+ addNetworkToRadar(net);
+
+ // Update channel graph
+ updateChannelGraph();
+
+ // Update security donut
+ updateSecurityDonut();
+
+ // Update signal meter if this is the targeted network
+ if (targetBssidForSignal === net.bssid) {
+ updateSignalMeter(net);
+ }
+ };
+
+ // Update targetNetwork to also set signal meter
+ const originalTargetNetwork = targetNetwork;
+ targetNetwork = function(bssid, channel) {
+ originalTargetNetwork(bssid, channel);
+
+ const net = wifiNetworks[bssid];
+ if (net) {
+ updateSignalMeter(net);
+ }
+ };
+
+ // ============== BLUETOOTH RECONNAISSANCE ==============
+
+ let btEventSource = null;
+ let isBtRunning = false;
+ let btDevices = {};
+ let btDeviceCount = 0;
+ let btBeaconCount = 0;
+ let btTrackerCount = 0;
+ let btRadarCtx = null;
+ let btRadarAngle = 0;
+ let btRadarAnimFrame = null;
+ let btRadarDevices = [];
+
+ // Refresh Bluetooth interfaces
+ function refreshBtInterfaces() {
+ fetch('/bt/interfaces')
+ .then(r => r.json())
+ .then(data => {
+ const select = document.getElementById('btInterfaceSelect');
+ if (data.interfaces.length === 0) {
+ select.innerHTML = '';
+ } else {
+ select.innerHTML = data.interfaces.map(i =>
+ ``
+ ).join('');
+ }
+
+ // Update tool status
+ const statusDiv = document.getElementById('btToolStatus');
+ statusDiv.innerHTML = `
+ hcitool:${data.tools.hcitool ? 'OK' : 'Missing'}
+ bluetoothctl:${data.tools.bluetoothctl ? 'OK' : 'Missing'}
+ ubertooth:${data.tools.ubertooth ? 'OK' : 'Missing'}
+ bettercap:${data.tools.bettercap ? 'OK' : 'Missing'}
+ `;
+ });
+ }
+
+ // Start Bluetooth scan
+ function startBtScan() {
+ const scanMode = document.querySelector('input[name="btScanMode"]:checked').value;
+ const iface = document.getElementById('btInterfaceSelect').value;
+ const duration = document.getElementById('btScanDuration').value;
+ const scanBLE = document.getElementById('btScanBLE').checked;
+ const scanClassic = document.getElementById('btScanClassic').checked;
+
+ fetch('/bt/scan/start', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({
+ mode: scanMode,
+ interface: iface,
+ duration: parseInt(duration),
+ scan_ble: scanBLE,
+ scan_classic: scanClassic
+ })
+ }).then(r => r.json())
+ .then(data => {
+ if (data.status === 'started') {
+ setBtRunning(true);
+ startBtStream();
+ } else {
+ alert('Error: ' + data.message);
+ }
+ });
+ }
+
+ // Stop Bluetooth scan
+ function stopBtScan() {
+ fetch('/bt/scan/stop', {method: 'POST'})
+ .then(r => r.json())
+ .then(data => {
+ setBtRunning(false);
+ if (btEventSource) {
+ btEventSource.close();
+ btEventSource = null;
+ }
+ });
+ }
+
+ function setBtRunning(running) {
+ isBtRunning = running;
+ document.getElementById('statusDot').classList.toggle('running', running);
+ document.getElementById('statusText').textContent = running ? 'Scanning...' : 'Idle';
+ document.getElementById('startBtBtn').style.display = running ? 'none' : 'block';
+ document.getElementById('stopBtBtn').style.display = running ? 'block' : 'none';
+ }
+
+ // Start Bluetooth event stream
+ function startBtStream() {
+ if (btEventSource) btEventSource.close();
+
+ btEventSource = new EventSource('/bt/stream');
+
+ btEventSource.onmessage = function(e) {
+ const data = JSON.parse(e.data);
+
+ if (data.type === 'device') {
+ handleBtDevice(data);
+ } else if (data.type === 'info' || data.type === 'raw') {
+ showInfo(data.text);
+ } else if (data.type === 'status') {
+ if (data.text === 'stopped') {
+ setBtRunning(false);
+ }
+ }
+ };
+
+ btEventSource.onerror = function() {
+ console.error('BT stream error');
+ };
+ }
+
+ // Handle discovered Bluetooth device
+ function handleBtDevice(device) {
+ const isNew = !btDevices[device.mac];
+ btDevices[device.mac] = device;
+
+ if (isNew) {
+ btDeviceCount++;
+ document.getElementById('btDeviceCount').textContent = btDeviceCount;
+ playAlert();
+ pulseSignal();
+
+ if (device.tracker) {
+ btTrackerCount++;
+ document.getElementById('btTrackerCount').textContent = btTrackerCount;
+ addTrackerAlert(device);
+ }
+ }
+
+ // Track in device intelligence
+ trackDevice({
+ protocol: 'Bluetooth',
+ address: device.mac,
+ message: device.name,
+ model: device.manufacturer,
+ type: device.type
+ });
+
+ // Update visualizations
+ addBtDeviceToRadar(device);
+ updateBtTypeChart();
+ updateBtManufacturerList();
+
+ // Add device card
+ addBtDeviceCard(device, isNew);
+ }
+
+ // Add Bluetooth device card to output
+ function addBtDeviceCard(device, isNew) {
+ const output = document.getElementById('output');
+ const placeholder = output.querySelector('.placeholder');
+ if (placeholder) placeholder.remove();
+
+ let card = document.getElementById('bt_' + device.mac.replace(/:/g, ''));
+
+ if (!card) {
+ card = document.createElement('div');
+ card.id = 'bt_' + device.mac.replace(/:/g, '');
+ card.className = 'sensor-card';
+ card.style.borderLeftColor = device.tracker ? 'var(--accent-red)' :
+ device.type === 'phone' ? 'var(--accent-cyan)' :
+ device.type === 'audio' ? 'var(--accent-green)' :
+ 'var(--accent-orange)';
+ output.insertBefore(card, output.firstChild);
+ }
+
+ const typeIcon = {
+ 'phone': 'š±', 'audio': 'š§', 'wearable': 'ā', 'tracker': 'š',
+ 'computer': 'š»', 'input': 'āØļø', 'other': 'š¶'
+ }[device.type] || 'š¶';
+
+ card.innerHTML = `
+
+
+
+
+
Manufacturer
+
${device.manufacturer}
+
+ ${device.tracker ? `
+
+
Tracker
+
${device.tracker.name}
+
` : ''}
+
+
+
+
+
+ `;
+
+ if (autoScroll) output.scrollTop = 0;
+ }
+
+ // Add tracker alert to visualization
+ function addTrackerAlert(device) {
+ const list = document.getElementById('btTrackerList');
+ const placeholder = list.querySelector('div[style*="text-align: center"]');
+ if (placeholder) placeholder.remove();
+
+ const alert = document.createElement('div');
+ alert.style.cssText = 'padding: 8px; margin-bottom: 5px; background: rgba(255,51,102,0.1); border-left: 2px solid var(--accent-red); font-family: JetBrains Mono, monospace;';
+ alert.innerHTML = `
+ ā ${device.tracker.name} Detected
+ ${device.mac}
+ `;
+ list.insertBefore(alert, list.firstChild);
+ }
+
+ // Target a Bluetooth device
+ function btTargetDevice(mac) {
+ document.getElementById('btTargetMac').value = mac;
+ showInfo('Targeted: ' + mac);
+ }
+
+ // Enumerate services for a device
+ function btEnumServicesFor(mac) {
+ document.getElementById('btTargetMac').value = mac;
+ btEnumServices();
+ }
+
+ // Enumerate services
+ function btEnumServices() {
+ const mac = document.getElementById('btTargetMac').value;
+ if (!mac) { alert('Enter target MAC'); return; }
+
+ showInfo('Enumerating services for ' + mac + '...');
+
+ fetch('/bt/enum', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({mac: mac})
+ }).then(r => r.json())
+ .then(data => {
+ if (data.status === 'success') {
+ let msg = 'Services for ' + mac + ': ';
+ if (data.services.length === 0) {
+ msg += 'None found';
+ } else {
+ msg += data.services.map(s => s.name).join(', ');
+ }
+ showInfo(msg);
+ } else {
+ showInfo('Error: ' + data.message);
+ }
+ });
+ }
+
+ // L2CAP Ping
+ function btPing() {
+ const mac = document.getElementById('btTargetMac').value;
+ if (!mac) { alert('Enter target MAC'); return; }
+
+ fetch('/bt/ping', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({mac: mac, count: 5})
+ }).then(r => r.json())
+ .then(data => {
+ if (data.status === 'success') {
+ showInfo('Ping ' + mac + ': ' + (data.reachable ? 'Reachable' : 'Unreachable'));
+ } else {
+ showInfo('Ping error: ' + data.message);
+ }
+ });
+ }
+
+ // DoS attack
+ function btDosAttack() {
+ const mac = document.getElementById('btTargetMac').value;
+ if (!mac) { alert('Enter target MAC'); return; }
+
+ if (!confirm('Send DoS ping flood to ' + mac + '?\\n\\nā Only test on devices you own!')) return;
+
+ showInfo('Starting DoS test on ' + mac + '...');
+
+ fetch('/bt/dos', {
+ method: 'POST',
+ headers: {'Content-Type': 'application/json'},
+ body: JSON.stringify({mac: mac, count: 100, size: 600})
+ }).then(r => r.json())
+ .then(data => {
+ showInfo('DoS test complete: ' + (data.message || 'Done'));
+ });
+ }
+
+ // Stub functions for other attacks
+ function btReplayAttack() { alert('Replay attack requires captured packets'); }
+ function btSpoofMac() { alert('MAC spoofing requires root privileges'); }
+ function btScanVulns() { alert('Vulnerability scanning not yet implemented'); }
+
+ // Initialize Bluetooth radar
+ function initBtRadar() {
+ const canvas = document.getElementById('btRadarCanvas');
+ if (!canvas) return;
+
+ btRadarCtx = canvas.getContext('2d');
+ canvas.width = 150;
+ canvas.height = 150;
+
+ if (!btRadarAnimFrame) {
+ animateBtRadar();
+ }
+ }
+
+ // Animate Bluetooth radar
+ function animateBtRadar() {
+ if (!btRadarCtx) { btRadarAnimFrame = null; return; }
+
+ const canvas = btRadarCtx.canvas;
+ const cx = canvas.width / 2;
+ const cy = canvas.height / 2;
+ const radius = Math.min(cx, cy) - 5;
+
+ btRadarCtx.fillStyle = 'rgba(0, 10, 20, 0.1)';
+ btRadarCtx.fillRect(0, 0, canvas.width, canvas.height);
+
+ // Grid circles
+ btRadarCtx.strokeStyle = 'rgba(138, 43, 226, 0.2)';
+ btRadarCtx.lineWidth = 1;
+ for (let r = radius / 4; r <= radius; r += radius / 4) {
+ btRadarCtx.beginPath();
+ btRadarCtx.arc(cx, cy, r, 0, Math.PI * 2);
+ btRadarCtx.stroke();
+ }
+
+ // Sweep line (purple for BT)
+ btRadarCtx.strokeStyle = 'rgba(138, 43, 226, 0.8)';
+ btRadarCtx.lineWidth = 2;
+ btRadarCtx.beginPath();
+ btRadarCtx.moveTo(cx, cy);
+ btRadarCtx.lineTo(cx + Math.cos(btRadarAngle) * radius, cy + Math.sin(btRadarAngle) * radius);
+ btRadarCtx.stroke();
+
+ // Device blips
+ btRadarDevices.forEach(dev => {
+ const age = Date.now() - dev.timestamp;
+ const alpha = Math.max(0.1, 1 - age / 15000);
+ const color = dev.isTracker ? '255, 51, 102' : '138, 43, 226';
+
+ btRadarCtx.fillStyle = `rgba(${color}, ${alpha})`;
+ btRadarCtx.beginPath();
+ btRadarCtx.arc(dev.x, dev.y, dev.isTracker ? 6 : 4, 0, Math.PI * 2);
+ btRadarCtx.fill();
+ });
+
+ btRadarAngle += 0.025;
+ if (btRadarAngle > Math.PI * 2) btRadarAngle = 0;
+
+ btRadarAnimFrame = requestAnimationFrame(animateBtRadar);
+ }
+
+ // Add device to BT radar
+ function addBtDeviceToRadar(device) {
+ const canvas = document.getElementById('btRadarCanvas');
+ if (!canvas) return;
+
+ const cx = canvas.width / 2;
+ const cy = canvas.height / 2;
+ const radius = Math.min(cx, cy) - 10;
+
+ // Random position based on MAC hash
+ let angle = 0;
+ for (let i = 0; i < device.mac.length; i++) {
+ angle += device.mac.charCodeAt(i);
+ }
+ angle = (angle % 360) * Math.PI / 180;
+ const r = radius * (0.3 + Math.random() * 0.6);
+
+ const x = cx + Math.cos(angle) * r;
+ const y = cy + Math.sin(angle) * r;
+
+ const existing = btRadarDevices.find(d => d.mac === device.mac);
+ if (existing) {
+ existing.timestamp = Date.now();
+ } else {
+ btRadarDevices.push({
+ x, y,
+ mac: device.mac,
+ isTracker: !!device.tracker,
+ timestamp: Date.now()
+ });
+ }
+
+ if (btRadarDevices.length > 50) btRadarDevices.shift();
+ }
+
+ // Update device type chart
+ function updateBtTypeChart() {
+ const canvas = document.getElementById('btTypeCanvas');
+ if (!canvas) return;
+
+ let phones = 0, audio = 0, wearables = 0, trackers = 0, other = 0;
+
+ Object.values(btDevices).forEach(d => {
+ if (d.tracker) trackers++;
+ else if (d.type === 'phone') phones++;
+ else if (d.type === 'audio') audio++;
+ else if (d.type === 'wearable') wearables++;
+ else other++;
+ });
+
+ document.getElementById('btPhoneCount').textContent = phones;
+ document.getElementById('btAudioCount').textContent = audio;
+ document.getElementById('btWearableCount').textContent = wearables;
+ document.getElementById('btTrackerTypeCount').textContent = trackers;
+ document.getElementById('btOtherCount').textContent = other;
+
+ // Draw donut
+ const ctx = canvas.getContext('2d');
+ const cx = canvas.width / 2;
+ const cy = canvas.height / 2;
+ const r = Math.min(cx, cy) - 2;
+ const inner = r * 0.6;
+
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ const total = phones + audio + wearables + trackers + other;
+ if (total === 0) return;
+
+ const data = [
+ { value: phones, color: '#00d4ff' },
+ { value: audio, color: '#00ff88' },
+ { value: wearables, color: '#ff8800' },
+ { value: trackers, color: '#ff3366' },
+ { value: other, color: '#888' }
+ ];
+
+ let start = -Math.PI / 2;
+ data.forEach(seg => {
+ if (seg.value === 0) return;
+ const angle = (seg.value / total) * Math.PI * 2;
+ ctx.fillStyle = seg.color;
+ ctx.beginPath();
+ ctx.moveTo(cx, cy);
+ ctx.arc(cx, cy, r, start, start + angle);
+ ctx.closePath();
+ ctx.fill();
+ start += angle;
+ });
+
+ ctx.fillStyle = '#000';
+ ctx.beginPath();
+ ctx.arc(cx, cy, inner, 0, Math.PI * 2);
+ ctx.fill();
+
+ ctx.fillStyle = '#fff';
+ ctx.font = 'bold 14px JetBrains Mono';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(total, cx, cy);
+ }
+
+ // Update manufacturer list
+ function updateBtManufacturerList() {
+ const manufacturers = {};
+ Object.values(btDevices).forEach(d => {
+ const m = d.manufacturer || 'Unknown';
+ manufacturers[m] = (manufacturers[m] || 0) + 1;
+ });
+
+ const sorted = Object.entries(manufacturers).sort((a, b) => b[1] - a[1]).slice(0, 6);
+
+ const list = document.getElementById('btManufacturerList');
+ if (sorted.length === 0) {
+ list.innerHTML = 'Scanning for devices...
';
+ } else {
+ list.innerHTML = sorted.map(([name, count]) =>
+ `${name}${count}
`
+ ).join('');
+ }
}