ProHoster > blog > amministrazione > Inversione e hacking dell'unità HDD esterna con crittografia automatica Aigo. Parte 2: prendere un dump da Cypress PSoC
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
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
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
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):
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:
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
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:
Ci colleghiamo tramite ISSP.
Iniziamo il calcolo del checksum utilizzando il vettore CHECKSUM-SETUP.
Riavviamo il processore dopo un tempo specificato T.
Leggiamo la RAM per ottenere il checksum corrente C.
Ripetere i passaggi 3 e 4, aumentando leggermente T ogni volta.
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:
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:
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!
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:
Riavvia il PSoC (e gli invia un numero magico).
Invia vettori di inizializzazione completi.
Chiama la funzione Arduino Cmnd_STK_START_CSUM (0x85), dove il ritardo in microsecondi viene passato come parametro.
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:
Quindi ho cambiato il codice PIN da "123456" a "1234567" e ho ottenuto:
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):
È 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:
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:
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.