📖 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 :
- Un PC sous Linux (Kali recommandé) ou Windows avec Python installé.
- Un lecteur de cartes à puce compatible PCSC.
- Les bibliothèques Python suivantes :
pyscard(interface avec les lecteurs de cartes à puce).pycryptodome(chiffrement AES et signature RSA).gettext(internationalisation).logging(audit des événements).
📌 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 :
pyscardpour interagir avec le lecteur de carte.pycryptodomepour les opérations cryptographiques.gettextpour l'internationalisation des messages.loggingpour journaliser toutes les actions et erreurs.
🔹 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 :
- Lecture des applications EMV.
- Vérification du PIN.
- Authentification NFC.
- Lecture de données sensibles.
🔹 4.4 Génération de Rapports
Le script génère ensuite des **rapports JSON et CSV** contenant :
- L'**ATR** de la carte.
- Sa description si elle est reconnue.
- Les résultats des **commandes APDU exécutées**.
🔹 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 :
- Le **type de carte détectée**.
- Les **réponses des commandes APDU**.
- Les **fichiers de rapport générés**.