逆向罗技固件升级程序实现平刷和降级

0xFF 免责声明

对设备的刷机操作有风险,使用本文提到任何工具产生的问题,还请自行承担!

0x00 起因

在某天罗技驱动提示升级后,毫不犹豫选择了升级。不巧几天后,发现k780键盘的某些组合键无法工作,连Ctrl+C都无法使用,这让只会复制黏贴的高级攻城狮情何以堪。接着很容易把锅甩给罗技的固件升级,想要重刷一下固件,在罗技官网一顿操作后,发现了一个叫做Firmware Update Tool的程序,结果这工具仅能做升级操作,同版本的固件都无法重新写入,更不要说用旧版本进行降级。无奈之下端起逆向大旗,废话不多说。下面以目前次新版本FirmwareUpdateTool_1.2.169_x86为例。

0x01 寻找界面突破口

我们先在界面上寻找一些特征,首先连接好优联(unifying)接收器,然后键盘通过优联与电脑连接。


欢迎界面
寻找设备

如果键盘未连接,随便按个键唤醒

唤醒键盘

提示优联接收器可升级,会进入一个叫做YOUR RECEIVER IS READY TO UPDATE的确认窗口,这里点击update就会进行升级,当然如果你的固件版本大于或者等于该升级程序的版本,会直接弹出没有设备需要升级的提示。

优联设备

这里需要注意的是,优联接收器和K780键盘都是有单独的固件的,升级是分开的。

0x02 逆向分析

FirmwareUpdateTool.exe文件(官网下载的文件为rar自解压包,要先进行解压)拉入IDA进行静态分析,在函数窗口能找到一些Q开头的函数,很明显是Qt框架写的界面程序,从文件中也能发现Qt的类库Qt5Core.dll等文件。

函数列表

尝试搜索界面上的字符串,发现并不能找到完全符合的,翻一下能看到:/translations/en.qm,基本可以确定用了Qt框架的多国语言模块,还有welcome-headerwelcome-text之类就是字符串索引key,双击welcome-header查看,sub_40CD40+115引用了,直接进入sub_40CD40+115

出现的字符串.png

0040CE5B处的call获取真正的字符串,接下来的通过QLabel::setText设置label的文本。

image.png

在sub_40CD40函数是一个稍微复杂的switch case的代码,按F5生成伪代码,可以看到根据a2的值进行了不同操作,sub_40CD40函数主要功能是对界面进行操作。

int __thiscall sub_40CD40(QWidget **this, int a2)
{
  ...

  switch ( a2 )
  {
    case 1:
      v6 = (const struct QString *)sub_40CD10(&v261, "welcome-header", 0, -1);
      v7 = v2[8];
      LOBYTE(v293) = 1;
      QLabel::setText(v7, v6);
      LOBYTE(v293) = 0;
      QString::~QString((QString *)&v261);
      v8 = (const struct QString *)sub_40CD10(&v233, "welcome-text", 0, -1);
      v9 = v2[9];
      LOBYTE(v293) = 2;
      QLabel::setText(v9, v8);
      ...
    case 2:
      v15 = (const struct QString *)sub_40CD10(&v217, "detecting-devices-header", 0, -1);
      v16 = v2[8];
      LOBYTE(v293) = 8;
      QLabel::setText(v16, v15);
      LOBYTE(v293) = 0;
      QString::~QString((QString *)&v217);
      v17 = (const struct QString *)sub_40CD10(&v219, "detecting-devices-text", 0, -1);
      v18 = v2[9];
      ...
    case 3:
      v21 = (const struct QString *)sub_40CD10(&v198, "unplug-receivers-header", 0, -1);
      v22 = v2[8];
      LOBYTE(v293) = 11;
      QLabel::setText(v22, v21);
      LOBYTE(v293) = 0;
      QString::~QString((QString *)&v198);
      v23 = sub_40CD10(&v259, "unplug-receivers-text", 0, -1);
      LOBYTE(v291) = 32;
      LOBYTE(v293) = 12;
      QChar::QChar(&v169, v291);
      ...
    
    case 6:
      v44 = (const struct QString *)sub_40CD10(&v213, "devices-up-to-date-header", 0, -1);
      v45 = v2[8];
      LOBYTE(v293) = 26;
      QLabel::setText(v45, v44);
      LOBYTE(v293) = 0;
      QString::~QString((QString *)&v213);
      v46 = (const struct QString *)sub_40CD10(&v251, "devices-up-to-date-text", 0, -1);
      v47 = v2[9];
      LOBYTE(v293) = 27;
      QLabel::setText(v47, v46);
      LOBYTE(v293) = 0;
      QString::~QString((QString *)&v251);
      v276 = QString::fromAscii_helper(":/Images/options.png", 20);
      LOBYTE(v293) = 28;
      v48 = (const struct QPixmap *)QPixmap::QPixmap(&v175, &v276, 0, 0, v170, v171);
      LOBYTE(v293) = 29;
      QLabel::setPixmap(v290[12], v48);
      LOBYTE(v293) = 28;
      QPixmap::~QPixmap((QPixmap *)&v175);
      LOBYTE(v293) = 0;
      QString::~QString((QString *)&v276);
      v284 = QString::fromAscii_helper(":/Images/tick.png", 17);
      LOBYTE(v293) = 30;
      v49 = (const struct QPixmap *)QPixmap::QPixmap(&v189, &v284, 0, 0, v170, v171);
      v2 = v290;
      LOBYTE(v293) = 31;
      QLabel::setPixmap(v290[13], v49);
      LOBYTE(v293) = 30;
      QPixmap::~QPixmap((QPixmap *)&v189);
      LOBYTE(v293) = 0;
      QString::~QString((QString *)&v284);
      v50 = (const struct QString *)sub_40CD10(&v197, "close-button", 0, -1);
      v51 = v2[10];
      LOBYTE(v293) = 32;
      QAbstractButton::setText(v51, v50);
      v14 = &v197;
      goto LABEL_36;
    case 7:
      v52 = (const struct QString *)sub_40CD10(&v249, "keyboard-update-ready-header", 0, -1);
      v53 = v2[8];
      LOBYTE(v293) = 33;
      QLabel::setText(v53, v52);
      LOBYTE(v293) = 0;
      QString::~QString((QString *)&v249);
      v54 = sub_40CD10(&v247, "keyboard-update-ready-text", 0, -1);
      ...
      ...
    case 12:
      v88 = (const struct QString *)sub_40CD10(&v266, "updating-keyboard-header", 0, -1);
      v89 = v2[8];
      LOBYTE(v293) = 57;
      QLabel::setText(v89, v88);
      LOBYTE(v293) = 0;
      QString::~QString((QString *)&v266);
      v90 = sub_40CD10(&v231, "updating-keyboard-text", 0, -1);
      ...
    case 13:
      ...
    case 16:
      v106 = (const struct QString *)sub_40CD10(&v204, "receiver-update-ready-header", 0, -1);
      v107 = v2[8];
      LOBYTE(v293) = 70;
      QLabel::setText(v107, v106);
      LOBYTE(v293) = 0;
      QString::~QString((QString *)&v204);
      v108 = (const struct QString *)sub_40CD10(&v223, "receiver-update-ready-text", 0, -1);
      v109 = v2[9];
      LOBYTE(v293) = 71;
      QLabel::setText(v109, v108);
      LOBYTE(v293) = 0;
      ...
  }
}

