Mi[fare|fun]: Recyclez vos Abonnements de Transports

Introduction

Le but de ce petit article est de montrer comment interagir avec une carte (RFID) Mifare Classic à l’aide d’un simple bout de code Python afin de, par exemple, s’en servir au sein de sa propre application. De plus, nous allons prendre comme exemple, non pas une carte vierge, mais une carte normalement dédiée à une autre application – un abonnement de transports publics – et démontrer comment il est possible de s’en servir également pour ses propres besoins sans altérer sa fonction originale.

Toutefois, avant de commencer, notons deux remarques importantes: (1) Cet article n’a pas pour but de parler des attaques menées sur les cartes Mifare Classic au cours de ces dernières années. Si ce sujet vous intéresse, de nombreuses publications sont disponibles sur la toile. (2) Le sujet de cet article n’est pas de casser la clé protégeant l’accès au contenu de l’abonnement lui-même ni même d’accéder d’une quelconque manière à ces données protégées.

Ceci étant dit, nous allons voir qu’il y a tout de même de quoi s’amuser avec une de ces cartes, et notamment qu’il est possible de “recycler” un ancien abonnement pour d’autres applications.

La Carte

La carte que nous allons utiliser est un abonnement de transports publics de la région lausannoise. Il s’agit en fait d’une carte “Mifare Classic 1K”, à savoir une des cartes RFID HF (13.56 MHz) les plus utilisées, notamment dans des application liées aux transports publics. L’organisation interne de la mémoire de ce type de cartes est largement documentée, nous allons donc nous limiter au strict minimum, à savoir que la mémoire est divisée en 16 secteurs indépendants, chacun d’eux protégé par une paire de clés d’accès.

Chacun de ces secteurs est divisé en 4 blocs, les 3 premiers servant à stocker des données et le dernier stockant les conditions d’accès ainsi que les clés elles-mêmes. Ces clés sont au nombre de deux – A et B – et les condition d’accès peuvent être définies en fonction de l’une ou l’autre (ce qui permet, par exemple, d’avoir une clé permettant uniquement de lire les données et une autre permettant de les modifier). Une carte Mifare Classic 1K dispose donc de 64 blocs, de 16 bytes chacun, ce qui fait un total de 1024 bytes (d’où le 1K).

Avant de pouvoir procéder à une opération de lecture ou d’écriture sur un bloc, il est nécessaire de s’authentifier pour ce bloc auprès de la carte. Ainsi, une seule carte Mifare Classic 1K pourrait être simultanément utilisée par 16 applications indépendantes, chacune utilisant un secteur (et donc une paire de clés) distinctes (pour autant que l’espace de stockage proposé par un seul bloc soit suffisant pour chaque application). Il est toutefois à noter que le tout premier bloc de la carte est un peu spécial et contient notamment l’identifiant unique (UID) de la carte.

Outils

Divers outils (software) disponibles sur le web permettent d’interagir avec une carte Mifare Classic, notamment l’excellent outil RFIDIOt. Dans notre cas, nous avons utilisé un petit script Python spécialement écrit pour l’occasion, que nous avons nommé pymfc.py. Ce script repose sur le module pyscard, prévu pour permettre d’écrire facilement des applications Python utilisant des cartes à puce au travers de l’interface PC/SC (incluse dans Windows) ou PCSCLite (systèmes *nix).

Pour ce qui est du matériel, nous utilisons un simple lecteur USB OMNIKEY 5321, capable d’interagir avec les cartes à puce “standard” ainsi qu’avec les cartes RFID (13.56 MHz). Ce lecteur – en plus d’être facile à trouver dans le commerce – a l’avantage de supporter l’API PC/SC nous permettant ainsi d’utiliser le module pyscard.

Lecture

