在這個專案中,我對「C by GE」應用程式進行了逆向工程,用於控制 GE WiFi 連接的智慧燈泡。為此,我首先反編譯 Android 應用程序,然後對應用程式用於與伺服器通訊的二進位協定進行逆向工程。有關更多詳細信息,請參閱 GE 的逆向工程 C。
該項目的最終產品是:
免責聲明:該程式碼是逆向工程的結果,並且沒有被協議規範告知。因此,無法保證它會繼續工作或適用於每個網路或智慧設備。雖然其他人在某些情況下成功使用了此 API,但程式碼可能會做出不正確的假設,而這些假設並不適用於所有用例。
伺服器目錄是一個獨立的 Web 應用程式和用於 GE 燈泡 C 的 JSON API 端點。網站如下所示:
如果您使用-email
和-password
參數來執行網站,網站將在您第一次載入時顯示一個雙重認證頁面。您將點擊一個按鈕並輸入發送到您的電子郵件的驗證碼。或者,您可以透過執行 login_2fa 命令提前登錄,並將-email
和-password
標誌設定為您的帳戶資訊。該指令將提示您輸入 2FA 驗證碼。輸入此程式碼後,指令將以 JSON blob 形式輸出會話資訊。然後,您可以將此 JSON 傳遞給伺服器的-sessinfo
參數,例如-sessinfo 'JSON HERE'
。請注意,部分會話將在一周後過期,但正在運行的伺服器實例在此時間後將繼續工作,因為會話的過期部分僅用於枚舉設備一次。
較新的帳戶需要使用雙重認證。您可以執行 2FA 握手來建立會話,如下所示:
callback , err := cbyge . Login2FA ( "my_email" , "my_password" , "" )
// Handle error...
sessionInfo , err := callback ( "2FA code from email" )
// Handle error...
session , err := cbyge . NewController ( sessionInfo , 0 )
// Handle error...
對於以前從未使用過 2FA 的舊帳戶,您可以直接登入:
session , err := cbyge . NewControllerLogin ( "my_email" , "my_password" )
// Handle error...
建立會話後,您可以像這樣列舉設備:
devs , err := session . Devices ()
// Handle error...
for _ , x := range devs {
fmt . Println ( x . Name ())
}
您可以像這樣控制燈泡:
x := devs [ 0 ]
session . SetDeviceStatus ( x , true ) // turn on
session . SetDeviceLum ( x , 50 ) // set brightness
session . SetDeviceCT ( x , 100 ) // set color tone (100=blue, 0=orange)
您也可以查詢燈泡的目前設定:
status , err := session . DeviceStatus ( x )
// Handle error...
fmt . Println ( status . IsOn )
fmt . Println ( status . ColorTone )
在本節中,我將向您介紹如何對 C by GE 協議的部分內容進行逆向工程。
第一步是使用 Apktool 反組譯 Android 應用程式。這會產生應用程式的 Smali 反彙編。我四處搜尋,搜尋了 URL 和網域。最初,我發現了這個:
.field public static final API_VERSION : L java/lang/String ; = " v2/ "
.field public static final BASE_URL : L java/lang/String ; = " https://api-ge.xlink.cn:443/ "
看到這個API 端點的使用位置,我很快就找到了一組基於JSON 的HTTP 調用,用於登錄、列出設備等。或2)更新設備的顏色或亮度。
應用程式必須有其他方式與智慧燈泡通訊。然而,反組譯過程中充斥著藍牙和 LAN 通訊的程式碼,我有點擔心沒有用來控制燈泡的全域 API 端點。更糟糕的是,每當我關閉藍牙然後嘗試使用它時,C by GE 應用程式都會抱怨。然而,我最終發現我可以打開應用程序,讓它做它的事情,然後關閉藍牙和 WiFi,同時仍然可以控制燈泡。我所要做的就是每當應用程式打開一個彈出視窗要求我「開啟位置追蹤」(請注意,藍牙和 WiFi 的一個奇怪的名稱)時,點擊 Android 的「後退」按鈕。
此時,我相當確定該應用程式沒有建立其他一些神秘的 HTTP(S) 連線。不過有趣的是,我確實在 Smali 代碼的其他地方找到了網域名稱「xlink.cn」:
.field public static final CM_SERVER_ADDRESS : L java/lang/String ; = " cm-ge.xlink.cn "
.field public static final CM_SERVER_PORT : I = 0x5ce2
天啊,這可能是基於套接字的原始協議嗎?我嘗試了一下,果然可以打開到cm-ge.xlink.cn:23778
的 TCP 連線。然而,Smali 也充滿了UDP封包的邏輯,因此我不確定該應用程式將使用哪種協定。考慮到這一點,我創建了 packet-proxy 並將其設定為偵聽連接埠 23778。 然後我將 Smali 程式碼中的網域cm-ge.xlink.cn
替換為我的IP 位址,將應用程式重新編譯為APK ,並將其安裝在我的手機中。
果然,我修補過的 C by GE 應用程式立即連接到我的資料包代理實例並開始聊天。值得注意的是,它僅在藍牙和 WiFi 關閉時才會執行此操作。除此之外,它似乎更喜歡其中一種與智慧燈泡進行本地通訊的方式。
應用程式選擇使用的協定是迄今為止最容易處理的結果:1)它是 TCP 而不是 UDP,2)它完全未加密。事後看來,缺乏加密是相當令人擔憂的,因為第一則訊息包含一個授權令牌,該令牌似乎永遠不會為我的帳戶改變。
我發現從應用程式到伺服器的訊息可以有效地「重播」。一旦我弄清楚哪些位元組(或“數據包”,感謝數據包代理)用於打開和關閉燈,我就可以簡單地打開一個新套接字並發送這些相同的數據包並獲得相同的結果。這是一個很好的跡象。最糟糕的情況是,我已經有了實現我自己想要的方法,即使它不是很通用。
此時,是時候深入研究該協議了。在結合資料包代理實驗和深入研究 Smali 反彙編之後,我對正在發生的通訊有了相當全面的了解。我注意到的第一件事是通訊發生在「訊息」中,該訊息以類型和長度欄位(大端)開始。接下來的事情是弄清楚哪些資料包類型在哪裡,以及最終如何對特定資料包本身進行編碼。以下是來自伺服器的資料包範例,其中包含我的三個設備的狀態:
73 00 00 00 60 47 e2 be ab 00 37 00 7e 00 01 00 00 f9 52 4e
00 03 00 00 00 03 00 03 00 81 01 00 00 81 01 00 00 00 00 35
00 00 00 27 00 00 00 00 00 00 00 02 00 00 01 00 00 00 01 00
00 00 00 35 00 00 00 27 00 00 00 00 00 00 00 01 00 00 01 00
00 00 01 00 00 00 00 35 00 00 00 27 00 00 00 00 00 00 00 c8
7e
一旦我制定了足夠的協議,我就為其創建了一個 API。此API可以列出裝置、取得其狀態並更新裝置的各種屬性(例如亮度和色調)。令人驚訝的是,我發現我的 API 比應用程式本身更快、更可靠。似乎在回退到遠端伺服器之前嘗試使用藍牙或 WiFi 會導致應用程式更加不穩定且可靠性降低。
最後一點,我並不擁有此應用程式支援的所有設備,因此我沒有動力(或很容易)對這些設備的工作方式進行逆向工程。例如,同一家公司生產智慧插座、感測器和電燈開關。