Notatnik techniczny

Dwa słowa o kartach Mifare

Karty RFID

Dziś zamierzam powiedzieć nieco o kartach zbliżeniowych, a szczególnie o kartach Mifare. Wszystkich czytelników zainteresowanych tematyką kart zbliżeniowych odsyłam do normy ISO/IEC 14443, dokument ten składa się z czterech części:

  • ISO/IEC 14443-1:2008 – Parametry fizyczne karty, takie jak wymiary, właściwości elektryczne, poziomy pamięć.
  • ISO/IEC 14443-2:2010 – Częstotliwości komunikacji radiowej.
  • ISO/IEC 14443-3:2011 – Algorytm wyboru karty, algorytm unikania kolizji.
  • ISO/IEC 14443-4:2008 – Protokół komunikacyjny.

Termin Mifare odnosi się do kilku różnych typów kart zbliżeniowych produkowanych przez firmę NXP. Niniejszy wpis będzie poświęcony dwóm z nich.

MIFARE Classic oraz MIFARE Ultralight

Karty te nie są całkowicie zgodne ze specyfikacją ISO/IEC 14443. Podlegają części 1, 2, 3, ale już nie części 4. Komunikacja z kartą odbywa się przy pomocy dedykowanego protokołu firmy NXP, natomiast dla wygody użytkowników wiele czytników emuluje komunikaty zgodne z ISO/IEC 14443-4.

MIFARE Classic

To karta pamięciowa z pewnymi funkcjami kontroli dostępu. Na chwile obecną nadal szeroko stosowana, pomimo faktu że algorytm kontroli dostępu do karty został złamany. Pierwszy blok danych służy tylko do odczytu, jest tam przechowywany numer seryjny karty. Tę funkcjonalność często wykorzystuje się do identyfikacji użytkownika. Ponieważ karta nie zapewnia w chwili obecnej poprawnej weryfikacji praw dostępu, funkcje weryfikacji integralności danych muszą być zapewnione przez warstwę aplikacji, np. poprzez kryptograficzne sumy kontrolne (tam gdzie to możliwe). Nota katalogowa karty zawiera szczegółowe informacje o prawach dostępu, strukturze pamięci oraz API karty. Oto mapa pamięci karty 1K:

MIFARE Classic mapa pamięci

Na diagramie widzimy:

  • 16 sektorów, każdy po 4 bloki o rozmiarze 16 bajtów,
  • pierwszy blok tylko do odczytu,
  • ostatni blok każdego sektora zawiera dwa klucze oraz prawa dostępu do danego sektora.

MIFARE Ultralight

To znacznie prostsza karta nieposiadająca żadnego systemu weryfikacji dostępu. Wyposażona jest w 512 bitów pamięci, fabrycznie ustawiony numer seryjny oraz bity bezpiecznikowe, umożliwiające trwałe i nieodwracalne zablokowanie możliwości zapisywania na kartę. Dokumentacja karty przedstawia mapę pamięci:

MIFARE Ultralight mapa pamięci

Na diagramie widzimy:

  • Numer seryjny karty zapisany 7 bajtów plus 2 na sumę kontrolną,
  • 2 bajty bitów bezpiecznikowych pozwalających blokować możliwość zapisu do pewnych części karty. Zapisywane wartości do tych obszarów pamięci będą poddane operacji OR z wartościami już obecnymi w pamięci. Jeśli jakikolwiek bit ustawimy ‘1’, to ta wartość będzie niemodyfikowalna.
  • 4 bajty danych typu OTP. Są to podobnie jak bity bezpiecznikowe dane, gdzie możemy zapisywać tylko jedynki. Jeśli ustawimy jakikolwiek bit, to nie będziemy mieli żadnej możliwości, aby go wyzerować.
  • 12 bloków po 4 bajty na dane użytkownika.

Chipy MIFARE mogą występować pod postacią kart plastikowych bez interfejsu stykowego, mogą być dodane jako dodatkowy układ do innych kart, jako breloczki, naklejki, itp. Poniżej kilka przykładów kart MIFARE.

MIFARE zdjęcia

Czytnik kart zbliżeniowych

