00. 背景 买的键盘不支持 VIA,改建只能找客服,这家店的技术有点忙,还有点傻。等了三天,没给我改成功 🤬,哥们是什么善茬吗?直接 Ghidra 启动!
详情
01. 基本按键映射修改 芯片是南京沁恒的 CH582 ,用的一个32位的RISC处理器,支持RV32IMAC指令集。客服给的固件是 intel hex 格式的,肯定是没有任何符号信息了,但是根据字符串拿官方库 bindiff 固件可以恢复 90% 的符号。
研究了一下,代码都是从第一行指令开始执行,最开始的代码都是把一堆东西映射到 0x20000000 这个地址,这个地址应该是 RAM 地址,为了把常用的东西放到 RAM 里提升效率,代码里看到 0x20000000 的地址自己映射一下就行了,本文实际上没用到,不再展开。
基本按键映射比较简单,一般来说都有一个数组来保存像这样,所以修改基本按键映射应该不用分析
我尝试搜索 14 26 08 15 17 (即,QWERT)但是搜索不到,拿到原版和修改版之后 diff 一下。
0x40(这个键实际上是右边的 alt) → 0x35(~`)
0x20(这个键实际上是右边的shift) → 0x40(F7)
0x4C(DEL) → 0x41(F8)
提取出来之后发现是按列存储的,如下图
其中红色的部分是键盘中不包含的按键,未知按键中 0xfe 为Fn,其他的按下图中解释即可。
这里上图中的 8 个并不是正常的 value,在协议中和正常的按键位置不同,所以在代码中必然有针对他们的判断,配置好内存映射,直接交叉引用就看到了。
逻辑基本上就是判断位置,这里我们把要修改成不是控制按键的地方全都改成 0xff 让他永远都不成立就行了。
剩下的修改基本按键映射,修改这个数组中的对应位置即可。
02. Fn 组合键修改 基本按键映射修改好了,但是更关键的是组合键。为此,我看了一下 Github 上一些开源键盘的 Fn 组合键的实现方式,一般是类似基本按键也有一个映射表。如果是这样就简单了,和上面类似只需要diff 一下找到这个映射表,对着修改就行了,但是很不幸这个开发者没有这样做。
要分析代码实现了吗?有点绝望,突然发现一个字符串 f12_deal 这不就是我要找的 Fn 组合键处理逻辑吗!因为这是一个左移 64 的小键盘没有 f1 - f12 ,需要用 Fn + 数字键实现。
f12_deal 这个没有找到交叉引用(使用 Binary Ninja 查看有交叉引用),但是和它相邻的 f10_deal 有。直接跟过去看看:
发现相邻的很多函数都是 Fn 组合键处理逻辑,但是这些函数找交叉引用都找不到,合理怀疑是用函数指针调用的,因为一般实现都是有个表,直接 010 Editor 搜一下:
很明显的结构:
strcut fn_layer_handler { uint32_t idx; void* fn_ptr; }; fn_layer_handler fn_layer_handler_arr[32];
交叉引用看看对这个表的处理,首先是对 fn_layer 所支持按键的扫描处理:
其中 fn_layer_key 是我自己命名的名字,数据是一堆 key-code。把里面的数据拿出来,根据卖键盘的给我的说明书翻译一下,刚好包含了说明书里的所有 Fn 组合键。
Keyboard w and W -- 2.4g 无线模式 Keyboard e and E -- 蓝牙模式 Keyboard q and Q -- 有线模式 Keyboard k and K -- Mac/Win 切换 Keyboard t and T -- 配对2.4g Keyboard / and ? -- 静音 Keyboard , and < -- 音量减 Keyboard . and > -- 音量加 Keyboard z and Z -- RGB 灯开关 Keyboard x and X -- RGB 灯模式+ Keyboard c and C -- RGB 灯模式- Keyboard v and V -- RGB 灯速度变化 Keyboard b and B -- 开启默认随机灯效模式 Keyboard n and N -- 开启/关闭电磁阀 Keyboard m and M -- 开启/关闭蜂鸣器 Keyboard 1 and ! -- 以下为 F1 -- F12 Keyboard 2 and @ Keyboard 3 and # Keyboard 4 and $ Keyboard 5 and % Keyboard 6 and ∧ Keyboard 7 and & Keyboard 8 and * Keyboard 9 and ( Keyboard 0 and ) Keyboard - and (underscore) Keyboard = and + Keyboard ESCAPE -- ~`键 Keyboard a and A -- 长按换第一个蓝牙设备 Keyboard s and S -- 长按换第二个蓝牙设备 Keyboard d and D -- 长按换第三个蓝牙设备 Keyboard f and F -- 长按换第四个蓝牙设备
扫描完之后就是处理了,和说明书里对应的非常完美刚好是最后 4 个是长按。
这就比较明朗了,只需要修改 fn_layer_key 数组里的值和对应位置的 fn_layer_handler_arr 的函数的实现即可实现对 Fn 层快捷键的 VIA。
03. Patch 实现 keystone 不支持 RISC-V,直接用编译器生成。
然后修改一下 ld 文件,把 .patch_code 段放到指定地址,然后提出来直接用即可。
下面是完整实现
from HID_const import *import structdef write_byte (file, addr, value ): file.seek(addr) file.write(struct.pack('<B' , value)) def write_dword (file, addr, value ): file.seek(addr) file.write(struct.pack('<I' , value)) def disable_rshift (file ): file.seek(0x729c + 2 ) file.write(bytes .fromhex('f00f' )) def disable_ralt (file ): file.seek(0x728a + 2 ) file.write(bytes .fromhex('f00f' )) keymap_offset = 0x32ca4 fn_layer_key_arr_offset = 0x30de0 fn_layer_handler_arr_offset = 0x30cc8 with open ('./3M_2X' , 'rb' ) as f: content = bytearray (f.read()) patched_f = open ('./3M_2X_patched' , 'wb' ) patched_f.write(content) disable_ralt(patched_f) write_byte(patched_f, keymap_offset + 7 * 9 + 7 - 1 , HID_KEYBOARD_GRV_ACCENT) disable_rshift(patched_f) write_byte(patched_f, keymap_offset + 7 * 11 + 6 - 1 , HID_KEYBOARD_F7) write_byte(patched_f, keymap_offset + 7 * 13 + 6 - 1 , HID_KEYBOARD_F8) write_byte(patched_f, fn_layer_key_arr_offset + 8 , HID_KEYBOARD_GRV_ACCENT) write_byte(patched_f, fn_layer_key_arr_offset + 9 , HID_KEYBOARD_LEFT_ARROW) write_byte(patched_f, fn_layer_key_arr_offset + 10 , HID_KEYBOARD_RIGHT_ARROW) write_byte(patched_f, fn_layer_key_arr_offset + 11 , HID_KEYBOARD_UP_ARROW) write_byte(patched_f, fn_layer_key_arr_offset + 12 , HID_KEYBOARD_DOWN_ARROW) write_byte(patched_f, fn_layer_key_arr_offset + 28 , HID_KEYBOARD_LEFT_BRKT) write_byte(patched_f, fn_layer_key_arr_offset + 29 , HID_KEYBOARD_RIGHT_BRKT) write_byte(patched_f, fn_layer_key_arr_offset + 30 , HID_KEYBOARD_SEMI_COLON) write_byte(patched_f, fn_layer_key_arr_offset + 31 , HID_KEYBOARD_SGL_QUOTE) write_byte(patched_f, fn_layer_key_arr_offset + 27 , HID_KEYBOARD_I) handler_insert_addr = 0x33000 write_dword(patched_f, fn_layer_handler_arr_offset + 27 * 8 + 4 , handler_insert_addr) write_byte(patched_f, fn_layer_key_arr_offset + 13 , HID_KEYBOARD_D) handler_delete_addr = 0x3305e write_dword(patched_f, fn_layer_handler_arr_offset + 13 * 8 + 4 , handler_delete_addr) write_byte(patched_f, fn_layer_key_arr_offset + 14 , HID_KEYBOARD_P) handler_print_screen_addr = 0x330bc write_dword(patched_f, fn_layer_handler_arr_offset + 14 * 8 + 4 , handler_print_screen_addr) patched_f.seek(0x33000 ) code = bytes .fromhex(""" 01 11 06 CE 22 CC 26 CA 83 07 15 00 02 C2 02 C4 23 06 01 00 A1 E3 83 07 05 00 89 C7 93 07 90 04 A3 03 F1 00 25 64 B7 74 00 20 93 07 44 4F 13 85 84 83 82 97 A1 47 63 FF A7 00 05 46 4C 00 13 85 84 83 13 04 A4 53 02 94 21 46 93 05 51 00 13 85 84 83 02 94 F2 40 62 44 D2 44 05 61 82 80 01 11 06 CE 22 CC 26 CA 83 07 15 00 02 C2 02 C4 23 06 01 00 A1 E3 83 07 05 00 89 C7 93 07 C0 04 A3 03 F1 00 25 64 B7 74 00 20 93 07 44 4F 13 85 84 83 82 97 A1 47 63 FF A7 00 05 46 4C 00 13 85 84 83 13 04 A4 53 02 94 21 46 93 05 51 00 13 85 84 83 02 94 F2 40 62 44 D2 44 05 61 82 80 01 11 06 CE 22 CC 26 CA 83 07 15 00 02 C2 02 C4 23 06 01 00 A1 E3 83 07 05 00 89 C7 93 07 60 04 A3 03 F1 00 25 64 B7 74 00 20 93 07 44 4F 13 85 84 83 82 97 A1 47 63 FF A7 00 05 46 4C 00 13 85 84 83 13 04 A4 53 02 94 21 46 93 05 51 00 13 85 84 83 02 94 F2 40 62 44 D2 44 05 61 82 80 """ )patched_f.write(code) patched_f.close() import intelhexintelhex.bin2hex('./3M_2X_patched' , './3M_2X_patched.hex' )
Patch 完之后烧写测试发现功能和预期完全一样,不禁大喊一声:高玉灿牛逼 😋