Para este projeto, fiz engenharia reversa do aplicativo "C by GE" para controlar lâmpadas inteligentes GE conectadas por WiFi. Para fazer isso, comecei descompilando o aplicativo Android e, em seguida, fiz engenharia reversa do protocolo binário que o aplicativo usa para se comunicar com um servidor. Para obter mais detalhes, consulte Engenharia reversa C da GE.
Os produtos finais deste projeto são:
Isenção de responsabilidade: este código é resultado de engenharia reversa e não foi informado por especificação de protocolo. Como resultado, não há garantia de que continuará a funcionar ou que funcionará para todas as redes ou dispositivos inteligentes. Embora outros tenham usado essa API com sucesso em alguns casos, é possível que o código faça suposições incorretas que não se sustentam em todos os casos de uso.
O diretório do servidor é um aplicativo da web independente e um endpoint de API JSON para lâmpadas C by GE. O site fica assim:
Se você executar o site com um argumento -email
e -password
, o site exibirá uma página de autenticação de dois fatores na primeira vez que você carregá-lo. Você apertará um botão e inserirá o código de verificação enviado para seu e-mail. Como alternativa, você pode fazer login antecipadamente executando o comando login_2fa com os sinalizadores -email
e -password
definidos com as informações da sua conta. O comando solicitará o código de verificação 2FA. Depois de inserir esse código, o comando exibirá as informações da sessão como um blob JSON. Você pode então passar esse JSON para o argumento -sessinfo
do servidor, por exemplo, como -sessinfo 'JSON HERE'
. Observe que parte da sessão expira após uma semana, mas uma instância de servidor em execução continuará funcionando após esse período, pois a parte expirável da sessão é usada apenas uma vez para enumerar dispositivos.
As contas mais recentes exigem o uso de autenticação de dois fatores. Você pode realizar um handshake 2FA para criar uma sessão assim:
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...
Para contas mais antigas que nunca usaram 2FA antes, você poderá fazer login diretamente:
session , err := cbyge . NewControllerLogin ( "my_email" , "my_password" )
// Handle error...
Depois de iniciar uma sessão, você pode enumerar dispositivos assim:
devs , err := session . Devices ()
// Handle error...
for _ , x := range devs {
fmt . Println ( x . Name ())
}
Você pode controlar lâmpadas assim:
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)
Você também pode consultar as configurações atuais de uma lâmpada:
status , err := session . DeviceStatus ( x )
// Handle error...
fmt . Println ( status . IsOn )
fmt . Println ( status . ColorTone )
Nesta seção, mostrarei como fiz engenharia reversa de partes do protocolo C by GE.
O primeiro passo foi desmontar o aplicativo Android com Apktool. Isso produz a desmontagem do Smali para o aplicativo. Procurando por URLs e nomes de domínio. Inicialmente, encontrei isto:
.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/ "
Ver onde esse endpoint da API foi usado rapidamente me levou a um conjunto de chamadas HTTP baseadas em JSON para fazer login, listar dispositivos etc. No entanto, esse endpoint não parecia fornecer uma maneira de 1) obter o status dos dispositivos ou 2) atualize a cor ou brilho dos dispositivos.
Deveria haver alguma outra maneira de o aplicativo se comunicar com as lâmpadas inteligentes. No entanto, a desmontagem estava repleta de códigos para comunicação Bluetooth e LAN, e fiquei um pouco preocupado por não haver um endpoint de API global para controlar as lâmpadas. O que era pior, o aplicativo C by GE reclamava sempre que eu desligava o Bluetooth e tentava usá-lo. No entanto, acabei descobrindo que poderia abrir o aplicativo, deixá-lo fazer seu trabalho e, em seguida, desligar o Bluetooth e o WiFi enquanto ainda tinha controle sobre as lâmpadas. Tudo o que eu precisava fazer era clicar no botão "voltar" do Android sempre que o aplicativo abrisse um pop-up pedindo para "Ativar rastreamento de localização" (um nome estranho para Bluetooth e WiFi, lembre-se).
Nesse ponto, eu tinha quase certeza de que o aplicativo não estava fazendo outras conexões HTTP(S) misteriosas. Curiosamente, porém, encontrei o domínio "xlink.cn" em outro lugar no código Smali:
.field public static final CM_SERVER_ADDRESS : L java/lang/String ; = " cm-ge.xlink.cn "
.field public static final CM_SERVER_PORT : I = 0x5ce2
Caramba, isso poderia ser um protocolo baseado em soquete bruto? Eu tentei e com certeza consegui abrir uma conexão TCP para cm-ge.xlink.cn:23778
. No entanto, o Smali também estava repleto de lógica para pacotes UDP , então eu não tinha certeza de qual protocolo o aplicativo usaria. Com isso em mente, criei o packet-proxy e configurei-o para escutar na porta 23778. Em seguida, substituí o domínio cm-ge.xlink.cn
pelo meu endereço IP no código Smali, recompilei o aplicativo em um APK e instalei-o em meu telefone.
Com certeza, meu aplicativo C by GE corrigido imediatamente se conectou à minha instância de proxy de pacote e começou a conversar. Notavelmente, isso só aconteceu quando o Bluetooth e o WiFi estavam desligados. Caso contrário, parecia preferir um daqueles para comunicação local com as lâmpadas inteligentes.
O protocolo que o aplicativo escolheu usar foi de longe o resultado mais fácil de lidar: 1) era TCP em vez de UDP, 2) era completamente descriptografado. A falta de criptografia é bastante alarmante em retrospectiva, já que a primeira mensagem inclui um token de autorização que parece nunca mudar para minha conta.
Descobri que as mensagens do aplicativo para o servidor poderiam ser “repetidas” de forma eficaz. Depois de descobrir quais bytes (ou "pacotes", graças ao proxy de pacote) eram usados para ligar e desligar luzes, eu poderia simplesmente abrir um novo soquete e enviar esses mesmos pacotes e obter os mesmos resultados. Este foi um ótimo sinal. Na pior das hipóteses, eu já tinha uma forma de implementar o que queria para mim, mesmo que não fosse muito geral.
Neste ponto, era hora de nos aprofundarmos no protocolo. Depois de uma combinação de experimentação com proxy de pacote e investigação na desmontagem do Smali, tive uma compreensão bastante geral de qual comunicação estava ocorrendo. A primeira coisa que notei foi que a comunicação acontecia em "mensagens", que começavam com um campo de tipo e comprimento (em big endian). O próximo passo foi descobrir quais tipos de pacotes, onde quais e, eventualmente, como os próprios pacotes específicos foram codificados. Aqui está um exemplo de pacote do servidor contendo os status dos meus três dispositivos:
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
Depois de ter o suficiente do protocolo elaborado, criei uma API para ele. Esta API pode listar dispositivos, obter seus status e atualizar diversas propriedades dos dispositivos (por exemplo, brilho e tom de cor). Surpreendentemente, descobri que minha API é muito mais rápida e confiável do que o próprio aplicativo. Parece que tentar usar Bluetooth ou WiFi antes de voltar para um servidor remoto faz com que o aplicativo seja muito mais instável e menos confiável do que poderia ser.
Como observação final, não possuo todos os dispositivos suportados por este aplicativo, então não estava motivado (ou facilmente capaz) de fazer engenharia reversa de como esses dispositivos funcionariam. Por exemplo, a mesma empresa produz tomadas inteligentes, sensores e interruptores de luz.