0.折腾原因
一直想有一个键盘+红点+触摸板的桌面组合放在办公室用。键盘+红点操作效率高,触摸板在看文档网页时翻页顺滑。几经转折发现了Thinkpad X1 Tablet gen2原装键盘,除了太薄手感一般之外,完美满足需求,而且这款键盘折叠部分里的排线很容易折断,导致价格非常便宜,很适合用来改装USB。所以很久之前改装了两个,然而这款键盘Fn和Ctrl位置不能交换,导致我一直误操作,所以全部出掉了。前段时间手痒又改装了一个,这次打算彻底解决Fn和Ctrl换位的问题,了却心愿。另本文主要记录思路,以便类似情况参考,并不是软件使用教程。
1.准备工作
首先需要了解该键盘的基本构造,好在之前折腾的时候拆过一个坏键盘,拆机图如下:
可以看到该键盘是usb协议的,核心是一个Sonix单片机,型号为:SN32F237FG。推测该单片机通过GPIO接收按键、红点和触摸板信号,转化为USBHid发给电脑。
这样的话,就需要更改单片机固件实现更改键位了。
2.工具准备
1.该单片机的文档(Sonix官网):SN32F237_V2.20_SC.pdf
2.该键盘的固件(Lenovo官网):n1olk08w.exe
3.USB用图表(USB-IF官网):hut1_12v2.pdf
4.逆向工具ghidra:https://github.com/NationalSecurityAgency/ghidra
5.其他工具:在研究过程中,我还用Keil研究并编译了该单片机的例程,并用bindiff将编译出的axf文件与固件对比,分析出了一些函数名。但事后总结思路,对这次逆向并未起到决定性作用,所以这次不再赘述,有兴趣的同学可以自行研究。
3.逆向过程
3.1 导入固件
固件解压出3个文件:一个exe,一个Config.ini,一个hex文件,不难看出hex文件就是本次要逆向的固件,导入ghidra,根据官网上的信息,选择指令集为ARM:LE:32:v7,导入ghidra。
3.2 寻找键码
按照一般经验,键盘固件中会有一个键码表,用于将接收到的信号转变为键码,对应USB用图表,就是04=A 05=B 06=C……这部分。
一般键盘固件中键码都是连续存储的,观察固件hex,在比较靠后的部分,找到了疑似区域(键码中间隔着01、02之类的):
对照上边的键码,大概解析出键码分布如下(红色的是改建验证过的,绿色的是还没验证的,供参考):
按照常理分析,在键码表中将Fn和Ctrl对换即可达成目的。研究到这里,是不是感觉即将大功告成?其实这才是入坑的开始。在这里我斗胆吐槽一下该键盘的固件:写得稀烂。
首先,在键码部分压根没有找到Fn键的踪影。其次,上图中绿色的8101和8110,推测应为左右Ctrl,然而修改后烧录验证,这两个键码根本没有生效。而把A键由0104改为8101,则成功变成左Ctrl键。所以推测,Fn和Ctrl键,在读键码表之前就被判定了。我们只能继续分析。
3.3 寻找Fn
在ghidra中载入固件之后,可以发现,在我们找到的键码表开头的地方,有DAT_00005a8c和DAT_00005a8d两个数值,分别被FUN_000020f0和FUN_000020fa引用。
顺藤摸瓜,观察FUN_000020f0和FUN_000020fa,都被FUN_00000e80引用:
来到FUN_00000e80,反汇编为伪代码:
大约可以看出,该函数有比较复杂的逻辑判断过程,猜测和处理按键信号有关。特别关注到判断条件里出现了几个疑似键码:0x6e(Esc)、0x3d(空格)、0x51(End,if结果里出现了0x49即Insert)。其中End键极为关键,该键盘Fn+End正是Insert键,故确认FUN_00000e80包含了Fn快捷键的判定和处理。经过多次更改后烧录,大概扒出了该函数的功能。
ulonglong FUN_00000e80(uint param_1,int param_2)
{
byte bVar1;
char *pcVar2;
byte bVar3;
uint uVar4;
byte *pbVar5;
int iVar6;
uint uVar7;
uint uVar8;
bool bVar9;
uVar4 = GetScanCodeHyperByte(param_2);
bVar1 = *(byte *)(DAT_00001264 + param_2);
uVar7 = (uint)bVar1;
/* 键码前两位为02 */
if ((uVar4 & 0x7f) == 2) {
/* Fn+ESC=FnLock */
if (uVar7 == 0x6e) {
if ((*DAT_00010268 == '\0') && ((*DAT_0000126c & 8) != 0)) {
*(undefined *)(DAT_00001260 + 0x2d) = 1;
}
else {
*(undefined *)(DAT_00001260 + 0x2d) = 0;
}
}
/* Fn+空格 控灯 */
if (uVar7 == 0x3d) {
*(undefined *)(DAT_00001260 + 0x2d) = 0;
if ((*DAT_00001268 == '\0') || ((*DAT_00001270 & 0x10) != 0)) {
*DAT_00001274 = '\x04';
pcVar2 = DAT_0000127c;
if ((*DAT_00001278 & 2) == 0) {
*DAT_0000127c = *DAT_0000127c + '\x01';
if (*pcVar2 == '\x03') {
*pcVar2 = '\0';
}
pbVar5 = DAT_00001278;
*DAT_00001278 = *DAT_00001278 & 0xf;
*DAT_00001278 = *DAT_0000127c << 4 | *pbVar5;
FUN_000032ca(6,*DAT_0000127c);
NotPinOut_GPIO_init();
}
else {
GPIO_Init();
}
}
else {
uVar4 = 1;
}
}
else {
/* Fn+4 待机 */
if (uVar7 == 5) {
if ((*DAT_00001268 == '\0') || ((*DAT_00001270 & 0x10) != 0)) {
if (*DAT_00001280 != '\x01') {
if ((*DAT_0000126c & 4) == 0) {
FUN_00000d44();
}
else if (*DAT_00001284 == '\0') {
*DAT_00001284 = '\x01';
FUN_00000d44();
}
}
}
else {
uVar4 = 1;
}
}
else if (*(char *)(DAT_00001260 + 0x2d) == '\0') {
uVar4 = 1;
}
else {
/* Esc */
uVar8 = uVar7 - 0x6e & 0xff;
if ((uVar8 == 0) && (*DAT_00001268 != '\0')) {
uVar4 = 1;
uVar7 = uVar8;
}
else {
/* F12 */
if (uVar8 == 0xd) {
iVar6 = FUN_00000a68();
if (iVar6 == 0) {
HID_GetReportInputEvent();
}
*DAT_00001274 = '\x02';
iVar6 = DAT_00001260;
*(undefined *)(DAT_00001260 + 0x2c) = 3;
*(undefined *)(iVar6 + 0x1c) = 3;
*(undefined *)(DAT_00001260 + 0x1d) = *(undefined *)(DAT_0000128c + 0x34);
*(undefined *)(DAT_00001260 + 0x1e) = *(undefined *)(DAT_0000128c + 0x35);
*(undefined *)(DAT_00001260 + 0x1f) = *(undefined *)(DAT_0000128c + 0x36);
uVar7 = uVar8;
}
else {
iVar6 = FUN_00000a68();
if ((iVar6 == 1) || (*DAT_00001288 != '\0')) {
*DAT_00001288 = '\x01';
uVar4 = 1;
*DAT_00001290 = 0;
uVar7 = uVar8;
}
else {
bVar9 = uVar7 != 0x7c;
uVar7 = uVar8;
if (bVar9) {
if (*DAT_00001274 != '\x02') {
HID_GetReportInputEvent();
}
*DAT_00001274 = '\x02';
iVar6 = DAT_00001260;
*(undefined *)(DAT_00001260 + 0x2c) = 3;
*(undefined *)(iVar6 + 0x1c) = 3;
*(undefined *)(DAT_00001260 + 0x1d) = *(undefined *)(DAT_0000128c + uVar8 * 4);
*(undefined *)(DAT_00001260 + 0x1e) = *(undefined *)(DAT_0000128c + uVar8 * 4 + 1) ;
*(undefined *)(DAT_00001260 + 0x1f) = *(undefined *)(DAT_0000128c + uVar8 * 4 + 2) ;
}
}
}
}
}
}
}
/* 键码前两位01或81 */
if ((uVar4 & 0x7f) == 1) {
*(undefined *)(DAT_00001260 + 0x2c) = 0;
bVar3 = GetScanCodeLowerByte(param_2);
if ((*DAT_00001274 == '\x02') && (uVar7 != 0x7c)) {
FUN_00000d90();
}
*DAT_00001274 = '\x01';
if ((uVar4 & 0x80) == 0) {
if ((int)param_1 < 8) {
*(byte *)(DAT_00001260 + 0x14 + param_1) = bVar3;
if ((*DAT_00001268 == '\0') || ((*DAT_00001270 & 0x10) != 0)) {
/* Fn+End=Insert(0x49) */
if (uVar7 == 0x51) {
*(undefined *)(DAT_00001260 + 0x14 + param_1) = 0x49;
}
else if (uVar7 == 0x20) {
*(undefined *)(DAT_00001260 + 0x14 + param_1) = 0x9a;
}
else if (uVar7 == 0x26) {
*(undefined *)(DAT_00001260 + 0x14 + param_1) = 0x47;
}
else if (uVar7 == 0x1a) {
*(undefined *)(DAT_00001260 + 0x14 + param_1) = 0x48;
}
else if (uVar7 == 5) {
*(undefined *)(DAT_00001260 + 0x14 + param_1) = 0x4f;
}
else if (uVar7 == 0x32) {
pbVar5 = (byte *)(DAT_00001260 + 0x14);
pbVar5[param_1] = 0x48;
*(byte *)(DAT_00001260 + 0x14) = *pbVar5 | 1;
}
else if (uVar7 == 0x6e) {
FUN_00000dc0();
*DAT_00001274 = '\x04';
}
else if (uVar7 == 0x7c) {
*DAT_00001274 = '\x02';
iVar6 = DAT_00001260;
*(undefined *)(DAT_00001260 + 0x2c) = 3;
*(undefined *)(iVar6 + 0x1c) = 3;
*(undefined *)(iVar6 + 0x1d) = 8;
*(undefined *)(iVar6 + 0x1e) = 0;
*(undefined *)(iVar6 + 0x1f) = 0;
}
else if (((uVar7 < 0x70) || (0x7b < uVar7)) &&
(iVar6 = FUN_00000a68(), iVar6 != 1)) {
*(undefined *)(DAT_00001260 + 0x14 + param_1) = 0;
*DAT_00001274 = '\x04';
*DAT_00001290 = 0;
}
}
param_1 = param_1 + 1 & 0xff;
}
}
else {
*(byte *)(DAT_00001260 + 0x14) = *(byte *)(DAT_00001260 + 0x14) | bVar3;
}
}
if ((((uVar4 & 0x80) == 0) && ((*DAT_00001270 & 0x20) == 0)) &&
((*DAT_00001268 != '\0' && (((*DAT_0000126c & 4) != 0 && ((*DAT_00001270 & 0x10) != 0)))))) {
*DAT_00001270 = *DAT_00001270 & 0xaf;
FUN_000032ca(1);
*DAT_0000126c = (*DAT_0000126c & 0xfc) + 1;
FUN_00000dc0();
UT_DelayNms(2);
}
return (ulonglong)CONCAT14(bVar1,param_1);
}
在上述代码中,我们可以观察到,每次执行Fn功能前,都要判定DAT_00001268是否为0。而经过烧录测试,Fn按下时DAT_00001268为0,反之为1。双击DAT_00001268显示其值为2000001D。至此,可以推断2000001D为Fn状态变量。
3.4寻找Ctrl
继续观察FUN_00000e80,发现FUN_00000a68出现了多次,应该也是一个重要的判定条件。反编译FUN_00000a68。
undefined4 FUN_00000a68(void)
{
undefined4 uVar1;
if (((((*DAT_00000e2c == '\x01') || (*DAT_00000e30 == '\x01')) ||
((*(byte *)(DAT_00000e28 + -0xc) & 2) != 0)) ||
(((*(byte *)(DAT_00000e28 + -0xc) & 0x20) != 0 || ((*(byte *)(DAT_00000e28 + -0xc) & 4) != 0 ))
)) || (((*(byte *)(DAT_00000e28 + -0xc) & 0x40) != 0 ||
(((*(byte *)(DAT_00000e28 + -0xc) & 8) != 0 ||
((*(byte *)(DAT_00000e28 + -0xc) & 0x80) != 0)))))) {
uVar1 = 1;
}
else {
uVar1 = 0;
}
return uVar1;
}
看到这个函数,我们大胆联想到USB键盘数据包的第一个字节的定义:
BYTE1 --
|--bit0: Left Control是否按下,按下为1
|--bit1: Left Shift 是否按下,按下为1
|--bit2: Left Alt 是否按下,按下为1
|--bit3: Left GUI 是否按下,按下为1
|--bit4: Right Control是否按下,按下为1
|--bit5: Right Shift 是否按下,按下为1
|--bit6: Right Alt 是否按下,按下为1
|--bit7: Right GUI 是否按下,按下为1
找一个二进制换算器即可得出0x1=左Ctrl 0x2=左Shift 0x4=左Alt 0x8=左徽标 0x10=右Ctrl 0x20=右Shift 0x40=右Alt 0x80=右徽标。Shift、Alt、徽标在这个函数中都出现了,那么DAT_00000e2c和DAT_00000e30代表什么也就呼之欲出了,经过验证分别为左Ctrl和右Ctrl的按下状态,他们在变量表中对应2000001E和20000027。
3.5 寻找写入上述变量的函数
上述结论,验证了Fn和Ctrl在走键码表之前就被单独判定的猜想。那么写入2000001D(Fn)、2000001E(左Ctrl)和20000027(右Ctrl)变量值的函数就显得至关重要。在变量表中,每个变量都对应多个数据,对涉及的函数逐个分析,发现FUN_00001ce0(Fn)、FUN_00001c68(左Ctrl)、FUN_000018a2(左Ctrl、Fn)、FUN_00001b48(右Ctrl)对上述变量进行了写入操作。而这四个函数,都出现在了FUN_00001e2e中:
巧合的是,FUN_00001e2e引用了FUN_000011f6,FUN_000011f6引用了FUN_00000e80。那么有理由推断,FUN_00001e2e开头对Fn、左右Ctrl这三个不走键码表的按键进行了判定。
3.6寻找修改部位
上面的工作大致定位了需要修改的位置,注意到上图中对DAT_00001f64=>2000002b进行判定后区分了FUN_00001ce0(Fn)、FUN_00001c68(左Ctrl)。而在FUN_000018a2中对DAT_00001ae4=>2000002b进行判定后区分了DAT_00001ae8=>2000001E、DAT_00001af0=>2000001D:
那么我们将涉及的两处判定反过来,即可实现Fn和Ctrl的换位。即修改如下两处:
修改后经验证,成功实现Fn和Ctrl换位。
3.7顺手修改大雷
修改固件的过程中,发现Fn+4=休眠,为了防止误触,顺手取消掉该功能。将固件中5abe位置的0221修改为0121,即可避免FUN_00000e80中进入待机。
4.吐槽和恰饭
根据逆向的结果,这款键盘的固件源码估计也是乱七八糟,没啥条理。因为我之前也没有逆向的基础,浪费了我一个月的时间,真是得不偿失。以上就是该键盘改键位的思路,同款键盘更改3.6的两处即可实现换位。如果不想折腾搭建环境之类的,也可以找我购买修改后的固件,抚慰一下我受伤的心灵。我在海鲜市场中也叫carrothu。
标签:Tablet,Ctrl,gen2,DAT,按下,FUN,固件,Fn From: https://www.cnblogs.com/carrothu-cn/p/18574597