Manipuler les tags ST25 avec la libNFC

Magazine
Marque
Hackable
Numéro
42
Mois de parution
mai 2022
Spécialité(s)


Résumé

Lorsqu'on parle de technologies NFC, on pense généralement aux tags comme les NTAG, les MIFARE Classic ou plus raisonnablement, les DESFire EV1. Mais il existe tout un monde en dehors des produits NXP souvent parfaitement pris en charge par des bibliothèques de haut niveau, et c'est là l'occasion parfaite d'explorer, plus en profondeur, les protocoles et fonctionnalités offertes par la libNFC. Penchons-nous donc sur les tags ST25TA de chez STMicroelectronics...


Body

Nous avons, par le passé, fait connaissance avec l'univers merveilleux des technologies RFID et NFC (voir Hackable 10 [1]), avons manipulé des tags et leur contenu et même testé leur sécurité avec l'outil par excellence dans ce domaine qu'est le Proxmark3 (dans le numéro 30 [2]). Dans nos expérimentations, cependant, nous sommes restés à un niveau d'abstraction assez élevé en utilisant principalement l'excellente libfreefare [3] reposant sur libNFC et prenant en charge une collection sympathique de tags : FeliCa Lite, MIFARE Classic, MIFARE DESFire, MIFARE DESFire EV1, MIFARE Ultralight, MIFARE Ultralight C et NTAG21x.

Remarquez que dans cette liste, seul FeliCa n'est pas un produit NXP (mais Sony) et que nous n'avons aucune trace de ST. Comment faire alors pour communiquer avec ces autres tags lorsqu'un projet le demande ? La réponse est relativement simple, « il faut descendre d'un niveau », ce qui signifie utiliser la libNFC directement, assimiler la documentation et comprendre l'utilisation des protocoles et standards d'usage. Choses que nous allons précisément faire ici en nous penchant sur les tags ST25TA de STMicroelectronics.

st25ta lecteurs-s

Pour nos développements nous aurons besoin d'un périphérique RFID/NFC comme l'un de ceux présentés ici. De gauche à droite, un ASK/LoGO, un SCL3711 très compact et un très populaire ACR122U.

1. Rappel sur les types de tags NFC

Le NFC Forum est l'association ou consortium international, initialement créé par Sony, Nokia et Philips (maintenant NXP) en 2004 afin de faire la promotion du NFC et d'établir les standards qui dictent l'implémentation et l'utilisation de cette technologie. Le NFC Forum définit cinq types de tags tels que détaillés dans le tableau suivant :

 

Type 1

Type 2

Type 3

Type 4

Type 5

UID

4 ou 7 octets

4 ou 7 octets

8 octets

7 octets

8 octets

Protocole de transmission

ISO 14443A

ISO 14443A

ISO 18092 / JIS X 6319

ISO 14443A+B

ISO 15693

Taille mémoire (octets)

96 à 2K

64 à 2K

jusqu’à 1M

jusqu’à 64K

jusqu’à 64K

Bits OTP

48

32

-

-

-

Anticollision

non

oui

oui

oui

oui

Vitesse de transmission

106 kbit/s

106 kbit/s

212 kbit/s, 424 kbit/s

106 kbit/s, 212 kbit/s, 424 kbit/s

26,48 kbit/s

Exemple de tag

Topaz

MIFARE Ultralight

FeliCa Lite

MIFARE DESFire EV1

ICODE SLIx

Remarque

Usage spécifique

Peu cher, très courant

Marché asiatique

Courant pour les applications nécessitant plus de mémoire

Détection d’effraction, grande distance de lecture

Notez l'absence de MIFARE Classic qui est une catégorie à part, car ne correspondant à aucun des cinq types standardisés. Techniquement, un MIFARE Classic n'est donc pas un tag NFC, même s'il s'agit encore aujourd'hui du tag que l'on rencontre malheureusement le plus couramment (malgré ses problèmes de sécurité).

En plus de ces spécifications, et de bien d'autres, le NFC Forum a également standardisé le format d'échange de données, ou NDEF (pour NFC Data Exchange Format) structurant la manière dont sont organisées et présentées les données dans un tag, ou dans les échanges avec un tag. Avec NDEF, les données sont stockées sous forme d'enregistrements, comportant un type (URI, texte, type MIME, etc.) et une charge utile (l'information elle-même). Ici encore, le standard est clair, un tag ne contenant pas des données NDEF n'est techniquement pas un tag NFC.

st25ta jeu1-s

ST met à disposition des lots de tags permettant d'évaluer les solutions et de procéder aux tests. Voici le ST25-TAG-BAG-UB regroupant plusieurs modèles de la gamme ST25 sous différentes formes (21 pièces).

2. ST25

L'écosystème NFC/RFID chez STMicroelectronics est assez riche et se divise en quatre gammes ou séries de tags :

  • Série ST25TV : tags NFC type 5 généralement utilisés pour les applications commerciales ou de consommation (livres, tickets, produits médicaux, etc.), dispositif anti-contrefaçon, authentification de produit, etc. Ces tags proposent également des mécanismes « tamper detect » fonctionnant comme des étiquettes de garantie, ainsi que l'Augmented NDEF permettant de dynamiquement actualiser les données NDEF.
  • Série ST25TN : tags NFC type 2, économiques, proposant jusqu'à 208 octets de stockage, principalement destinés à l'étiquetage de produits (vêtements, équipement de sport, alcool, etc.). Ces tags proposent également l'Augmented NDEF pour générer dynamiquement du contenu NDEF.
  • Série ST25TB : tags RFID ISO14443-2 type B avec de 512 à 4096 bits d'EEPROM pour les titres de transport ou la billetterie (spectacles, salon, etc.).
  • Série ST25TA : tags NFC type 4 utilisant ISO 14443A et permettant un stockage NDEF jusqu'à 8 Ko pour des usages génériques allant de l'étiquetage aux cartes de visite NFC en passant par le pairing Blutooth, etc. Cette gamme est anciennement connue sous le nom SRTAG. À noter que certains de ces tags/puces supportent également une sortie programmable (GPO) permettant de les interfacer avec un microcontrôleur.

