La piste d'une intrusion physique est suspectée.
On vous demande d'investiguer toute trace d'effraction sur les lecteurs de
carte.
Après avoir compromis à distance la climatisation (SCADA) puis les
hyperviseurs, l’attaquant a forcément eu besoin d’un accès physique pour
implanter le maliciel sur ces systèmes isolés.
On suspecte alors une intrusion en présentiel : l’attaquant a dû passer par
la porte d’entrée avant d’agir sur site.
Fortuitement, des problèmes ayant été récemment remontés sur les accès
physiques, deux sniffeurs Proxmark3 ont été installés près des lecteurs des
portes d'entrée et de sortie à des fins de débogage.
Ceux-ci ont capturé toutes les transactions RFID entre les cartes et les
deux lecteurs jusqu’à aujourd’hui !
L'entrée étant surveillée de manière assez stricte, les employés sont toujours
obligés de badger individuellement :
l'attaquant n'a pas pu se faufiler derrière une personne sans s'authentifier
lui-même avec une carte compromise.
La technologie utilisée par les lecteurs est Mifare Classic, qui est basée sur
ISO14443-A. Les sniffeurs sont des Proxmark3 cadencés à 13,56MHz.
Pour vous éviter d'avoir à traiter les dépassements d'entier sur les
horodatages, et pour gérer la limite de taille de buffer du logiciel Proxmark3,
les traces `entry_combined.trace` et `exit_combined.trace` sont divisées
par tranche toutes les 5 minutes environ.
Les fichiers `*_combined.trace` sont ceux originaux sortant directement des
Proxmark3, et correspondent exactement à la concaténation des autres traces.
On dispose d'une retranscription de toutes les trames RFID entre les lecteurs
d'entrée et de sortie depuis environ 13h40 le 8 mars 2025, selon les noms
des fichiers.
Pour analyser le contenu d'une trace en particulier, on peut utiliser
l'outil proxmark3.
Voici un exemple de commandes pour afficher
directement l'analyse d'une trace depuis cet outil :
$ proxmark3
library loaded
[=] OFFLINE mode. Check "proxmark3 -h" if it's not what you want.
8888888b. 888b d888 .d8888b.
888 Y88b 8888b d8888 d88P Y88b
888 888 88888b.d88888 .d88P
888 d88P 888Y88888P888 8888"
8888888P" 888 Y888P 888 "Y8b.
888 888 Y8P 888 888 888
888 888 " 888 Y88b d88P
888 888 888 "Y8888P"
[offline] pm3 --> trace load -f entry_20250308_134110102.trace
[+] Loaded 5850 bytes from binary file `entry_20250308_134110102.trace`
[+] Recorded Activity (TraceLen = 5850 bytes)
[?] Hint: Try `trace list -1 -t ...` to view trace. Remember the `-1` param
[offline] pm3 --> trace list -1 -t mf
[+] Recorded activity ( 5850 bytes )
[=] start = start of start frame. end = end of frame. src = source of transfer.
[=] ISO14443A - all times are in carrier periods (1/13.56MHz)
Start | End | Src | Data (! denotes parity error) | CRC | Annotation
------------+------------+-----+-------------------------------------------------------------------------+-----+--------------------
0 | 1056 | Rdr |26(7) | | REQA
13980960 | 13982016 | Rdr |26(7) | | REQA
[...]
133462784 | 133463840 | Rdr |26(7) | | REQA
137514416 | 137515472 | Rdr |26(7) | | REQA
151036000 | 151038464 | Rdr |93 20 | | ANTICOLL
151039636 | 151045524 | Tag |0A 09 6C DA B5 | |
151066832 | 151077360 | Rdr |93 70 0A 09 6C DA B5 D4 8C | ok | SELECT_UID
151078532 | 151082052 | Tag |08 B6 DD | ok |
154818400 | 154819392 | Rdr |52(7) | | WUPA
155040720 | 155041712 | Rdr |52(7) | | WUPA
155042964 | 155045332 | Tag |04 00 | |
155058640 | 155069168 | Rdr |93 70 0A 09 6C DA B5 D4 8C | ok | SELECT_UID
155070340 | 155073860 | Tag |08 B6 DD | ok |
155615408 | 155620176 | Rdr |60 04 D1 3D | ok | AUTH-A(4)
155622116 | 155626852 | Tag |FB A2 0D 13 | | AUTH: nt (lfsr16 index 43601)
155628208 | 155637520 | Rdr |F3! 9E 26! 0A D5! E7 E8! F5 | | AUTH: nr ar (enc)
155638772 | 155643508 | Tag |2F 76! F6! 59! | | AUTH: at (enc)
155755552 | 155760256 | Rdr |31! 7E 88! 6A | |
| | * | key 535253464943 prng WEAK | |
| | * |60 04 D1 3D | ok | AUTH-A(4)
155762276 | 155766948 | Tag |30! C3! E5! 3A | | AUTH: nt (enc)
155768352 | 155777728 | Rdr |92! 91 A5 89! 1E 46 E5 B4! | | AUTH: nr ar (enc)
155778916 | 155783652 | Tag |B2! 5A 3C 6E | | AUTH: at (enc)
155867536 | 155872304 | Rdr |7D! 3F 91! F9! | |
| | * | last used key 535253464943| |
| | * |30 04 26 EE | ok | READBLOCK(4)
155873620 | 155894420 | Tag |43! 0A DE! 8B B3! 9A A5 3D 80 92! E4! ED C0 D0 31 47 47 6A | |
| | * |50 52 29 B9 6A 86 16 EA 00 01 5F 7C 8F 63 8A 09 75 E3 | ok |
156056064 | 156060832 | Rdr |3D! 35! BB! 64 | |
| | * |60 05 58 2C | ok | AUTH-A(5)
156062788 | 156067524 | Tag |19! DE! 7D 4C | | AUTH: nt (enc)
156068864 | 156078176 | Rdr |4A! F9 98 41! 7C 7A! CB F2! | | AUTH: nr ar (enc)
156079428 | 156084100 | Tag |53! BB! 0A 92! | | AUTH: at (enc)
156182384 | 156187152 | Rdr |53 9F! C0 AD | |
| | * | last used key 535253464943| |
| | * |30 05 AF FF | ok | READBLOCK(5)
156188468 | 156209268 | Tag |13 E9! 49 84! 08! 22 20 1F 43 94! 6E! 1B! D0! D0! 6E! C0! 9A DF! | |
| | * |20 25 12 31 00 00 D6 03 56 49 53 49 54 45 55 52 1C CC | ok |
156209268 | 156210324 | Rdr |26(7) | | REQA
170190228 | 170191284 | Rdr |26(7) | | REQA
174241860 | 174242916 | Rdr |26(7) | | REQA
178293492 | 178294548 | Rdr |26(7) | | REQA
[...]
proxmark3 a le bon goût d'automatiquement effectuer une attaque mfkey32
dès la détection d'une authentification réussie auprès
du lecteur dans la trace, si on lui précise qu'il s'agit d'une carte
Mifare Classic (-t mf).
L'annotation key 535253464943 prng WEAK indique directement que
la clé utilisée par le lecteur pour s'authentifier avec la carte.
Reprenons l'exemple précédent un peu plus en détail pour voir à quoi
est censé ressembler une authentification honnête :
REQA envoyés indiquent que le lecteur recherche une carte ISO14443-AANTICOLL est envoyé par le lecteur pour que les cartes répondent par leurSELECT_UID;AUTH-A(4));nt, nr, ar, at);Pour chaque fichier, on peut facilement détecter le nombre d'authentification
effectuées pendant la période en comptant le nombre d'anticollision,
par exemple :
rm *_combined.* # On n'utilise pas les traces combinées
for file in *.trace;
do
# L'analyse de proxmark3 prend un peu de temps, on l'enregistre pour
# des utilisations futures.
output=$file"_analysis"
proxmark3 -c "trace load -f "$file"; hf mf list -1" > "$output";
echo -n "$output: ";
grep ANTICOLL "$output" | wc -l;
done
Voici un histogramme des valeurs que l'on obtient sur la porte d'entrée :
2025-03-08_13:41:10 ######
2025-03-08_13:46:26 #
2025-03-08_13:51:43 ##
2025-03-08_13:57:00 ####
2025-03-08_14:02:17 ##
2025-03-08_14:07:33 ####
2025-03-08_14:12:50 ##
2025-03-08_14:18:07
2025-03-08_14:23:24
2025-03-08_14:28:40 ####
2025-03-08_14:33:57 #
2025-03-08_14:39:14
2025-03-08_14:44:30 #
2025-03-08_14:49:47 #
2025-03-08_14:55:04
2025-03-08_15:00:21
2025-03-08_15:05:37
2025-03-08_15:10:54
2025-03-08_15:16:11
2025-03-08_15:21:28 ##
2025-03-08_15:26:44
[...]
2025-03-09_03:19:24
2025-03-09_03:24:41
2025-03-09_03:29:57
2025-03-09_03:35:14
2025-03-09_03:40:31 #########################################
2025-03-09_03:45:48
2025-03-09_03:51:04
2025-03-09_03:56:21
[...]
2025-03-09_07:48:38
2025-03-09_07:53:54
2025-03-09_07:59:11
2025-03-09_08:04:28 #
2025-03-09_08:09:45 #
2025-03-09_08:15:01 ####
2025-03-09_08:20:18 ##
2025-03-09_08:25:35 ##
2025-03-09_08:30:52 ############
2025-03-09_08:36:08 ########
2025-03-09_08:41:25 ###
2025-03-09_08:46:42 #####
2025-03-09_08:51:58 ########
2025-03-09_08:57:15 #########
2025-03-09_09:02:32 #######
2025-03-09_09:07:49 ##########
2025-03-09_09:13:05 ####
2025-03-09_09:18:22 ##
2025-03-09_09:23:39 #########
2025-03-09_09:28:56 ##
2025-03-09_09:34:12
2025-03-09_09:39:29 #
2025-03-09_09:44:46
[...]
On remarque clairement une anomalie entre 3h40 et 3h45 du matin le 9 mars.
On charge alors le fichier de trace correspondant à cette date précise
dans proxmark3 :
[offline] pm3 --> trace load -f entry_20250309_034031444.trace
[offline] pm3 --> trace list -1 -t mf
Start | End | Src | Data (! denotes parity error) | CRC | Annotation
------------+------------+-----+-------------------------------------------------------------------------+-----+--------------------
0 | 1056 | Rdr |26(7) | | REQA
18030384 | 18031440 | Rdr |26(7) | | REQA
[...]
2244465488 | 2244466544 | Rdr |26(7) | | REQA
2248517120 | 2248518176 | Rdr |26(7) | | REQA
2266547504 | 2266548560 | Rdr |26(7) | | REQA
2280069088 | 2280070144 | Rdr |26(7) | | REQA
2280071316 | 2280073684 | Tag |44 00 | |
2280082256 | 2280084720 | Rdr |93 20 | | ANTICOLL
2280085908 | 2280091732 | Tag |88 04 26 4D E7 | |
2280113088 | 2280123616 | Rdr |93 70 88 04 26 4D E7 48 CB | ok | SELECT_UID
2280124788 | 2280128308 | Tag |04 DA 17 | ok |
2280137520 | 2280138640 | Rdr |95! | | SELECT_XXX-2
2280141172 | 2280147060 | Tag |B8 1A B6 D6 C2 | |
2280168352 | 2280169472 | Rdr |95! | | SELECT_XXX-2
2280172400 | 2280173456 | Rdr |1E(7) | |
2280174064 | 2280174608 | Rdr |01(3) | |
2280175088 | 2280176912 | Rdr |72! 06! | |
2280180052 | 2280183572 | Tag |08 B6 DD | ok |
2283948976 | 2283949968 | Rdr |52(7) | | WUPA
2283952836 | 2283953476 | Tag |04(4) | |
2284106292 | 2284108660 | Tag |44 00 | |
2284133668 | 2284137188 | Tag |04 DA 17 | ok |
2284150240 | 2284156160 | Rdr |95 50 B8 1A B6 | | ANTICOLL-2
2284157348 | 2284159780 | Tag |D6 C2 | |
2284176464 | 2284186928 | Rdr |95 70 B8 1A B6 D6 C2 94 28 | ok | SELECT_UID-2
2284188180 | 2284191700 | Tag |08 B6 DD | ok |
2284722992 | 2284727760 | Rdr |60 04 D1 3D | ok | AUTH-A(4)
2284731124 | 2284735796 | Tag |DA 23 7B 71 | | AUTH: nt
2287671280 | 2287672272 | Rdr |52(7) | | WUPA
2287673508 | 2287675876 | Tag |44 00 | |
2287689184 | 2287699648 | Rdr |93 70 04 26 4D B8 D7 D0 9F | ok | SELECT_UID
2287778400 | 2287784320 | Rdr |95 50 1A B6 D6 | | ANTICOLL-2
2288002128 | 2288003120 | Rdr |52(7) | | WUPA
2288004356 | 2288006724 | Tag |44 00 | |
2288020032 | 2288030496 | Rdr |93 70 04 26 4D B8 D7 D0 9F | ok | SELECT_UID
2288109248 | 2288115168 | Rdr |95 50 1A B6 D6 | | ANTICOLL-2
2336282640 | 2336283696 | Rdr |26(7) | | REQA
2336284868 | 2336287236 | Tag |44 00 | |
2336295936 | 2336298400 | Rdr |93 20 | | ANTICOLL
2336299572 | 2336305396 | Tag |88 04 26 4D E7 | |
2336332848 | 2336333392 | Rdr |02(3) | |
2336335920 | 2336336208 | Rdr |00(1) | |
2336338340 | 2336341860 | Tag |04 DA 17 | ok |
2336354708 | 2336360596 | Tag |B8 1A B6 D6 C2 | |
2336381920 | 2336392384 | Rdr |95 70 B8 1A B6 D6 C2 94 28 | ok | SELECT_UID-2
2336393620 | 2336397140 | Tag |08 B6 DD | ok |
2340166836 | 2340167476 | Tag |04(4) | |
2340320292 | 2340322660 | Tag |44 00 | |
2340335984 | 2340346512 | Rdr |93 70 88 04 26 4D E7 48 CB | ok | SELECT_UID
2340347684 | 2340351204 | Tag |04 DA 17 | ok |
2340364128 | 2340370048 | Rdr |95 50 B8 1A B6 | | ANTICOLL-2
2340371220 | 2340373652 | Tag |D6 C2 | |
2340390352 | 2340400816 | Rdr |95 70 B8 1A B6 D6 C2 94 28 | ok | SELECT_UID-2
2340402052 | 2340405572 | Tag |08 B6 DD | ok |
2340980272 | 2340985040 | Rdr |60 04 D1 3D | ok | AUTH-A(4)
2340988388 | 2340993060 | Tag |B7 F3 D0 4E | | AUTH: nt
2340994496 | 2341003872 | Rdr |1E! DB! E2 44 07! 13! 30 A6! | | AUTH: nr ar (enc)
2343928816 | 2343929808 | Rdr |52(7) | | WUPA
2343931044 | 2343933412 | Tag |44 00 | |
2344036624 | 2344037808 | Rdr |71! | |
2344038800 | 2344039856 | Rdr |1E(7) | |
2344040464 | 2344041008 | Rdr |01(3) | |
2344041488 | 2344041840 | Rdr |02(2) | |
[...]
Ici, on voit que l'attaquant entame une dizaine de fois une communication avec
le lecteur de carte, mais s'arrête immédiatement après avoir reçu le nonce.
C'est le procédé généralement utilisé pour effectuer une attaque mfkey32
et récupérer la clé du lecteur depuis l'extérieur.
On retrouve assez clairement la procédure sur
la documentation de Flipper
par exemple.
Pour retrouver l'heure exacte, proxmark3 nous indique que la première
interaction s'effectue 2 280 071 316 battements après le début de cette trace.
Cadencé à 13,56MHz comme l'indique l'énoncé, cela correspond à 02:48,147
secondes depuis le début de la trace, indiquée à 03:40:31.444
selon le nom du fichier.
On obtient donc une première interaction avec le lecteur le 09 mars 2025,
à 03:43:19,591 environ.
(Concernant les secondes exactes, les réponses 03:43:19 et 03:43:20 étaient
acceptées.)
Une fois la clé principale du lecteur obtenue avec l'attaque mfkey32,
l'attaquant est en mesure de lire le contenu du bloc #4 (en fait, tout le
secteur #1) de n'importe quelle carte de l'entreprise.
Ainsi, en s'approchant du badge de n'importe quel employé, l'attaquant serait
en mesure de lire le contenu de la carte dans ce secteur, puis de plus tard
émuler celle-ci devant le lecteur.
Cependant, l'attaquant n'a encore aucun moyen de savoir exactement quels
secteurs de la carte sont utiles pour pénétrer dans l'entreprise.
Pour s'assurer de pouvoir rentrer, il faut nécessairement que l'attaquant
clone l'entièreté de la carte d'un employé.
Pour obtenir les clés de tous les autres secteurs d'une carte depuis
une première clé, cela se fait en réalité assez facilement grâce à une autre
vulnérabilité des Mifare Classic, que l'on exploite avec une
« Nested Attack ».
En connaissance d'une première clé de secteur, cette attaque permet de
recouvrer toutes les autres clés d'une carte, et ainsi de lire
son contenu dans son intégralité, et de la cloner parfaitement par la suite.
Grâce à cette attaque, après avoir récupéré une première clé de secteur depuis
le lecteur la nuit du 09 mars, l'attaquant est en mesure de reproduire à
l'identique n'importe quelle carte Mifare Classic de l'entreprise au simple
contact de celle-ci.
Passons en revue les propositions pour confirmer que seules les propositions
mfkey32 et Nested Attack sont raisonnables :
prng WEAK indiquée parproxmark3 sur toutes les authentifications.mfkey32 directement sur le lecteur.mfkey32 sur le lecteur. De plus, cela aurait été visibleVient alors la dernière partie de ce challenge.
Pour identifier la carte clonée, il faut analyser le comportement de chaque
employé à travers les logs.
Voici un script qui calcule pour chaque carte son ordre d'entrée et de sortie
dans l'entreprise :
from os import listdir
from collections import defaultdict
from datetime import datetime, timedelta
def extract_date(filename):
# Extrait la date de commencement d'une trace depuis le nom du fichier
data = filename.split(".")[0].split("_")[1:]
year = int(data[0][:4])
month = int(data[0][4:6])
day = int(data[0][6:])
hour = int(data[1][:2])
minute = int(data[1][2:4])
seconds = int(data[1][4:6])
mils = int(data[1][6:])
return datetime(
year=year,
month=month,
day=day,
hour=hour,
minute=minute,
second=seconds,
microsecond=1000*mils)
files = listdir(".")
files.sort(key = extract_date)
# Associe à chaque carte la liste des entrées/sorties détectées pour cette
# carte.
events = defaultdict(lambda: [])
for file in files:
if file.endswith(".trace_analysis"):
kind = file.split('_')[0] # entry/exit
start = extract_date(file) # horodatage début de la trace
with open(file, 'r') as f:
read_uid = False # si vrai, extraire l'UID de la carte à la prochaine trame
t = None # horodatage de la trame
for line in f:
if line.endswith("ANTICOLL\n"):
read_uid = True
timestamp = int(line.split(" |")[0])
sec = timestamp / 13560000
t = start + timedelta(seconds=sec)
elif read_uid:
uid = bytes.fromhex(line.split("|")[3][0:14])
events[uid].append((t, kind))
read_uid = False
# On affiche l'ordre des événements pour chaque carte
for uid in events:
events[uid].sort()
print(uid.hex(), end = ' ')
for e in events[uid]:
if e[1] == 'entry':
print('>', end = '')
else:
print('<', end = '')
print()
On obtient le résultat suivant :
1502e492 ><><><><><
65b55071 ><><><><
[...]
81150b62 <><><><><
fdbffad4 <><><><>><<><
97c4aee9 <><><
4209db73 <><><><><
35deaeff <><><><><
61a0b433 <><><><><
2f15f127 <><><><><
03e324b2 <><><><><><
7e9ad8c0 <><><><><
f0c17e79 <><><><><
1e55bc3f <><><><><
b4730422 <><><><><
e02102f8 <><><><><
8d9b0653 <><><><><
50fda9bd <><><><
8660ba15 <><><><><<
0e46e2fc <><><><>
cb228acb <><><><
0a096cda <><><><
f19905e7 <><><
933c6a81 <><><><
8804264d >>>>>>>>
93708804 >
On remarque quatre anomalies :
fdbffad4 rentre deux fois consécutives à 12h45 et 13h16, puis8660ba15 semble sortir deux fois à 19h12 et 20h19.proxmark3 sur le lecteur d'entrée a simplement dû êtreproxmark3 du lecteur de sortie.8804264d et 93708804.La carte clonée est donc la première.