ESP32 Marauder · Volume 8

ESP32 Marauder Firmware Volume 8 — SD Card Layout, Evil Portal Templates, and Customization

Directory tree, FAT32 constraints, beacon-spam SSID format, Evil Portal HTML authoring with per-OS bypass recipes, settings schema

Contents

SectionTopic
1About this volume
2The SD-card directory tree
· 2.1Standard tree (mainline)
· 2.2Fork-specific additions
3SD card formatting requirements
4Beacon-spam SSID list format
5Evil Portal HTML authoring
· 5.1File priority and locations
· 5.2Form structure and field naming
· 5.3Asset bundling (CSS / JS / images)
· 5.4Per-OS captive-portal bypass recipes
· 5.5Common pretext templates
· 5.6Size constraints
6Settings.txt schema
7Cross-fork SD compatibility
8Wordlists and cred-log housekeeping
9Backup, transfer, and chain-of-custody
10Resources

1. About this volume

Vol 8 is the SD card volume. Most user customization of Marauder happens on the SD card, not in firmware rebuilds — Evil Portal HTML, beacon-spam SSID lists, wordlists for offline cracking, settings persistence. This volume documents the layout, formats, and authoring conventions for all of it.

The Evil Portal HTML authoring section (§ 5) is the largest — Evil Portal is the most operationally complex feature in Marauder and the most user-customizable. The captive-portal bypass recipes per OS (§ 5.4) are the most-asked-about content in this volume.


2. The SD-card directory tree

2.1 Standard tree (mainline)

When Marauder mainline boots for the first time with an SD card mounted, it creates the canonical directory tree:

/marauder/                          ← root of all Marauder content on the card
├── pcaps/                          ← packet capture output (Vol 4 + Vol 6)
│   ├── eapol_<timestamp>.pcap
│   ├── pmkid_<timestamp>.pcap
│   └── ble_<timestamp>.pcap
├── evil_portal/                    ← Evil Portal user content (Vol 5 § 5)
│   ├── index.html                  ← the captive page (override SPIFFS bundled)
│   ├── style.css                   ← optional asset
│   └── logo.png                    ← optional asset
├── wordlists/                      ← user-supplied wordlists (Vol 9 § 4)
│   ├── rockyou.txt                 ← typical staple — 14 MB compressed
│   └── custom_<engagement>.txt
├── beacons.txt                     ← beacon-spam SSID list (§ 4)
├── creds.txt                       ← captured Evil Portal credentials (Vol 5 § 5.4)
├── evil_portal.log                 ← Evil Portal connection events
└── settings.txt                    ← runtime settings (§ 6)

Standalone files (not under any subdirectory):

  • beacons.txt — SSID list for beacon spam
  • creds.txt — credential capture log
  • evil_portal.log — connection/disconnect events
  • settings.txt — runtime config

Files written by Marauder are append-only (creds.txt, evil_portal.log) or rewritten in full (settings.txt). User-edited files (beacons.txt, evil_portal/index.html) are read-only from Marauder’s perspective.

2.2 Fork-specific additions

ForkAdditional directoriesNotes
Mainline(none beyond § 2.1)The baseline tree
Ghost ESP/marauder/airtags/, /marauder/ble_spam/AirTag detection logs + BLE-spam-payload customization
Bruce/Bruce/ parallel root with sub-GHz / IR / RFID subdirsBruce uses its own /Bruce/ root; the /marauder/ tree co-exists for Marauder-class features
Bad PinguinoSubset of mainline treeOnly pcaps/ and settings.txt typically

Cross-fork migration (Vol 7 § 8) preserves the /marauder/ tree. Fork-specific additions remain on the card as inert directories after switching forks — harmless.


3. SD card formatting requirements

FAT32 mandatory. ExFAT is explicitly not supported — Arduino-ESP32’s SD.h driver does not include exFAT support in default builds, and Marauder ships against the default. NTFS, ext4, and other filesystems are likewise unsupported.

Card size and cluster size:

