📖 Tutoriel : Audit de Cartes à Puce avec Python

Ce guide détaille l'utilisation d'un script Python permettant l'analyse et l'audit de cartes à puce (EMV, NFC, MIFARE). Nous allons couvrir l'installation des dépendances, l'explication du code et les implications en matière d'éthique et de sécurité.

1️⃣ Introduction et Éthique

Ce script permet d'extraire des informations des cartes à puce via un lecteur connecté à un ordinateur. Il est destiné à des fins éducatives, de cybersécurité et de tests d'intrusion sur des systèmes sous contrôle légal.

⚠️ Avertissement : L'utilisation non autorisée de cet outil sur des cartes qui ne vous appartiennent pas est illégale. Ce guide est publié uniquement à titre pédagogique et ne doit être utilisé que dans un cadre légal (audit interne, pentesting agréé, recherche).

2️⃣ Installation et Prérequis

📌 2.1 Outils Nécessaires

Avant de commencer, vous aurez besoin des éléments suivants :

📌 2.2 Installation des Dépendances

Installez les bibliothèques nécessaires avec :

pip install pyscard pycryptodome

3️⃣ Explication du Code

Nous allons maintenant examiner en détail le script Python.

📜 Le Script Complet


import sys
import json
import csv
from smartcard.System import readers
from smartcard.util import toHexString
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
import logging
import gettext

# Initialisation du logging
logging.basicConfig(filename='audit.log', level=logging.INFO,
                    format='%(asctime)s:%(levelname)s:%(message)s')

# Internationalisation et localisation
gettext.bindtextdomain('audit_tool', 'locale')
gettext.textdomain('audit_tool')
_ = gettext.gettext

# Base de données d'ATR (EMV, MIFARE, NFC)
KNOWN_ATRS = {
    "3B 24 00 80 72 94 43": "MPCOS-3DES 64K / EMV Filter (Gemplus)",
    "3B 2A 00 80 65 A0 58 04 01 62 72 D6 43": "Gemplus GemCombiXplore MPCOS Pro",
    "3B 2A 00 80 65 A2 01 .. .. .. 72 D6 43": "Gemplus MPCOS EMV 4 Byte sectors",
    "3B 2A 00 80 65 A2 01 00 00 00 72 D6 43": "MPCOS_EMV_4B",
    "3B 2A 00 80 65 A2 01 01 01 3C 72 D6 43": "Estonian bank 'SEB' (VISA Electron)",
    "3B 02 10 50": "Visa (Bank)",
    "3B 02 15 40": "MasterCard (Bank)",
    "3B 39 13 00 80 64 20 04 01 83 82 90 00": "Credit Suisse CSX Mastercard Debit",
    "3B 02 14 50": "Schlumberger Multiflex 3k",
    "3B 9F 96 80 1F C7 80 31 A0 73 BE 21 13 66": "NXP MIFARE DESFire EV1",
    "3B 8F 80 01 80 4F 0C A0 00 00 03 06 03 00 01 00 00 00 00 6A": "NXP MIFARE Classic 1K",
    "3B 80 80 01 01": "NXP MIFARE Ultralight",
    "3B 88 80 01 00 00 00 00 00 77": "NXP JCOP 21",
    "3B 81 80 01 80 80": "NXP JCOP 31",
    "3B DA 18 FF 81 B1 FE 75 1F 03 00 31 C5 73 C0 01 60": "Gemalto IDPrime MD 830",
    # Ajoutez d'autres ATR ici
}

