miyakejima/hs75t-custom-firmware
GitHub: miyakejima/hs75t-custom-firmware
为 HelloGanss HS75T 键盘逆向并注入随机延迟 opcode 的自定义固件补丁,附 WPF 桌面宏编程工具。
Stars: 0 | Forks: 0
# hs75t 自定义 firmware
**HelloGanss HS75T** 无线机械键盘的自定义固件补丁及配套桌面工具——源于一个简单的问题:*硬件宏可以有随机延迟吗?*
## 背景故事
HS75T 将宏直接存储在键盘上,并通过硬件回放——无需保持任何软件处于打开状态。问题是厂商固件仅支持**固定延迟**。固定延迟使得宏回放每次的节奏都完全相同,这很容易被检测到。
目标是为板载宏解释器添加一个**随机延迟 opcode**,以便硬件回放可以在编程后自然地改变其时序,而无需 PC 的任何介入。
### 逆向工程固件
该键盘运行在 **HFD2201KBA** MCU 上——这是一颗频率为 48 MHz 的 ARM Cortex-M0,拥有 64 KB 的 flash 和 8 KB 的 SRAM(与 SN32F248B 芯片相同)。厂商以自解压可执行文件的形式提供固件,其中封装了一个原始的 `.bin` 文件以及一个 `SnxHidLib.DLL` 烧录工具。
使用 Ghidra,定位并完整映射了宏解释器循环:
- 解释器从板载 flash 偏移量 `0x0414` 遍历到 `0x1FFF` 的字节缓冲区
- 每个宏槽位均以 null 终止符 (`0x00`) 结尾——payload 内部不允许出现 null 字节
- opcode:`01 02 key` = Key Down(按下),`01 03 key` = Key Up(抬起),`01 04 lo hi` = 固定延迟(以 base-255 非零方式编码)
- 位于 `0x1C8E` 的固定延迟处理分支即为注入点
### Code Cave 注入
该固件在最后一个实际函数之后有一段 136 字节的零填充空间——这是一个位于 `0xD864` 的干净 code cave。补丁操作:
1. 将 `0x1C8E` 处的 opcode 分支重定向到该 code cave
2. code cave 会检查新的 `0x08` opcode
3. 若匹配:读取 `min_lo min_hi max_lo max_hi`,解码 base-255 数值,使用以时间戳和存储偏移量为种子的 XOR-shift 算法生成伪随机数,并在 `[min, max]` 毫秒内等待随机时长
4. **跳过阶段保护**机制可防止恢复重放机制将解释器困在无限延迟循环中(这是在 v1 版本中发现的关键 bug)
5. 每次修改后都会重新计算二进制文件末尾的校验和尾迹
6. 若不匹配:回落至原始的 opcode 处理程序
最终结果是一个新的 opcode:`01 08 min_lo min_hi max_lo max_hi`——**随机延迟**——完全可与现有的宏缓冲区格式互操作。
## Hs75tTool — 桌面配套工具
这是一款通过 USB HID 将宏编程录入键盘的 WPF / .NET 9 桌面应用程序。

