Drugie podejście do dekodowania komunikacji z CLU z telefonem. Tym razem się udało!!!111
Wielki fart
Tym razem miałem szczęście. Szukając czegoś zupełnie innego, okazało się że gdy projekt jest otwarty, OM tworzy tymczasowe pliki. Dzięki czemu w katalogu workspace, gdzie znajduje się mój projekt o nazwie “Test2”, znalazłem plik “properties.xml”. Jego zawartość wygląda następująco:
<object-stream> <ProjectProperties id="1"> <ipRangeStart>192.168.1.2</ipRangeStart> <ipRangeEnd>192.168.1.255</ipRangeEnd> <validateRangeIp>false</validateRangeIp> <version>6</version> <projectCipherKey id="2"> <keyBytes id="3">KY1Ajg+pDBQcP2cHnIFNRQ==</keyBytes> <ivBytes id="4">/gV+nXMOUlBbuc3uhkk/eA==</ivBytes> </projectCipherKey> <autoUpdateInterfacesBase>false</autoUpdateInterfacesBase> <encryptedCLU class="tree-map" id="5"> <entry> <string>192.168.2.200</string> <boolean>true</boolean> </entry> </encryptedCLU> <readyToUpdate>false</readyToUpdate> </ProjectProperties> </object-stream>
Jest to absolutnie WSZYSTKO czego szukałem przez kilka godzin! Zacznijmy od najważniejszego – CipherKey. Jest klucz i wektor, których nie byłem w stanie wyłuskać. Zawsze jakaś metoda w Javie, nie była do końca zdekompilowna. A tu nagle, mam obie wartości podane w XML. Gdy zamknę projekt – plik jest usuwany. Możliwe, że dlatego nie znalazłem go wcześniej. Moje wcześniejsze podejście do szyfrowania można przeczytać tutaj: http://domktorymysli.pl/2017/11/grenton-komunikacja-z-telefonem/.
Jest też druga ciekawa rzecz, która przewijała się w źródłach OMa. Mianowicie “encryptedCLU” – Wydaje mi się, że ta flaga pozwala wyłączyć szyfrowanie w CLU. Co pozwoliłoby dużo dokładniej prześledzić komunikację. Żeby zmienić flagę muszę sprawdzić jak OM otwiera plik projektu i jak go zapisuje. Na tę chwilę wygląda na to, że plik projektu to zserailizowany obiekt Javy, ale nie miałem więcej czasu, żeby się temu przyjrzeć. Na tę chwilę najważniejsze jest zdekodowanie wiadomości.
Klucz
Dla przypomnienia, Grenton generuje “klucz”, który jest wysyłany (wraz z layotuem) do aplikacji mobilnej za pomocą OMa. Klucz ten może wyglądać następująco:
973E936A72AEE156AAD50D45BDFFA6547874C922DE349E5CF63D85FB54FE488673A1E964E7856C4AB83FB654B1FA52BD9A2035CDE049296C9657D6BF89C193A40B24E09CA06BEB151F58BC1345A0BD3815E072C7ADCCAAF2D952F763F5C54696
Powyższy string o długość 192 znaków, nie jest kluczem szyfrującym. W dużym skrócie, po zdekompilowaniu źródeł wyczytałem, że “klucz” ten to złączenie(konkatenacja) klucza (keyBytes) oraz wektora inicjującego(ivBytes). Następnie, wynik złączenia jest szyfrowany za pomocą kolejnego klucza zaszytego w OM i w aplikacji mobilnej. Do tego dekompiler nie mógł sobie poradzić z plikami odpowiedzialnymi za tę obfluskację, dlatego tak wiele czasu zajęło mi zrozumienie czym jest ten ciąg znaków. Dzięki temu, że OM zapisuje te klucze w osobnym pliku, nie musiałem dłużej wyłuskiwać kluczy i mogłem przejść do dekodowania wiadomości.
Dekomplilacja
Do dekomplacji źródeł użyłem narzędzia “FernFlower”, wspomagałem się też “DJ Java Decompiler 3.12” oraz IDE IntelliJ IDEA.
Jak pisałem już w poprzednim poście cała komunikacja odbywa się za pomocą protokołu UDP. Wiadomości są szyfrowane za pomocą standardu AES. Klucz ma długość 128 bit.
CipherKey cipherKey = CipherKey.createFromString("KY1Ajg+pDBQcP2cHnIFNRQ==", "/gV+nXMOUlBbuc3uhkk/eA=="); System.out.println(cipherKey.getSecretKey().getAlgorithm()); System.out.println(cipherKey.getSecretKey().getFormat()); System.out.println(cipherKey.getSecretKey().getEncoded().length * 8);Wynik: AES RAW 128
Wiadomości, które przychodzą i wychodzą do/z CLU mają długość max 2000 bajtów. Gdzieś w kodzie, znalazłem wiadomości o długości 1500 bajtów, ale jeszcze nie wiem gdzie one zostały użyte.
Deszyfrowanie komunikatów
Na podstawie zdekodowanych źródeł napisałem klasę dekodera. Najwięcej problemów miałem z paddingami, dopiero sprawa wyjaśniła się, gdy sprawdziłem w jaki sposób komunikaty są wysyłane i że mają długość 2000 bajtów. Metoda, pozwalająca zdekodować komunikat Grentona, wygląda następująco:
public MessageDecoded decode(byte[] message, int msgLength) throws NoSuchPaddingException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, ShortBufferException, NoSuchProviderException, InvalidKeyException { { Security.addProvider(new BouncyCastleProvider()); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC"); cipher.init(Cipher.DECRYPT_MODE, cipherKey.getSecretKey(), cipherKey.getIvSpec()); byte[] output = new byte[cipher.getOutputSize(msgLength)]; int length = cipher.update(message, 0, msgLength, output, 0); length += cipher.doFinal(output, length); byte[] result = new byte[length]; System.arraycopy(output, 0, result, 0, length); return new MessageDecoded(result); } }
Jest to standardowe użycie biblioteki Cipher w Javie. Za pomocą tej metody mogę zdekodować wiadomości jakie wymieniło CLU z telefonem. Oto kilka przykładów:
CipherKey cipherKey = CipherKey.createFromString("KY1Ajg+pDBQcP2cHnIFNRQ==", "/gV+nXMOUlBbuc3uhkk/eA=="); Decoder decoder = new Decoder(cipherKey); /* 0000 10 63 61 85 29 5a 6d bd 45 67 0d 1e 05 db 7d 45 .ca.)Zm.Eg....}E 0010 32 44 22 c5 54 8e 8f c2 01 4e 4d a0 13 ba 76 26 2D".T....NM...v& 0020 39 0d b9 11 81 f9 5e 73 a2 43 0e 44 6d 97 d1 cc 9.....^s.C.Dm... 0030 5d 6d 6e 53 5f 96 a5 98 e0 0a 0e 99 f0 e5 12 48 ]mnS_..........H */ String s = "10636185295a6dbd45670d1e05db7d45324422c5548e8fc2014e4da013ba7626390db91181f95e73a2430e446d97d1cc5d6d6e535f96a598e00a0e99f0e51248"; MessageEncoded msg = MessageEncoded.createFromString(s); MessageDecoded messageDecoded = decoder.decode(msg); assertEquals("req:192.168.1.104:00be11:DOUT_8565:execute(2, 0)\r\n", messageDecoded.toString());
Wydaje mi się, że telefon o IP 192.168.1.104 poprosił DOUT_8565 o wyłączenie 2 wyjścia. Fraza 00be11 to ID komunikatu. Jest to wygenerowana losowa liczba, która pozwala na powiązanie ze sobą wysłanych komend. Pozwala to na obejście problemów, które tworzy komunikacja po UDP.
Kolejna wiadomość:
CipherKey cipherKey = CipherKey.createFromString("KY1Ajg+pDBQcP2cHnIFNRQ==", "/gV+nXMOUlBbuc3uhkk/eA=="); Decoder decoder = new Decoder(cipherKey); /* 0000 d8 91 bb 00 a5 3a 9a 19 c1 68 bd f1 f0 23 e5 0e .....:...h...#.. 0010 4a 04 0f 5f 25 5b 9d ed 27 64 33 48 9d 67 a2 78 J.._%[..'d3H.g.x */ String s = "d891bb00a53a9a19c168bdf1f023e50e4a040f5f255b9ded276433489d67a278"; MessageEncoded msg = MessageEncoded.createFromString(s); MessageDecoded messageDecoded = decoder.decode(msg); assertEquals("resp:192.168.2.200:0000be11:nil", messageDecoded.toString());
Jeszcze nie wiem co oznacza ten komunikat. Adres 192.168.2.200 to adres Mojego CLU.
Kolejna wiadomość:
CipherKey cipherKey = CipherKey.createFromString("KY1Ajg+pDBQcP2cHnIFNRQ==", "/gV+nXMOUlBbuc3uhkk/eA=="); Decoder decoder = new Decoder(cipherKey); /* 0000 d8 91 bb 00 a5 3a 9a 19 c1 68 bd f1 f0 23 e5 0e .....:...h...#.. 0010 4e df e1 d3 97 35 0f 3b 2c b6 43 e6 b2 43 d8 df N....5.;,.C..C.. 0020 0b d5 93 1b 48 76 a4 c9 26 ca c5 fa 5e f7 d0 13 ....Hv..&...^... 0030 81 98 aa c3 e7 b7 20 a3 12 30 e1 8d 58 ff b8 31 ...... ..0..X..1 0040 3d c8 0d ed 3a 12 3e 0e f1 b2 0a 9e f7 57 0b da =...:.>......W.. */ String s = "d891bb00a53a9a19c168bdf1f023e50e4edfe1d397350f3b2cb643e6b243d8df0bd5931b4876a4c926cac5fa5ef7d0138198aac3e7b720a31230e18d58ffb8313dc80ded3a123e0ef1b20a9ef7570bda"; MessageEncoded msg = MessageEncoded.createFromString(s); MessageDecoded messageDecoded = decoder.decode(msg); assertEquals("resp:192.168.2.200:00009fe5:clientReport:210:{0,1,0,0,1,1,0,0}", messageDecoded.toString().trim());
Ten komunikat służy prawdopodobnie do synchronizacji CLU z telefonem.
Poświęciłem trochę czasu i napisałem bibliotekę (na tę chwilę w Javie), która służy do szyfrowania i deszyfrowania wiadomości dla Grentona. Biblioteka jest dostępna pod adresem: https://github.com/Domktorymysli/grenton-encoder. Potrzeba Javy 9 i Mavena, aby sobie samodzielnie zbudować paczkę.
Oraz napisałem bardzo prostego klienta, który w tej chwili potrafi jedynie wysłać wiadomość do CLU. Kod dostępny jest pod adresem https://github.com/Domktorymysli/grenton-simple-client. Natomiast tak wygląda użycie klienta:
public class App { public static void main( String[] args ) { try { Clu clu = new Clu("192.168.2.200", 1234, false); Encoder encoder = new EncoderGrenton("KY1Ajg+pDBQcP2cHnIFNRQ==", "/gV+nXMOUlBbuc3uhkk/eA=="); Api api = new Api(clu, encoder); CluCommand command = new CluCommand("req:local_ip:000001:DOUT_8565:execute(0, 1)"); System.out.println("Trying to send message."); api.send(command); System.out.println("Message was sent."); } catch (UnknownHostException e) { e.printStackTrace(); } catch (GrentonIoException e) { e.printStackTrace(); } catch (GrentonEncoderException e) { e.printStackTrace(); } } }
Sam chcesz sprawdzić?
Ściągnij:
- Javę 9
- IntelliJ community edition
- Mavena 3
- Pliki z repozytorium git https://github.com/Domktorymysli/grenton-simple-client
Wykonaj następujące kroki:
- Włącz projekt w OM. W katalogu workspace poszukaj pliku properties.xml. Wklej klucze do kodu
- Poszukaj komend w plikach XML GUI
Dodatkowo:
- Wiresharkiem możesz podejrzeć komunikację między OM (Symylator GUI a CLU)
Podsumowanie:
- Miałem trochę szczęścia, że znalazłem rozszyfrowane klucze. Przyśpieszyło to znacząco moją pracę.
- Wydaje mi się, że można wyłączyć szyfrowanie w ramach projektu. Wtedy będzie mi łatwiej zrozumieć jak działa komunikacja.
- Komunikacja między CLU a telefonem wygląda całkiem zgrabnie. Nie mogę się doczekać, aż będę miał trochę więcej czasu, żeby się lepiej temu przyjrzeć.
Co w przyszłości?
- Chcę napisać prostego klienta, z komunikacją w dwie strony.
- Może podłączę Grentona pod Domoticz?
Pozdrawiam,
T