# Commandes APDU (EMV, NFC, MIFARE, ISO)
APDU_COMMANDS = [
    {"name": _(\"Select EMV Application\"), "command": [0x00, 0xA4, 0x04, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x03, 0x10, 0x10, 0x00]},
    {"name": _(\"Get Processing Options\"), "command": [0x80, 0xA8, 0x00, 0x00, 0x02, 0x83, 0x00, 0x00]},
    {"name": _(\"Read Record 1\"), "command": [0x00, 0xB2, 0x01, 0x0C, 0x00]},
    {"name": _(\"Read Binary\"), "command": [0x00, 0xB0, 0x00, 0x00, 0x10]},
    {"name": _(\"Authenticate MIFARE\"), "command": [0xFF, 0x86, 0x00, 0x00, 0x05, 0x01, 0x00, 0x00, 0x60, 0x00]},
    {"name": _(\"NDEF Read\"), "command": [0xFF, 0xCA, 0x00, 0x00, 0x00]},
    {"name": _(\"Select File\"), "command": [0x00, 0xA4, 0x00, 0x00, 0x02, 0x3F, 0x00]},
    {"name": _(\"Verify PIN\"), "command": [0x00, 0x20, 0x00, 0x01, 0x08]},
    {"name": _(\"Change Reference Data\"), "command": [0x00, 0x24, 0x00, 0x01, 0x08]},
    {"name": _(\"Put Data\"), "command": [0x00, 0xDA, 0x00, 0x00, 0x10]},
    # Ajoutez plus de commandes ici
]

# Lecture de l'ATR
def get_atr():
    """
    Connecte au lecteur de carte, détecte et retourne l'ATR.
    """
    try:
        r = readers()
        if not r:
            print(_("Aucun lecteur de carte détecté."))
            sys.exit(1)
        print(_("Lecteurs détectés :"))
        for i, reader in enumerate(r):
            print(f"{i + 1}: {reader}")
        choix = int(input("Choisissez un lecteur (numéro) : ")) - 1
        reader = r[choix]
        connection = reader.createConnection()
        connection.connect()
        atr = connection.getATR()
        atr_hex = toHexString(atr)
        print(_("ATR détecté : "), atr_hex)
        return atr_hex, connection
    except Exception as e:
        logging.error(f"Erreur lors de la lecture de l'ATR : {e}")
        sys.exit(1)

# Analyse de l'ATR
def analyze_atr(atr_hex):
    """
    Analyse un ATR et retourne la description correspondante.
    """
    for known_atr, description in KNOWN_ATRS.items():
        if matches_atr(atr_hex, known_atr):
            print(_("Correspondance trouvée : "), description)
            return description
    print(_("ATR inconnu ou non référencé."))
    return _("Inconnu")

# Correspondance avec les wildcards
def matches_atr(atr_hex, known_atr):
    """
    Compare un ATR détecté avec un ATR connu, prenant en charge les wildcards.
    """
    atr_parts = atr_hex.split()
    known_parts = known_atr.split()
    if len(atr_parts) != len(known_parts):
        return False
    for atr_byte, known_byte in zip(atr_parts, known_parts):
        if known_byte != ".." and atr_byte != known_byte:
            return False
    return True

# Interprétation des réponses APDU
def interpret_apdu_response(response):
    """
    Interprète les réponses APDU, y compris les status words et les données retournées.
    """
    if response["status_word"] == "90 00":
        return _("Succès")
    elif response["status_word"] == "6A 82":
        return _("Fichier non trouvé")
    elif response["status_word"] == "69 82":
        return _("Commande non autorisée")
    else:
        return _("Statut inconnu")

# Envoi des commandes APDU
def send_apdu_commands(connection):
    """
    Envoie toutes les commandes APDU définies et retourne les résultats.
    """
    print(_("=== Envoi des commandes APDU ==="))
    results = []
    for command in APDU_COMMANDS:
        try:
            data, sw1, sw2 = connection.transmit(command["command"])
            response = {
                "name": command["name"],
                "command": command["command"],
                "response": data,
                "status_word": f"{sw1:02X} {sw2:02X}",
            }
            interpretation = interpret_apdu_response(response)
            print(f"{command['name']} : SW={response['status_word']}, Data={toHexString(data)}, Interpretation={interpretation}")
            results.append(response)
        except Exception as e:
            logging.error(f"Erreur lors de l'exécution de {command['name']}: {e}")
    return results

# Chargement de commandes personnalisées
def load_custom_apdu(file_path):
    """
    Charge des commandes APDU personnalisées à partir d'un fichier JSON.
    """
    try:
        with open(file_path, "r") as file:
            custom_commands = json.load(file)
            for cmd in custom_commands:
                APDU_COMMANDS.append(cmd)
        print(_("Commandes APDU personnalisées chargées avec succès."))
    except Exception as e:
        logging.error(f"Erreur lors du chargement des commandes personnalisées : {e}")

# Génération des rapports
def generate_reports(atr_hex, description, apdu_results):
    """
    Génère des rapports JSON et CSV basés sur les résultats des commandes APDU.
    """
    json_report = {
        "ATR": atr_hex,
        "Description": description,
        "APDU_Results": apdu_results,
    }
    with open("audit_report.json", "w") as json_file:
        json.dump(json_report, json_file, indent=4)
    print(_("Rapport JSON généré : audit_report.json"))
    
    with open("audit_report.csv", "w", newline="") as csv_file:
        writer = csv.writer(csv_file)
        writer.writerow(["Commande", "Données", "Status Word", "Interprétation"])
        for result in apdu_results:
            interpretation = interpret_apdu_response(result)
            writer.writerow([
                result["name"],
                toHexString(result["response"]),
                result["status_word"],
                interpretation
            ])
    print(_("Rapport CSV généré : audit_report.csv"))

# Fonction principale
def main():
    """
    Point d'entrée principal pour l'audit de carte.
    """
    print(_("=== Lecture de l'ATR et audit de carte ==="))
    atr_hex, connection = get_atr()
    print(_("\n=== Analyse de l'ATR ==="))
    description = analyze_atr(atr_hex)
    print(_("\n=== Chargement des commandes personnalisées ==="))
    load_custom_apdu("custom_apdu.json")
    print(_("\n=== Tests approfondis APDU ==="))
    apdu_results = send_apdu_commands(connection)
    print(_("\n=== Génération des rapports ==="))
    generate_reports(atr_hex, description, apdu_results)

if __name__ == "__main__":
    main()
            

4️⃣ Explication du Fonctionnement

🔹 4.1 Initialisation et Configuration

Le script commence par importer les bibliothèques nécessaires et à configurer :

🔹 4.2 Lecture et Analyse de l’ATR

La fonction get_atr() permet d’interroger la carte et récupérer son **ATR** (Answer To Reset), qui est une signature unique identifiant le type de carte.

Le script compare ensuite cet **ATR** à une base de données de références connues pour identifier le modèle et la technologie de la carte.

🔹 4.3 Envoi de Commandes APDU

Une fois la carte détectée, le script envoie des **commandes APDU** (Application Protocol Data Unit) pour récupérer des informations :

🔹 4.4 Génération de Rapports

Le script génère ensuite des **rapports JSON et CSV** contenant :

🔹 4.5 Sécurité et Sensibilité des Données

Ce script ne stocke **aucune donnée sensible** et n'exécute que des commandes de lecture et d’analyse. Il peut être utilisé uniquement dans un cadre légal.

5️⃣ Utilisation du Script

📌 5.1 Exécution

Lancez simplement le script :

python audit_carte.py

📌 5.2 Résultats Attendus

Le script affichera :