### 功能
- **连接 / 读取 / 恢复** —— 通过 HID 从键盘拉取实时宏缓冲区
- **保存** —— 将完整的缓冲区写回;保存后,键盘将独立播放宏
- **16 个宏槽位**,带有单槽位时间轴视图
- **录制** —— 全局键盘和鼠标钩子可捕获系统范围内的按键操作
- 真实延迟 —— 可选地根据按键之间的实际时间间隔插入固定延迟
- 追加模式 —— 在不清除的情况下添加到现有行
- 鼠标点击 / 滚轮录制
- **操作编辑器** —— 添加、重新排序、复制、删除事件;重新绑定按键、切换按下/抬起、在之后插入点按
- **将所有固定延迟转换为随机延迟** —— 将槽位中的每个固定延迟替换为由可配置的 ±offset 限定边界的随机延迟(需要已打补丁的固件)
- **播放模式**:单次、重复 N 次、停止、按住
- **导入 / 导出** —— 将槽位保存/加载为 `.json` 文件
- **紧急按键释放** —— 为所有 HID 键码发送 KeyUp,以解开卡住的按键
## 仓库结构
```
hs75t-custom-firmware/
├── src/ # C# source — Hs75tTool WPF app
│ ├── Hs75tTool.sln
│ ├── Hs75tTool.Core/ # HID client, codec, macro model
│ ├── Hs75tTool.Desktop/ # WPF frontend (MainWindow, Services)
│ └── Hs75tTool.Tests/ # Unit tests (MSTest)
├── firmware/
│ ├── HFD2201KBA_SN32F248B_VENDOR_STOCK.bin # Original unmodified firmware
│ └── HFD2201KBA_SN32F248B_patched_v5.bin # Current patched firmware (v5)
├── tools/ # Flashing and bootloader utilities
│ ├── reboot_to_bootloader.py # Software bootloader entry via HID magic bytes
│ ├── CHECK_BOOTLOADER.bat # Verify keyboard is in bootloader mode
│ ├── ENTER_BOOTLOADER.bat # Force keyboard into bootloader
│ ├── SonixFlasherC/ # Open-source Sonix flash tool (third-party)
│ └── ...
├── patching/ # Python analysis and patch verification scripts
│ ├── pack_v2.py # Repack patched binary into vendor SFX format
│ ├── verify_sfx_contents.py # Verify SFX contents match expected binaries
│ └── ...
├── docs/
│ ├── 00_Hardware_Identification.md
│ ├── 02_Official_Flasher_Protocol_Analysis.md
│ ├── 06_Original_Walkthrough2.md # Full RE walkthrough and HID protocol reference
│ ├── 07_Patched_Disassembly.md # Capstone disassembly of the injected cave
│ └── firmware_full_decompiled.c # Ghidra decompiled C output (reference)
└── CHANGELOG.md
```
## 硬件信息
| 字段 | 值 |
|---|---|
| 键盘 | HelloGanss HS75T |
| MCU | HFD2201KBA (ARM Cortex-M0, 48 MHz) |
| Flash | 64 KB |
| SRAM | 8 KB |
| USB VID | `0x05AC` |
| USB PID | `0x0256` |
| 固件版本 | V1.15(干电池变体) |
| Hook 地址 | `0x1C8E` |
| Code cave 地址 | `0xD864`(136 字节) |
## 刷入已打补丁的固件
### 要求
- Python 3.x
- [SonixFlasherC](tools/SonixFlasherC/)(已包含)
- libusb(SonixFlasherC 必需)
### 步骤
1. **进入 bootloader 模式**
python tools/reboot_to_bootloader.py
或者运行 `tools/ENTER_BOOTLOADER.bat`。键盘将断开连接,并作为 Sonix bootloader 设备重新枚举。
2. **刷入已打补丁的固件**
SonixFlasherC -d SN248B -r 0 -t 0 -w firmware/HFD2201KBA_SN32F248B_patched_v5.bin
3. **验证**键盘是否正常重新枚举并且工具能够连接。
要恢复官方固件,请使用相同的步骤刷入 `firmware/HFD2201KBA_SN32F248B_VENDOR_STOCK.bin`。
## 构建工具
需要 .NET 9 SDK 和 Windows 系统。
```
cd src
dotnet build Hs75tTool.sln
dotnet run --project Hs75tTool.Desktop
```
## 宏协议参考
宏存储在偏移量 `0x0414` 到 `0x1FFF` 的板载 flash 中。每个槽位均以 null 终止。**宏 payload 中绝不能出现 `0x00`**——它会被视为槽位终止符。
### Opcode 表
| 字节 | 含义 |
|---|---|
| `01 02 key` | Key Down |
| `01 03 key` | Key Up |
| `01 04 lo hi` | 固定延迟(以 base-255 非零方式编码) |
| `01 08 min_lo min_hi max_lo max_hi` | **随机延迟** —— 自定义扩展,仅限已打补丁的固件 |
### Base-255 非零编码
延迟值经过编码以避免出现 null 字节:
```
lo = (ms % 255) + 1
hi = (ms // 255) + 1
decoded = (lo - 1) + (hi - 1) * 255
```
## 致谢
- **[SonixFlasherC](https://github.com/SonixQMK/SonixFlasherC)** —— 开源 Sonix HID 烧录工具(在原始许可证下包含)
标签:云资产清单, 固件修改, 外设驱动, 嵌入式开发, 桌面工具, 硬件宏, 逆向工具, 逆向工程