Equipés de nos outils – pymfc.py et le lecteur USB – nous allons essayer de lire le contenu de notre abonnement de transports publics. Bien entendu nous ne connaissons pas les clés secrètes utilisées par la compagnie de transports pour protéger ses données. Ce que nous connaissons, en revanche, c’est les clés par défaut utilisées par le constructeur lors de la fabrication des cartes (appelées clés “de transport”). Ces clés sont publiques et disponibles dans la documentation:

Clé A : A0A1A2A3A4A5
Clé B : B0B1B2B3B4B5

Nous pouvons donc essayer de lire la carte à l’aide d’une de ces clés, par exemple la clé A. En tentant de lire l’intégralité de la carte (blocs 0 à 63) nous obtenons le résultat ci-dessous (tronqué). A noter que l’identifiant de la carte ainsi que le contenu du premier bloc ont été masqués afin d’éviter toute identification.

[sourcecode language=”text”]
$> python pymfc.py -c r -k A0A1A2A3A4A5 -t A -b 0-63
[#] Connected to reader: OMNIKEY CardMan 5×21 00 01
[#] Reading card UID: XX XX XX XX
[#] Reading card data using key: A0A1A2A3A4A5 (A)
[#] ========== CARD DATA ==========
[*] Sector 00 | block 00 : XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
[*] Sector 00 | block 01 : XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX XX
[*] Sector 00 | block 02 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] Sector 00 | block 03 : 00 00 00 00 00 00 69 67 89 00 00 00 00 00 00 00
[*] Sector 01 | block 00 : — NO ACCESS —
[*] Sector 01 | block 01 : — NO ACCESS —
[*] Sector 01 | block 02 : — NO ACCESS —
[*] Sector 01 | block 03 : — NO ACCESS —
[*] Sector 02 | block 00 : — NO ACCESS —
[*] Sector 02 | block 01 : — NO ACCESS —
[*] Sector 02 | block 02 : — NO ACCESS —
[*] Sector 02 | block 03 : — NO ACCESS —
[*] Sector 03 | block 00 : — NO ACCESS —
[*] Sector 03 | block 01 : — NO ACCESS —
[*] Sector 03 | block 02 : — NO ACCESS —
[*] Sector 03 | block 03 : — NO ACCESS —
[*] Sector 04 | block 00 : — NO ACCESS —
[*] Sector 04 | block 01 : — NO ACCESS —
[*] Sector 04 | block 02 : — NO ACCESS —
[*] Sector 04 | block 03 : — NO ACCESS —
[*] Sector 05 | block 00 : — NO ACCESS —
[*] Sector 05 | block 01 : — NO ACCESS —
[*] Sector 05 | block 02 : — NO ACCESS —
[*] Sector 05 | block 03 : — NO ACCESS —
[*] Sector 06 | block 00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] Sector 06 | block 01 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] Sector 06 | block 02 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] Sector 06 | block 03 : 00 00 00 00 00 00 7F 07 88 69 00 00 00 00 00 00
[*] Sector 07 | block 00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] Sector 07 | block 01 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] Sector 07 | block 02 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] Sector 07 | block 03 : 00 00 00 00 00 00 7F 07 88 69 00 00 00 00 00 00
…continued…
[/sourcecode]

On constate donc que les secteurs 1 à 5 sont illisibles et on peut donc en déduire qu’il s’agit des secteurs utilisés par la compagnie de transports. Par contre, tous les autres secteurs ont été laissés en configuration par défaut et sont donc lisibles et à notre entière disposition! On notera au passage qu’il est impossible de lire les clé de chiffrement (stockées dans le dernier bloc de chaque secteur). Ainsi les 6 premiers (clé A) et 6 derniers (clé B) bytes de ses blocs retournent toujours 0x00 lorsqu’on tente de les lire.

Ecriture

Prenons donc possession d’un secteur, par exemple le dernier (secteur 15, donc les blocs 60 à 63). Pour cela, commençons par choisir deux clés “secrètes” pour notre application:

Clé A: 0A0A0A0A0A0A
Clé B: 0B0B0B0B0B0B