Card sizeFAT32-spec statusMarauder verdict
1-2 GBNative FAT32 / FAT16 — fine
4 GBFAT32 native — fine✓ (the cheapest viable card)
8-32 GBFAT32 native — sweet spotrecommended
64 GBAbove FAT32 spec — formatter dependentWorks if formatted FAT32 on host with appropriate cluster size; manufacturer’s exFAT-by-default format won’t work
128-256 GBSame as 64 GBSame
> 256 GBPractical FAT32 limitNot recommended; cluster size grows large, performance degrades

Speed class:

  • Class 10 / U1 minimum (10 MB/s sustained write) — sufficient for any Marauder capture
  • U3 / V30 / faster — no benefit; SPI bus is the bottleneck (~2 MB/s peak)

Pre-formatting on host:

Format the card before inserting into Marauder. Windows’ built-in formatter handles up to 32 GB as FAT32 natively; for larger cards, use a third-party formatter (Rufus, EaseUS, or the SD Association’s SD Card Formatter tool with the “Overwrite” option). Mac and Linux can format any size as FAT32 via the terminal:

# Linux:
sudo mkfs.vfat -F 32 /dev/sdX1
# Mac (via Disk Utility GUI is simpler; CLI:)
diskutil eraseDisk FAT32 MARAUDER MBRFormat /dev/diskN

Recovery from “no SD card detected”:

Marauder’s first-boot directory creation only fires if the card is detected. If the SD slot reports no card and the card is physically inserted:

  1. Try a known-good card (4 GB FAT32 class-10) to rule out card vs slot issue.
  2. Check Marauder serial output (USB) — SD mount failed messages appear if the card is detected but unmountable.
  3. Re-format on host; ensure FAT32 not exFAT.
  4. If the slot has hardware issues (loose contacts), a microSD-to-microSD spring-mount adapter rarely helps — replace the host hardware.

4. Beacon-spam SSID list format

/marauder/beacons.txt carries the SSID list for beacon-spam attacks (Vol 5 § 3.3). Format:

  • One SSID per line.
  • UTF-8 encoding. Emoji and non-ASCII characters work; multi-byte UTF-8 sequences count against the 32-byte SSID limit.
  • 32-byte SSID hard limit per line. The 802.11 SSID-length field is 1 byte (255 max value) but Marauder truncates to 32 bytes per long-standing IEEE convention. Lines exceeding 32 bytes are silently truncated; ASCII users are fine up to 32 characters.
  • Comment lines start with # and are skipped at parse time.
  • Empty lines are skipped.
  • Maximum total entries: ~500-1000 depending on host hardware (PSRAM-equipped S3 hosts more; classic-ESP32 hosts less). Exceeding the limit silently truncates the list.

Example beacons.txt:

# Marauder beacon-spam custom list — 2026-05-13
# One SSID per line, 32-byte UTF-8 max
#
# Rick Roll classics
Never Gonna Give You Up
Never Gonna Let You Down
Never Gonna Run Around
Rick Roll Free WiFi
#
# Corporate-impersonation pretexts (use only on authorized engagements)
Conference_Guest
Hotel_WiFi
Free_Airport_WiFi
Starbucks_WiFi
#
# Emoji (each emoji is ~4 bytes UTF-8 — so ~7 emoji per line max)
🎶 Rick Roll 🎶
📡 Free WiFi 📡

Marauder reads beacons.txt at attack-start; runtime changes to the file are not picked up until the attack stops and restarts. To switch between lists during an attack: stop, swap the file, restart.

Multi-list switching pattern: keep multiple files on SD (beacons_rickroll.txt, beacons_pretexts.txt, beacons_random.txt) and either edit settings to point at the right one (some forks support this) or rename the active file to beacons.txt on the host before insertion.


5. Evil Portal HTML authoring

5.1 File priority and locations

Marauder looks for Evil Portal content in priority order:

  1. /marauder/evil_portal/index.html on the SD card (preferred) — overrides everything.
  2. /data/index.html in the firmware’s SPIFFS / LittleFS partition — fallback when no SD content.

