Add WiFi reconnaissance features

New features:
- Client vendor lookup: Shows manufacturer for WiFi clients using OUI database
- 5GHz channel utilization graph: Visual display of 5GHz channel usage
- Proximity alerts: Watch list for specific MAC addresses with popup alerts
- Rogue AP detection: Alerts when same SSID appears on multiple BSSIDs
- Handshake capture status: Pulsing indicator during active capture
- Sound toggle already exists via MUTE button

UI improvements:
- Added ROGUE counter to WiFi stats bar
- Added Proximity Alerts section with watch list management
- Both 2.4GHz and 5GHz channel graphs update in real-time

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
James Smith
2025-12-21 17:31:23 +00:00
parent dc5510d0b7
commit 77680da014

View File

@@ -701,6 +701,11 @@ HTML_TEMPLATE = '''
} }
} }
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.clear-btn { .clear-btn {
padding: 8px 16px; padding: 8px 16px;
background: transparent; background: transparent;
@@ -1674,6 +1679,20 @@ HTML_TEMPLATE = '''
</div> </div>
</div> </div>
<div class="section">
<h3>Proximity Alerts</h3>
<div class="info-text" style="margin-bottom: 8px;">
Alert when specific MAC addresses appear
</div>
<div class="form-group">
<input type="text" id="watchMacInput" placeholder="AA:BB:CC:DD:EE:FF">
</div>
<button class="preset-btn" onclick="addWatchMac()" style="width: 100%; margin-bottom: 8px;">
Add to Watch List
</button>
<div id="watchList" style="max-height: 80px; overflow-y: auto; font-size: 10px; color: var(--text-dim);"></div>
</div>
<div class="section"> <div class="section">
<h3>Attack Options</h3> <h3>Attack Options</h3>
<div class="info-text" style="color: var(--accent-red); margin-bottom: 10px;"> <div class="info-text" style="color: var(--accent-red); margin-bottom: 10px;">
@@ -1812,7 +1831,8 @@ HTML_TEMPLATE = '''
<div class="stats" id="wifiStats" style="display: none;"> <div class="stats" id="wifiStats" style="display: none;">
<div>APs: <span id="apCount">0</span></div> <div>APs: <span id="apCount">0</span></div>
<div>CLIENTS: <span id="clientCount">0</span></div> <div>CLIENTS: <span id="clientCount">0</span></div>
<div>HANDSHAKES: <span id="handshakeCount">0</span></div> <div>HANDSHAKES: <span id="handshakeCount" style="color: var(--accent-green);">0</span></div>
<div style="color: var(--accent-red);">ROGUE: <span id="rogueApCount">0</span></div>
</div> </div>
<div class="stats" id="btStats" style="display: none;"> <div class="stats" id="btStats" style="display: none;">
<div>DEVICES: <span id="btDeviceCount">0</span></div> <div>DEVICES: <span id="btDeviceCount">0</span></div>
@@ -1847,6 +1867,25 @@ HTML_TEMPLATE = '''
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">13</span></div> <div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">13</span></div>
</div> </div>
</div> </div>
<div class="wifi-visual-panel">
<h5>Channel Utilization (5 GHz)</h5>
<div class="channel-graph" id="channelGraph5g" style="font-size: 7px;">
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">36</span></div>
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">40</span></div>
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">44</span></div>
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">48</span></div>
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">52</span></div>
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">56</span></div>
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">60</span></div>
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">64</span></div>
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">100</span></div>
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">149</span></div>
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">153</span></div>
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">157</span></div>
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">161</span></div>
<div class="channel-bar-wrapper"><div class="channel-bar" style="height: 2px;"></div><span class="channel-label">165</span></div>
</div>
</div>
<div class="wifi-visual-panel"> <div class="wifi-visual-panel">
<h5>Security Overview</h5> <h5>Security Overview</h5>
<div class="security-container"> <div class="security-container">
@@ -2000,6 +2039,7 @@ HTML_TEMPLATE = '''
if (mode === 'wifi') { if (mode === 'wifi') {
refreshWifiInterfaces(); refreshWifiInterfaces();
initRadar(); initRadar();
initWatchList();
} else if (mode === 'bluetooth') { } else if (mode === 'bluetooth') {
refreshBtInterfaces(); refreshBtInterfaces();
initBtRadar(); initBtRadar();
@@ -3092,6 +3132,137 @@ HTML_TEMPLATE = '''
let apCount = 0; let apCount = 0;
let clientCount = 0; let clientCount = 0;
let handshakeCount = 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 = '<div style="color: #555;">No MACs in watch list</div>';
} else {
container.innerHTML = watchMacs.map(mac =>
`<div style="display: flex; justify-content: space-between; align-items: center; padding: 2px 0;">
<span>${mac}</span>
<button onclick="removeWatchMac('${mac}')" style="background: none; border: none; color: var(--accent-red); cursor: pointer; font-size: 10px;">✕</button>
</div>`
).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 = `
<div style="font-weight: bold; color: var(--accent-red);">⚠ PROXIMITY ALERT</div>
<div>Watched ${type} detected:</div>
<div style="font-family: monospace; font-size: 14px;">${mac}</div>
<button onclick="this.parentElement.remove()" style="margin-top: 8px; padding: 4px 12px; cursor: pointer;">Dismiss</button>
`;
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 // Refresh WiFi interfaces
function refreshWifiInterfaces() { function refreshWifiInterfaces() {
@@ -3291,6 +3462,12 @@ HTML_TEMPLATE = '''
document.getElementById('apCount').textContent = apCount; document.getElementById('apCount').textContent = apCount;
playAlert(); playAlert();
pulseSignal(); 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 // Update recon display
@@ -3305,6 +3482,10 @@ HTML_TEMPLATE = '''
// Add to output // Add to output
addWifiNetworkCard(net, isNew); addWifiNetworkCard(net, isNew);
// Update both channel graphs
updateChannelGraph();
updateChannel5gGraph();
} }
// Handle discovered WiFi client // Handle discovered WiFi client
@@ -3315,14 +3496,19 @@ HTML_TEMPLATE = '''
if (isNew) { if (isNew) {
clientCount++; clientCount++;
document.getElementById('clientCount').textContent = 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({ trackDevice({
protocol: 'WiFi-Client', protocol: 'WiFi-Client',
address: client.mac, address: client.mac,
message: client.probes || '[No probes]', message: (client.probes || '[No probes]') + vendorInfo,
bssid: client.bssid bssid: client.bssid,
vendor: client.vendor
}); });
} }
@@ -3400,8 +3586,12 @@ HTML_TEMPLATE = '''
}).then(r => r.json()) }).then(r => r.json())
.then(data => { .then(data => {
if (data.status === 'started') { if (data.status === 'started') {
showInfo('Capturing handshakes for ' + bssid + '. File: ' + data.capture_file); showInfo('🎯 Capturing handshakes for ' + bssid + '. File: ' + data.capture_file);
setWifiRunning(true); 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 { } else {
alert('Error: ' + data.message); alert('Error: ' + data.message);
} }
@@ -5113,6 +5303,8 @@ def parse_airodump_csv(csv_path):
if len(parts) >= 6: if len(parts) >= 6:
station = parts[0] station = parts[0]
if station and ':' in station: if station and ':' in station:
# Lookup vendor from OUI database
vendor = get_manufacturer(station)
clients[station] = { clients[station] = {
'mac': station, 'mac': station,
'first_seen': parts[1], 'first_seen': parts[1],
@@ -5120,7 +5312,8 @@ def parse_airodump_csv(csv_path):
'power': parts[3], 'power': parts[3],
'packets': parts[4], 'packets': parts[4],
'bssid': parts[5], '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: except Exception as e:
print(f"[WiFi] Error parsing CSV: {e}") print(f"[WiFi] Error parsing CSV: {e}")