Skip to main content

        Artemisz - Featured image

Artemisz

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.

Cover

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.

Hardware — scanning networks Hardware — main dashboard

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 FAILED

The 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.

Display Display Display

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.

Web admin — scanner and attack controls

Web admin — advanced attacks, BLE, Karma

Web admin — captured credentials

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 pacingesp_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

  1. Install PlatformIO
  2. Clone the repo: git clone https://github.com/ssnrshnn/artemisz.git
  3. Build and flash: pio run -t upload
  4. 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