上面对内部代码进行精简, 重点来关注一下case 7keyboard-update-ready-header这个是键盘准备升级的状态,case 12就是键盘正在升级的状态,case 16是接收器准备升级的状态。

优联接收器刷机

我们先来看看case 16是怎么进入的。查看函数sub_40CD40的引用有:sub_409C90+11sub_409C90+10C,直接进入函数sub_409C90,也是个充斥着switch case的函数,直接F5伪代码查看,定位到关键代码:

关键代码

也就是执行到state=16就能进入case 16,载入OD动态调试,将7E9D72位置的指令nop掉,这样就能达到永远都能进入接收器升级的界面,F9运行起来。

成功提示该界面,选择升级即可刷新固件,这里注意一下,整个升级过程不需要外网,FirmwareUpdateTool中集成了固件,所以为什么官方需要发布新版本FirmwareUpdateTool,这也是一个原因,可能也考虑到了离线升级这种场景。

接收器准备升级
接收器升级中
升级成功

这样简单的爆破后,接收器的固件就可以任意刷了。

键盘刷机

根据上面分析继续查看函数sub_409C90,找到如下关键处:

可以看到v5的值很关键,到这里你肯定想到直接给v5设置个非0值不就行了,但是事情没这么简单,这边*(_DWORD *)(v3 + 0x88) = v5v5进行了保存操作,说明这个值可能不是一个简单的bool类型,随便改为非0可能会对升级造成影响(这个也经过了测试证实了我们的猜测,会导致进入键盘升级界面,但是升级报错)。继续跟入函数sub_40AAA0,该函数内有大量的复杂操作,我们从返回值去快速定位到关键代码:

函数sub_40AAA0

继续跟入函数sub_40A8D0,这个函数里面读取了设备信息,需配合OD动态调试,不然难以分析,这里就不演示了,现在回看一下,其实也比较容易猜到这边的代码的逻辑,分析结果如下:

函数sub_40A8D0

找到关键跳转,将jb直接改为jmp即可。

关键跳转

载入OD,在OD中修改跳转,直接运行起来。


修改关键跳转

成功进入k780键盘准备更新界面,选择update后,进入升级界面,升级时键盘指示灯红绿交替。

键盘准备更新
键盘更新中

到这里,你会发现,仅做了关键跳转的修改,优联接收器也能随便刷了,可以说明它们都使用函数sub_40A8D0进行版本判断,那就一举两得了

0x03 后记

虽然干得热火朝天,但是刷完之后,我的K780键盘故障依旧,其实是硬件问题(这就很尴尬),后面我会另外介绍我的k780是如何复活的。

文件

官方FirmwareUpdateTool_1.2.169_x86

FirmwareUpdateTool_1.2.169_x86修改版 可平刷降级
链接: https://pan.baidu.com/s/1xGec18il4IkoHm-dJoXRHA 提取码: g8si

你可能感兴趣的:(逆向罗技固件升级程序实现平刷和降级)