Maintenant définissons ces clés comme clés de protection pour le secteur 15. Pour cela, il faut ré-ecrire le dernier bloc de ce secteur (conformément au conditions d’accès par défaut,  il est nécessaire de s’authentifier avec la clé B pour pouvoir ré-ecrire ce bloc). Nous prendrons toutefois garde à ne pas altérer les conditions d’accès mais uniquement les clés en elles-mêmes.

[sourcecode language=”text”]
$> python pymfc.py -c w -k B0B1B2B3B4B5 -t B -b 63 -d 0A0A0A0A0A0A7F0788690B0B0B0B0B0B

[#] Connected to reader: OMNIKEY CardMan 5×21 00 01
[#] Reading card UID: XX XX XX XX
[#] Writing data to card using key: B0B1B2B3B4B5 (B)
[/sourcecode]

On note alors qu’il n’est plus possible de lire le contenu de ce secteur avec la clé de transport utilisée précédemment. Par contre il est possible de le lire avec notre nouvelle clé “secrète”. Nous sommes maintenant “propriétaires” de ce secteur qui ne pourra être accédé que par les lecteurs disposant de notre clé.

[sourcecode language=”text”]
$> python pymfc.py -c r -k A0A1A2A3A4A5 -t A -b 60-63

[#] Connected to reader: OMNIKEY CardMan 5×21 00 01
[#] Reading card UID: XX XX XX XX
[#] Reading card data using key: A0A1A2A3A4A5 (A)
[#] ========== CARD DATA ==========
[*] Sector 15 | block 00 : — NO ACCESS —
[*] Sector 15 | block 01 : — NO ACCESS —
[*] Sector 15 | block 02 : — NO ACCESS —
[*] Sector 15 | block 03 : — NO ACCESS —
[#] ========== END DATA ==========

$> python pymfc.py -c r -k 0A0A0A0A0A0A -t A -b 60-63

[#] Connected to reader: OMNIKEY CardMan 5×21 00 01
[#] Reading card UID: XX XX XX XX
[#] Reading card data using key: 0A0A0A0A0A0A (A)
[#] ========== CARD DATA ==========
[*] Sector 15 | block 00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] Sector 15 | block 01 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] Sector 15 | block 02 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] Sector 15 | block 03 : 00 00 00 00 00 00 7F 07 88 69 00 00 00 00 00 00
[#] ========== END DATA ==========
[/sourcecode]

A partir de là il ne nous reste plus qu’à stocker sur les trois premiers blocs de ce secteur les données “utiles” à notre application. On pourrait, par exemple, y stocker des données d’identification de l’utilisateur ou encore un mot de passe afin d’utiliser la carte comme token d’authentification.

[sourcecode language=”text”]
$> python pymfc.py -c w -k 0A0A0A0A0A0A -t A -b 60 -d 68656C6C30776F726C64000000000000
[#] Connected to reader: OMNIKEY CardMan 5×21 00 01
[#] Reading card UID: XX XX XX XX
[#] Writing data to card using key: 0A0A0A0A0A0A (A)

$> python pymfc.py -c r -k 0A0A0A0A0A0A -t A -b 60-63
[#] Connected to reader: OMNIKEY CardMan 5×21 00 01
[#] Reading card UID: XX XX XX XX
[#] Reading card data using key: 0A0A0A0A0A0A (A)
[#] ========== CARD DATA ==========
[*] Sector 15 | block 00 : 68 65 6C 6C 30 77 6F 72 6C 64 00 00 00 00 00 00
[*] Sector 15 | block 01 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] Sector 15 | block 02 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
[*] Sector 15 | block 03 : 00 00 00 00 00 00 7F 07 88 69 00 00 00 00 00 00
[#] ========== END DATA ==========
[/sourcecode]

Bien entendu, au vu des attaques présentées ces dernières années sur Mifare Classic il vaut certainement mieux ne pas s’en servir pour des applications trop sensibles 😉