When the SD content is present, the SPIFFS bundled version is ignored entirely — Marauder doesn’t fall back per-asset. If your custom page references style.css and you only have index.html on SD, the style won’t load (the SPIFFS bundled style.css is unreachable in mixed-mode).

Always put the full asset set on SD when overriding — index.html plus any CSS, JS, images. Place them in /marauder/evil_portal/ and reference them relatively from the HTML.

5.2 Form structure and field naming

The minimum viable Evil Portal HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Network Sign-In</title>
</head>
<body>
    <h2>Sign in to continue</h2>
    <form action="/get" method="get">
        <label>Email: <input type="email" name="email" required></label><br>
        <label>Password: <input type="password" name="password" required></label><br>
        <button type="submit">Connect</button>
    </form>
</body>
</html>

Critical details:

  • action="/get" and method="get" — Marauder logs URL query parameters. The form fields end up as [email protected]&password=hunter2 in the GET request, which Marauder parses out of the URL.
  • Newer mainline builds also support method="post" with action="/post". POST is preferred for credentials (doesn’t expose them in URL access logs).
  • Field name attributes become the keys in /marauder/creds.txt. Match field names to what makes sense in the engagement report.
  • Required fields prevent empty submissions; not strictly necessary but reduces noise in the cred log.

Beyond email + password:

Add whatever fields the pretext calls for — phone number, room number, employee ID, conference badge code. Marauder logs all of them.

5.3 Asset bundling (CSS / JS / images)

Static assets go alongside index.html in /marauder/evil_portal/. Reference them relatively:

<link rel="stylesheet" href="style.css">
<img src="logo.png" alt="Company">
<script src="app.js"></script>

Marauder’s HTTP server (Vol 5 § 5.1) serves any file requested under the captive AP. Asset paths resolve relative to the request URL — /style.css is served from /marauder/evil_portal/style.css.

Size guidance:

  • Keep total page (HTML + CSS + JS + images) under 64 KB. Smaller is faster — captive-portal sheets display the page incrementally; large pages render visibly slowly on iOS captive sheets.
  • Avoid external CDN references — they won’t resolve (DNS spoof points everything to Marauder; Marauder doesn’t have the CDN content). Bundle everything locally.
  • Inline CSS / small JS rather than separate files when possible — fewer HTTP round-trips on resource-constrained mobile captive sheets.

5.4 Per-OS captive-portal bypass recipes

(See Vol 5 § 5.2 for the underlying detection mechanism.)

Default behavior — Marauder serves index.html to every URL. iOS / Android / Windows captive sheets pop, showing the page directly. Effective for attended attacks where the user immediately interacts.

Recipe A — Suppress iOS captive sheet (stealthy long-duration capture):

iOS tests with captive.apple.com/hotspot-detect.html. To suppress the sheet, serve the Apple-expected success page to that exact URL:

In /marauder/evil_portal/index.html, do not change anything. The trick is to configure Marauder to serve the Apple success page at /hotspot-detect.html (the URL iOS specifically tests). Some forks expose this as a setting; mainline does not directly.

For mainline operators, the workaround: edit the firmware’s HTTP handler to add a path-routing exception. This requires a rebuild (Vol 10). For Ghost ESP, there’s a settings toggle.

Apple’s expected success body:

<HTML><HEAD><TITLE>Success</TITLE></HEAD><BODY>Success</BODY></HTML>

When iOS gets this, it considers the network “open” and doesn’t pop the captive sheet. The user must manually navigate to the captive page (e.g., open Safari and hit any URL) to see it.

Use case: sustained capture in venues where you want devices to stay-connected-quietly until users actively need a URL.

Recipe B — Suppress Android sheet (HTTP 204 short-circuit):

Android tests with connectivitycheck.gstatic.com/generate_204 and expects HTTP 204. Serve HTTP 204 (no content) to that exact URL.