Nos expérimentations ici concerneront les ST25TA qui sont adaptés à un usage versatile tout en offrant des capacités de stockage intéressantes, et donc se rapprochant le plus des produits pris en charge par la libfreefare. Pour procéder à nos développements et nos essais, rien de plus simple puisque non seulement ces tags utilisent un protocole de transmission ISO 14443A parfaitement pris en charge par les lecteurs les plus courants (ACR122U, ASK/LoGO et SCL3711), mais sont disponibles à la vente sous la forme de lots d'échantillons chez des détaillants comme Mouser par exemple.

Pour cet article, j'ai donc jeté mon dévolu sur trois produits :

  • ST25-TAG-BAG-UB : un lot de 21 tags à différents formats (autocollant, carte, étiquette de garantie, etc.) couvrant les séries ST25TA et ST25TV (référence « 511-ST25-TAG-BAG-UB » chez Mouser pour ~20 €).
  • ST25-TAG-BAG-A : un lot de 6 tags ST25TA de différentes formes collés sur un support cartonné (référence « 511-ST25-TAG-BAG-A » pour ~3,50 €).
  • CLOUD-ST25TA02KB : un tag ST25TA02KB-P de démonstration prenant la forme d'un amusant circuit imprimé à l'apparence d'un nuage, permettant d'évaluer la fonctionnalité GPO (référence « 511-CLOUD-ST25TA02KB » pour ~4,50 €). On remarquera que, pour des gens du marketing, une sortie à connecter à un microcontrôleur suffit à nous basculer dans le monde du « cloud ». On vit une époque formidable ! Messieurs les ingénieurs de ST, sachez que je compatis...

La série ST25TA se compose de 10 produits, dont 6 sont « non recommandés pour de nouveaux designs », ce qui ne laisse que 4 modèles :

  • ST25TA02KB-D : EEPROM 2 kbit avec GPO (General Purpose digital Output) à collecteur ouvert nécessitant donc une résistance de rappel.
  • ST25TA02KB-P : EEPROM 2 kbit avec GPO avec buffer/driver CMOS (nécessite de fournir une tension de référence, mais sans courant consommé). C'est le composant équipant le PCB « nuage ».
  • ST25TA16K : EEPROM 16 kbit (2 Ko).
  • ST25TA64K : EEPROM 64 kbit (8 Ko).

Les quatre modèles proposent, bien entendu, un stockage NDEF ainsi qu'une protection d'accès aux données (lecture et/ou écriture) par mot de passe 128 bits. Un mécanisme de signature électronique est également disponible sous la désignation « TruST25 », mais la note d'application (AN5101) n'est pas disponible publiquement et nécessite la signature d'un accord de non-divulgation (NDA).

Je ne détaillerai pas dans cette présentation l'architecture et l'organisation mémoire des ST25TA. Ceci est parfaitement documenté en détail dans les datasheets ([4] [5] [6]), qui sont précisément faites pour cela. Nous verrons plutôt ceci au fur et à mesure de notre prise en main de la libNFC dans la suite de l'article.

st25ta jeu2-s

Le lot ST25-TAG-BAG-A est plus modeste et ne regroupe que 6 tags, mais il s'agit exclusivement de ST25TA dont il est question dans cet article. Très économique pour rapidement expérimenter, ce « bag » présente les tags déjà collés à un support cartonné, ce qui est relativement peu pratique.

3. Phase 1 : établir le contact

Nous allons utiliser la libNFC en version 1.8.0 (la plus récente stable à cette date), normalement disponible pour toutes les distributions GNU/Linux. Étant donné que notre cadre rédactionnel est celui des systèmes embarqués et du développement bas niveau, nous ne traiterons que de ce système d'exploitation, même si la libNFC est également utilisable sous Windows et/ou macOS.

Si vous utilisez une distribution basée sur Debian (Ubuntu, Raspberry Pi OS, Raspbian, Armbian, etc.), vous aurez besoin du minimum pour développer (Make, GCC, etc.), des paquets de développement pour la libUSB (libusb-dev) et bien entendu, de ceux pour la libNFC (libnfc-dev) elle-même. Je pars ici du principe que votre lecteur RFID/NFC est correctement configuré et pris en charge, et qu'il fonctionne sans problème. Ainsi, un simple nfc-poll (paquet libnfc-examples) doit vous afficher effectivement le minimum d'informations sur le tag présenté (ATQA, UID, ATS, etc.) avant d'entamer vos expérimentations.

Le paquet libnfc-dev intégrant un profil pkg-config, la compilation de notre code pourra se faire très simplement avec quelque chose comme gcc -O2 -Wall `pkg-config --cflags --libs libnfc` -o st25 source.c ou, plus raisonnablement, en écrivant rapidement un Makefile sur cette base.

La première étape pour manipuler nos tags ST25TA consiste à initialiser le framework, accéder au périphérique, sélectionner le tag et obtenir une réponse de sa part. La libNFC maintient une structure interne de fonctionnement formant un contexte qu'il faut initialiser avant toute opération. De plus, une autre structure sert de représentation pour le (ou les) périphérique(s) NFC que nous comptons utiliser. Ces deux éléments sont déclarés en variable globale :

nfc_context *context;
nfc_device *pnd;

Cette approche nous permet d'implémenter une fonction utilitaire chargée de faire le ménage en cas d'erreur :

void failquit()
{
  if(pnd) nfc_close(pnd);
  if(context) nfc_exit(context);
  exit(EXIT_SUCCESS);
}

Également pour nous faciliter la vie et rendre le code plus facile à lire, nous ajoutons une fonction permettant d'afficher une série de uint8_t (unsigned char) en hexadécimal :

void print_hex(uint8_t *pbtData, size_t szBytes)
{
  size_t szPos;
  for(szPos = 0; szPos < szBytes; szPos++) {
    printf("%02X", pbtData[szPos]);
  }
}

Ceci fait, nous pouvons enfin nous attaquer au sujet qui nous intéresse, et donc au main() où notre première tâche sera d'initialiser le contexte de la libNFC (oui, mes sorties seront en anglais, et elles le sont toujours, dans l'éventualité que le code devienne quelque chose de plus sérieux et diffusable) :

