Artemisz — Building a WiFi Offensive Security Toolkit on an ESP32
14 attack modes, handshake capture, credential phishing with real-time password verification, and BLE spam — all on a $10 microcontroller.

Github: github.com/ssnrshnn/artemisz
The Idea
I wanted to build a pocket-sized WiFi pentesting tool that didn’t rely on a laptop, Raspberry Pi, or any external hardware beyond the bare minimum. The ESP32 can do station mode, AP mode, promiscuous sniffing, and raw frame injection — all on a single $5 chip. Add a $3 TFT display, five buttons, and a perfboard, and you have a standalone offensive security device for under $15.
The result is Artemisz: 2,800 lines of C++ running on an ESP32-D0WD-V3, packing 14 different attack modes, a web admin panel, a serial CLI, and a TFT dashboard.
Hardware
| ESP32-D0WD-V3
| 1.9" IPS TFT
| 5× tactile buttons
| Perfboard + wires
The display connects via SPI (GPIO 18/23/5/2/4/15), and the five buttons connect to GPIOs 12/13/14/27/33 with internal pull-ups — each one wired to GND on the other side. No external resistors needed.
What It Can Do
Attack Modes
| Mode | What It Does |
|---|---|
| Deauth | Targeted deauth/disassoc against a selected AP — per-client + broadcast bursts |
| Deauth All | Round-robin deauth across all visible networks |
| Evil Twin | Clones target SSID, serves a captive portal, phishes the WiFi password, then verifies it against the real AP |
| CSA Attack | Injects forged Channel Switch Announcement beacons — bypasses WPA3/PMF |
| Auth Flood | Spoofs mass authentication requests to exhaust the AP’s association table |
| Nuke | Deauth + CSA + Auth Flood combined |
| Karma | Sniffs probe requests, responds with matching beacons, can auto-clone into Evil Twin |
| Beacon Spam | Floods 30 fake SSIDs (“FBI Surveillance Van”, etc.) |
| Random Beacon | Broadcasts randomly generated SSIDs |
| AP Clone | Floods cloned beacons of the target with varying BSSIDs |
| Rick Roll | Song lyrics as 16 beacon SSIDs |
| Probe Flood | Mass probe requests with spoofed MACs |
| BLE Spam | Sour Apple (iOS), Swiftpair (Windows), Samsung Galaxy popup spam |
| Deauth Detection | Passive mode — counts deauth frames and alerts |
Capture Capabilities
- 4-Way Handshake — sniffs EAPOL messages, exports as PCAP (Wireshark) or HCCAPX (Hashcat)
- PMKID — extracts from EAPOL Message 1’s vendor IE
- Credentials — stores up to 20 SSID/password pairs with verified/failed status, persisted to flash
Evil Twin: The Centerpiece
The Evil Twin is the most complex attack and the one I spent the most time on. Here’s how the full flow works:
Step 1: Deauth
When you start the Evil Twin, the ESP32 immediately begins sending deauthentication frames to kick clients off the real AP. Each burst sends 10 frames — 5 deauth (0xC0) and 5 disassoc (0xA0) — in both directions (spoofed as client→AP and AP→client) using multiple reason codes (0x07, 0x06, 0x01). The target’s clients see their connection drop.
Step 2: Fake AP
Simultaneously, the ESP32 reconfigures its SoftAP from “Artemisz” to the target’s exact SSID on the same channel, with no password. The victim’s device sees a familiar network name and either auto-connects or the user connects manually.
Step 3: Captive Portal
Once connected, the victim’s device performs a connectivity check. Every major OS does this differently:
| OS | Endpoint | Expected Response |
|---|---|---|
| iOS/macOS | /hotspot-detect.html |
HTML (not “Success”) |
| Android | /generate_204 |
HTTP 200 (not 204) |
| Windows | /connecttest.txt |
HTML redirect |
| Firefox | /canonical.html |
HTML redirect |
Artemisz intercepts all of these. iOS and Windows get a 302 redirect to the portal. Android gets an HTTP 200 with a meta-refresh (returning 204 would tell Android “you’re online” and skip the portal). Samsung devices hit /mobile/status.php, which is also handled. Every unknown URL gets a 302 catch-all.
The portal itself mimics a “firmware update” page asking the user to re-enter their WiFi password. A jQuery eye icon lets them toggle password visibility — a detail that makes it feel more legitimate.
Step 4: Real-Time Password Verification
This is the part that makes Artemisz different from most Evil Twin tools. When the victim submits a password, the ESP32 doesn’t just save it — it verifies it against the real AP.
1. Receive password via POST
2. Pause all attacks (grace period = 15 seconds)
3. WiFi.disconnect()
4. WiFi.begin(real_ssid, submitted_password, target_channel, target_bssid)
5. Poll WiFi.status() with 2-second meta-refresh on client
6. WL_CONNECTED → save as VERIFIED, stop deauth
7. WL_CONNECT_FAILED → save as FAILED, show "Wrong password, try again"
8. Timeout (10s) → save as FAILEDThe victim sees a “Connecting…” page that auto-refreshes every 2 seconds until the result comes back. If the password is wrong, they’re sent back to the form. If it’s right, the credential is stored in NVS flash and survives reboots.
The Single-Radio Problem
The ESP32 has one radio. It can’t transmit deauth frames and serve HTTP responses at the same time without the TX buffer overflowing. When the buffer fills up, DNS responses fail silently (WiFiUdp endPacket(): could not send data: 12), the captive portal never loads, and the victim just sees “No Internet.”
The solution is a grace period system:
| Event | Grace Period | Deauth Interval |
|---|---|---|
| Evil Twin starts | 5 seconds (complete pause) | — |
| New client connects to fake AP | 5 seconds (extended) | — |
| Clients connected, grace expired | — | 2000ms (1 burst every 2 seconds) |
| No clients on fake AP | — | 80ms (aggressive) |
| Password submitted | 15 seconds (complete pause) | — |
| Normal deauth (no Evil Twin) | — | 100ms |
This keeps memory free for the captive portal while still maintaining enough deauth pressure to prevent the victim from reconnecting to the real AP.
Bypassing ESP-IDF’s Frame Injection Block
The ESP-IDF firmware blocks raw frame injection for management frames like deauth and disassoc. When you call esp_wifi_80211_tx() with a deauth frame, it gets silently dropped by an internal function called ieee80211_raw_frame_sanity_check.
The bypass is a weak-symbol override:
extern "C" int ieee80211_raw_frame_sanity_check(int32_t arg, int32_t arg2, int32_t arg3) {
return 0;
}This replaces the firmware’s implementation with one that always returns “pass.” The -Wl,-zmuldefs linker flag allows the duplicate symbol, and -Wl,--no-undefined is removed to prevent the linker from complaining. This technique is well-known in the ESP32 security research community — used by ESP32Marauder, esp32-wifi-penetration-tool, and others.
Promiscuous Mode Across AP Switches
When the Evil Twin starts, the ESP32 switches its SoftAP from “Artemisz” to the cloned SSID via WiFi.softAPdisconnect(true) followed by WiFi.softAP(). The problem: softAPdisconnect(true) resets the radio and clears promiscuous mode, killing the sniffer callback.
Without the sniffer, the ESP32 can’t:
- Collect client MACs for targeted deauth
- Capture EAPOL handshakes
- Track probe requests for Karma
- Monitor signal strength
The fix is to explicitly re-enable promiscuous mode after every AP reconfiguration:
void switchAPConfig(const char* ssid, const char* password, uint8_t channel) {
dnsServer.stop();
WiFi.softAPdisconnect(true); // kills promiscuous mode
WiFi.softAP(ssid, password, channel);
esp_wifi_set_promiscuous(true); // re-enable
esp_wifi_set_promiscuous_rx_cb(&snifferCallback);
}EAPOL Handshake Capture
The sniffer callback (marked IRAM_ATTR for fast execution from internal RAM) filters for EAPOL frames by checking for the 802.1X EtherType (0x888E) in the data payload.
Once an EAPOL frame is found, the message number is determined from the Key Info field:
| Key Info Flags | Message |
|---|---|
| ACK set, MIC clear | Message 1 (AP → Client, contains ANonce) |
| ACK clear, MIC set, nonce ≠ 0 | Message 2 (Client → AP, contains SNonce) |
| ACK set, MIC set | Message 3 (AP → Client) |
| ACK clear, MIC set, nonce = 0 | Message 4 (Client → AP) |
Each message is stored as a raw 802.11 frame (up to 400 bytes) in a handshake struct keyed by (BSSID, client MAC). Up to 5 handshakes can be stored simultaneously.
PMKID Extraction
PMKID hides inside Message 1’s Key Data field. The code searches for a vendor-specific IE with OUI 00:0F:AC:04:
[0xDD] [length] [00:0F:AC:04] [PMKID — 16 bytes]If found, the 16-byte PMKID is extracted and stored separately. PMKIDs are valuable because they can be cracked without capturing the full 4-way handshake — only Message 1 (AP → Client) is needed.
Export Formats
- PCAP: Standard packet capture format. PCAP global header + per-packet headers wrapping the raw 802.11 frames. Opens in Wireshark.
- HCCAPX: Hashcat’s native format. 393-byte records containing ESSID, AP MAC, ANonce, STA MAC, SNonce, key version, MIC, and the full EAPOL frame. Ready for GPU cracking.
CSA Attack — Bypassing WPA3/PMF
802.11w (Protected Management Frames) protects unicast management frames from spoofing. But beacon frames are broadcast and never encrypted, even with PMF enabled.
The CSA attack exploits this by injecting forged beacons that include a Channel Switch Announcement IE:
Element ID: 0x25 (CSA)
Length: 3
Mode: 1 (prohibit transmission during switch)
New Channel: [random channel ≠ current]
Count: 2 (switch after 2 beacon intervals)The BSSID of the beacon matches the real AP, so clients trust it. They obey the CSA, switch to the wrong channel, and lose connectivity. This is functionally equivalent to a deauth attack but works against WPA3 networks with PMF enabled. Artemisz sends 5 CSA beacons every 50ms.
The Nuke mode enables Deauth + CSA + Auth Flood simultaneously — maximal disruption.
Karma Attack
Karma works by passively sniffing probe requests. Every WiFi device periodically broadcasts “Are you there, $SSID?” for each remembered network. Artemisz captures these probes (storing client MAC, SSID, RSSI, and hit count) and responds with matching beacon frames.
The device sees a beacon for its trusted network, connects without user interaction, and gets redirected to the captive portal.
When no other attack is active, Karma hops across all 13 channels every 300ms to catch probes from devices on any channel. The web admin panel shows all captured probes with a Clone button that instantly launches an Evil Twin for that specific SSID.
BLE Spam
Artemisz can also spam Bluetooth Low Energy advertisements to trigger pairing popups on nearby devices:
| Mode | Target | Technique |
|---|---|---|
| Sour Apple | iOS/macOS | Apple manufacturer IE (0x4C00) with AirDrop action codes |
| Swiftpair | Windows | Microsoft pairing service UUID (0xFE2C) with fake device names |
| Samsung | Samsung Galaxy | Samsung manufacturer IE (0xFF75) with model IDs |
Each mode broadcasts on all 3 BLE advertising channels every 100ms with randomized device addresses and payloads. The BLE stack is initialized on-demand and runs alongside WiFi without issues — the ESP32’s radio supports concurrent WiFi + BLE.
Three Interfaces
TFT Display
The 170×320 ST7789 display shows a live dashboard: uptime, network count, target info, attack status, RSSI with color coding (green/yellow/red), and a button legend. Credentials scroll with Up/Down buttons.
Web Admin Panel
Connect to the Artemisz AP and navigate to 192.168.4.1/admin (HTTP Basic Auth: admin / artemisz1337). The panel provides full control:
- Network scanner with SSID, BSSID, channel, signal strength, and select buttons
- Attack controls for all 14 modes
- Karma probe table with per-probe Clone button
- Handshake capture table with PCAP/HCCAPX download
- PMKID display with hex dump
- Credential log with verified/failed status
The HTML is rendered with chunked Transfer-Encoding to avoid building the entire response in RAM — critical when the ESP32 is simultaneously running WiFi AP, promiscuous sniffer, DNS server, and deauth loops.
Serial CLI
At 115200 baud, type help to see all commands. Everything the web panel can do is available via serial: scan, select, deauth, eviltwin, karma, nuke, csa, beacon, blespam, status, creds, and more.
Memory Management
The ESP32 has 320KB of SRAM. With WiFi (AP + STA), promiscuous mode, DNS, HTTP, and optionally BLE all running, free heap drops to dangerous levels. Here’s how Artemisz stays alive:
Chunked HTTP responses — instead of building a complete HTML string in RAM, the admin panel streams each table row individually using webServer.sendContent().
Flash-stored templates — static HTML/CSS is stored in flash using const char[] with raw string literals, keeping it out of the heap entirely.
IRAM for the sniffer — the promiscuous callback is marked IRAM_ATTR to run from internal high-speed RAM, avoiding cache misses during the ~100+ calls/second it receives.
Fixed-size circular buffers — the signal monitor uses a 170-byte ring buffer that overwrites old data instead of growing.
TX buffer pacing — esp_wifi_80211_tx() queues frames in a ~16-frame hardware FIFO. Sending faster than the radio can transmit causes ESP_ERR_NO_MEM. The 100ms deauth interval with 5-frame bursts keeps the queue at ~2–3 frames.
Platform Constraints
This project is pinned to espressif32@6.7.0 (Arduino Core 2.0.x / ESP-IDF 4.4). Arduino Core 3.x (ESP-IDF 5.x) blocks the ieee80211_raw_frame_sanity_check override, making raw management frame injection impossible. The version pin in platformio.ini handles this automatically.
Building It
- Install PlatformIO
- Clone the repo:
git clone https://github.com/ssnrshnn/artemisz.git - Build and flash:
pio run -t upload - Open serial monitor:
pio device monitor
The platformio.ini includes all TFT_eSPI config as build flags — no need to manually edit User_Setup.h in the library folder.
Disclaimer
This tool is for authorized security research and educational purposes only. Unauthorized access to computer networks is illegal under the Computer Fraud and Abuse Act (US), Computer Misuse Act (UK), and equivalent laws worldwide. Always obtain written permission before testing any network you don’t own.
Built by ssnrshnn — github.com/ssnrshnn/artemisz