tworjaga/ESP32Gotchi
GitHub: tworjaga/ESP32Gotchi
一款运行在 ESP32 上的低成本自主式 Wi-Fi 握手包嗅探器与接入点记录器,能够独立捕获 WPA/WPA2 四步握手并输出带有 GPS 位置信息的 PCAP 和 CSV 日志。
Stars: 4 | Forks: 0
# ESP32Gotchi
[](https://www.espressif.com/en/products/socs/esp32)
[](LICENSE)
[](https://www.arduino.cc/)
[](https://github.com/tworjaga)
## 概述
ESP32Gotchi 是一个独立的被动式 Wi-Fi 握手包嗅探器和 AP 记录器,其灵感来源于 Pwnagotchi 项目。它运行在价值约 10-15 欧元的硬件组合上,无需主机计算机,并直接将标准的 PCAP 文件和 CSV 日志写入 microSD 卡。从开机起,所有操作均自主进行。
该固件使用 FreeRTOS 运行五个独立任务、一个混杂模式 Wi-Fi 回调、符合 IEEE 802.11-2020 标准的 EAPOL 解析、一个专门用于防止 I/O 阻塞数据包处理的 SD 写入任务,以及一个用于地理位置标记的可选 GPS 任务。
## 硬件
### 物料清单
| 组件 | 规格 | 预估成本 |
|-----------|--------------|--------------|
| MCU | ESP32 DevKit V1, 30-pin, ESP32-WROOM-32 | ~5 EUR |
| 显示屏 | 0.96″ SSD1306 OLED, 128×64, I2C (4-pin) | ~3 EUR |
| 存储 | MicroSD SPI 模块,兼容 3.3V | ~1 EUR |
| 按钮 | 轻触开关 | <0.50 EUR |
| LED | 3mm 或 5mm LED + 220 Ω 电阻 | <0.50 EUR |
| 电源(便携) | LiPo 3.7V + TP4056 USB-C 充电模块 | ~2 EUR |
| **GPS (可选)** | **Neo-6M UART 模块** | **~3 EUR** |
**总计:~10–15 EUR (包含 GPS)**
### 接线
**OLED — I2C**
```
ESP32 GPIO21 -> SDA
ESP32 GPIO22 -> SCL
ESP32 3.3V -> VCC
ESP32 GND -> GND
```
**MicroSD — SPI**
```
ESP32 GPIO18 -> SCK
ESP32 GPIO23 -> MOSI
ESP32 GPIO19 -> MISO
ESP32 GPIO5 -> CS
ESP32 3.3V -> VCC
ESP32 GND -> GND
```
**按钮 — GPIO4**
```
ESP32 GPIO4 -> Button -> GND
(internal pull-up enabled in firmware)
```
**LED (可选)**
```
ESP32 GPIO2 -> 220 ohm resistor -> LED anode
LED cathode -> GND
```
**GPS Neo-6M — UART2 (可选, v1.2.2)**
```
ESP32 GPIO16 -> Neo-6M TX (UART2 RX)
ESP32 GPIO17 -> Neo-6M RX (UART2 TX)
ESP32 3.3V -> Neo-6M VCC
ESP32 GND -> Neo-6M GND
```
**电池电压检测 — ADC (可选, v1.2.2)**
```
Vbat -> 100k resistor -> GPIO34 -> 100k resistor -> GND
(100k/100k divider: Vadc = Vbat / 2)
```
### 供电选项
仅 USB(开发/测试台使用):
```
USB -> ESP32 DevKit V1
```
便携(电池供电):
```
LiPo 3.7V -> TP4056 -> ESP32 VIN
```
## 硬件架构
```
ESP32-WROOM-32
|-- OLED SSD1306 (I2C: GPIO21/22)
|-- MicroSD module (SPI: GPIO18/19/23/5)
|-- Tactile button (GPIO4, active-low)
|-- Status LED (GPIO2, optional)
|-- Neo-6M GPS (UART2: GPIO16/17, optional) ← v1.2.2
|-- Batt ADC divider (GPIO34, optional) ← v1.2.2
|-- LiPo + TP4056 (optional, portable)
```
## 固件
### 架构
五个具有明确核心绑定的 FreeRTOS 任务:
| 任务 | 核心 | 优先级 | 栈 | 功能 |
|------|------|----------|-------|----------|
| `task_hop` | 0 | **6** | 2 KB | 在信道 1–11 之间循环切换,停留 200 ms。核心 0 上的最高优先级——保证运行。 |
| `task_proc` | 0 | 5 | 6 KB | 从队列中提取数据包,解析 802.11/EAPOL,管理握手槽位。在空闲时段排空 AP 日志队列。 |
| `task_write` | 0 | **4** | 6 KB | 接收(按槽位索引)已完成的握手包,将 PCAP 写入 SD 卡。排空 AP 日志队列,并每 60 秒写入一次统计信息 CSV。 |
| `task_ui` | 1 | 1 | 4 KB | 每 200 ms 更新一次 OLED,处理 LED、按钮和电池 ADC 读取。 |
| `task_gps` | 1 | 2 | 3 KB | 解析来自 UART2 的 NMEA 报文;更新 `g_gps` 结构体和软件 RTC。*(v1.2.2 新增)* |
### 内存布局
所有数据包存储均在启动时静态分配。运行时不使用 `malloc()` 或 `free()`(任务栈和一个 GPS 语句缓冲区是仅有的动态分配,仅在启动时执行一次)。
| 区域 | 大小 | 用途 |
|--------|------|---------|
| `pkt_pool_mem[32][1600]` | 51 200 B | 传输中的数据包缓冲区(由 `promisc_cb` 申请,由 `task_proc` 释放) |
| `hs_raw_pool_mem[32][1600]` | 51 200 B | 握手帧存储(保留至 PCAP 写入完毕,然后释放) |
| `g_hs[16]` 元数据 | ~640 B | 握手槽位状态(指向 `hs_raw_pool_mem` 的索引,而非帧数据) |
| `g_ap_table[256][6]` | 1 536 B | AP 哈希表 |
| **用户静态内存总计** | **~104 KB** | 完全在 Wi-Fi 协议栈之后剩余的约 200 KB 可用范围内 |
### EAPOL 检测
实现了 IEEE 802.11-2020 §12.7.2 key_info 位字段:
| 消息 | Pairwise | ACK | MIC | Install | Secure |
|---------|----------|-----|-----|---------|--------|
| Msg 1 | 1 | 1 | 0 | 0 | 0 |
| Msg 2 | 1 | 0 | 1 | 0 | 0 |
| Msg 3 | 1 | 1 | 1 | 1 | 1 |
| Msg 4 | 1 | 0 | 1 | 0 | 1 |
必须捕获到所有四个消息才能将握手标记为完成。不完整的槽位将在 15 秒后过期。
### PCAP 输出
文件被写入 SD 卡的 `/handshakes/` 目录下。
命名规则:`hs__.pcap`
示例:`hs_aa_bb_cc_dd_ee_ff_3721.pcap`
当 GPS 时间可用时,PCAP 数据包时间戳将使用 GPS UTC 时钟,而不是 `millis()`。
格式:标准 libpcap(魔数 `0xa1b2c3d4`),网络类型 105 (IEEE 802.11)。文件无需转换即可直接在 Wireshark 中打开。
### AP 日志输出 (v1.2.2 新增)
在信标和探测响应帧中看到的新 BSSID 会被记录到 `/aplog/aps.csv`:
```
timestamp,bssid,rssi_dbm,channel
2024-06-01T14:32:01Z,AA:BB:CC:DD:EE:FF,-62,6
2024-06-01T14:32:04Z,11:22:33:44:55:66,-74,11
```
当文件大小达到 4 MB 时,会自动轮转为 `aps_1.csv`。当 GPS 定位可用时,时间戳为 ISO-8601 UTC 格式;否则为运行时间字符串(`UP+00042s`)。
### 统计日志输出 (v1.2.2 新增)
设备统计信息快照将每 60 秒追加到 `/stats/stats.csv`:
```
timestamp,ap_count,pkt_rate,free_heap,rssi_last,rssi_drops,gps_lat,gps_lon,gps_fix
2024-06-01T14:33:00Z,14,87,148320,-67,203,48.208176,16.373819,1
```
文件达到 4 MB 时将轮转为 `stats_1.csv`。
### OLED 显示布局
显示屏现在有四个页面,通过短按按钮循环切换。
**第 1 页 — 统计**
```
(o_o) 1/4
HS: 12
CH: 6
AP: 34
PKT: 128
RSSI:-67 D:203
```
**第 2 页 — GPS**
```
(o_o) 2/4
FIX: YES
LAT:48.20818
LON:16.37382
ALT:183m
SAT:8 HDOP:1.1
```
**第 3 页 — SD**
```
(o_o) 3/4
SD: OK
FREE:3821MB
APS: 14KB
STS: 2KB
WR: OK
```
**第 4 页 — 系统**
```
(o_o) 4/4
HEAP:148320B
UP: 01:22:07
PKT: 87/s
DRP: 203
BAT: 78%
```
表情状态:
- `(o_o)` — 正常扫描中
- `(^o^)` — 正在收集 EAPOL 帧
- `(X_X)` — 错误(SD 卡缺失、空间不足)
- `(-_-)` — 空闲
### LED 模式
| 模式 | 含义 |
|---------|---------|
| 慢闪 (1 Hz) | 正常扫描中 |
| 快闪 (5 Hz) | 正在进行握手捕获 |
| 单次短闪 | 握手包已保存至 SD 卡 |
| 3 次长闪 (2 s) | SD 卡错误 — 重复提示 |
### 按钮行为
| 按压时长 | 操作 |
|---------------|--------|
| 短按 (50 ms – 3 s) | 切换 OLED 页面 (统计 → GPS → SD → 系统 → 统计) |
| 长按 (3 s – 6 s) | `ESP.restart()` |
| 超长按 (≥ 6 s) | 进入深度睡眠(通过同一按钮唤醒) |
### GPS 集成 (v1.2.2 新增)
将 Neo-6M(或兼容)GPS 模块连接到 UART2 (GPIO16/GPIO17)。`task_gps` 会解析带有 NMEA 校验和验证的 `$GPRMC` 和 `$GPGGA` 语句。当存在有效的定位时:
- `/aplog/aps.csv` 和 `/stats/stats.csv` 中的所有时间戳都将切换为 ISO-8601 UTC。
- PCAP 数据包记录的时间戳将使用 GPS UTC 而非 `millis()`。
- GPS 页面(第 2 页)将显示纬度、经度、海拔、卫星数量和 HDOP。
如果超过 10 秒未收到状态为 `A` 的有效 `$GPRMC` 报文,定位将被视为过期;固件会将 `FIX: YES` 降级为 `FIX: NO`,并停止报告坐标,直到下一次获得良好定位。如果在开机后 60 秒内未获得定位,将向 Serial 输出一次警告日志,并在没有 GPS 的情况下继续运行。
GPS 模块是可选的。如果未接线,`g_gps_available` 将保持为 `false`,并且所有时间戳将回退到运行时间字符串。无需修改代码。
### 电池监控 (v1.2.2 新增)
### 启动自检 (v1.2.2 新增)
在启动时,固件会在启动任何任务之前,在 OLED 上运行可视化自检序列:
```
Self-test v1.2.2
SD: OK
free: 3821MB
```
```
Self-test v1.2.2
GPS: UART OK
```
```
Self-test v1.2.2
HEAP: 152340B
```
```
Self-test v1.2.2
WIFI: OK
```
```
Cheapagotchi
v1.2.2 Ready
```
每个画面显示约 600 ms。如果 SD 卡检测失败,将显示 `SD: FAIL`,设备将在不记录 SD 日志的情况下继续运行。
### 深度睡眠 (v1.2.2 新增)
超长按按钮(≥ 6 秒)会触发干净的关机序列:
1. 禁用混杂模式。
2. 冲刷写入队列(超时时间最多 2 秒)。
3. 挂起 `task_write`。
4. 干净地卸载 SD 卡。
5. OLED 显示 `SLEEP ZZZ / Hold BTN to wake`。
6. `esp_deep_sleep_start()` — 在 GPIO4 为 LOW(同一按钮)时唤醒。
## 构建与烧录
### 要求
- Arduino IDE 2.x 或 PlatformIO
- 由 Espressif 提供的 ESP32 板包,版本 2.0.x 或更高
- U8g2 库(通过 Arduino Library Manager 安装)
- GPS 或电池监控不需要额外的库
### Arduino IDE
1. 安装板包:`File -> Preferences -> Additional Boards Manager URLs`
添加:`https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json`
2. 安装 U8g2:`Tools -> Manage Libraries -> search "U8g2"`
3. 板设置:
Board : ESP32 Dev Module
Partition scheme : Default 4MB with spiffs
CPU Frequency : 240 MHz
Flash mode : QIO
Upload speed : 921600
4. 打开 `Cheapagotchi.ino`,编译并烧录。
### PlatformIO
```
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps = olikraus/U8g2
board_build.partitions = default.csv
```
## SD 卡
- 格式:FAT32
- 建议最小容量:2 GB
- 固件会在首次启动时自动创建 `/handshakes/`、`/aplog/` 和 `/stats/`。
- 每次写入 PCAP 前的最小可用空间检查:1 MB。如果空间低于阈值,设备将继续嗅探但跳过保存。
- CSV 文件在达到 4 MB 时轮转(`aps.csv` → `aps_1.csv`,`stats.csv` → `stats_1.csv`)。
- 如果 SD 卡缺失或发生故障,设备将在下一次重试周期重试初始化,并显示 `SD: ERR`。
## Serial 调试输出
以 115200 波特率连接。输出示例:
```
[BOOT] ESP32 Cheapagotchi v1.2.2
[SD] OK
[WIFI] promiscuous active
[MEM] free heap: 152340 bytes
[GPS] no fix within 60 s — continuing without fix
[HS] aa:bb:cc:dd:ee:ff -> 11:22:33:44:55:66 msg1
[HS] aa:bb:cc:dd:ee:ff -> 11:22:33:44:55:66 msg2
[HS] aa:bb:cc:dd:ee:ff -> 11:22:33:44:55:66 msg3
[HS] aa:bb:cc:dd:ee:ff -> 11:22:33:44:55:66 msg4
[HS] saved /handshakes/hs_aa_bb_cc_dd_ee_ff_3721.pcap total=1
[STAT] pkt/s=23 rssi=-67dBm drops=142 thr=-80dBm ap_log_drops=0 hs_pool_drops=0
[BTN] page -> 1
[SLEEP] entering deep sleep
```
## 仓库结构
```
ESP32Gotchi/
|-- Cheapagotchi.ino # Full firmware source
|-- README.md
|-- LICENSE
|-- hardware/
| └── BOM.md # Bill of materials
└── docs/
└── pcap_analysis.md # Notes on opening captures in Wireshark
```
## 故障排除
| 症状 | 可能原因 | 解决方法 |
|---------|-------------|-----|
| 开机显示 `SD: ERR` | 未插入 SD 卡、接线错误、不是 FAT32 格式 | 检查 SPI 接线,重新格式化为 FAT32 |
| OLED 空白 | I2C 地址不匹配或接线故障 | 检查 SDA/SCL,使用 I2C 扫描仪确认地址 0x3C |
| 未捕获到握手包 | 附近没有发生 WPA2 4 次握手交换 | 使用测试 AP;基于 deauth 的抓包超出了本固件的范围 |
| 设备反复重启 | 看门狗触发 — 任务挂起 | 检查 Serial 输出的最后一行日志;通过 Issues 提交报告 |
| PCAP 无法在 Wireshark 中打开 | 写入损坏(保存期间断电) | 删除未完成的文件;确保电源稳定 |
| 第 2 页显示 `GPS: N/A` | GPS 的 UART 未接线或模块无响应 | 检查 GPIO16/17 接线;GPS 是可选的 |
| 60 秒后显示 `FIX: NO` | 看不到卫星 | 移步室外;等待首次定位(冷启动可能需要 1-2 分钟) |
| `BAT: 0%` | 分压器未接线或引脚错误 | 检查 GPIO34 / 分压电阻;该功能需要硬件支持 |
| Serial 显示 `ap_log_drops > 0` | AP 日志队列已满 — 信标密度过高 | 在密集的射频环境中属于正常现象;记录并未丢失,只是进行了限流 |
## 技术规格
| 参数 | 数值 |
|-----------|-------|
| MCU | Xtensa LX6 双核, 240 MHz |
| RAM | 520 KB SRAM |
| Wi-Fi | 802.11 b/g/n, 2.4 GHz |
| 扫描信道 | 1 – 11 |
| 信道停留时间 | 200 ms |
| 数据包队列深度 | 32 个 |
| 数据包池块 | 32 × 1 600 B (静态) |
| 握手原始帧池块 | 32 × 1 600 B (静态) |
| 最大并发握手槽位 | 16 |
| 最大跟踪 AP 数量 | 192 (哈希表, 256 个桶) |
| 握手槽位超时 | 15 s |
| 新槽位速率限制 | 每 100 ms 1 个 |
| RSSI 过滤阈值 | –80 dBm (可调) |
| PCAP 网络类型 | 105 (802.11) |
| GPS 波特率 | 9 600 (Neo-6M 默认值) |
| GPS 定位过期超时 | 10 s |
| 统计日志记录间隔 | 60 s |
| CSV 轮转大小 | 4 MB |
| 电池 ADC 采样 | 16 次采样平均值 |
| 电池更新间隔 | 30 s |
| 看门狗超时 | 30 s |
| 最小 SD 可用空间 | 1 MB |
| 运行时堆内存分配 | **0** (不包括一次性的 GPS 缓冲区) |
## 法律声明
此工具仅供**经过授权的安全研究和教育用途**使用。
在大多数司法管辖区,未经明确书面许可捕获您不拥有的网络的 Wi-Fi 握手包是违法的。
作者不对滥用行为承担任何责任。
## 许可证
MIT — 详情请见 [LICENSE](LICENSE)。
## 联系方式
作者:[@tworjaga](https://github.com/tworjaga)
Telegram:[@smtrcv](https://t.me/smtrcv)
Issues:[github.com/tworjaga/ESP32Gotchi/issues](https://github.com/tworjaga/ESP32Gotchi/issues)
标签:ESP32, FreeRTOS, UML, Wi-Fi嗅探, 数据抓包, 物联网, 网络安全, 防御绕过, 隐私保护