int main(int argc, const char *argv[])
{
  nfc_init(&context);
  if(context == NULL) {
    printf("Unable to init libnfc (malloc)\n");
    exit(EXIT_FAILURE);
  }

Nous pouvons maintenant utiliser la bibliothèque et commencer par nous connecter au lecteur NFC/RFID avec :

  pnd = nfc_open(context, NULL);
  
  if(pnd == NULL) {
    fprintf(stderr, "Unable to open NFC device!\n");
    exit(EXIT_FAILURE);
  }

nfc_open(), utilisé de cette façon, ouvrira le premier périphérique disponible, qui peut être celui spécifié par la variable d'environnement LIBNFC_DEFAULT_DEVICE, le premier listé dans /etc/nfc/libnfc.conf ou /etc/nfc/devices.d/*, ou encore tout simplement le premier automatiquement détecté. Une fois pnd nous permettant d'accéder au périphérique, nous l'ouvrons en tant qu'initiateur (par opposition à « cible » pour l'émulation de tags) :

  if(nfc_initiator_init(pnd) < 0) {
    nfc_perror(pnd, "nfc_initiator_init");
    exit(EXIT_FAILURE);
  }
  printf("NFC reader: %s opened\n", nfc_device_get_name(pnd));

Nous voici fin prêts pour nous occuper de ce qui se trouve potentiellement posé sur le lecteur, mais devons auparavant spécifier le type de modulation et de débit que nous souhaitons utiliser, ainsi que déclarer une nouvelle variable qui représentera notre tag dans la suite du code :

  const nfc_modulation mod = {
    .nmt = NMT_ISO14443A,
    .nbr = NBR_106
  };
 
  nfc_target nt;

NMT_ISO14443A et NBR_106 sont spécifiés dans nfc-types.h et correspondent respectivement à la modulation et au débit correspondants au tag que nous cherchons, nous pouvons ensuite sélectionner le premier tag présent (normalement le seul), avec :

  if(nfc_initiator_select_passive_target(pnd, mod, NULL, 0, &nt) > 0) {
    printf("ISO14443A tag found. UID: ");
    print_hex(nt.nti.nai.abtUid, nt.nti.nai.szUidLen);
    printf("\n");
  } else {
    fprintf(stderr, "No ISO14443A tag found!\n");
    failquit();
  }

nfc_initiator_select_passive_target() retournera le nombre de cibles sélectionnées ou une valeur négative en cas d'échec, mais surtout peuplera notre structure nt que nous pourrons ensuite analyser pour, comme ici, obtenir l'UID du tag. Notre cible restera sélectionnée jusqu'à utilisation de nfc_initiator_deselect_target() ou la fin de notre programme qui, précisément, se résume à :

  // Close NFC device
  nfc_close(pnd);
  // Release the context
  nfc_exit(context);
  exit(EXIT_SUCCESS);
}

L'exécution du présent exemple, après compilation, devrait vous afficher quelque chose comme :

$ ./st25ta_read
NFC reader: ASK / LoGO opened
  ISO14443A tag found. UID: 02C4004E3771A4

Nous savons maintenant nous adresser à notre tag, via la libNFC et le lecteur USB (ici ASK/LoGO), et le sélectionner pour ouvrir un canal de communication pour de futurs échanges. Il est temps de passer aux choses sérieuses et de gentiment (mais fermement) le questionner.

st25ta detecteur-s

Le lot de tags ST25-TAG-BAG-UB inclut un détecteur de champ RFID (« NFC Field Detector ») qui s'illumine à proximité d'un lecteur actif. C'est également un tag ST25TV02K qui n'est malheureusement pas compatible avec un lecteur comme l'ACR122U ne supportant pas ISO 15693.

4. Phase 2 : apprendre à parler APDU

Oublions un instant qu'il s'agit là de RFID ou même d'une communication sans contact. Oublions même la libNFC puisque celle-ci n'est qu'un intermédiaire entre nous et le tag, au même titre que le lecteur ou la liaison USB. Tout ceci fonctionne exactement comme un protocole réseau, par encapsulation, au travers de couches successives. Ce qui nous intéresse ici cependant, c'est l'échange de données entre nous (notre code) et le tag. Le protocole utilisé à ce niveau est celui décrit par le standard ISO/IEC 7816-4 et repose sur l'utilisation d'APDU.

L'APDU ou Application Protocol Data Unit est l'unité de communication utilisée dans le domaine des smartcards (et non uniquement des tags RFID/NFC), exactement comme les trames, datagrammes et paquets le sont dans celui du réseau. Il existe deux types d'APDU, ceux de commande ou C-APDU, et ceux de réponse ou R-APDU. Un C-APDU se compose ainsi :

  • CLA (1 octet) : la classe ou type d'instructions utilisé (standard, propriétaire, etc.) ;
  • INS (1 octet) : le code d'instruction (sélection de fichiers, d'application, lecture, etc.) ;
  • P1 P2 (2 octets) : les paramètres de l'instruction ;
  • Lc (0, 1 ou 3 octets) : encode la longueur en octets des données envoyées. Ce champ n'est pas la taille en octets, mais un encodage de cette valeur qui est nommée Nc ;
  • DATA (variable) : les données à envoyer (s'il y en a) ;
  • Le (0, 1, 2 ou 3 octets) : encode la longueur maximum en octets des données attendues en retour. À quelques nuances près, le même mécanisme que pour Lc est utilisé et la taille est désignée par Ne.

Un R-APDU est sensiblement plus simple :

  • DATA (variable) : les données de la réponse (s'il y en a) ;
  • SW1 SW2 (2 octets) : le code d'état (ou de réponse) de la commande (0x90 0x00 pour « commande exécutée » par exemple).

Pour utiliser ce mécanisme afin de procéder à des échanges avec le tag, il nous suffit donc de lui envoyer des C-APDU véhiculant des ordres et d'attendre des R-APDU, contenant soit une réponse (code d'état), soit les données attendues + le code d'état. Ces APDU sont majoritairement standardisés et, dans le cas des ST25TA, se divisent en trois catégories :

  • le jeu de commandes NFC Forum Type 4 Tag permettant la sélection, la lecture et l'écriture (classe 0x00) ;
  • celui du standard ISO/IEC 7816-4 gérant les commandes dépendantes du contrôle d'accès aux données, dont l'authentification (classe 0x00 également) ;
  • et celui propriétaire de ST (classe 0xa2) fournissant entre autres une commande de lecture alternative, ainsi que d'autres, sortant du cadre du standard NFC.

Nous allons utiliser ces commandes, décrites relativement clairement dans la datasheet des ST25TA, dans un instant. Mais avant cela, il est important de comprendre l'organisation de la mémoire et la structure même d'un tag NFC. Le terme « smartcard » n'est pas utilisé par hasard et un tag n'est pas une simple EEPROM sans contact. Il se compose d'une ou plusieurs applications, fournissant l'accès à un ou plusieurs fichiers. Dans le cas des ST25TA, il n'y a qu'une application, fournissant un unique fichier NDEF (cf. note), mais les MIFARE DESFire EV1, par exemple, peuvent contenir jusqu'à 28 applications gérant chacune 32 fichiers.

Il semble y avoir une omission dans la datasheet du ST25TA64K, puisque l'application Android « ST25 NFC Tap » [7] permet de reconfigurer le tag de façon à diviser sa mémoire jusqu'à 8 zones (area) de tailles égales, et donc supporter jusqu'à 8 fichiers NDEF (en mettant à jour le fichier CC en conséquence).

La datasheet (« DocID027764 Rev 4 » - 21/02/2017) n'en fait étrangement aucunement mention, si ce n'est en précisant que l'octet à l'offset 7 du fichier système indique le « NDEF File number » (number dans le sens « nombre » et non « numéro ») en spécifiant « RFU » pour « Reserved for Future Use » et un accès en écriture à « none », alors que l'application Android change effectivement cette valeur (avec un simple 0x00 0xd6, UpdateBinary).

La datasheet serait-elle périmée ou nous cache-t-on des choses ?

Ainsi pour accéder aux données, il est nécessaire de sélectionner l'application qui gère le fichier, puis le fichier dans lequel nous souhaitons lire ou écrire. Chacune de ces étapes utilise des commandes et réponses transmises sous forme d'APDU. Applications et fichiers sont désignés par un identifiant (ID). L'application « NDEF Tag » d'un ST25TA possède l'ID 0xD2760000850101 et gère trois fichiers :

  • 0xE101 : le fichier système ST contenant diverses informations dont l'UID du tag, le volume de mémoire ou le code identifiant le modèle. Dans les cas des tags ST25TA02KB-P et D, c'est également là que se trouvent la configuration GPO ainsi qu'un compteur 20 bits configurable.
  • 0xE103 : Capability Container (CC) qui est un fichier standardisé fonctionnant comme un README pour les applicatifs NFC en fournissant la taille maximum du fichier NDEF, son ID, la taille maximum des données dans les APDU, ou encore les permissions sur le fichier.
  • 0x0001 : le fichier NDEF lui-même, composé de deux octets spécifiant sa taille suivie d'un message NDEF composé d'un ou plusieurs enregistrements, eux-mêmes composés d'un en-tête et d'une charge utile. Le format NDEF sort du cadre du présent article, mais nous évoquerons brièvement une solution pratique en conclusion. Notez simplement qu'il y a une distinction entre le fichier NDEF débutant par deux octets spécifiant sa taille et le message NDEF qui se trouve juste passé ces octets, et qui est constitué d'enregistrements NDEF. Comprenez bien que tout ce petit monde n'est rien d'autre qu'une série d'octets les uns à la suite des autres en mémoire et que c'est à la charge du développeur d'interpréter ces informations. Ce n'est pas le problème du tag si vous lisez au-delà de l'enregistrement que vous visez et dans bien des cas, vous n'aurez aucune erreur en retour.

La datasheet ST précise clairement la manière « officielle » de lire le message NDEF :

  • sélectionner l'application « NDEF Tag » ;
  • sélectionner le fichier CC ;
  • lire le fichier CC ;
  • vérifier les permissions ;
  • le cas échéant, fournir le mot de passe de lecture ;
  • sélectionner le fichier NDEF dont l'ID a été obtenu via le fichier CC ;
  • lire le contenu du fichier NDEF.

Oui, nous connaissons déjà l'ID du fichier NDEF puisque celui-ci est spécifié à maintes reprises dans la datasheet, mais la norme impose qu'il soit nécessaire d'utiliser le Capability Container pour l'obtenir. Ceci est une procédure standard qui assure la compatibilité et fait que votre smartphone est capable d'afficher le contenu NDEF d'un tag ST25TA, mais également de n'importe quel autre produit compatible NFC.

st25ta cloud-s

Discussion hypothétique marketing/ingénieur chez ST : « C'est un ST25TA02KB-P ? », « Oui », « Et ça a une sortie qu'on branche à un microcontrôleur », « Oui », « Les microcontrôleurs ont des interfaces réseau, hein ? », « Oui, parfois. Pourquoi ? », « Et on peut les connecter au Web », « Hmmm oui, par exemple, mais... », « Donc, c'est du cloud ! », « Attends, quoi ?! », « Hey Roger ! Les ST25TA02KB-P, c'est du cloud ! On va faire un circuit en forme de nuage ! », *facepalm*...

5. Phase 3 : pouvoir communiquer avec le tag

Je pense que nous avons à présent assez parlé de théorie et il est grand temps de passer à la pratique. Notre code, pour l'heure, ne fait pas grand-chose, mais nous avons engagé les pourparlers et sommes donc prêts pour échanger des APDU. La libNFC met à notre disposition une fonction nfc_initiator_transceive_bytes() qui, comme son nom l'indique, permet de « transceiver » des octets ou, en d'autres termes, envoyer des données à notre cible et en recevoir en retour (comme transmit et receive).

La fonction prend en argument un pointeur vers le périphérique (pnd), un pointeur vers un tableau de uint8_t (unsigned char), un entier (size_t) en précisant la taille, un pointeur vers un tableau de uint8_t destiné à recevoir la réponse, un entier (size_t) spécifiant la taille maximum des données à mettre dans ce tableau et enfin, un entier spécifiant un délai d'expiration en millisecondes (-1 pour utiliser la valeur par défaut). Cette fonction retourne le nombre d'octets reçus en réponse en cas de réussite ou une valeur négative en cas d'échec (correspondant à un code d'erreur libNFC).

