@@ -2000,6 +2039,7 @@ HTML_TEMPLATE = '''
if (mode === 'wifi') {
refreshWifiInterfaces();
initRadar();
+ initWatchList();
} else if (mode === 'bluetooth') {
refreshBtInterfaces();
initBtRadar();
@@ -3092,6 +3132,137 @@ HTML_TEMPLATE = '''
let apCount = 0;
let clientCount = 0;
let handshakeCount = 0;
+ let rogueApCount = 0;
+ let ssidToBssids = {}; // Track SSIDs to their BSSIDs for rogue AP detection
+ let watchMacs = JSON.parse(localStorage.getItem('watchMacs') || '[]');
+ let alertedMacs = new Set(); // Prevent duplicate alerts per session
+
+ // 5GHz channel mapping for the graph
+ const channels5g = ['36', '40', '44', '48', '52', '56', '60', '64', '100', '149', '153', '157', '161', '165'];
+
+ // Initialize watch list display
+ function initWatchList() {
+ updateWatchListDisplay();
+ }
+
+ // Add MAC to watch list
+ function addWatchMac() {
+ const input = document.getElementById('watchMacInput');
+ const mac = input.value.trim().toUpperCase();
+ if (!mac || !/^([0-9A-F]{2}:){5}[0-9A-F]{2}$/.test(mac)) {
+ alert('Please enter a valid MAC address (AA:BB:CC:DD:EE:FF)');
+ return;
+ }
+ if (!watchMacs.includes(mac)) {
+ watchMacs.push(mac);
+ localStorage.setItem('watchMacs', JSON.stringify(watchMacs));
+ updateWatchListDisplay();
+ }
+ input.value = '';
+ }
+
+ // Remove MAC from watch list
+ function removeWatchMac(mac) {
+ watchMacs = watchMacs.filter(m => m !== mac);
+ localStorage.setItem('watchMacs', JSON.stringify(watchMacs));
+ alertedMacs.delete(mac);
+ updateWatchListDisplay();
+ }
+
+ // Update watch list display
+ function updateWatchListDisplay() {
+ const container = document.getElementById('watchList');
+ if (!container) return;
+ if (watchMacs.length === 0) {
+ container.innerHTML = '
No MACs in watch list
';
+ } else {
+ container.innerHTML = watchMacs.map(mac =>
+ `
+ ${mac}
+
+
`
+ ).join('');
+ }
+ }
+
+ // Check if MAC is in watch list and alert
+ function checkWatchList(mac, type) {
+ const upperMac = mac.toUpperCase();
+ if (watchMacs.includes(upperMac) && !alertedMacs.has(upperMac)) {
+ alertedMacs.add(upperMac);
+ // Play alert sound multiple times for urgency
+ playAlert();
+ setTimeout(playAlert, 300);
+ setTimeout(playAlert, 600);
+ // Show prominent alert
+ showProximityAlert(mac, type);
+ }
+ }
+
+ // Show proximity alert popup
+ function showProximityAlert(mac, type) {
+ const alertDiv = document.createElement('div');
+ alertDiv.className = 'proximity-alert';
+ alertDiv.innerHTML = `
+
⚠ PROXIMITY ALERT
+
Watched ${type} detected:
+
${mac}
+
+ `;
+ alertDiv.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #1a1a2e; border: 2px solid var(--accent-red); padding: 20px; border-radius: 8px; z-index: 10000; text-align: center; box-shadow: 0 0 30px rgba(255,0,0,0.5);';
+ document.body.appendChild(alertDiv);
+ // Auto-dismiss after 10 seconds
+ setTimeout(() => alertDiv.remove(), 10000);
+ }
+
+ // Check for rogue APs (same SSID, different BSSID)
+ function checkRogueAP(ssid, bssid) {
+ if (!ssid || ssid === 'Hidden' || ssid === '[Hidden]') return false;
+
+ if (!ssidToBssids[ssid]) {
+ ssidToBssids[ssid] = new Set();
+ }
+
+ const isNewBssid = !ssidToBssids[ssid].has(bssid);
+ ssidToBssids[ssid].add(bssid);
+
+ // If we have more than one BSSID for this SSID, it could be rogue (or just multiple APs)
+ if (ssidToBssids[ssid].size > 1 && isNewBssid) {
+ rogueApCount++;
+ document.getElementById('rogueApCount').textContent = rogueApCount;
+ playAlert();
+ showInfo(`⚠ Potential Rogue AP: "${ssid}" seen on multiple BSSIDs (${ssidToBssids[ssid].size} total)`);
+ return true;
+ }
+ return false;
+ }
+
+ // Update 5GHz channel graph
+ function updateChannel5gGraph() {
+ const bars = document.querySelectorAll('#channelGraph5g .channel-bar');
+ const labels = document.querySelectorAll('#channelGraph5g .channel-label');
+
+ // Count networks per 5GHz channel
+ const channelCounts = {};
+ channels5g.forEach(ch => channelCounts[ch] = 0);
+
+ Object.values(wifiNetworks).forEach(net => {
+ const ch = net.channel?.toString().trim();
+ if (channels5g.includes(ch)) {
+ channelCounts[ch]++;
+ }
+ });
+
+ const maxCount = Math.max(1, ...Object.values(channelCounts));
+
+ bars.forEach((bar, i) => {
+ const ch = channels5g[i];
+ const count = channelCounts[ch] || 0;
+ const height = Math.max(2, (count / maxCount) * 50);
+ bar.style.height = height + 'px';
+ bar.className = 'channel-bar' + (count > 0 ? ' active' : '') + (count > 3 ? ' congested' : '') + (count > 5 ? ' very-congested' : '');
+ });
+ }
// Refresh WiFi interfaces
function refreshWifiInterfaces() {
@@ -3291,6 +3462,12 @@ HTML_TEMPLATE = '''
document.getElementById('apCount').textContent = apCount;
playAlert();
pulseSignal();
+
+ // Check for rogue AP (same SSID, different BSSID)
+ checkRogueAP(net.essid, net.bssid);
+
+ // Check proximity watch list
+ checkWatchList(net.bssid, 'AP');
}
// Update recon display
@@ -3305,6 +3482,10 @@ HTML_TEMPLATE = '''
// Add to output
addWifiNetworkCard(net, isNew);
+
+ // Update both channel graphs
+ updateChannelGraph();
+ updateChannel5gGraph();
}
// Handle discovered WiFi client
@@ -3315,14 +3496,19 @@ HTML_TEMPLATE = '''
if (isNew) {
clientCount++;
document.getElementById('clientCount').textContent = clientCount;
+
+ // Check proximity watch list
+ checkWatchList(client.mac, 'Client');
}
- // Track in device intelligence
+ // Track in device intelligence with vendor info
+ const vendorInfo = client.vendor && client.vendor !== 'Unknown' ? ` [${client.vendor}]` : '';
trackDevice({
protocol: 'WiFi-Client',
address: client.mac,
- message: client.probes || '[No probes]',
- bssid: client.bssid
+ message: (client.probes || '[No probes]') + vendorInfo,
+ bssid: client.bssid,
+ vendor: client.vendor
});
}
@@ -3400,8 +3586,12 @@ HTML_TEMPLATE = '''
}).then(r => r.json())
.then(data => {
if (data.status === 'started') {
- showInfo('Capturing handshakes for ' + bssid + '. File: ' + data.capture_file);
+ showInfo('🎯 Capturing handshakes for ' + bssid + '. File: ' + data.capture_file);
setWifiRunning(true);
+ // Update handshake indicator to show active capture
+ const hsSpan = document.getElementById('handshakeCount');
+ hsSpan.style.animation = 'pulse 1s infinite';
+ hsSpan.title = 'Capturing: ' + bssid;
} else {
alert('Error: ' + data.message);
}
@@ -5113,6 +5303,8 @@ def parse_airodump_csv(csv_path):
if len(parts) >= 6:
station = parts[0]
if station and ':' in station:
+ # Lookup vendor from OUI database
+ vendor = get_manufacturer(station)
clients[station] = {
'mac': station,
'first_seen': parts[1],
@@ -5120,7 +5312,8 @@ def parse_airodump_csv(csv_path):
'power': parts[3],
'packets': parts[4],
'bssid': parts[5],
- 'probes': parts[6] if len(parts) > 6 else ''
+ 'probes': parts[6] if len(parts) > 6 else '',
+ 'vendor': vendor
}
except Exception as e:
print(f"[WiFi] Error parsing CSV: {e}")