0xFF 免責聲明
對設備的刷機操作有風險西雀,使用本文提到任何工具產(chǎn)生的問題艇肴,還請自行承擔豆挽!
0x00 起因
在某天羅技驅(qū)動提示升級后,毫不猶豫選擇了升級膛檀。不巧幾天后咖刃,發(fā)現(xiàn)k780鍵盤的某些組合鍵無法工作憾筏,連Ctrl+C都無法使用氧腰,這讓只會復制黏貼的高級攻城獅情何以堪古拴。接著很容易把鍋甩給羅技的固件升級,想要重刷一下固件紧帕,在羅技官網(wǎng)一頓操作后是嗜,發(fā)現(xiàn)了一個叫做Firmware Update Tool的程序,結(jié)果這工具僅能做升級操作站绪,同版本的固件都無法重新寫入崇众,更不要說用舊版本進行降級。無奈之下端起逆向大旗锰蓬,廢話不多說芹扭。下面以目前次新版本FirmwareUpdateTool_1.2.169_x86為例舱卡。
0x01 尋找界面突破口
我們先在界面上尋找一些特征,首先連接好優(yōu)聯(lián)(unifying)接收器矫钓,然后鍵盤通過優(yōu)聯(lián)與電腦連接新娜。
如果鍵盤未連接概龄,隨便按個鍵喚醒
提示優(yōu)聯(lián)接收器可升級私杜,會進入一個叫做YOUR RECEIVER IS READY TO UPDATE的確認窗口衰粹,這里點擊update
就會進行升級寄猩,當然如果你的固件版本大于或者等于該升級程序的版本骑疆,會直接彈出沒有設備需要升級的提示。
這里需要注意的是椎镣,優(yōu)聯(lián)接收器和K780鍵盤都是有單獨的固件的状答,升級是分開的惊科。
0x02 逆向分析
把FirmwareUpdateTool.exe
文件(官網(wǎng)下載的文件為rar自解壓包亮钦,要先進行解壓)拉入IDA進行靜態(tài)分析蜂莉,在函數(shù)窗口能找到一些Q
開頭的函數(shù)映穗,很明顯是Qt框架寫的界面程序,從文件中也能發(fā)現(xiàn)Qt的類庫Qt5Core.dll
等文件宿接。
嘗試搜索界面上的字符串澄阳,發(fā)現(xiàn)并不能找到完全符合的碎赢,翻一下能看到:/translations/en.qm
肮塞,基本可以確定用了Qt框架的多國語言模塊姻锁,還有welcome-header
位隶、welcome-text
之類就是字符串索引key
,雙擊welcome-header
查看赋荆,sub_40CD40+115
引用了懊昨,直接進入sub_40CD40+115
酵颁。
0040CE5B處的call獲取真正的字符串躏惋,接下來的通過QLabel::setText
設置label的文本其掂。
在sub_40CD40函數(shù)是一個稍微復雜的switch case
的代碼款熬,按F5生成偽代碼攘乒,可以看到根據(jù)a2
的值進行了不同操作则酝,sub_40CD40函數(shù)主要功能是對界面進行操作沽讹。
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;
...
}
}
上面對內(nèi)部代碼進行精簡, 重點來關(guān)注一下case 7
中keyboard-update-ready-header
這個是鍵盤準備升級的狀態(tài)叹谁,case 12
就是鍵盤正在升級的狀態(tài)焰檩,case 16
是接收器準備升級的狀態(tài)析苫。
優(yōu)聯(lián)接收器刷機
我們先來看看case 16
是怎么進入的。查看函數(shù)sub_40CD40的引用有:sub_409C90+11
国旷、sub_409C90+10C
议街,直接進入函數(shù)sub_409C90特漩,也是個充斥著switch case的函數(shù)骨杂,直接F5偽代碼查看搓蚪,定位到關(guān)鍵代碼:
也就是執(zhí)行到state=16
就能進入case 16
妒潭,載入OD動態(tài)調(diào)試雳灾,將7E9D72位置的指令nop
掉,這樣就能達到永遠都能進入接收器升級的界面炒嘲,F(xiàn)9運行起來夫凸。
成功提示該界面夭拌,選擇升級即可刷新固件啼止,這里注意一下兵罢,整個升級過程不需要外網(wǎng)卖词,F(xiàn)irmwareUpdateTool中集成了固件,所以為什么官方需要發(fā)布新版本FirmwareUpdateTool噪生,這也是一個原因东囚,可能也考慮到了離線升級這種場景页藻。
這樣簡單的爆破后璃吧,接收器的固件就可以任意刷了畜挨。
鍵盤刷機
根據(jù)上面分析繼續(xù)查看函數(shù)sub_409C90巴元,找到如下關(guān)鍵處:
可以看到v5
的值很關(guān)鍵务冕,到這里你肯定想到直接給v5
設置個非0值不就行了幻赚,但是事情沒這么簡單落恼,這邊*(_DWORD *)(v3 + 0x88) = v5
對v5
進行了保存操作佳谦,說明這個值可能不是一個簡單的bool類型滋戳,隨便改為非0可能會對升級造成影響(這個也經(jīng)過了測試證實了我們的猜測奸鸯,會導致進入鍵盤升級界面娄涩,但是升級報錯)。繼續(xù)跟入函數(shù)sub_40AAA0扬虚,該函數(shù)內(nèi)有大量的復雜操作努隙,我們從返回值去快速定位到關(guān)鍵代碼:
繼續(xù)跟入函數(shù)sub_40A8D0,這個函數(shù)里面讀取了設備信息辜昵,需配合OD動態(tài)調(diào)試荸镊,不然難以分析,這里就不演示了堪置,現(xiàn)在回看一下躬存,其實也比較容易猜到這邊的代碼的邏輯,分析結(jié)果如下:
找到關(guān)鍵跳轉(zhuǎn)晋柱,將jb直接改為jmp即可。
載入OD雁竞,在OD中修改跳轉(zhuǎn)钦椭,直接運行起來。
成功進入k780鍵盤準備更新界面碑诉,選擇update后彪腔,進入升級界面,升級時鍵盤指示燈紅綠交替进栽。
到這里德挣,你會發(fā)現(xiàn),僅做了關(guān)鍵跳轉(zhuǎn)的修改快毛,優(yōu)聯(lián)接收器也能隨便刷了格嗅,可以說明它們都使用函數(shù)sub_40A8D0進行版本判斷,那就一舉兩得了
0x03 后記
雖然干得熱火朝天唠帝,但是刷完之后屯掖,我的K780鍵盤故障依舊,其實是硬件問題(這就很尷尬)襟衰,后面我會另外介紹我的k780是如何復活的贴铜。
文件
官方FirmwareUpdateTool_1.2.169_x86
FirmwareUpdateTool_1.2.169_x86修改版 可平刷降級
鏈接: https://pan.baidu.com/s/1xGec18il4IkoHm-dJoXRHA 提取碼: g8si