Plutôt que d'utiliser directement nfc_initiator_transceive_bytes(), nous pouvons créer une fonction utilitaire prenant les mêmes arguments, mais affichant, en prime, les APDU à l'écran pour nous faciliter la mise au point du code :

int CardTransmit(nfc_device *pnd,
                 uint8_t *capdu,
                 size_t capdulen,
                 uint8_t *rapdu,
                 size_t *rapdulen)
{
  int res;
  size_t szPos;
 
  printf("=> ");
 
  for(szPos=0; szPos<capdulen; szPos++) {
    printf("%02x ", capdu[szPos]);
  }
  printf("\n");
 
  if((res = nfc_initiator_transceive_bytes(
         pnd, capdu, capdulen, rapdu, *rapdulen, -1)) < 0) {
    fprintf(stderr, "transceive error! %s\n", nfc_strerror(pnd));
    return(-1);
  }
 
  printf("<= ");
  for(szPos = 0; szPos < res; szPos++) {
    printf("%02x ", rapdu[szPos]);
  }
  printf("\n");
 
  *rapdulen = (size_t)res;
 
  return(0);
}

La petite subtilité ici réside dans le fait d'utiliser la valeur de retour (res) pour mettre à jour l'un des paramètres passés en argument. Ainsi, nous passons à notre fonction un pointeur vers le tableau destiné à accueillir le R-APDU ainsi qu'un pointeur vers la valeur en décrivant la taille (rapdulen), et nous obtenons en retour, dans la même variable (*rapdulen), la quantité d'octets constituant effectivement la réponse.