Same caveat as iOS — requires firmware-level routing exception or fork-specific setting. Implementation effort similar to Recipe A.

Use case: mixed-OS environment where you want Android devices to stay quiet alongside iOS handling above.

Recipe C — Serve different pages per User-Agent (advanced):

Some Evil Portal forks expose User-Agent-based template selection. Useful when you want OS-specific pretexts (e.g., “Apple ID required” on iOS users, “Microsoft account login” on Windows users). Mainline doesn’t support this; community templates implement it in JavaScript on the client side (detect user agent → redirect to template HTML).

Implementation is fragile (UA strings change) but raises engagement quality.

5.5 Common pretext templates

Common Evil Portal pretexts the community ships:

PretextWhen applicableField names typically captured
Generic “Wi-Fi sign-in”Most common — works in most public Wi-Fi contextsemail, password
Hotel check-inHospitality venuesroom_number, last_name, password
Conference regTech conferencesemail, badge_code
ISP / cable-modem loginResidentialusername, password
Corporate Wi-Fi pretextOffice buildingsemployee_id, password
Captive-portal terms of serviceLow-engagement; click-to-accept variant(no creds — operational signal only)
Apple ID phishing variantApple-heavy eventsapple_id, password, verification_code
Microsoft account variantCorporate Windows-heavy environmentsemail, password, mfa_code

Templates ship in the Marauder repo’s data/ directory (browse github.com/justcallmekoko/ESP32Marauder/tree/master/data) and in the Ghost ESP / Bruce equivalents. Community-contributed templates also live on dedicated GitHub repos — search “Evil Portal HTML template”.

5.6 Size constraints

  • Total index.html + bundled assets: keep under 64 KB. Faster render on captive sheets.
  • Single asset file: keep under 16 KB. Larger files block the page load.
  • Total /marauder/evil_portal/ directory: keep under 1 MB. Beyond this, SD-card seek time on the SPI bus becomes a UX problem.

For high-fidelity engagements (real-looking corporate branding, logos, polished CSS), the budget is tight. Use SVG for logos instead of PNG (smaller, scales well); inline CSS rather than separate files; minify HTML/JS.


6. Settings.txt schema

/marauder/settings.txt is a simple key:value text file Marauder loads at boot and rewrites when settings change in the menu UI.

Format:

  • One key:value pair per line
  • No quotes around values
  • Whitespace around : is not tolerated — key:value not key : value
  • Empty lines and lines starting with # are skipped
  • Unknown keys are silently ignored (forward-compatibility with newer firmware)
  • Values are parsed by type based on the key — strings, integers, booleans (true/false)

Documented keys (mainline at v1.12.x):

KeyTypeDefaultNotes
EvilPortalSSIDstring”Marauder”SSID Evil Portal advertises
ChannelHopint1Hop interval in 100 ms units; 0 = static
StaticChannelint6Channel for static-channel mode
Brightnessint (0-100)75TFT backlight
ColorThemeint (0-3)0Display color palette
Countrystring”US”Channel-plan region (some forks only; mainline is build-time)
RandomMACbooltrueRandomize source MAC for attacks
GPSEnabledboolfalseActivate GPS module (only relevant on HAS_GPS builds)

Example /marauder/settings.txt:

# Marauder runtime settings
EvilPortalSSID:CompanyGuest
ChannelHop:0
StaticChannel:6
Brightness:60
ColorTheme:1
RandomMAC:true

Cross-fork delta:

  • Ghost ESP adds keys for runtime country code, runtime beacon-rate, BLE-spam variant selection, AirTag-detect filter thresholds.
  • Bruce’s settings file is in a different format entirely (key=value rather than key:value, and namespaced by module).

Migration tip: don’t blindly copy settings.txt from mainline to Bruce — re-do settings from the menu UI on Bruce after migration.


7. Cross-fork SD compatibility

(See also Vol 7 § 8.)

