Aerial Warflying (Guide)
- Biohazard

- 4 days ago
- 12 min read

How To Aerial Wardrive/Warfly
Aerial wardriving gives you a 3D signal propagation map that ground surveys can't produce. WiFi signals bleed upward through windows, roof vents, and atriums. Rooftop APs and point-to-point bridges are invisible from the parking lot but wide open from 100 meters up. This is advanced physical-layer recon and it's incredibly effective when authorized. This is a guide on how to aerial warflying / wardriving works.
The Regulatory Hard Constraints
Before touching hardware, know your ceiling. Literally.
Jurisdiction | Max Altitude | Line of Sight | Weight Limit | Key Rule |
FAA (US, Part 107) | 400 ft AGL | Required | 55 lbs | Remote ID mandatory. No night ops without waiver. |
EASA (EU) | 120m AGL | Required | Varies by class | Open category. C1/C2 classification needed. |
CASA (Australia) | 120m AGL | Required | 2-25 kg | ReOC for commercial ops. |
UK CAA | 120m AGL | Required | 250g-25kg | Operator ID + Flyer ID required. |
What this means operationally:
400 ft (120m) is your altitude ceiling without a special waiver. That's plenty — WiFi reaches well above 400 ft.
You must maintain visual line of sight with the drone. No FPV-only flights. This limits practical survey radius to roughly 500-800 meters.
Remote ID (US) broadcasts your position, altitude, and control station location. This is visible to anyone with a receiver app. For a pentest, factor this into OPSEC — your client should notify local law enforcement or aviation authorities if the drone flight might attract attention near a sensitive facility.
Flying over people not involved in the operation is generally prohibited unless they're under covered structure or you have a specific waiver.
Controlled airspace (near airports) requires LAANC authorization in the US, which is instant for most altitudes but caps at specific ceilings.
Pentest-specific: Your authorization letter should explicitly mention "aerial wireless survey via UAV" as an authorized activity. A standard internal pentest authorization does not automatically cover operating an aircraft over the facility. Get it in writing.
Hardware: Drone + Payload
Drone Selection
Drone | Flight Time | Payload | GPS Accuracy | Best For |
DJI Mavic 3 Enterprise | 45 min | ~200g external | RTK-capable | Professional, long surveys |
DJI Mini 4 Pro | 34 min | ~50g external (risky) | Standard | Lightweight payload only |
DJI Matrice 350 | 55 min | 2.7 kg | RTK standard | Heavy payload, dual operator |
Autel EVO Max | 42 min | ~300g | RTK option | Alternative to DJI ecosystem |
DIY F450/F550 Hex | 15-25 min | 500g-2kg | Custom (Pixhawk) | Full control, no geo-fencing |
DJI Mavic 3 Enterprise is the sweet spot. 45 minutes of flight covers a medium campus. The RTK module gives centimeter-level GPS, which means precise AP geolocation. The mechanical shutter is irrelevant for WiFi but the flight time and payload capacity matter.
Critical on DJI: Geo-fencing. DJI drones will refuse to fly in restricted zones. If the client is near an airport, prison, or government facility, test this in advance. DJI's unlock process requires submitting documentation and can take 24-48 hours for custom zones.
Payload: Raspberry Pi + WiFi + GPS + Battery
You need an independent capture rig that flies on the drone and logs GPS-tagged WiFi data. The drone's own telemetry is on different frequencies (2.4 GHz and 5.8 GHz for control/video) and won't contaminate the survey.
Components:
Raspberry Pi Zero 2 W — $15
Lightest full-Linux SBC. 512MB RAM is enough for headless Kismet.
Alfa AWUS036ACM or Panda PAU0D — $25-40
USB WiFi adapter with monitor mode.
The AWUS036ACM (MT7612U) supports both 2.4 and 5 GHz.
Remove the plastic case to save weight.
GPS module (NEO-M8N or similar) — $15-30
Dedicated GPS for the payload, independent of drone's GPS.
USB GPS puck or UART GPS module on Pi GPIO.
18650 battery + 5V regulator — $15
Or a small USB power bank (1000-2000 mAh).
The Pi Zero + WiFi adapter draws ~500mA at 5V.
A 2000mAh USB bank runs the payload for ~4 hours.
The drone's own battery can't power the payload (voltage mismatch,
and tapping into drone power can cause flight controller issues).
3D-printed mount or velcro straps — $5
Secure payload under the drone.
Keep the WiFi antenna vertical and away from the drone body.
Carbon fiber frames block WiFi. Plastic/magnesium frames less so.
Total payload weight: ~120-180g. Well within the DJI Mavic 3's 200g external payload capacity (though DJI officially recommends against third-party payloads — weight is the concern, not policy).
Wiring Diagram (Simplified)
[USB Battery Pack 5V]
│
├──→ [Raspberry Pi Zero 2W] ← micro USB power
│ │
│ └──→ [Alfa AWUS036ACM] ← USB OTG adapter
│
└──→ [USB GPS Puck]
│
└──→ [Pi USB port #2]
Antenna Positioning
WiFi antennas are omnidirectional in the horizontal plane. On a drone, the ideal position is:
Vertical orientation: hanging below the drone, clear of the landing gear
Distance from drone body: at least 15 cm separation from motors, ESCs, and carbon fiber
Below the battery: the battery is dense metal — it's a signal blocker
No carbon fiber in the radiation pattern: carbon fiber is electrically conductive and absorbs RF
A 15cm SMA extension cable lets you position the antenna away from the noise while keeping the heavy Pi + battery centered:
[Drone body]
│
└── Velcro strap
│
[Payload tray: Pi + Battery + WiFi adapter]
│
└── 15cm SMA extension
│
└── [Antenna hanging vertically in clear air]
Payload Software: Headless Kismet
Kismet runs on the Pi Zero 2W without a GUI. It logs to SQLite and can sync to a ground station over WiFi.
Pi Zero Setup
bash
# Flash Raspberry Pi OS Lite (no desktop) to SD card
# Boot, connect via serial or pre-configure WiFi
# Update
sudo apt update && sudo apt upgrade -y
# Install dependencies
sudo apt install -y gpsd gpsd-clients kismet
# Add pi user to kismet group
sudo usermod -aG kismet pi
# Configure Kismet for headless capture
sudo nano /etc/kismet/kismet.conf
Minimal kismet.conf for aerial survey:
bash
# Kismet configuration for drone payload
# Only what's needed — stripped for performance on Pi Zero
# WiFi source
source=wlan1:name=drone_wifi,hop=true,channel_hop=true,channel_hoprate=3/sec
# GPS
gps=gpsd:host=localhost,port=2947
# Logging
log_prefix=/home/pi/wardrive/%d_%B_%Y_%H_%M
log_types=kismet
# Disable everything we don't need
# No web server (saves CPU/RAM)
# No Bluetooth scanning
# No alerting
GPS configuration:
bash
# gpsd should auto-detect the USB GPS puck
# Verify:
ls /dev/ttyUSB*
# Should show /dev/ttyUSB0 or /dev/ttyACM0
sudo systemctl enable gpsd
sudo nano /etc/default/gpsd
bash
# /etc/default/gpsd
START_DAEMON="true"
USBAUTO="true"
DEVICES="/dev/ttyUSB0"
GPSD_OPTIONS="-n"
Auto-Start on Power-Up
The payload should start capturing the moment it gets power — no SSH, no manual commands:
bash
# Create systemd service for Kismet
sudo nano /etc/systemd/system/kismet-drone.service
ini
[Unit]
Description=Kismet Drone Wardriving
After=network.target gpsd.service
Wants=gpsd.service
[Service]
Type=simple
User=pi
ExecStart=/usr/bin/kismet -c wlan1 --no-ncurses
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
bash
sudo systemctl enable kismet-drone.service
sudo systemctl start kismet-drone.service
# Verify it's running
sudo systemctl status kismet-drone.service
# Should show active (running)
# Check logs
tail -f /var/log/syslog | grep -i kismet
Ground Station Sync (Optional)
If you want live data on the ground while flying:
Option A: Kismet remote capture
Run Kismet on the ground laptop in remote-capture mode. The drone Pi sends packets over WiFi. The ground laptop does the heavy processing.
bash
# On drone Pi:
kismet_cap_linux_wifi --connect tcp://GROUND_STATION_IP:3501 --source wlan1
# On ground laptop:
kismet -c remote_capture:host=0.0.0.0,port=3501
This requires a WiFi link between drone and ground. DJI's OcuSync/transmission system uses 2.4/5.8 GHz — same bands you're surveying. The drone's telemetry may interfere with survey WiFi. Test this in advance.
Option B: Offline capture (recommended)
Capture everything to SD card on the drone. Download after landing. No interference, no link dependency, no extra weight.
bash
# After landing, pull the SD card or SCP the files:
scp pi@drone-payload.local:/home/pi/wardrive/*.kismet ./flight_data/Flight Planning
Survey Patterns
Grid Pattern (Full Coverage):
┌─────────────────────┐
│ → → → → → → → → → │
│ ← ← ← ← ← ← ← ← ← │
│ → → → → → → → → → │
│ ← ← ← ← ← ← ← ← ← │
└─────────────────────┘
Building / Campus Area
Altitude: Survey at 3-4 altitudes (50m, 100m, 150m, 200m AGL)
Spacing: 30-50 meter line spacing (WiFi beam width is wide enough that tighter spacing adds little value)
Speed: 5-8 m/s (fast enough to cover area, slow enough for Kismet to capture multiple beacons per AP)
Each altitude pass: 15-30 minutes depending on campus size
Building Envelope Spiral:
┌─────────────────┐
│ ╭──────────╮ │
│ │ Building │ │
│ ╰──────────╯ │
└─────────────────┘
Drone spirals outward from building center
Best for mapping signal leakage from a single building
Start at roof level + 10m, spiral outward in 10m altitude increments
Measures exactly where signal drops below usable threshold
Pre-Flight Checklist
bash
☐ Authorization letter explicitly includes UAV operations
☐ NOTAM filed (if required — US: not usually for Part 107 under 400ft)
☐ LAANC authorization obtained (if in controlled airspace)
☐ Airspace check: B4UFLY, AirMap, or DJI Fly app
☐ Weather: wind < 15 kts, no precipitation, visibility > 5km
☐ Drone batteries charged (3-4 packs for extended survey)
☐ Payload battery charged
☐ SD card formatted, enough space (>2 GB free)
☐ Kismet auto-starts on payload power-up (verified on ground)
☐ GPS fix obtained (both drone AND payload GPS)
☐ No people under flight path (clear area or schedule off-hours)
☐ Client security notified (don't want guards calling police on a drone)
☐ Spare SD cards in case of corruption
☐ Remote ID broadcasting (required in US — verify in DJI app)
Flight Execution
bash
# 1. Power on payload (battery plugged into Pi)
# Wait 60 seconds for boot + Kismet start + GPS fix
# Verify: small LED on GPS puck blinking = fix acquired
# 2. Power on drone, verify GPS, home point set
# 3. Take off, ascend to first survey altitude
# Hover for 30 seconds at altitude (GPS averaging)
# Verify stable hover, no vibration issues with payload
# 4. Execute survey pattern
# Maintain constant speed (automated waypoint mission)
# DJI Pilot 2 / UgCS / Litchi for automated grid missions
# Litchi is particularly good for custom survey patterns
# with programmable altitude changes
# 5. Land, swap battery, reload for next altitude
# 6. After all altitudes: hover at cardinal points
# North, South, East, West of building at multiple altitudes
# These are calibration points for signal triangulationData Processing
After landing, you have .kismet files from each flight. These are SQLite databases. Process them the same as ground wardriving data, but now you have altitude (Z-axis) for every AP.
Extract and Convert
bash
# Combine all flight databases
kismetdb_merge --in flight1.kismet --in flight2.kismet --in flight3.kismet \
--out combined_aerial.kismet
# Export to CSV with altitude
kismetdb_dump_devices --in combined_aerial.kismet --out aerial_aps.csv
# The CSV contains: BSSID, SSID, signal (min/max/avg), locations,
# encryption, channels, first/last seen times
# Each AP may have multiple location entries (from different altitudes)
3D Visualization
This is where aerial wardriving shines. You get signal strength at multiple altitudes, creating a 3D heatmap.
python
#!/usr/bin/env python3
"""
3D signal propagation visualizer from aerial wardrive data.
Generates an interactive 3D plot of APs and signal strength.
"""
import csv
import json
import numpy as np
from collections import defaultdict
def parse_aerial_csv(csv_file):
"""Parse Kismet CSV, group by AP, collect 3D positions"""
aps = defaultdict(list)
with open(csv_file) as f:
reader = csv.DictReader(f)
for row in reader:
bssid = row.get('devmac', '')
ssid = row.get('ssid', '')
signal = row.get('signal', '')
lat = row.get('lat', '')
lon = row.get('lon', '')
alt = row.get('alt', '') # Altitude in meters
if not bssid:
continue
try:
signal = float(signal) if signal else None
lat = float(lat) if lat else None
lon = float(lon) if lon else None
alt = float(alt) if alt else None
except (ValueError, TypeError):
continue
if all(v is not None for v in [signal, lat, lon, alt]):
aps[bssid].append({
'ssid': ssid,
'lat': lat,
'lon': lon,
'alt': alt,
'signal': signal
})
return aps
def generate_signal_cone(ap_data):
"""For each AP, estimate ground-level signal propagation"""
results = []
for bssid, readings in ap_data.items():
if len(readings) < 3:
continue
ssid = readings[0]['ssid']
# Find strongest reading (closest to AP)
strongest = max(readings, key=lambda r: r['signal'])
# Estimate AP position: highest signal (closest proximity) +
# signal falloff rate
signals = np.array([r['signal'] for r in readings])
lats = np.array([r['lat'] for r in readings])
lons = np.array([r['lon'] for r in readings])
alts = np.array([r['alt'] for r in readings])
# Simple estimator: strongest signal point weighted average
weights = np.exp((signals - signals.max()) / 6) # 6 dB decay
est_lat = np.average(lats, weights=weights)
est_lon = np.average(lons, weights=weights)
# Altitude: adjust for building height
est_alt = alts[np.argmax(signals)] # Altitude of strongest reading
# Max distance where signal was still detected
max_signal_reading = readings[np.argmax([
np.sqrt((r['lat'] - est_lat)**2 + (r['lon'] - est_lon)**2)
for r in readings
])]
results.append({
'bssid': bssid,
'ssid': ssid,
'est_lat': est_lat,
'est_lon': est_lon,
'est_alt': est_alt,
'max_signal': strongest['signal'],
'max_distance': np.sqrt(
(max_signal_reading['lat'] - est_lat)**2 +
(max_signal_reading['lon'] - est_lon)**2
) * 111000, # Rough meters from degrees
'num_readings': len(readings)
})
return results
def export_cesium_json(results, output_file="aerial_3d.json"):
"""Export to Cesium/CesiumJS format for 3D globe visualization"""
features = []
for ap in results:
features.append({
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [ap['est_lon'], ap['est_lat'], ap['est_alt']]
},
"properties": {
"bssid": ap['bssid'],
"ssid": ap['ssid'],
"signal": ap['max_signal'],
"radius": ap['max_distance'],
"readings": ap['num_readings']
}
})
geojson = {
"type": "FeatureCollection",
"features": features
}
with open(output_file, 'w') as f:
json.dump(geojson, f, indent=2)
print(f"[+] Exported {len(features)} APs to {output_file}")
def main():
import sys
csv_file = sys.argv[1] if len(sys.argv) > 1 else "aerial_aps.csv"
print("[*] Parsing aerial wardrive data...")
aps = parse_aerial_csv(csv_file)
print(f"[+] {len(aps)} unique APs found")
print("[*] Estimating AP positions and signal cones...")
results = generate_signal_cone(aps)
# Filter for client SSIDs
client_results = [r for r in results if 'Corp' in r['ssid']]
print(f"[+] {len(client_results)} client APs identified")
# Sort by signal leakage distance
client_results.sort(key=lambda r: r['max_distance'], reverse=True)
print("\n[+] TOP SIGNAL LEAKERS:")
print(f"{'SSID':<25} {'Est Pos':<15} {'Max Signal':<12} {'Max Distance':<15}")
print("-" * 70)
for ap in client_results[:10]:
print(f"{ap['ssid']:<25} "
f"({ap['est_lat']:.5f},{ap['est_lon']:.5f}) "
f"{ap['max_signal']:>5.0f} dBm "
f"{ap['max_distance']:>5.0f} m")
# Export
export_cesium_json(results)
if __name__ == "__main__":
main()
Google Earth Visualization
For a simpler approach that doesn't need a 3D web framework:
bash
# Kismet's kismetdb_to_kml tool includes altitude
kismetdb_to_kml --in combined_aerial.kismet --out aerial_3d.kml
# Open in Google Earth Pro
# Each AP marker is at its estimated altitude
# Use the time slider to see signal capture over flight time
# Tilt the view to see vertical signal propagationWhat Aerial Reveals That Ground Doesn't
Finding | Altitude Discovery | Impact |
Rooftop APs | Only visible from above roof line | APs on the roof broadcasting down — no ground coverage but attackers with drones can see them |
Point-to-point bridges | Highly directional, main lobe at altitude | Inter-building links visible. Can they be intercepted? |
Upper-floor signal leakage | Signal bleeding upward through windows | Executive offices, boardrooms on upper floors broadcasting credentials to the sky |
Atrium/non-linear propagation | Signal escaping through central atriums | Internal APs visible from above via architectural RF paths |
Rogue APs on roof | Employees installing unauthorized APs for "rooftop WiFi" | Common finding — completely invisible from ground |
Vertical signal boundaries | Exact altitude where signal drops below -85 dBm | Defines the "safe altitude" for an attacker in a neighboring building or aircraft |
Multi-path reflections | AP appearing at two locations (direct + reflection off glass building) | Helps identify reflective surfaces that extend signal range unpredictably |
Advanced: Signal Triangulation from Multiple Positions
With a drone, you can actively triangulate AP positions by flying a box around the building:
bash
# 1. Fly a 4-point box at constant altitude (e.g., 100m AGL)
# North edge, East edge, South edge, West edge
# Hover at each point for 30 seconds
# 2. Record signal strength at each point
# 3. Signal strength differences between the 4 points
# triangulate the AP's position in 3D
# 4. Kismet does this automatically with enough data points
# The position estimate improves with more angles
The math is simple: if the signal is strongest on the North side and weakest on the South, the AP is closer to the North wall, probably on a high floor. If the signal is equally strong on all sides, the AP is near the building center. If signal is very strong from above, the AP is on the roof.
Practical Limitations
Drone noise and attention: A Mavic 3 at 120m is audible. People look up. If the pentest is covert, this is a problem. Mitigation: fly at maximum legal altitude. At 120m, the drone is a speck. The buzz is faint but present. Schedule flights during noisy times (HVAC running, traffic, construction).
Battery logistics: 45 minutes sounds like a lot. At 8 m/s survey speed with turns, you cover roughly 15-20 hectares per battery. A medium campus needs 3-4 batteries for a proper multi-altitude survey. Land, swap, relaunch. Each battery swap takes 3-5 minutes.
GPS signal under the drone: The payload GPS is under the drone body. Drone body + battery = GPS signal attenuation. If the payload GPS can't get a fix, mount the GPS puck on top of the battery with velcro, antenna pointing up through a gap. Test on the ground first — run Kismet with the payload under the powered-on drone. Verify the GPS gets a fix with the drone above it.
WiFi interference from drone telemetry: The DJI transmission system uses 2.4 GHz and 5.8 GHz. Your survey is on those same bands. The drone's telemetry will appear in your scan. Filter it out during analysis:
bash
# DJI OcuSync SSID patterns to exclude:
# "DJI-*", "Mavic-*", "Phantom-*"
# Or exclude by OUI (DJI MAC prefix: 60:60:1F, etc.)
Carbon fiber drone frames: If you're using a DIY carbon fiber frame, the frame itself is an RF shield. Hang the antenna well below the frame on a rigid extension. Test on the ground: capture with and without the drone powered on above the payload. If AP counts drop significantly, reposition the antenna.
Reporting Additions for Aerial Survey
markdown
## Aerial Wireless Survey
### Flight Parameters
- Drone: DJI Mavic 3 Enterprise
- Payload: Raspberry Pi Zero 2W + Alfa AWUS036ACM + NEO-M8N GPS
- Software: Kismet 2024.x headless
- Survey Altitudes: 50m, 100m, 150m, 200m AGL
- Pattern: Grid, 40m line spacing, 6 m/s survey speed
- Total Flight Time: 2 hours 15 minutes (3 batteries)
- Authorization: [Reference UAV waiver / Part 107 license]
### Aerial-Only Findings
1. Rooftop AP "CorpWiFi-Roof" discovered at 45m AGL (building is 40m)
Not visible from any ground survey point.
Radiating -55 dBm at 120m altitude directly above building.
2. Point-to-point bridge between Building A and Building B
Operating on channel 149 (5.8 GHz)
Main lobe interceptable at 80-100m altitude along the path.
3. Upper floor signal leakage:
14th floor executive suite: CorpWiFi-Exec visible at 200m altitude
with -72 dBm (still usable). This signal is not visible from ground
but is accessible from neighboring high-rise or aerial approach.
### 3D Signal Propagation Map
[Attach aerial_3d.kml or screenshot of 3D visualization]
### Combined Ground + Aerial Assessment
Ground survey: Signal leakage to 380m horizontal at ground level
Aerial survey: Signal leakage to 200m vertical, 420m horizontal at altitude
Combined threat envelope: [provide 3D model of attack surface]



Comments