Cette fonction impose de préparer des tableaux pour procéder à l'échange alors que, dans bien des cas, et en particulier en phase d'expérimentation, il peut être bien plus intéressant de passer des C-APDU arbitraires de façon plus souple. Nous implémentons donc une seconde déclinaison de cette fonction ainsi :

#define RAPDUMAXSZ 512
#define CAPDUMAXSZ 512
 
int strCardTransmit(nfc_device *pnd,
                    const char *line,
                    uint8_t *rapdu,
                    size_t *rapdulen)
{
  int res;
  size_t szPos;
  uint8_t *capdu = NULL;
  size_t capdulen = 0;
  *rapdulen = RAPDUMAXSZ;
 
  uint32_t temp;
  int indx = 0;
  char buf[5] = {0};
 
  // vérifications
  if(!strlen(line) || strlen(line) % 2 ||
      strlen(line) > CAPDUMAXSZ*2)
    return(-1);
 
  // allocation buffer
  if(!(capdu = malloc(strlen(line)/2))) {
    fprintf(stderr, "malloc error: %s\n", strerror(errno));
    nfc_close(pnd);
    nfc_exit(context);
    exit(EXIT_FAILURE);
  }
 
  // conversion
  while (line[indx]) {
    if(line[indx] == '\t' || line[indx] == ' ') {
      indx++;
      continue;
    }
 
    if(isxdigit(line[indx])) {
      buf[strlen(buf) + 1] = 0x00;
      buf[strlen(buf)] = line[indx];
    } else {
      // pas de l'hexa
      free(capdu);
      return(-1);
    }
 
    if(strlen(buf) >= 2) {
      sscanf(buf, "%x", &temp);
      capdu[capdulen] = (uint8_t)(temp & 0xff);
      *buf = 0;
      capdulen++;
    }
    indx++;
  }
 
  // erreur si octet hex incomplet
  if(strlen(buf) > 0) {
    free(capdu);
    return(-1);
  }
 
[...]

Le reste sera identique à CardTransmit(), mais nous intégrons directement la conversion d'une chaîne de caractères (*line) représentant une suite de valeurs hexa en un tableau de uint8_t. Ceci nous permet de très facilement envoyer des C-APDU, spécifiés sous une forme humainement intelligible, et ce, sans nous soucier de leur taille ou encore de devoir à chaque fois préciser la taille du tableau pour le R-APDU (qui sera déclaré directement en utilisant la valeur RAPDUMAXSZ). Nous verrons plus loin que la taille des données dans les APDU est bornée et qu'il nous faudra prendre en compte cette information par ailleurs, mais ces deux fonctions sont prévues pour être génériques et réutilisables avec d'autres tags, voire d'autres applications de type RFID/NFC (voir l'article sur l'exploration de l'écran e-paper NFC de WaveShare [8]).

st25ta proxmark3RDV-s

Le Proxmark 3 RDV4 est un outil incontournable dès lors que l'on souhaite explorer sérieusement les technologies RFID/NFC. C'est à la fois un outil de développement, une plateforme d'expérimentation, un analyseur de protocole et un équipement de pentest. Le tout avec une communauté de développeurs très active et passionnée.

6. Phase 4 : lire le tag

Nous voici maintenant équipés pour « parler APDU » avec notre tag et nous pouvons, juste après l'affichage de l'UID, commencer à nous adresser à lui. Les APDU sont directement spécifiés dans la datasheet, à commencer par la sélection de l'application « NDEF Tag ». Il s'agit d'une commande de la classe 0x00, identifiée par l'instruction 0xa4 et ayant pour paramètres 0x04 0x00. La taille des données transmises est de 7 octets et celles-ci se résument tout simplement à l'ID de l'application : 0xD2760000850101. Enfin, la taille maximum des données attendues est à 0x00 et donc 256 (voir le standard ISO 7816-4, Section 5 « Basic Organizations », si vous en avez le courage et quelque 200 € à dépenser [9]).

Pour sélectionner l'application, nous devons donc utiliser :

  // Select App 0xD2760000850101
  if(strCardTransmit(pnd,
                     "00a4 0400 07 d2760000850101 00",
                     resp, &respsz) < 0)
    fprintf(stderr, "CardTransmit error!\n");
 
  if(respsz < 2 || resp[respsz-2] != 0x90 ||
     resp[respsz-1] != 0x00) {
    fprintf(stderr, "Application select. Bad response !\n");
    failquit();
  }

Notre C-APDU est "00a4 0400 07 d2760000850101 00" et l'utilisation de notre fonction strCardTransmit() nous permet de garder un semblant de lisibilité. Pour nous assurer que la réponse est valide, nous vérifions sa taille puis le fait qu'elle soit effectivement 0x9000, « Command completed ». Ceci sera fait après chaque utilisation de strCardTransmit() ou CardTransmit(), mais ne sera pas repris dans le code qui suit. Nous pouvons ensuite enchaîner sur la sélection du fichier CC et sa lecture :

  if(strCardTransmit(pnd,
                     "00a4 000c 02 e103",
                     resp, &respsz) < 0)
    fprintf(stderr, "CardTransmit error!\n");
 
  if(strCardTransmit(pnd,
                     "00b0 0000 0f",
                     resp, &respsz) < 0)
    fprintf(stderr, "CardTransmit error!\n");
 

Remarquez que la première commande utilise le même couple classe/instruction puisqu'il s'agit également d'une sélection, mais les paramètres sont différents puisque cela concerne un fichier et ce dernier est désigné par son ID, 0xe103, en guise de données.

La lecture utilise l'instruction 0xb0 de la même classe où les paramètres P1 et P2 précisent l'offset à partir duquel la lecture doit commencer par rapport au début du fichier (ici 0) et où 0x0f correspond à Le, le nombre d'octets à lire et à nous retourner.

L'exécution de ce code nous affiche à l'écran :

=> 00 a4 04 00 07 d2 76 00 00 85 01 01 00
<= 90 00
=> 00 a4 00 0c 02 e1 03
<= 90 00
=> 00 b0 00 00 0f
<= 00 0f 20 00 f6 00 f6 04 06 00 01 20 00
00 00 90 00

Cette dernière réponse, moins « 90 00 », constitue le contenu du fichier CC qui, selon la datasheet, nous apprend que nous pouvons lire et écrire des données par paquets d'un maximum de 246 octets (0x00f6), que le fichier NDEF possède l'ID 0001 et que sa taille maximum est de 8192 octets (0x2000). On pourra éventuellement copier ce contenu dans un autre tableau qu'on castera sur une structure comme :

struct st25taCC_t {
     uint8_t size[2];
     uint8_t vmapping;
     uint8_t nbread[2];
     uint8_t nbwrite[2];
     uint8_t tfield;
     uint8_t vfield;
     uint8_t id[2];
     uint8_t maxsize[2];
     uint8_t readaccess;
     uint8_t writeaccess;
};

Gardons cependant cet article le plus concis possible afin d'arriver à notre objectif de lecture rapidement. À l'offset 0x09, nous trouvons l'ID du fichier, et pouvons l'utiliser pour une nouvelle sélection, en composant un C-APDU de toutes pièces. Mais avant cela, nous devons nous assurer que le fichier est effectivement lisible. Les deux derniers octets des données de la réponse que nous venons de recevoir déterminent les permissions actives sur le fichier NDEF, respectivement en lecture ou en écriture. 0x00 indique que le fichier est accessible sans aucune sécurité. 0x80 fixe l'état à « verrouillé » (locked) signifiant qu'une authentification est nécessaire avant une tentative de lecture ou d'écriture (mais après la sélection).

Comprenez bien que les deux opérations sont totalement distinctes. Nous avons un octet pour la lecture et un autre pour l'écriture, et de la même manière un mot de passe de 128 bits pour la lecture et un autre pour l'écriture. Les deux mots de passe sont par défaut 128 bits (16 octets) à zéro.

Et, enfin, 0xFE pour la lecture et 0xFF pour l'écriture indiquent que le fichier est définitivement verrouillé et de ce fait que les opérations de lecture et/ou d'écriture ne sont pas possibles. Ceci permet, par exemple, de configurer le tag en lecture seule de façon définitive. Cette configuration ne peut être changée que dans un sens et via l'utilisation d'un C-APDU spécifique à ST (classe/instruction 0xA2 0x28). Le verrouillage permanent n'est, de plus, possible que si le fichier est déjà verrouillé (0x80).

Dans notre cas d'exemple, nous nous contenterons de nous assurer que l'octet à l'offset 13 (0x0D) du CC est à bien à 0x00 et, ce faisant, pourrons entamer la lecture du tag. Nous aurons également besoin de faire attention à une autre valeur fournie par le CC à l'offset 0x03, nous indiquant la taille maximum de données pouvant être lues en une fois. Celle-ci est stockée sur deux octets, mais la datasheet nous indique qu'elle est fixée à 0x00F6 et donc vaut 246. Ceci signifie que, quelle que soit la taille du fichier NDEF, nous devrons, dans la majorité des cas, lire le fichier en plusieurs fois.

Avec tous ces éléments, nous pouvons maintenant sélectionner le fichier NDEF :

  // taille de lecture
  uint16_t readsz = (resp[3] << 8) | resp[4];
 
  // NDEF lisible ?
  if(resp[13] != 0) {
    fprintf(stderr, "NDEF file locked!\n");
    failquit();
  }
 
  // C-APDU sélection
  uint8_t selndefapdu[7] =
     { 0x00, 0xa4, 0x00, 0x0c, 0x02, 0x00, 0x00 };
  // ajustement ID
  selndefapdu[5] = resp[9];
  selndefapdu[6] = resp[10];
 
  // sélection
  respsz = RAPDUMAXSZ;
  if(CardTransmit(pnd, selndefapdu, 7, resp, &respsz) < 0)
    fprintf(stderr, "CardTransmit error!\n");

Pour effectivement lire le fichier, nous devons commencer par en récupérer les deux premiers octets afin d'en connaître la taille. Ceci est impératif et va au-delà de la simple économie en termes d'opérations de lecture. Si nous tentons de lire au-delà du fichier, nous obtiendrons une erreur avec l'APDU de lecture standardisée NFC Forum (c'est la seule vérification existante, la lecture d'enregistrements dans le message NDEF n'est pas encadrée par le tag). Si nous souhaitons lire la mémoire directement et arbitrairement, il nous faudrait utiliser le jeu de commandes propriétaires ST (0xa2 0xb0 pour ExtendedReadBinary).

Commençons par récupérer ces deux octets et les convertir en un entier 16 bits que nous utilisons directement pour allouer la mémoire où nous copierons le message NDEF :

  if(strCardTransmit(pnd, "00b0 0000 02", resp, &respsz) < 0)
    fprintf(stderr, "CardTransmit error!\n");
 
  unsigned int bytestoread = (resp[0] << 8) | resp[1];
 
  uint8_t *ndef;
  if(!(ndef = malloc(bytestoread))) {
    fprintf(stderr, "malloc error: %s\n", strerror(errno));
    failquit();
  }

Et nous procédons ensuite à la lecture dans une simple boucle while :

  uint16_t pos = 2;
  uint16_t end = bytestoread+pos;
  uint8_t rndefapdu[5] = { 0x00, 0xb0, 0x00, 0x00, 0x00 };
 
  while(pos < end-1) {
    uint8_t nread = pos+readsz < end ? readsz : end-pos;
 
    // ajustement C-APDU
    rndefapdu[2] = (pos >> 8);
    rndefapdu[3] = pos & 255;
    rndefapdu[4] = nread;
 
    // Lecture
    respsz = RAPDUMAXSZ;
    if(CardTransmit(pnd, rndefapdu, 5, resp, &respsz) < 0)
      fprintf(stderr, "CardTransmit error!\n");
 
    // Vérification
    if(respsz < 2 || resp[respsz-2] != 0x90 ||
       resp[respsz-1] != 0x00) {
      fprintf(stderr, "NDEF file read. Bad response !\n");
      failquit();
    }
 
    // Copie en buffer
    memcpy(ndef+pos-2, resp, nread);
    pos=pos+nread;
  }

Et affichons le contenu légèrement formaté :

  for(int i=0; i < bytestoread; i++) {
    printf("%02x ", ndef[i]);
    if(!((i+1)%40))
      printf("\n");
  }
  printf("\n");
 
  if(ndef)
    free(ndef);

Bien entendu, tout ceci n'est qu'expérimental, mais nous pouvons désormais, en supprimant les autres messages, rediriger la sortir standard de notre code pour créer un fichier, ou simplement copier coller depuis le terminal :

$ ./st25ta_read
91 01 07 54 02 66 72 70
6c 6f 70 11 01 09 54 02
66 72 74 69 74 69 74 69
51 01 17 55 04 63 6f 6e
6e 65 63 74 2e 65 64 2d
64 69 61 6d 6f 6e 64 2e
63 6f 6d

7. Bonus NDEF

Le décodage et l'encodage des données au format NDEF sortent totalement du cadre du présent article et ne sont pas quelque chose qu'il est possible de traiter de manière concise (cf. cette page W3C pour comprendre [10]). Ce qu'il est possible de faire, en revanche, est de tout simplement s'en remettre à un outil tiers comme la libNDEF qui, même si elle n'est pas toute jeune (dernier commit il y a 7 ans), fournit deux outils fort pratiques : ndef-decode et ndef-encode.

La libNDEF est écrite en C++ et possède une dépendance vers Qt Core, ce qui n'est pas forcément très amusant. En revanche, le dépôt GitHub [11] contient les éléments permettant de créer proprement un paquet Debian/Ubuntu/Raspbian relativement simplement. Pour cela, récupérez les sources avec git clone puis pliez-vous simplement d'un dpkg-buildpackage -b pour créer les paquets que vous installerez directement avec dpkg -i. En cas de problème, vérifiez simplement que vous ayez bien installé toutes les dépendances de construction : debhelper (>= 7), libqt4-dev, et qt4-qmake.

Si vous voulez bien faire et étant donné que ces paquets n'installent pas par défaut les deux utilitaires, utilisez dch -i pour ajouter une entrée à debian/changelog, puis éditer le fichier debian/libndef.install pour ajouter une ligne et changer le contenu en :

usr/lib/lib*.so.*
usr/bin/ndef*

Reconstruisez les paquets (libndef et libndef-dev), installez, et vous voici avec le duo ndef-decode/ndef-encode parfaitement et proprement intégrés à votre système et en mesure d'encoder et décoder du NDEF très simplement. Ainsi, en couplant cela avec xxd, il est possible de traiter directement le contenu NDEF sous forme de valeurs hexadécimales :

$ echo "d1010c550173742e636f6d2f73743235" \
| xxd -r -p - | ndef-decode
 
Use stdin as input file
NDEF message is valid and contains 1 NDEF record(s).
NDEF record (1) type name format: NFC Forum well-known type
NDEF record (1) type: U
NDEF record (1) payload (uri): http://www.st.com/st25

et inversement :

$ ndef-encode -u http://www.st.com/st25 | xxd -p -
d1010c550173742e636f6d2f73743235

Vous pouvez également vouloir utiliser la bibliothèque elle-même pour vos développements (libndef-dev) à condition de tolérer la dépendance à Qt et, bien entendu le mélange C/C++...

Conclusion... de l'article

Un grand nombre de points n'ont pas été couverts dans cet article et en particulier l'authentification, la configuration de l'accès aux données, les procédures d'écriture, la configuration GPO (pour les ST25TA02KB-D et ST25TA02KB-P), etc. L'idée ici n'était pas de détailler tout le code de ce qui va très certainement se transformer en un utilitaire de lecture (et/ou d'écriture) de tag ST25TA, mais d'aborder les concepts NFC au plus bas niveau et des APDU en particulier.

Nous avons vu qu'implémenter une telle solution, une fois les principes assimilés, tient en peu de choses et que la très grande majorité des informations est présente dans les datasheets. Prendre en charge un tag ne présente pas de difficulté particulière et ce que nous venons de voir avec les ST25TA est parfaitement transposable à n'importe quel autre produit, pour peu que le constructeur daigne diffuser les informations sans trop de restrictions.

Bien entendu, de la même manière que les caractéristiques spécifiques des MIFARE DESFire EV2 ne sont pas supportées par la libfreefare (uniquement ce qui est compatible avec EV1), il est peu probable qu'une solution pleinement open source puisse voir le jour pour les fonctionnalités spécifiques comme TruST25 ou Augmented NDEF, puisque les informations ne sont distribuées que sous NDA. La concurrence acharnée entre les fabricants explique peut-être cette pratique courante dans le domaine, sans parler du vol de technologie pure et simple de la part d'acteurs peu scrupuleux souvent hors de portée de la législation occidentale. Mais cela reste toutefois extrêmement frustrant d'un point de vue technique, sachant que ces intéressantes fonctionnalités sont à portée de main, mais pourtant inaccessibles...

Références

[1] https://connect.ed-diamond.com/Hackable/hk-010

[2] https://connect.ed-diamond.com/Hackable/hk-030/proxmark-l-incontournable-materiel-pour-tester-la-securite-rfid-et-nfc

[3] https://github.com/nfc-tools/libfreefare

[4] https://www.st.com/resource/en/datasheet/st25ta64k.pdf

[5] https://www.st.com/resource/en/datasheet/st25ta16k.pdf

[6] https://www.st.com/resource/en/datasheet/st25ta02kb-d.pdf

[7] https://play.google.com/store/apps/details?id=com.st.st25nfc

[8] https://connect.ed-diamond.com/Hackable/hk-034/ecran-e-paper-nfc-une-histoire-d-exploration-et-de-code

[9] https://www.iso.org/standard/77180.html

[10] https://w3c.github.io/web-nfc/#the-ndef-record-and-fields

[11] https://github.com/nfc-tools/libndef



Article rédigé par

Par le(s) même(s) auteur(s)

Jouons avec une passerelle ZigBee LIDL

Magazine
Marque
Hackable
Numéro
53
Mois de parution
mars 2024
Spécialité(s)
Résumé

J'aime bien LIDL, on y trouve des produits qu'on n'a pas ailleurs. De délicieuses olives farcies aux amandes, des pistaches décortiquées non salées, de la litière agglomérante pas chère, parfois des Guinness West Indies Porter... et des passerelles domotiques Ethernet/Zigbee à 20 €. Ai-je besoin d'une telle passerelle ? Non. Je n'ai pour l'instant aucun périphérique Zigbee dans mon installation domotique. Ce que j'ai, en revanche, c'est de la curiosité. Celle de savoir ce que renferme ce produit et éventuellement, d'apprendre comment je pourrais en faire un autre usage. C'est parti pour un week-end de chasse au trésor...

Arduino + ESP-IDF + OTA + Secure Boot : le meilleur des deux mondes

Magazine
Marque
Hackable
Numéro
53
Mois de parution
mars 2024
Spécialité(s)
Résumé

Lorsqu'il s'agit d'utiliser un microcontrôleur de la famille ESP32, deux approches sont généralement raisonnablement envisageables. La première consiste à utiliser l'environnement dédié proposé par Espressif, ESP-IDF, et la seconde à reposer sur un niveau d'abstraction supérieur via le framework, et implicitement, l'environnement Arduino. L'une assure une maîtrise totale du développement et l'autre, une plus grande facilité d'implémentation du projet que vous avez en tête. Le coût du choix de l'une ou de l'autre solution est, pour la première, une complexité accrue et pour la seconde, un environnement relativement limité, sinon simpliste. Il existe cependant une voie du milieu qu'il est important de considérer...

Gestion des périphériques USB sous [Free|Net|Open]BSD

Magazine
Marque
GNU/Linux Magazine
Numéro
268
Mois de parution
mars 2024
Spécialité(s)
Résumé

Lorsqu'on fait connaissance avec l'un des systèmes héritiers du BSD d'origine, à savoir FreeBSD, NetBSD ou encore OpenBSD, tout en ayant une certaine expérience de GNU/Linux, il est relativement facile de retrouver ses petits. Certes, depuis quelques années, le système d'init et la gestion des services de GNU/Linux se sont drastiquement écartés des principes propres à la philosophie Unix, jusqu'alors respectés par GNU/Linux. Dans l'ensemble, la transition est relativement aisée, mais il y a cependant un point sur lequel les différences sont telles que quelques explications s'avèrent nécessaires : la gestion des périphériques « hotplug » (USB) et de leurs permissions.

Les derniers articles Premiums

Les derniers articles Premium

Quarkus : applications Java pour conteneurs

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Initié par Red Hat, il y a quelques années le projet Quarkus a pris son envol et en est désormais à sa troisième version majeure. Il propose un cadre d’exécution pour une application de Java radicalement différente, où son exécution ultra optimisée en fait un parfait candidat pour le déploiement sur des conteneurs tels que ceux de Docker ou Podman. Quarkus va même encore plus loin, en permettant de transformer l’application Java en un exécutable natif ! Voici une rapide introduction, par la pratique, à cet incroyable framework, qui nous offrira l’opportunité d’illustrer également sa facilité de prise en main.

De la scytale au bit quantique : l’avenir de la cryptographie

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Imaginez un monde où nos données seraient aussi insaisissables que le célèbre chat de Schrödinger : à la fois sécurisées et non sécurisées jusqu'à ce qu'un cryptographe quantique décide d’y jeter un œil. Cet article nous emmène dans les méandres de la cryptographie quantique, où la physique quantique n'est pas seulement une affaire de laboratoires, mais la clé d'un futur numérique très sécurisé. Entre principes quantiques mystérieux, défis techniques, et applications pratiques, nous allons découvrir comment cette technologie s'apprête à encoder nos données dans une dimension où même les meilleurs cryptographes n’y pourraient rien faire.

Les nouvelles menaces liées à l’intelligence artificielle

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Sommes-nous proches de la singularité technologique ? Peu probable. Même si l’intelligence artificielle a fait un bond ces dernières années (elle est étudiée depuis des dizaines d’années), nous sommes loin d’en perdre le contrôle. Et pourtant, une partie de l’utilisation de l’intelligence artificielle échappe aux analystes. Eh oui ! Comme tout système, elle est utilisée par des acteurs malveillants essayant d’en tirer profit pécuniairement. Cet article met en exergue quelques-unes des applications de l’intelligence artificielle par des acteurs malveillants et décrit succinctement comment parer à leurs attaques.

Les listes de lecture

7 article(s) - ajoutée le 01/07/2020
La SDR permet désormais de toucher du doigt un domaine qui était jusqu'alors inaccessible : la réception et l'interprétation de signaux venus de l'espace. Découvrez ici différentes techniques utilisables, de la plus simple à la plus avancée...
8 article(s) - ajoutée le 01/07/2020
Au-delà de l'aspect nostalgique, le rétrocomputing est l'opportunité unique de renouer avec les concepts de base dans leur plus simple expression. Vous trouverez ici quelques-unes des technologies qui ont fait de l'informatique ce qu'elle est aujourd'hui.
9 article(s) - ajoutée le 01/07/2020
S'initier à la SDR est une activité financièrement très accessible, mais devant l'offre matérielle il est parfois difficile de faire ses premiers pas. Découvrez ici les options à votre disposition et les bases pour aborder cette thématique sereinement.
Voir les 31 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous