逆向罗技固件升级程序实现平刷和降级
0xFF 免责声明
对设备的刷机操作有风险,使⽤本⽂提到任何⼯具产⽣的问题,还请⾃⾏承担!
0x00 起因
在某天罗技驱动提⽰升级后,毫不犹豫选择了升级。不巧⼏天后,发现k780键盘的某些组合键⽆法⼯作,连Ctrl+C都⽆法使⽤,这让只会复制黏贴的⾼级攻城狮情何以堪。接着很容易把锅甩给罗技的固件升级,想要重刷⼀下固件,在罗技官⽹⼀顿操作后,发现了⼀个叫做Firmware Update Tool的程序,结果这⼯具仅能做升级操作,同版本的固件都⽆法重新写⼊,更不要说⽤旧版本进⾏降级。⽆奈之下端起逆向⼤旗,废话不多说。下⾯以⽬前次新版本FirmwareUpdateTool_1.2.169_x86为例。
0x01 寻界⾯突破⼝
我们先在界⾯上寻⼀些特征,⾸先连接好优联(unifying)接收器,然后键盘通过优联与电脑连接。
欢迎界⾯
寻设备
如果键盘未连接,随便按个键唤醒
唤醒键盘
提⽰优联接收器可升级,会进⼊⼀个叫做YOUR RECEIVER IS READY TO UPDATE的确认窗⼝,这⾥点击update就会进⾏升级,当然如果你的固件版本⼤于或者等于该升级程序的版本,会直接弹出没有设备需要升级的提⽰。
梦见钱包被偷
优联设备
这⾥需要注意的是,优联接收器和K780键盘都是有单独的固件的,升级是分开的。
0x02 逆向分析
大型网游排行榜把⽂件(官⽹下载的⽂件为rar⾃解压包,要先进⾏解压)拉⼊IDA进⾏静态分析,在函数窗⼝能到⼀些Q开头的函数,很明显是Qt框架写的界⾯程序,从⽂件中也能发现Qt的类库Qt5Core.dll等⽂件。
函数列表
尝试搜索界⾯上的字符串,发现并不能到完全符合的,翻⼀下能看到:/translations/en.qm,基本可以确定⽤了Qt框架的多国语⾔模块,还
有welcome-header、welcome-text之类就是字符串索引key,双击welcome-header查看,sub_40CD40+115引⽤了,直接进⼊sub_40CD40+115。
2009感动中国人物出现的字符串.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;
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];为什么u盘不显示
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;