FileMainlineGhost ESPBruceNotes
pcaps/*.pcapStandard link-type-105 format; identical
evil_portal/index.html(uses /Bruce/portal/)Path differs on Bruce
beacons.txtSame format; Ghost ESP supports more entries
wordlists/*.txtPlain text
creds.txt✓ (csv)✓ (csv)✓ (different field order)Format compatible at parse-line level
evil_portal.logPlain text log
settings.txtNativeCompatible + extra keysIncompatibleBruce uses different format

The migration recipe:

  1. Backup /marauder/ to host.
  2. Flash the new firmware (Vol 7 § 8.3).
  3. Reinsert SD; let new firmware create any missing directories.
  4. Restore your customizations (beacons.txt, evil_portal/index.html).
  5. Re-do settings in the menu UI — don’t trust the carried-over settings.txt.

8. Wordlists and cred-log housekeeping

/marauder/wordlists/ is for host-side tools, not Marauder itself — Marauder doesn’t crack passwords on-device. The wordlists live with the captures for portability (you carry the SD card to your laptop and run hashcat against wordlists/rockyou.txt against pcaps/eapol_*.pcap).

Staple wordlists:

  • rockyou.txt — the all-purpose 14 MB list. Pre-2010 leak. Top-10K-most-common passwords still hit a substantial fraction of consumer Wi-Fi.
  • darkc0de.txt — alternative classic.
  • Custom engagement-specific lists — generated from target-organization name, employee names, location names, common phrases.

creds.txt housekeeping:

  • Append-only from Marauder’s perspective. Manual rotation is operator’s job.
  • After every engagement: extract creds.txt to host, hash (sha256), securely delete from SD before next deployment.
  • Don’t ever ship creds.txt over the network in plain text — it’s plaintext credentials. Encrypt before any transfer.

9. Backup, transfer, and chain-of-custody

For engagement-deliverable workflows, the SD card carries plaintext credentials and personally-identifiable data. Chain-of-custody discipline:

At capture-time (in the field):

  1. Photo the SD card in situ with a known timestamp source (your phone clock) before removal — provenance metadata.
  2. Power down Marauder cleanly before SD removal (don’t pull during a write).
  3. Place SD in a labeled physical envelope or sealed container; note collection time + location.

At transfer:

  1. Hash the SD contents immediately on the receiving host:

    cd /mnt/sd_marauder
    find . -type f -exec sha256sum {} \; > /tmp/marauder_capture_<engagement>.sha256
  2. Verify the hash file on a separate host before delivery.

  3. Encrypt the SD contents at rest: tar + age / gpg for archive transfer.

At engagement close:

  1. Sanitize SD card — secure-erase by writing random data over the entire card multiple times. Linux:

    sudo dd if=/dev/urandom of=/dev/sdX bs=4M status=progress
    # Repeat 2-3 times for thoroughness
    sudo mkfs.vfat -F 32 /dev/sdX1
  2. Mac / Windows: equivalent tools (Disk Utility’s “Erase + Most Secure”, Rufus’ wipe option).

  3. Document the sanitization in the engagement report.

Don’t:

  • Don’t transfer creds.txt over unencrypted channels (email, Slack, plaintext HTTPS without strong identity verification).
  • Don’t keep creds.txt on the SD past the engagement-deliverable date.
  • Don’t share SD cards between engagements without sanitization in between.

Reference: ../../../_shared/legal_ethics.md for the broader Hack Tools posture on captured data.


10. Resources

Upstream

Evil Portal templates community

FAT32 tools

Wordlists

Forward references in this series

  • Capture analysis pipeline (post-SD-card workflow): Vol 9
  • Build toolchain (for firmware-level Evil Portal customization): Vol 10
  • Operational posture / chain-of-custody full treatment: Vol 11 § 7

This is Volume 8 of a twelve-volume series. Next: Vol 9 covers the host-side analysis pipeline — pcap → Wireshark / tshark / scapy / hashcat / aircrack-ng / bettercap. What to do with the captures once they’re off the SD card.