Gdy chcemy komunikować się z kartami zbliżeniowymi, potrzebujemy czytnika. Ja korzystam z ACR122.

To tani czytnik na USB działający bardzo dobrze w środowisku Windows. Czytnik zakłada, że komunikujemy się z kartami zgodnymi z ISO/IEC 14443-4, a w przypadku kart Mifare sam emuluje poszczególne rozkazy karty.

Szczegółowe informacje o tym czytniku można znaleźć w tym dokumencie.

MIFARE czytnik ACR122

Komunikacja z kartą

System Windows obsługuje karty inteligentne bardzo dobrze (tylko te zgodne z ISO). Smart Card API Windows pozwala nam wymieniać APDU z kartą dzięki dowolnemu kompatybilnemu czytnikowi np. ACR122. Wszystkie czytniki USB, jakie znam, nie posiadają dedykowanych sterowników, a są one obsługiwane przez standardowe sterowniki Windows.

W poniższym przykładzie zainstalowałem bibliotekę pomocniczą do obsługi kart inteligentnych w środowisku Python pyscard. Biblioteka pyscard jest tylko wygodną nakładką na standardowe API Windows, zawiera również kilka opcji dodatkowych.

Wszystko, co robimy dzięki tej bibliotece, możemy zrobić również przy pomocy Smart Card API Windows. Jeśli nie chcemy ręcznie kompilować biblioteki pyscard, możemy pobrać gotowy instalator msi.

Poniżej klasa wywołująca wybrane komendy czytnika ACR122.

from smartcard.System import readers
from smartcard.ATR import ATR
from smartcard.util import toHexString
import smartcard.Exceptions


class NoACR122ReaderException(Exception): pass
class CardException(Exception): pass
class UnknownReaderException(Exception): pass
class OperationFailedException(Exception): pass
class FunctionNotSupportedException(Exception): pass
class FunctionArgumentException(Exception): pass

