Décryptage hardware des vulnérabilités des cartes magnétiques - Masterclass éthique 2025
"Frère d'armes, fin 2025 - on plonge dans l'archéologie numérique pour mieux comprendre le présent. Les cartes magnétiques sans puce sont des reliques vulnérables qui persistent dans certains pays et systèmes legacy. Comprendre leurs faiblesses, c'est comprendre pourquoi la migration vers EMV/chip est cruciale."
— Platon-Y, PCTAMALOU 2025
# Engagement Éthique - À exécuter avant toute expérimentation
print("JE M'ENGAGE À :")
print("1. N'utiliser ces connaissances QUE pour l'éducation et la défense")
print("2. Tester UNIQUEMENT sur des cartes non-bancaires que je possède")
print("3. Ne JAMAIS reproduire ces techniques sur des systèmes réels")
print("4. Partager mes découvertes pour améliorer la sécurité collective")
print("5. Respecter la devise : 'Comprendre pour protéger, jamais pour nuire'")
Analogie : Imaginez un livre audio sur cassette, mais au lieu de musique, il contient vos informations bancaires en clair.
Un lecteur basique peut capturer toutes les données en un seul swipe. Pas d'authentification, pas de challenge-response.
Données en clair → écriture sur carte vierge = clone parfait. Fonctionne sur tous les terminaux sans puce.
Interception possible entre la carte et le lecteur. Pas de chiffrement, pas d'intégrité.
Mastercard/Visa phasent out magstripe depuis 2024. Mais legacy persiste, exposant les retardataires.
Walkman cassette player ou VCR head
Alternative : Lecteur magstripe eBay (10-15€)
LM358 ou TL082
Alimentation : 9V battery ou USB 5V
Résistances : 10kΩ (2), 100kΩ (1)
Condensateurs : 100nF (1), 10µF (1)
Jack 3.5mm (TRS)
USB Sound Card (option qualité)
# Schéma Electronique - Amplificateur pour Tête Magnétique
"""
+9V
|
R1 (10kΩ)
|
Tête ----○---○---- LM358 (Pin 3: Non-Inv Input)
Signal | |
| C1 (100nF)
| |
| Ground
|
R2 (100kΩ) ---- Output to Jack 3.5mm
|
C2 (10µF) ---- Ground
|
Ground
Fonction :
- R1/R2 : Gain = 1 + R2/R1 = ×11 (ajustable)
- C1 : Filtre passe-haut (coupe DC offset)
- C2 : Filtre passe-bas (réduit noise HF)
- Alimentation : 9V battery ou régulateur 5V→9V
"""
#!/usr/bin/env python3
"""
MagStripe Maestro v1.0 - Décodeur de bandes magnétiques
Usage éthique uniquement - PCTAMALOU 2025
"""
import pyaudio
import numpy as np
from scipy import signal
import bitarray
import json
from datetime import datetime
class MagStripeDecoder:
def __init__(self, sample_rate=44100, chunk_size=1024):
self.sample_rate = sample_rate
self.chunk_size = chunk_size
self.threshold = 0.15 # Ajuster selon signal
self.min_peak_distance = int(sample_rate / 10000) # ~10kHz max
def capture_swipe(self, duration=5):
"""Capture audio du swipe via entrée micro"""
p = pyaudio.PyAudio()
# Trouver l'entrée micro
device_index = None
for i in range(p.get_device_count()):
dev = p.get_device_info_by_index(i)
if dev['maxInputChannels'] > 0 and 'microphone' in dev['name'].lower():
device_index = i
break
if device_index is None:
raise Exception("Aucun microphone trouvé")
stream = p.open(
format=pyaudio.paFloat32,
channels=1,
rate=self.sample_rate,
input=True,
input_device_index=device_index,
frames_per_buffer=self.chunk_size
)
print("[*] Prêt à capturer - Swipez la carte maintenant...")
frames = []
for _ in range(0, int(self.sample_rate / self.chunk_size * duration)):
data = stream.read(self.chunk_size)
frames.append(np.frombuffer(data, dtype=np.float32))
stream.stop_stream()
stream.close()
p.terminate()
audio_data = np.concatenate(frames)
print(f"[+] Capture réussie : {len(audio_data)} échantillons")
return audio_data
def preprocess_signal(self, raw_signal):
"""Filtrage et normalisation du signal"""
# Filtre passe-bande 1kHz-10kHz (plage typique magstripe)
nyquist = self.sample_rate / 2
low = 1000 / nyquist
high = 10000 / nyquist
b, a = signal.butter(4, [low, high], btype='band')
filtered = signal.filtfilt(b, a, raw_signal)
# Normalisation
normalized = filtered / np.max(np.abs(filtered))
return normalized
def detect_flux_reversals(self, signal):
"""Détection des changements de polarité (flux reversals)"""
# Dérivée pour détecter changements rapides
derivative = np.diff(signal)
# Détection des peaks (zéros de la dérivée avec changement de signe)
peaks = []
for i in range(1, len(derivative)-1):
if derivative[i-1] > 0 and derivative[i+1] < 0 and signal[i] > self.threshold:
peaks.append(i)
elif derivative[i-1] < 0 and derivative[i+1] > 0 and signal[i] < -self.threshold:
peaks.append(i)
peaks = np.array(peaks)
# Élimination des doublons (trop proches)
if len(peaks) > 0:
mask = np.ones(len(peaks), dtype=bool)
for i in range(1, len(peaks)):
if peaks[i] - peaks[i-1] < self.min_peak_distance:
mask[i] = False
peaks = peaks[mask]
print(f"[*] {len(peaks)} flux reversals détectés")
return peaks
def decode_f2f_to_bits(self, peaks):
"""Conversion F2F (Frequency Double Frequency) → bits"""
if len(peaks) < 3:
return bitarray.bitarray()
bits = bitarray.bitarray()
for i in range(1, len(peaks)):
time_distance = peaks[i] - peaks[i-1]
# En F2F : temps court = 1, temps long = 0
# On calcule la moyenne mobile pour adaptation
if i > 2:
avg_short = np.mean([peaks[j] - peaks[j-1] for j in range(max(1, i-3), i)])
threshold = avg_short * 1.5
else:
threshold = (self.sample_rate / 2000) * 1.5 # Estimation 2kHz
bits.append('1' if time_distance < threshold else '0')
return bits
def decode_track(self, bits, track_number):
"""Décodage des bits selon le format de track"""
if track_number == 1:
# Track 1 : 7-bit (6 data + 1 parity) alphanumérique
chars = []
for i in range(0, len(bits) - 6, 7):
chunk = bits[i:i+7]
if len(chunk) == 7:
# Vérification parité paire
if chunk.count(1) % 2 == 0:
# ASCII 6-bit (ISO/IEC 7811)
data_bits = chunk[:6]
value = 0
for bit in data_bits:
value = (value << 1) | bit
# Mapping spécial Track 1
if 0 <= value <= 9:
chars.append(str(value))
elif 10 <= value <= 35:
chars.append(chr(ord('A') + value - 10))
elif value == 36: chars.append(' ')
elif value == 37: chars.append('%')
elif value == 38: chars.append('&')
elif value == 39: chars.append('\'')
elif value == 40: chars.append('(')
elif value == 41: chars.append(')')
elif value == 42: chars.append('*')
elif value == 43: chars.append('+')
elif value == 44: chars.append(',')
elif value == 45: chars.append('-')
elif value == 46: chars.append('.')
elif value == 47: chars.append('/')
elif value == 58: chars.append(':')
elif value == 59: chars.append(';')
elif value == 60: chars.append('<')
elif value == 61: chars.append('=')
elif value == 62: chars.append('>')
elif value == 63: chars.append('?')
return ''.join(chars)
else:
# Tracks 2 & 3 : 5-bit (4 data + 1 parity) numérique
chars = []
for i in range(0, len(bits) - 4, 5):
chunk = bits[i:i+5]
if len(chunk) == 5:
# Vérification parité paire
if chunk.count(1) % 2 == 0:
data_bits = chunk[:4]
value = 0
for bit in data_bits:
value = (value << 1) | bit
if 0 <= value <= 9:
chars.append(str(value))
elif value == 10: chars.append(';')
elif value == 11: chars.append('<')
elif value == 12: chars.append('=')
elif value == 13: chars.append('>')
elif value == 14: chars.append('?')
return ''.join(chars)
def parse_track_data(self, track_string, track_number):
"""Parsing des données selon le format ISO/IEC 7811"""
if not track_string:
return {"error": "Données vides"}
if track_number == 1:
# Format: %B1234567890123456^DOE/JOHN^2512101100123456789?
try:
parts = track_string.split('^')
if len(parts) >= 3:
return {
"format_code": parts[0][0] if parts[0] else "",
"card_number": parts[0][1:] if len(parts[0]) > 1 else "",
"name": parts[1],
"expiration_date": parts[2][:4] if len(parts[2]) >= 4 else "",
"service_code": parts[2][4:7] if len(parts[2]) >= 7 else "",
"discretionary_data": parts[2][7:] if len(parts[2]) > 7 else ""
}
except:
pass
elif track_number == 2:
# Format: ;1234567890123456=2512101100123456?
try:
if '=' in track_string:
card_num, rest = track_string.split('=', 1)
card_num = card_num.strip(';')
return {
"card_number": card_num,
"expiration_date": rest[:4] if len(rest) >= 4 else "",
"service_code": rest[4:7] if len(rest) >= 7 else "",
"discretionary_data": rest[7:] if len(rest) > 7 else ""
}
except:
pass
return {"raw_data": track_string}
def run_decoding(self):
"""Workflow complet de décodage"""
print("=" * 60)
print("MAGSTRIPE MAESTRO v1.0 - PCTAMALOU 2025")
print("=" * 60)
# 1. Capture
raw_audio = self.capture_swipe(duration=3)
# 2. Prétraitement
processed = self.preprocess_signal(raw_audio)
# 3. Détection flux reversals
peaks = self.detect_flux_reversals(processed)
if len(peaks) < 10:
print("[!] Trop peu de peaks détectés - mauvais signal")
return None
# 4. Conversion F2F → bits
bits = self.decode_f2f_to_bits(peaks)
if len(bits) < 40:
print("[!] Trop peu de bits décodés")
return None
print(f"[+] {len(bits)} bits décodés")
# 5. Décodage par track
results = {}
# Tentative de séparation des tracks (basé sur densité)
track1_bits = bits[:int(len(bits) * 0.4)] # ~40% premiers bits
track2_bits = bits[int(len(bits) * 0.4):int(len(bits) * 0.8)]
track3_bits = bits[int(len(bits) * 0.8):]
# Décodage
track1_raw = self.decode_track(track1_bits, 1)
track2_raw = self.decode_track(track2_bits, 2)
track3_raw = self.decode_track(track3_bits, 2) # Track 3 même format que 2
# Parsing
results["track1"] = self.parse_track_data(track1_raw, 1)
results["track2"] = self.parse_track_data(track2_raw, 2)
results["track3"] = {"raw_data": track3_raw}
# Metadata
results["metadata"] = {
"timestamp": datetime.now().isoformat(),
"sample_rate": self.sample_rate,
"bits_decoded": len(bits),
"peaks_detected": len(peaks),
"version": "1.0"
}
return results
def main():
"""Fonction principale"""
decoder = MagStripeDecoder()
try:
results = decoder.run_decoding()
if results:
print("\n" + "=" * 60)
print("RÉSULTATS DU DÉCODAGE")
print("=" * 60)
print("\n📊 Track 1 (Alphanumérique):")
for key, value in results["track1"].items():
print(f" {key}: {value}")
print("\n📊 Track 2 (Numérique):")
for key, value in results["track2"].items():
print(f" {key}: {value}")
print("\n📊 Track 3 (Propriétaire):")
print(f" raw_data: {results['track3']['raw_data']}")
# Sauvegarde JSON
with open(f"magstripe_decode_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json", 'w') as f:
json.dump(results, f, indent=2)
print(f"\n[+] Résultats sauvegardés en JSON")
except KeyboardInterrupt:
print("\n[!] Interruption utilisateur")
except Exception as e:
print(f"[!] Erreur: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
# Installation des dépendances si nécessaire
print("Vérification des dépendances...")
print("pip install pyaudio numpy scipy bitarray")
print("\nNote: PyAudio peut nécessiter portaudio sur Linux:")
print("sudo apt-get install portaudio19-dev python3-pyaudio")
main()
# Installation complète des dépendances
# Ubuntu/Debian :
sudo apt-get update
sudo apt-get install portaudio19-dev python3-pip python3-dev
# Installation Python packages :
pip install pyaudio numpy scipy bitarray
# Vérification installation :
python3 -c "import pyaudio, numpy, scipy, bitarray; print('✅ Toutes les dépendances sont installées')"
# Alternative si pyaudio pose problème :
# pip install pipwin
# pipwin install pyaudio # Windows seulement
# Pour améliorer la capture audio :
# - Utiliser une carte son USB externe
# - Désactiver la réduction de bruit
# - Régler le gain microphone au maximum
# Script de vérification sécurité carte (conceptuel)
def check_card_security(card_type):
security_features = {
"magstripe": {
"risk": "CRITIQUE",
"recommendation": "REMPLACER IMMÉDIATEMENT",
"features": ["Données en clair", "Clonage facile", "Pas d'authentification"]
},
"emv_chip": {
"risk": "FAIBLE",
"recommendation": "CONSERVER + PIN fort",
"features": ["Cryptographie", "Transaction unique", "Authentification"]
},
"contactless": {
"risk": "MODÉRÉ",
"recommendation": "Limite à 50€, surveillance",
"features": ["Tokenization", "Limite montant", "NFC sécurisé"]
},
"mobile": {
"risk": "FAIBLE",
"recommendation": "RECOMMANDÉ",
"features": ["Biométrie", "Token unique", "Chiffrement E2E"]
}
}
return security_features.get(card_type, {"risk": "INCONNU"})
# Utilisation
votre_carte = "magstripe" # À changer !
sec_info = check_card_security(votre_carte)
print(f"Niveau de risque : {sec_info['risk']}")
print(f"Recommandation : {sec_info['recommendation']}")
Améliorez ce système avec une des fonctionnalités avancées :