Zero-Configuration, Serverless Hardware Honeypot on a Single ESP32-S3
π Language: English Β· Π£ΠΊΡΠ°ΡΠ½ΡΡΠΊΠ°
The live dashboard streaming captured attacks in real time β protocol traps, GeoIP-located sources, harvested credentials and a captured shell session. (Illustrative data.)
ShadowSentry S3 is a self-contained Edge Deception hardware honeypot. It turns a single ESP32-S3 board (~$5) into an invisible trap for botnets, scanners and malware inside your local network. No Raspberry Pi, no cloud servers, no external databases β all processing and logging happen on one chip.
Honeypot traps (Core 0)
- 5 protocol traps β RTSP (554, fake Hikvision camera), HTTP (80, fake NVR login + per-request fingerprinting), Telnet (23), SSH (22), FTP (21). Every credential pair is captured.
- Real SSH server (wolfSSH) β genuine SSH-2.0 handshake (curve25519 + ECDSA host key) that decrypts the auth exchange and captures the plaintext username + password β not just a banner fingerprint.
- Interactive fake shell (Cowrie-style) β Telnet and SSH accept the login and drop the attacker into a believable Ubuntu 20.04 shell that answers recon commands while logging every command; download/exec commands (
wget/curl/chmod +x/./β¦) are flagged as IOCs and escalated.
Network & radio monitors (Core 1)
- ARP-spoof / MITM monitor β watches the lwIP ARP cache for cache-poisoning signatures (gateway MAC change, one MAC claiming many IPs).
- Wi-Fi threat monitor β detects 802.11 deauth/disassoc attacks (incl. the single-frame deauth used for handshake capture) via reason-code-classified forced disconnects + promiscuous broadcast-flood sniffing.
- MAC + vendor for every event β resolves the attacker's L2 MAC from the ARP table with a best-effort OUI vendor guess; randomized (private) MACs flagged.
- Threat-intel / GeoIP enrichment β resolves each attacker IP to country, ISP/ASN and a reputation tag (
hosting/proxy/mobile) via ip-api.com; private IPs labelledPrivate LANwith no external call.
Platform
- Live web dashboard + REST API on port 9999 (HTTP Basic Auth) β stat cards, attack feed, clickable attack-detail modal. A dedicated WebSocket push server (port 9998) streams every new attack to the dashboard in real time, so the feed updates the instant a probe lands instead of waiting for the next poll.
- Telegram alerts β async queue with resilient delivery (waits for reconnect + retries, so an alert survives the very deauth that knocks the board offline).
- SPIFFS persistence β attack log ring buffer + all-time counter survive reboots.
- Dual-core design β honeypot traps on Core 0, admin/alerting/monitors on Core 1.
- Zero-config & serverless β flash, set Wi-Fi + Telegram token, done. mDNS
.localname, no cloud, no database.
Thanks to the dual-core Xtensa LX7 processor, the project is split into two isolated worlds:
| Core | Role | Tasks |
|---|---|---|
| Core 0 β Hacker World | Receives attacks | RTSP :554 Β· HTTP :80 Β· Telnet :23 Β· SSH :22 Β· FTP :21 |
| Core 1 β Admin World | Management & alerts | Admin Panel :9999 Β· Telegram Β· SPIFFS |
Attacker / bot
β
ββ Port 554 (RTSP) β Fake Hikvision DS-2CD camera ββ
ββ Port 80 (HTTP) β Fake Hikvision NVR login page β
ββ Port 23 (Telnet) β Fake Ubuntu 20.04 server ββββΊ log_store β SPIFFS
ββ Port 22 (SSH) β Fake OpenSSH 8.9p1 β β
ββ Port 21 (FTP) β Fake vsFTPd 3.0.5 ββ βΌ
Telegram Alert
β
Admin Panel :9999
(Dark-mode Dashboard)
| Protocol | Captured | Example |
|---|---|---|
| RTSP | Username + Password | admin:12345 from the Basic Auth header |
| HTTP | Username + Password + path + User-Agent | NVR login POST form; every request (GET/POST/other) is fingerprinted by path and the scanner's User-Agent |
| Telnet | Username + Password + post-login commands | Login prompt, then a fake interactive shell that logs every command |
| SSH | Username + Password + post-login commands | Real SSH-2.0 server (wolfSSH) β root:hunter2, then the interactive shell |
| FTP | Username + Password | USER admin / PASS password (RFC 959) |
Real SSH server (wolfSSH). Port 22 is a genuine SSH-2.0 server, not a banner: wolfSSH performs the full key exchange (curve25519) and presents an ECDSA host key, so the device terminates the crypto and captures the plaintext username and password the attacker submits β something a banner-only trap can't do. The login is then accepted (any password; it's a decoy) and the attacker lands in the same interactive fake shell as Telnet, with every command logged. One caveat: wolfSSH advertises its own version string, so a fingerprinting client can tell it isn't OpenSSH β the value here is credential + command capture, not banner mimicry.
Interactive fake shell (Cowrie-style). Instead of endlessly replying "Login incorrect", the Telnet and SSH honeypots accept the login and drop the attacker into a believable Ubuntu 20.04 shell that answers common recon commands (
ls,cat /etc/passwd,uname -a,ps,ifconfig,wget, β¦) while logging every command they type as aShellevent. Capturing the post-login command set reveals attacker TTPs and IOCs β which payloads they fetch, which binaries they try to run β that a credential-only honeypot never sees. Download/exec commands (wget/curl/tftp/chmod +x/./β¦) are flagged and escalated to a Telegram alert. Nothing is ever executed: responses are canned, the filesystem is fictional, and downloads are faked. Tunable viaTELNET_SHELL_ENABLE/TELNET_LOGIN_GRANT_ATTEMPTinconfig.h.
MAC address for every protocol. Since the attacker is on the same local network, for each event ShadowSentry resolves their MAC via the lwIP ARP table and shows it together with a best-effort vendor guess (OUI). A randomized MAC (private, typical for smartphones) is flagged separately. The MAC is shown both in the dashboard and in the Telegram alert.
Threat-intel enrichment. A background worker resolves each attacker IP to its country, ISP/ASN and a reputation tag (
hosting/proxy/mobile) via ip-api.com β free and key-less, so it works the moment you flash it. The result (with a country flag) shows in the dashboard and the Telegram alert; private/LAN source IPs are labelledPrivate LANwithout any external call. Lookups run off the hot path and are cached by IP. Tunable viaGEOIP_ENABLE/GEOIP_CACHE_SIZEinconfig.h.
Wi-Fi threat monitor (radio layer). The ESP32 isn't just a TCP stack β it's a Wi-Fi radio, so it can catch 802.11 deauthentication / disassociation attacks that no software honeypot on a normal network stack can see β including the single-frame deauth used to force a reconnect and capture a WPA2 handshake. Two signals: (1) forced disconnects β a deauth knocks the device off-air, so it always observes its own drop, and each drop is classified by its 802.11 reason code (a deauth-induced drop carries a low code 1β9; a benign RF loss reports 200+ and is ignored), so even one deauth-attributable disconnect fires an alert with no false positives from a flaky link; (2) broadcast deauth floods sniffed in promiscuous mode, carrying the attacker's transmitter MAC and target BSSID. Either raises a
WiFievent. Runs on the channel the device is associated to β no channel hopping. (Rogue/evil-twin AP detection would need channel hopping and is out of scope for now.) Tunable viaWIFI_MON_ENABLE/WIFI_MON_DEAUTH_DISC_THRESHOLDinconfig.h.
ARP-spoof / MITM monitor. A background task periodically scans the lwIP ARP cache for cache-poisoning signatures β the gateway's MAC changing after a stable baseline is learned, or one MAC claiming several IPs β and raises an
ARPevent (dashboard feed + Telegram) when it sees one. This catches L2 man-in-the-middle attacks that the port honeypots are blind to, since they never complete a TCP handshake. Scope: it detects spoofing that targets this host or is broadcast network-wide (the default for bettercap/ettercap); a strictly point-to-point spoof between two other hosts is out of scope. Tunable viaARP_MONITOR_ENABLE/ARP_SCAN_INTERVAL_S/ARP_ALERT_COOLDOWN_Sinconfig.h.
Detection in action. Verified on real ESP32-S3 hardware against a live bettercap ARP spoof β the instant the attacker poisoned the board's gateway entry, the monitor logged it and pushed a Telegram alert (values below are anonymized):
ARP-MON: Gateway 192.168.1.1 MAC changed
aa:bb:cc:dd:ee:01 -> de:ad:be:ef:13:37 (MITM redirect)
- ESP32-S3 DevKit (any board with β₯ 4 MB Flash)
- USB cable for flashing
- 2.4 GHz Wi-Fi network
| Component | Version |
|---|---|
| ESP-IDF | v5.2+ |
| Python | 3.8+ |
| CMake | 3.16+ |
git clone --recursive https://github.com/espressif/esp-idf.git ~/esp/esp-idf
cd ~/esp/esp-idf
git checkout v5.2.1
./install.sh esp32s3
. ./export.shDownload and run the ESP-IDF Windows Installer.
After installation, open the ESP-IDF Command Prompt for all commands below.
# Copy the configuration template
cp main/config.h.example main/config.h
# Edit it for your setup
nano main/config.hAll parameters live in a single file β main/config.h:
// Network identity (what shows up in the router's device list)
#define DEVICE_HOSTNAME "Hikvision-NVR"
// Wi-Fi
#define WIFI_SSID "YourWiFiSSID"
#define WIFI_PASSWORD "YourWiFiPassword"
// Telegram (get it from @BotFather)
#define TELEGRAM_BOT_TOKEN "YOUR_BOT_TOKEN"
#define TELEGRAM_CHAT_ID "YOUR_CHAT_ID"
// Admin panel β http://<ip>:9999
#define ADMIN_PASSWORD "changeme1"
#define ADMIN_PORT 9999
// Honeypot ports
#define RTSP_PORT 554
#define HTTP_PORT 80
#define TELNET_PORT 23
#define SSH_PORT 22
#define FTP_PORT 21
main/config.his added to.gitignoreβ real credentials never reach the repository.
- Send
/newbotto @BotFather - Copy the returned token into
TELEGRAM_BOT_TOKEN - Send any message to your bot, then open:
https://api.telegram.org/bot<TOKEN>/getUpdates - Find
"chat":{"id":...}β that is yourTELEGRAM_CHAT_ID
# 1. Clone the repository
git clone https://github.com/Rdx1S/ShadowSentryS3.git
cd ShadowSentryS3
# 2. Activate ESP-IDF
. ~/esp/esp-idf/export.sh
# 3. Copy and fill in the config
cp main/config.h.example main/config.h
nano main/config.h
# 4. Build and flash (replace /dev/ttyUSB0 with your port)
idf.py -p /dev/ttyUSB0 flash monitor| OS | Command |
|---|---|
| Linux | ls /dev/ttyUSB* or ls /dev/ttyACM* |
| macOS | ls /dev/cu.usb* |
| Windows | Device Manager β Ports (COM & LPT) |
After flashing, the monitor shows:
I (426) MAIN: ββββββββββββββββββββββββββββββββββββββββ
I (428) MAIN: β ShadowSentry S3 v1.0 β
I (434) MAIN: β Edge Deception HoneyPot β
I (439) MAIN: β ESP32-S3 | ESP-IDF v5.x β
I (444) MAIN: ββββββββββββββββββββββββββββββββββββββββ
I (1827) WIFI: IP acquired: 192.168.1.105
I (1830) WIFI: Admin panel β http://192.168.1.105:9999
I (1842) WIFI: mDNS started β http://hikvision-nvr.local:9999
I (1904) RTSP: Honeypot listening on port 554
I (1910) HTTP: Honeypot listening on port 80
I (1916) TELNET: Honeypot listening on port 23
I (1924) SSH: Honeypot listening on port 22
I (1930) FTP: Honeypot listening on port 21
I (1938) ADMIN: Admin panel on port 9999
Open a browser β http://192.168.1.105:9999
Login: admin / the password from ADMIN_PASSWORD.
No need to memorize the IP. Thanks to mDNS the device is always reachable at a stable name β
http://hikvision-nvr.local:9999β regardless of the address DHCP assigns. Works out of the box on macOS, Linux (avahi), Windows 10+, iOS and Android. The name is configurable viaMDNS_HOSTNAMEinconfig.h.
A dark-mode web interface with a real-time WebSocket feed (new attacks appear instantly), backed by a 10-second poll for reconciliation and threat-intel updates. Every stat card and distribution row is clickable to filter the feed/table by attack type:
- 6 stat cards β Total, Unique IPs, RTSP, HTTP, Telnet, SSH, FTP
- Donut chart β real-time breakdown of attacks per protocol
- Attack table β timestamp, IP, MAC + vendor, protocol, captured credentials, payload
- Footer β device uptime, free heap, Wi-Fi signal strength (RSSI)
- Clear button β wipes the logs from flash
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/attacks |
Attack log + statistics (JSON) |
GET |
/api/status |
Uptime / heap / RSSI (JSON) |
POST |
/api/clear |
Clear the log |
All endpoints are protected with HTTP Basic Auth (admin / ADMIN_PASSWORD).
ShadowSentryS3/
βββ CMakeLists.txt ESP-IDF root build file
βββ sdkconfig.defaults ESP32-S3 defaults (240 MHz, dual-core)
βββ partitions.csv NVS(24KB) + App(3MB) + SPIFFS(1MB)
βββ main/
βββ config.h.example β Configuration template (copy to config.h)
βββ config.h β Real settings (in .gitignore)
βββ idf_component.yml Managed dependencies (espressif/mdns)
βββ main.c Entry point, task distribution across cores
βββ wifi_manager.c/h Wi-Fi STA, DHCP hostname, SNTP, mDNS, ARP helpers
βββ arp_monitor.c/h ARP-spoof / MITM detector (Core 1)
βββ wifi_monitor.c/h Wi-Fi deauth-flood detector (promisc) (Core 1)
βββ geoip.c/h Threat-intel enrichment (ip-api.com) (Core 1)
βββ index.html Dashboard HTML (embedded into the firmware)
βββ CMakeLists.txt
βββ honeypot/ ββ Core 0 β Hacker World ββββββββββββββ
β βββ rtsp_trap.c/h Port 554, Fake Hikvision, Base64 creds
β βββ http_trap.c/h Port 80, Fake NVR login page
β βββ telnet_trap.c/h Port 23, Fake Ubuntu 20.04 login
β βββ fake_shell.c/h Interactive post-login shell (command capture)
β βββ ssh_trap.c/h Port 22, Real SSH server (wolfSSH) β captures creds
β βββ ssh_hostkey.h Embedded ECDSA host key (decoy)
β βββ ftp_trap.c/h Port 21, Fake vsFTPd 3.0.5, full creds
βββ admin/ ββ Core 1 β Admin World βββββββββββββββ
β βββ admin_panel.c/h Port 9999, HTTP Basic Auth, REST API
β βββ ws_server.c/h Port 9998, live WebSocket event push
β βββ telegram.c/h Async FreeRTOS queue β Telegram Bot API
βββ storage/
βββ log_store.c/h RAM ring buffer (200 entries) + SPIFFS
No legitimate home-network device (laptop, phone, Smart TV) ever connects to ports 554, 80, 23, 22 or 21 on the ESP32 board.
Any connection to ShadowSentry S3 = a 100% anomaly.
Typical detection scenarios:
| Threat | Behaviour | Detection time |
|---|---|---|
| Mirai / Mozi botnet | Brute-force RTSP/Telnet/FTP | < 5 s |
| Post-login intrusion | Commands run in the fake Telnet shell (recon, payload fetch) | per command |
| Ransomware lateral movement | Subnet scanning | < 5 s |
| SSH scanner | Version fingerprint on port 22 | < 1 s |
| Web scanner | GET / on port 80 | < 1 s |
| Manual scan (nmap) | SYN on any port | < 1 s |
| ARP spoofing / MITM | Gateway MAC change or one MAC claiming many IPs | β€ scan interval (8 s) |
| Wi-Fi deauth / disassoc attack | Reason-code-classified forced disconnect (β₯1) or broadcast flood (promiscuous) | β€ window (2 s) |
All components ship with ESP-IDF β nothing to install separately:
lwIPβ TCP/IP stack, raw socketsFreeRTOSβ multitasking, queuesesp_http_clientβ Telegram Bot APImbedTLSβ Base64 decode for Basic AuthSPIFFSβ flash file systemesp_netif_sntpβ time synchronization
One component is fetched automatically by the component manager (declared in main/idf_component.yml, downloaded on the first build):
espressif/mdnsβ.localname resolution (reach the admin panel by name)
Won't connect to Wi-Fi
ESP32-S3 supports 2.4 GHz only. Check the SSID/password in config.h.
idf.py: command not found
. ~/esp/esp-idf/export.shPermission denied on /dev/ttyUSB0 (Linux)
sudo usermod -a -G dialout $USER
# Log out and back inSPIFFS: mount failed error
idf.py -p /dev/ttyUSB0 erase-flash
idf.py -p /dev/ttyUSB0 flashTelegram doesn't send alerts
Make sure the bot isn't blocked and you sent it /start.
TELEGRAM_CHAT_ID is a number (can be negative for groups).
Shipped
- 5 protocol honeypot traps (RTSP / HTTP / Telnet / SSH / FTP)
- Per-attack MAC + vendor (OUI) resolution
- HTTP request fingerprinting (method / path / User-Agent)
- Telegram alerts with resilient delivery + retry
- Live web dashboard + REST API, clickable attack-detail modal
- mDNS
.localresolution - ARP-spoof / MITM monitor
- Threat-intel / GeoIP enrichment (country Β· ASN Β· reputation)
- Wi-Fi deauth/disassoc threat monitor (reason-code classified)
- Interactive fake shell with per-command logging + IOC escalation (Telnet)
- Real SSH server via wolfSSH β credential capture + fake shell
- Live WebSocket dashboard β real-time event push (polling fallback)
Planned
- SSH version-banner spoofing (present as OpenSSH, not wolfSSH)
- Per-device SSH host-key generation at first boot (NVS-cached)
- Rogue / evil-twin AP detection (requires channel hopping)
- Additional protocol traps (SMB, MQTT, UPnP)
MIT β use, modify and distribute freely.
