前言

最近買了一把 Logitech 的人體工學垂直滑鼠 Lift

下載了 Logi Options+ 來安裝,沒想到它只適用 macOS 10.15 以上版本。

用了些技巧成功安裝後,發現它超肥,GUI + LaunchAgent 將近 1GB,而且很吃資源。

雖然 GUI 界面漂亮,但日常使用根本不會打開幾次,而且設定過的滑鼠跟我開發的手勢 App 發生衝突。

我尋思這一點都不優雅,不如藉此機會更新自己的 App 來支援這個新玩具。


實作過程

修改 Logi Options+ 安裝程式

可以從 官網 下載,看了一下安裝程式才 40MB 不到,Resources 裡也沒有主程式的蹤跡。

我猜測主程式並沒有包含在安裝程式裡,應該是安裝過程中再從伺服器下載。

所以我推斷這個安裝程式應該不會太複雜,總之先丟進 IDA 看看。

找到函式-[AppDelegate _isUnsupportedOS]

...

__text:0000000100016B4F          mov     rdi, cs:classRef_NSProcessInfo ; void *
__text:0000000100016B56          mov     rsi, cs:selRef_processInfo ; char *
__text:0000000100016B5D          call    cs:_objc_msgSend_ptr
__text:0000000100016B63          test    rax, rax
__text:0000000100016B66          jz      loc_100016C55
__text:0000000100016B6C          mov     rdx, cs:selRef_operatingSystemVersion
__text:0000000100016B73          lea     rdi, [rbp+var_40]
__text:0000000100016B77          mov     rsi, rax
__text:0000000100016B7A          call    _objc_msgSend_stret

...

__text:0000000100016BFD          mov     rdi, cs:classRef_NSString ; void *
__text:0000000100016C04          mov     rcx, qword ptr [rbp+var_40]
__text:0000000100016C08          mov     r8, qword ptr [rbp+var_40+8]
__text:0000000100016C0C          mov     rsi, cs:selRef_stringWithFormat_ ; char *
__text:0000000100016C13          lea     rdx, cfstr_DD   ; "%d.%d"
__text:0000000100016C1A          mov     rbx, cs:_objc_msgSend_ptr
__text:0000000100016C21          xor     eax, eax
__text:0000000100016C23          call    rbx ; _objc_msgSend
__text:0000000100016C25          mov     rsi, cs:selRef_compare_options_ ; char *
__text:0000000100016C2C          lea     rdx, cfstr_1015 ; "10.15"
__text:0000000100016C33          mov     ecx, 40h
__text:0000000100016C38          mov     rdi, rax        ; void *
__text:0000000100016C3B          call    rbx ; _objc_msgSend

清楚地看到它讀取了ProcessInfo類別裡的operatingSystemVersion變數,參考連結

然後用NSString類別的compare:options:實體方法來判斷 macOS 版本,參考連結

接下來有兩種思考方向,要嘛偽裝 Mac 的 OS 版本,要嘛修改安裝程式的邏輯,我選擇後者。

透過cfstr_1015順藤摸瓜,找到1008E6F05

__cstring:00000001008E6F05 a1015           db '10.15',0

切到 Hex View 透過之前 貼文 的方式修改二進位檔案。

重新簽名之後再打開安裝程式,恭喜可以安裝了。


發現 Logi Options+ 問題

Logi Options+ 是個功能優秀的軟體,不過它為了跨平台,是使用 Electron + Qt 開發。

不僅加了一堆 Frameworks,還要兼容 x86、arm 架構的處理器,導致它的容量巨肥。

我明白廠商使用跨平台開發是出於成本考量,但是近 1GB 的容量佔用真的讓我無法接受。

也因為它的跨平台開發讓我後期的逆向工程超級痛苦。

另外,一般滑鼠側邊鍵被按下時會發送Btn_down訊息,放開時發送Btn_up訊息。

我開發的手勢 App 中有個函式是偵測滑鼠側邊鍵長按,就是透過兩個訊息的時間差來判斷是否長按。

這隻滑鼠居然是放開時才一次發送Btn_downBtn_up兩個訊息,讓我手勢 App 的長按功能失效。

如果將 LaunchAgent 殺掉,經過設定過的滑鼠,側邊鍵、滾輪也會失效,重新配對後恢復正常。

不過使用 CoreBluetooth 讀取 Characteristics 能讀到設定過的滑鼠側邊鍵和滾輪的訊號。

綜上,我推斷,Logi Options+ 在設定滑鼠的過程中應該有透過藍牙往滑鼠裡 write value。

Read Characteristics 不是問題,現在是要找出到底寫入了什麼 value 進去。


Hook Logi Options+

於 IDA 中可以找到____ZN5devio10MacOSBlepp7dowrite開頭的函式。

其中實作了-[CBPeripheral writeValue:forCharacteristic:type:]方法。

不過動態調試 macOS 第三方 App 前,要先關閉 Mac 的系統完整保護(SIP),參考連結

可用csrutil status來查詢目前的 SIP 狀態。

  • Disable SIP

    1. 讓 Mac 進入復原模式,參考連結
    2. 點選「工具程式」>「終端機」
    3. csrutil disable
    4. reboot
  • Enable SIP

    1. 讓 Mac 進入復原模式,參考連結
    2. 點選「工具程式」>「終端機」
    3. csrutil enable
    4. reboot

接著用 Frida attach LaunchAgent 的 PID 追蹤相關函式:

frida-trace -p PID -m "*[CB* *]"

一無所獲。

於是用 LLDB attach LaunchAgent 的 PID:

lldb -p PID

在可疑的邏輯處下斷點,佐 IDA 分析邏輯走向,最後找到一條 6 bytes 的jz指令,參考連結

__text:0000000100AAE6C3                 jz      loc_100AAE7A9

因為跨平台開發的關係,pseudocode 可讀性極差,看不太出來原本是做了什麼判斷。

不過,我只需要知道writeValue:參數而已,所以硬核地nop掉這條邏輯就可以了,參考連結

(lldb) memory write 0x1038606C3 -s 6 0x909090909090

別忘了加上 ASLR 產生的記憶體偏移。

再操作一下 GUI 設定滑鼠,中獎:

883639 ms  -[CBPeripheral writeValue:<093d0056 2300560c 00000000 00000000 0000> forCharacteristic:<CBCharacteristic: 0x7fc7b380fc50, UUID = 00010001-0000-1000-8000-011F2000046D, properties = 0x1E, value = <093d0056 33005608 00000000 00000000 0000>, notifying = YES> type:nil]

writeValue:forCharacteristic:參數一覽無遺,接下來就是自己實作了。


Swift 實作 Logi 藍牙通訊

上面那條函式調用 log 用 Swift 會寫成:

peripheral.writeValue(Data([0x09, 0x3d, 0x00, 0x56 ,0x23, 0x00, 0x56, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]), for: CBUUID(string: "00010000-0000-1000-8000-011F2000046D"), type: .withoutResponse)

如果要一次性地寫入多個數值,建議把type改為.withResponse

再於 delegate 的peripheral(_:didWriteValueFor:error:)函式中遞迴寫入,參考連結


後記

本文只是提供逆向工程中的思考方向,Swift 寫法可以參考 Github 上的 這個 項目。

如果讀者真的好奇 Logi Options+ 的寫入規則,不妨實作一次,相信會有更深的體會。

妳現在可以將肥大的 Logi Options+ 從 Mac 中徹底刪除了。

對了,調試完畢後別忘了 Enable SIP 哦。