在这个项目中,我对“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 调用,用于登录、列出设备等。但是,这个端点似乎没有提供一种方法来 1) 获取设备的状态,或者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 会导致应用程序更加不稳定且可靠性降低。
最后一点,我并不拥有此应用程序支持的所有设备,因此我没有动力(或很容易)对这些设备的工作方式进行逆向工程。例如,同一家公司生产智能插座、传感器和电灯开关。