class ACR122_Reader:
    def __init__(self):
        try:
            self.readers = readers()
            self.acr122_readers = [a for a in self.readers if str(a).find('ACR122')]
            if len(self.acr122_readers) == 0:
                raise NoACR122ReaderException()
            self.selected_reader = self.acr122_readers[0]
            self.connection = self.selected_reader.createConnection()
            self.connection.connect()
        except smartcard.Exceptions.NoCardException:
            raise CardException

    def showATRInfo(self):
        atr = ATR(self.connection.getATR())
        print('ATR                        : {}'.format(toHexString(self.connection.getATR())))
        print('historical bytes           : {}'.format(toHexString(atr.getHistoricalBytes())))
        print('checksum                   : {}    ok? {}: '.format(hex(atr.getChecksum()), atr.checksumOK))
        print('support for [T0, T1, T15]  : {}'.format([atr.isT0Supported(), atr.isT1Supported(), atr.isT15Supported()]))

    def getReadersTotalCount(self):
        return len(self.readers)

    def getReader(self):
        return self.selected_reader

    def close(self):
        if self.connection:
            self.connection.disconnect()

    def sendAPDU(self, apdu):
        return self.connection.transmit( apdu )
    #==================================

    def getFirmwareVersion(self):
       APDU = [0xFF, 0x00, 0x48, 0x00, 0x00]
       data, sw1, sw2 = self.sendAPDU(APDU)
       if sw1 != 49 or sw2 != 48: raise OperationFailedException()
       return "".join(map(chr, data))

    def setBuzzerStatus(self, value):
        new_status = [0x00,0xff][int(value)]
        APDU = [0xFF, 0x00, 0x52, new_status, 0x00]
        data, sw1, sw2 = self.sendAPDU(APDU)
        if sw1 != 0x90: raise OperationFailedException()

    #==================================

    def ParseResponce(self, sw1, sw2):
        if sw1 == 0x63 and sw2 == 0x00: raise OperationFailedException()
        if sw1 == 0x6A and sw2 == 0x081: raise FunctionNotSupportedException()
        if sw1 != 0x90 and sw2 == 0x00: raise UnknownReaderException()

    def GetSerialNumber(self):
        APDU = [0xFF, 0xCA, 0x00, 0x00, 0x04]
        data, sw1, sw2 = self.sendAPDU(APDU)
        self.ParseResponce(sw1, sw2)
        return data

    def LoadAuthenticationKeys(self, key, key_location = 0):
        if not isinstance(key, list): raise FunctionArgumentException()
        if len(key) != 6: raise FunctionArgumentException()
        if not key_location in [0x00,0x01]: raise FunctionArgumentException()
        APDU = [0xFF, 0x82, 0x00, key_location, 0x06] + key
        data, sw1, sw2 = self.sendAPDU(APDU)
        self.ParseResponce(sw1, sw2)

    def Authentication(self, block, use_key_A, key_location = 0):
        if not isinstance(block, int): raise FunctionArgumentException()
        if block < 0 or block > 255: raise FunctionArgumentException()
        if not isinstance(use_key_A, bool): raise FunctionArgumentException()
        if not key_location in [0x00,0x01]: raise FunctionArgumentException()

        KEY_TYPE = [0x60, 0x61][int(not use_key_A)]
        AUTHENTICATION_DATA = [0x01, 0x00, block, KEY_TYPE, key_location]
        APDU = [0xFF, 0x86, 0x00, 0x00, 0x05] + AUTHENTICATION_DATA
        data, sw1, sw2 = self.sendAPDU(APDU)
        self.ParseResponce(sw1, sw2)

    def TryAuthentication(self, block, use_key_A, key_location = 0):
        KEY_TYPE = [0x60, 0x61][int(not use_key_A)]
        AUTHENTICATION_DATA = [0x01, 0x00, block, KEY_TYPE, key_location]
        APDU = [0xFF, 0x86, 0x00, 0x00, 0x05] + AUTHENTICATION_DATA
        data, sw1, sw2 = self.sendAPDU(APDU)
        if sw1 != 0x90 and sw2 == 0x00:
            return False
        return True

    def ReadBinaryBlocks(self, block, bytesNum):
        if not isinstance(block, int): raise FunctionArgumentException()
        if block < 0 or block > 255: raise FunctionArgumentException()
        if not isinstance(bytesNum, int): raise FunctionArgumentException()
        if bytesNum < 0: raise FunctionArgumentException()
        APDU = [0xFF, 0xB0, 0x00, block, bytesNum]
        data, sw1, sw2 = self.sendAPDU(APDU)
        self.ParseResponce(sw1, sw2)
        return data

    def WriteBinaryBlocks(self, block, bytes):
        if not isinstance(block, int): raise FunctionArgumentException()
        if block < 0 or block > 255: raise FunctionArgumentException()
        if not isinstance(bytes, list): raise FunctionArgumentException()
        if not len(bytes) in [4, 16]: raise FunctionArgumentException()
        APDU = [0xFF, 0xD6, 0x00, block, len(bytes)] + bytes
        data, sw1, sw2 = self.sendAPDU(APDU)
        self.ParseResponce(sw1, sw2)

Warto zaznaczyć, że nie rozmawiamy bezpośrednio z kartą. Wszystkie pakiety APDU mają klasę 0xFF, co zgodnie z dokumentacją czytnika oznacza, że będą zinterpretowane i przetworzone przez czytnik.

v Zaraz po wybraniu karty otrzymujemy pakiet ATR (Answer to reset). Ten ciąg bajtów pozwala określić podstawowe właściwości karty (zarówno stykowej, jak i bezstykowej), zawiera informacje o wspieraniu niskopoziomowych protokołów (T0/T1), maksymalne rozmiary ramek oraz ciąg bajtów umożliwiający identyfikację karty. Poniżej kilka linków do analizy ATR kart inteligentnych:

UWAGA: Aby wysłać jakikolwiek pakiet APDU do czytnika, nawet taki, który sprawdza wersje firmware, urządzenie musi być połączone z kartę. Po szczegóły odsyłam do dokumentacji czytnika.

Przykładowa sesja

Poniżej przedstawiam program demonstracyjny zrzucający zawartość dwóch pierwszych sektorów karty. Zakładamy, że jest to karta, do której pasuje domyślny klucz. W przypadku kart MIFARE Ultralight, które nie posiadają mechanizmów kontroli dostępu, powinniśmy usunąć poniższe linie.

