美文网首页
逆向罗技固件升级程序实现平刷和降级

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

作者: hellotimo | 来源:发表于2021-10-02 00:38 被阅读0次

    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

    相关文章

      网友评论

          本文标题:逆向罗技固件升级程序实现平刷和降级

          本文链接:https://www.haomeiwen.com/subject/krhjnltx.html