Zamek hotelowy - zhakuj i zrób lepiej, sam. czyli historia na temat reverse-engineeringu klamki Michał Leszczyński, Marek Klimowicz, Jarosław Jedynak
Geneza projektu
Zadanie - wysokopoziomowo Znaleźć jakiś sposób, żeby odprawiać gości apartamentów bez fizycznego wydawania im kluczy.
Rozwiązania? Istniejące systemy tego typu?
Zamki z IEEE 802.15.4
Rozwiązania? Dystrybutor kluczy/kart dostępowych? Źródło: https:shops.ksq.com.au/products/automatic-key-dispenser
Rozwiązania? Wysyłanie kart dostępowych pocztą przed przyjazdem?
Rozwiązania? Rynkowa innowacja - zamki offline operujące kodami czasowymi
RentingLock kody dostępowe ważne kilka godzin/dni całkowicie offline pomysł podobny do Authenticatora /tokenów RSA? dodatkowo wsparcie kart zbliżeniowych
Kody czasowe w RentingLock
>>> datetime.datetime.now() - datetime.timedelta(minutes=9929998) datetime.datetime(2000, 1, 1, 1, 2, 12, 814778)
RentingLock - kody czasowe
RentingLock - kody czasowe
Jak mogą działać kody do klamek?
Jak mogą działać kody do klamek?
Jak mogą działać kody do klamek?
"Przykładowa" implementacja kod: 021743
"Przykładowa" implementacja kod: interpretacja: dzień startu: dni ważności: 021743 021743 1743 02
"Przykładowa" implementacja kod 1: kod 2: kod 3: kod 4: 021743 011745 041746 021750
"Przykładowa" implementacja kod 1: kod 2: kod 3: kod 4: 0x021743 0x011745 0x041746 0x021750
"Przykładowa" implementacja kod 1: kod 2: kod 3: kod 4: 0x021743 0x011745 0x041746 0x021750 = = = = 137027 071493 268102 137040
RentingLock - kody czasowe 2018-09-11 2018-09-11 2018-09-11 2018-09-11 2018-09-11 2018-09-11 20:00 20:00 20:00 20:00 20:00 20:00 2018-09-11 2018-09-11 2018-09-11 2018-09-12 2018-09-12 2018-09-12 21:00 22:00 23:00 00:00 01:00 02:00 23266241 23069633 23135169 23462849 23528385 23331777 00000001011000110000001111000001 00000001011000000000001111000001 00000001011000010000001111000001 00000001011001100000001111000001 00000001011001110000001111000001 00000001011001000000001111000001
RentingLock - kody czasowe 00000001011000110000001111000001 00000001011000000000001111000001 00000001011000010000001111000001 00000001011001100000001111000001 00000001011001110000001111000001 00000001011001000000001111000001
RentingLock - kody czasowe Prosta funkcja: def unoverlay(code, duration): return code ^ (duration << 16)
RentingLock - kody czasowe Prosta funkcja: def unoverlay(code, duration): return code ^ (duration << 16) W innej wersji klamki: def unoverlay_alt(code, duration): return (code - 10000000) ^ (duration << 16)
Atak przedłużenia kodu Kupujemy jeden nocleg w hotelu - Dostajemy "legalny" kod na jeden dzień - Wyliczamy kod działający 99 dni - Profit? -
Atak przedłużenia kodu Kupujemy jeden nocleg w hotelu - Dostajemy "legalny" kod na jeden dzień - Wyliczamy kod działający 99 dni - Profit? - Darmowe noclegi? -
Atak przedłużenia kodu Kupujemy jeden nocleg w hotelu - Dostajemy "legalny" kod na jeden dzień - Wyliczamy kod działający 99 dni - Profit? - Darmowe noclegi? - - Trudno wykrywalna kradzież?
Atak przedłużenia kodu Kupujemy jeden nocleg w hotelu - Dostajemy "legalny" kod na jeden dzień - Wyliczamy kod działający 99 dni - Profit? - Darmowe noclegi? - - Trudno wykrywalna kradzież? - Prezentacja na konferencję?
RentingLock - kody czasowe Prosta funkcja: const uint32_t SECRET =...; uint32_t generate_code(uint16_t start_hour, uint8_t duration) { return hash(start_hour, SECRET) ^ (duration << 16) }
RentingLock - kody czasowe Prosta funkcja: const uint32_t SECRET =...; uint32_t generate_code(uint16_t start_hour, uint8_t duration) { return hash(start_hour, SECRET) ^ (duration << 16) } - Samej funkcji hashującej nie zgadliśmy, ale też nie jest potrzebna do ataku. - (A gdybyśmy zgadli to i tak byśmy się nie przyznali)
Reakcja firmy
Reakcja firmy Dzień dobry, zakupiłem Państwa zamek w celu skonsultowania jego bezpieczeństwa (...). Jak widać, kody w reprezentacji binarnej różnią się zaledwie kilkoma bitami. To oznacza, że data i godzina wyjazdu są jawnie zapisane w kodzie i wyglądają na przewidywalne. W związku z tym mam obawy, że bardzo łatwo można przerobić jakiś kod dostępowy w taki sposób, aby wydłużyć jego ważność. Proszę o przedstawienie Państwa stanowiska na ten temat. Czy taki atak na RentingLock v5 jest faktycznie możliwy? Przyjazd Wyjazd Wygenerowany kod Kod w reprezentacji binarnej 13.09.2018 13:00 14.09.2018 13:00 *26 231 916 1100100000100010001101100 13.09.2018 13:00 14.09.2018 14:00 *26 297 452 1100100010100010001101100 13.09.2018 13:00 14.09.2018 15:00 *26 362 988 1100100100100010001101100
Reakcja firmy Dzień dobry Panie Michale, Klucz jest zaszyfrowanym ciągiem danych mówiących o jego ważności. Szyfrowanie odbywa się za pomocą zmiennego klucza którego wartość jest wyliczana przez funkcję mieszającą na podstawie unikalnego kodu zamka oraz daty rozpoczęcia najmu. Dodatkowym zabezpieczeniem może być aktywowanie funkcji, która blokuje zamek po 5-7 nieudanych próbach otwarcia.
Reakcja firmy Dzień dobry, domyślam się że funkcja mieszająca jest stosowana na dacie przyjazdu, a długość pobytu jest kodowana później za pomocą zwykłej funkcji xor. (...) Potencjalny włamywacz mógłby wynająć apartament na jeden dzień, następnie przyjść do tego samego apartamentu dwa tygodnie później i wejść za pomocą podrobionego kodu. Testowałem ten atak na fizycznym urządzeniu (...) Proszę o komentarz merytoryczny.
Minusy takiego rozwiązania Paranoja intuicja mówiła dobrze Outsourcowanie bezpieczeństwa do modelu SaaSowego brzmi groźnie bo Twoje bezpieczeństwo zależy od programu, którego nigdy nie zobaczysz!
Jest podatność, co teraz?
Jest podatność, co teraz? Wiemy, że tego konkretnego produktu raczej nie będziemy rekomendowali, ale weszliśmy już trochę w temat...
Jest podatność, co teraz? Jest na rynku drugie urządzenie z identyczną obudową, a w dodatku tańsze, tylko trzeba by zmienić firmware Teoretyczny przypadek: co jeżeli ktoś już kupił kilka takich urządzeń i chciałby zwiększyć ich bezpieczeństwo?
Zajrzyjmy do środka...
Analiza PCB
Cele analizy PCB zdobycie wystarczającej wiedzy pozwalającej na sterowanie komponentami wyznaczenie mapy wyprowadzeń mikrokontrolera określenie zasad sterowania poszczególnych komponentów dodatkowe elementy wykonawcze, polaryzacja sygnałów itp. umożliwienie stworzenia własnego, lepszego i znacznie bezpieczniejszego oprogramowania klamki
Sekcja zasilania Wstępne rozpoznanie bloki funkcjonalne znane układy - datasheet! braki w komponentach złącza z procesu produkcji programowania i/lub debug utrudnienia - silkscreen, conformal coating Układ napędu elektrozamka Detekcja karty/sygnalizacja
Rozpoznawanie komponentów Stabilizator (3.3V) i monitor napięcia baterii CV520 (NFC) EEPROM ATmega 164PA-AU Zegar czasu rzeczywistego
Identyfikacja odczytanie pełnego symbolu z obudowy układu odczytanie skróconego symbolu i lookup co on oznacza the hard way: identyfikacja funkcji i szukanie zbliżonego układu po pinoucie rebranding układów - jeden układ pod wieloma symbolami
Datasheety Najprostsza metoda: googlowanie po odczytanym symbolu, niekiedy na chińskich stronach
Połączenia między układami - reversowanie ścieżek Konieczne usunięcie wszelkiego rodzaju lakierów pokrywających PCB metoda wizualna - część połączeń ewidentnie widać sugerowanie się typical application z datasheetów testowanie ciągłości pomiędzy pinami multimetrem
Reversowanie ścieżek Warto zwrócić uwagę na typical application w datasheet - może akurat na PCB jest właśnie taki układ komponentów lub chociaż zbliżony?
Połączenia między układami Szyny komunikacji, piny przerwań i inne dodatkowe GPIO SPI (soft)
Połączenia między układami - pinout MF CV520
Połączenia między układami - pinout MF CV520
Hardware vs bit-bang protokołu Sprzętowe wsparcie protokołów takich jak I2C czy SPI odciąża główny program przerzucając odpowiedzialność za generowanie zegarów i transfer danych na sprzęt. Implementacja programowa kosztuje czas procesora i pamięć programu, której jest niewiele.
Połączenia między układami - MF CV520<>ATmega?? mikrokontroler posiada szereg dedykowanych pinów do różnych zadań kompromis łatwiejszej obsługi programowej a sprzętowej nie zawsze połączenia realizowane są zgodnie z oczekiwaniami i rozsądkiem??
Komunikacja z MF CV520 Wiemy jak wygląda otoczenie układu NFC i gdzie jest podpięty do mikroprocesora. Scalak NFC posiada jednak mnóstwo rejestrów konfiguracyjnych. Z chińskiego datasheeta wiemy jakie to rejestry, ale nie wiemy jak powinniśmy ich używać. Ale zacznijmy od początku...
Komunikacja z MF CV520 - podsłuchujemy Podlutowanie się do magistrali komunikacyjnej między mikrokontrolerem, a układem CV520 (SPI). Zoom
Komunikacja z MF CV520 - dump
Komunikacja z MF CV520
Komunikacja z MF CV520
Komunikacja z MF CV520 - parsowanie logów spi_write(reg_11_modereg, 0x3d); spi_write(reg_2d_treloadreg, 0x1e); spi_write(reg_2c_treloadreg, 0x00); spi_write(reg_2a_tmodereg, 0x8d); spi_write(reg_2b_tprescalerreg, 0x3e); spi_write(reg_15_txaskreg, 0x40); spi_read(reg_08_status2reg); spi_write(reg_08_status2reg, 0x00); spi_write(reg_0d_bitframingreg, 0x07); spi_read(reg_14_txcontrolreg); spi_write(reg_14_txcontrolreg, 0x83); spi_write(reg_02_comlenreg, 0xf7); spi_read(reg_04_comirqreg); spi_write(reg_04_comirqreg, 0x14); spi_write(reg_01_commandreg, 0x00); spi_read(reg_0a_fifolevelreg); spi_write(reg_0a_fifolevelreg, 0x80); spi_write(reg_09_fifodatareg, 0x52); spi_write(reg_01_commandreg, 0x0c); 3dh 1eh 00h 8dh 3eh 40h 00h 00h 07h 80h 83h f7h 14h 14h 00h 00h 80h 52h 0ch 00111101 00011110 00000000 10001101 00111110 01000000 00000000 00000000 00000111 10000000 10000011 11110111 00010100 00010100 00000000 00000000 10000000 01010010 00001100 spi_read(reg_0d_bitframingreg); spi_write(reg_0d_bitframingreg, 0x87); polling przerwań? spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_0d_bitframingryeg); spi_write(reg_0d_bitframingreg, 0x07); spi_read(reg_06_errorreg); spi_read(reg_0a_fifolevelreg); spi_read(reg_0c_controlreg); spi_read(reg_09_fifodatareg); spi_read(reg_09_fifodatareg); 07h 00000111 87h 10000111 04h 00000100 04h 00000100 44h 01000100 44h 01000100 44h 01000100 44h 01000100 44h 01000100 44h 01000100 44h 01000100 44h 01000100 64h 01100100 07h 00000111 07h 00000111 00h 00000000 02h 00000010 10h 00010000 04h 00000100 00h 00000000
Komunikacja z MF CV520 - parsowanie logów spi_read(reg_0c_controlreg); spi_write(reg_0c_controlreg, 0x90); spi_write(reg_01_commandreg, 0x00); spi_read(reg_08_status2reg); spi_write(reg_08_status2reg, 0x00); spi_write(reg_0d_bitframingreg, 0x00); spi_read(reg_0e_collreg); spi_write(reg_0e_collreg, 0x20); spi_write(reg_02_comlenreg, 0xf7); spi_read(reg_04_comirqreg); spi_write(reg_04_comirqreg, 0x64); spi_write(reg_01_commandreg, 0x00); spi_read(reg_0a_fifolevelreg); spi_write(reg_0a_fifolevelreg, 0x80); spi_write(reg_09_fifodatareg, 0x93); spi_write(reg_09_fifodatareg, 0x20); spi_write(reg_01_commandreg, 0x0c); spi_read(reg_0d_bitframingreg); spi_write(reg_0d_bitframingreg, 0x80); spi_read(reg_04_comirqreg); 10h 90h 00h 00h 00h 00h a0h 20h f7h 64h 64h 00h 00h 80h 93h 20h 0ch 00h 80h 04h 00010000 10010000 00000000 00000000 00000000 00000000 10100000 00100000 11110111 01100100 01100100 00000000 00000000 10000000 10010011 00100000 00001100 00000000 10000000 00000100 spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); 04h 04h 04h 04h 04h 44h 44h 44h 44h 44h 44h 44h 44h 44h 44h 44h 44h 44h 44h 00000100 00000100 00000100 00000100 00000100 01000100 01000100 01000100 01000100 01000100 01000100 01000100 01000100 01000100 01000100 01000100 01000100 01000100 01000100
Komunikacja z MF CV520 - parsowanie logów spi_read(reg_04_comirqreg); spi_read(reg_04_comirqreg); spi_read(reg_0d_bitframingreg); spi_write(reg_0d_bitframingreg, 0x00); spi_read(reg_06_errorreg); spi_read(reg_0a_fifolevelreg); spi_read(reg_0c_controlreg); spi_read(reg_09_fifodatareg); spi_read(reg_09_fifodatareg); spi_read(reg_09_fifodatareg); spi_read(reg_09_fifodatareg); spi_read(reg_09_fifodatareg); spi_read(reg_0c_controlreg); spi_write(reg_0c_controlreg, 0x90); spi_write(reg_01_commandreg, 0x00); spi_read(reg_0e_collreg); spi_write(reg_0e_collreg, 0xa0); spi_write(reg_01_commandreg, 0x0f); spi_write(reg_11_modereg, 0x3d); spi_write(reg_2d_treloadreg, 0x1e); 44h 64h 00h 00h 00h 05h 10h ceh 7eh 09h c3h 7ah 10h 90h 00h 20h a0h 0fh 3dh 1eh 01000100 01100100 00000000 00000000 00000000 00000101 00010000 11001110 01111110 00001001 11000011 01111010 00010000 10010000 00000000 00100000 10100000 00001111 00111101 00011110 spi_write(reg_2c_treloadreg, 0x00); spi_write(reg_2a_tmodereg, 0x8d); spi_write(reg_2b_tprescalerreg, 0x3e); spi_write(reg_15_txaskreg, 0x40); 00h 8dh 3eh 40h 00000000 10001101 00111110 01000000
Komunikacja z MF CV520 - parsowanie logów Komendy zebrane w całość (zgodnie z normą ISO 14443): Polecenie: 52 (WUPA; wzbudzenie karty ) Odpowiedź: 04 00 (ATQA) Polecenie: 93 20 (Anticollision level 1; zapytanie karty o UID ) Odpowiedź: CE 7E 09 C3 7A (UID + BCC)
Otwieranie kartami - jeszcze raz Poleganie na UID karty to bardzo zły pomysł. Karty zostały zaprojektowane tak, aby nie dało się ich łatwo sklonować. Oprogramowanie producenta pozwala na to przy wykonaniu zaledwie kilku operacji.
Custom firmware
Jak to zrobić dobrze? Skoro PCB jest zreversowany, jesteśmy gotowi do napisania własnego firmware.
Jak (fizycznie) wgrać własny firmware?
Jak (fizycznie) wgrać własny firmware?
Jak (fizycznie) wgrać własny firmware?
Ale można to zrobić lepiej! co by nie podpinać złączki za każdym razem
Stos protokołów NFC
Android - Host Card Emulation manifests/androidmanifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http:schemas.android.com/apk/res/android" package="poc.nfc.nfcpoc"> <uses-feature android:name="android.hardware.nfc.hce" android:required="true" /> <uses-permission android:name="android.permission.nfc" /> <application...>... <service android:name=".cardservice" android:exported="true" android:permission=android.permission.bind_nfc_service"> <intent-filter> <action android:name="android.nfc.cardemulation.action.host_apdu_service"/> <category android:name="android.intent.category.default"/> </intent-filter> <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/aid_list"/> </service> </application> </manifest>
Android - Host Card Emulation res/xml/aid_list.xml <host-apdu-service xmlns:android="http:schemas.android.com/apk/res/android" android:description="@string/app_name" android:requiredeviceunlock="false"> <aid-group android:description="@string/app_name" android:category="other"> <aid-filter android:name="f0deadbeef"/> </aid-group> </host-apdu-service>
Android - Host Card Emulation poc/nfc/nfcpoc/cardservice.java public class CardService extends HostApduService { private static final byte[] SELECT_APDU = HexStringToByteArray("00A4040005F0DEADBEEF00"); private static final byte[] UNKNOWN_CMD_SW = HexStringToByteArray("0000"); @Override public byte[] processcommandapdu(byte[] commandapdu, Bundle extras) { if (Arrays.equals(SELECT_APDU, commandapdu)) { ByteArrayOutputStream os = new ByteArrayOutputStream(); try { os.write("hello world!".getbytes()); os.write(new byte[] {(byte)0x90, 0x00}); return os.tobytearray(); } catch (IOException e) { e.printstacktrace(); return UNKNOWN_CMD_SW; } } else { return UNKNOWN_CMD_SW; } } }
Bootloader
Bootloader Optiboot z obsługą NFC? ;) łatwiejszy rozwój programu aktualizacje bezpieczeństwa DLC (:D) GitHub: icedevml / avr-nfc-bootloader
Kody czasowe (tym razem poprawnie)
Kody czasowe
Kody czasowe Przykładowo: ["2018-08-01", "2018-08-05"].aQLTyi50zwxSlenhitOs8n-IxBo
Kody czasowe Przykładowo: 2587477101591455233519905265892584940800500925044951563818095 0644503771538732938999530428852891259499828828275476220772917 9964701426287
Kody czasowe Enkodowanie daty ts(date) -> timestamp / 3600 Przykładowo: ts(2018-08-05) -> 17748 (uint16)
Kody czasowe SipHash jest szybki i produkuje 64 bitowy hash. (ts(2018-08-01), ts(2018-08-05), signature) Przykładowo: (17744, 17748, 9217976558055515389)
Kody czasowe Przykładowo: (17744, 4, 9217976558055515389)
Kody czasowe Limitujemy długość pobytu do 64 dni i stosujemy modulo. Przykładowo: (16, 4, 2011) -> 8238084
Kody czasowe Ta część kodu ( metadane ) nie będzie wyglądała losowo w analizie różnicowej. Rozwiązanie? Wyprowadzić klucz szyfrujący z drugiej części. key = siphash(signature) % 4096 metadata ^= key Przykładowo: ((16, 4) ^ key, 2011) -> 8238084
Kody czasowe W jednym momencie będzie tyle aktywnych kodów: n (1, 64] n = 2 + 3 + + 63 + 64 = 2079 2079 / 108 = 0,0021%
Kody czasowe Szansa na trafienie właściwego podpisu: 1 / 24414 = 0,0041%
Kody czasowe Implementowalne w praktyce na bardzo wolnych mikrokontrolerach Np. ATmega 164PA @ 1 MHz
Podsumowanie
Kwestie etyczne Aby wgrać własny firmware do klamki i tak trzeba ją kupić, więc producent nadal otrzymuje tyle samo pieniędzy. Użycie customowego firmware przerzuca na nas odpowiedzialność za jego poprawność. Ostrożnie napisany i zewnętrznie audytowany kod zapewnia znacznie wyższy poziom bezpieczeństwa.
Q&A Dziękujemy za uwagę! Kontakt do nas: - ml@icedev.pl - neoneq911@gmail.com - msm@tailcall.net Podziękowania dla: - pwn.m0d3 - oryginalny zleceniodawca GitHub: icedevml / avr-nfc-bootloader