reader.LoadAuthenticationKeys(key)
reader.Authentication(start_block, True)

Powinniśmy także zmienić rozmiar bloku zgodnie z dokumentacją. Karta Ultralight posiada 512 bitów pamięci podzielonej na 16 stron/bloków po 4B każdy.

Odczyt pierwszych sektorów karty MIFARE Classic

from __future__ import print_function
from ACR122_Reader import *
 
DEFAULT_KEY = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff]
 
def dump_mifare_sectors(reader, sectors, key = DEFAULT_KEY):
    for sector in sectors:
        dump_mifare_sector(reader, sector, key)
 
def dump_mifare_sector(reader, sector, key = DEFAULT_KEY):
    print("=========== sector {} ===============".format(sector))
    start_block = sector * 4
    number_of_blocks = 4
    if sector > 31:
        start_block = 32 * 4 + 16 * (sector - 32)
        number_of_blocks = 16
 
    reader.LoadAuthenticationKeys(key)
    reader.Authentication(start_block, True)
    for i in range(number_of_blocks):
        block_num = start_block + i
        data = reader.ReadBinaryBlocks(block_num, 16)
        data = ' '.join('%02x'%i for i in data)
        print("block [{0:02d} | {1:02d}]     {2}".format(block_num, i, data))
 
def main():
    try:
        reader = ACR122_Reader()
        print()
        print("Selected reader is: {} ver. {} serial: {}".format(reader.getReader(), reader.getFirmwareVersion(),reader.GetSerialNumber()))
        reader.showATRInfo()
 
        dump_mifare_sectors(reader, [0, 1])
        reader.close()
 
    except CardException:
        print('ERROR card problem')
    except NoACR122ReaderException:
        print('ERROR no ACR122 reader connected')
    except OperationFailedException:
        print('ERROR Operation Failed')
    except FunctionNotSupportedException:
        print('ERROR Function Not Supported')
    except UnknownReaderException:
        print('ERROR Unknown Reader Exception')
    except FunctionArgumentException:
        print('ERROR Wrong argument')
 
if __name__ == "__main__":
    main( )

Wynik dla czystej karty

Selected reader is: ACS ACR122 0 ver. ACR122U2 serial: [32, 209, 75, 132]
ATR                        : 3B 8F 80 01 80 4F 0C A0 00 00 03 06 03 00 01 00 00 00 00 6A
historical bytes           : 80 4F 0C A0 00 00 03 06 03 00 01 00 00 00 00
checksum                   : 0x6a    ok? True:
support for [T0, T1, T15]  : [True, True, False]
=========== sector 0 ===============
block [00 | 00]     20 d1 4b 84 3e 88 04 00 c1 85 14 98 61 80 48 12
block [01 | 01]     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
block [02 | 02]     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
block [03 | 03]     00 00 00 00 00 00 ff 07 80 69 ff ff ff ff ff ff
=========== sector 1 ===============
block [04 | 00]     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
block [05 | 01]     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
block [06 | 02]     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
block [07 | 03]     00 00 00 00 00 00 ff 07 80 69 ff ff ff ff ff ff

Co więcej o kartach inteligentnych?

Poniżej postanowiłem wypisać tematy, którymi również chciałbym się zając w przyszłości. Jeśli wśród z nich znajduje się ten, który interesuje Cię bardziej, niż pozostałe, napisz o tym w komentarzu, a ja specjalnie dla Ciebie stworzę wpis, w którym omówię detale wybranego zagadnienia.

  • Przykład bardziej zaawansowanych kryptograficznie kart zbliżeniowych, np. MIFARE DESFire.
  • Analiza protokołu komunikacyjnego, komunikacja bezpośrednio z kartą, bez żadnej logiki zaszytej w firmware czytnika.
  • Łamanie zabezpieczeń karty MIFARE Classic, jak złamać klucz dostępu.
  • Kilka słów o kartach płatniczych i standardzie EMV..
  • Pisanie własnej aplikacji na kartę inteligentną.