ProHoster > Blog > administration > Reversering og hacking af Aigo selvkrypterende eksternt HDD-drev. Del 2: At tage et dump fra Cypress PSoC
Reversering og hacking af Aigo selvkrypterende eksternt HDD-drev. Del 2: At tage et dump fra Cypress PSoC
Dette er den anden og sidste del af artiklen om hacking af eksterne selvkrypterende drev. Lad mig minde dig om, at en kollega for nylig bragte mig en Patriot (Aigo) SK8671-harddisk, og jeg besluttede at vende den om, og nu deler jeg, hvad der kom ud af det. Før du læser videre, så sørg for at læse første del artikel.
4. Vi begynder at tage et dump fra det interne PSoC-flashdrev
Så alt tyder på (som vi fastslog i [første del]()), at PIN-koden er gemt i flash-dybderne af PSoC. Derfor skal vi læse disse flashdybder. Foran nødvendigt arbejde:
tage kontrol over "kommunikation" med mikrocontrolleren;
finde en måde at kontrollere, om denne "kommunikation" er beskyttet mod læsning udefra;
finde en måde at omgå beskyttelsen.
Der er to steder, hvor det giver mening at lede efter en gyldig PIN-kode:
intern flash-hukommelse;
SRAM, hvor pinkoden kan gemmes for at sammenligne den med pinkoden indtastet af brugeren.
Når jeg ser fremad, vil jeg bemærke, at jeg stadig formåede at tage et dump af det interne PSoC-flashdrev - uden om dets sikkerhedssystem ved hjælp af et hardwareangreb kaldet "cold boot tracing" - efter at have vendt de udokumenterede muligheder i ISSP-protokollen. Dette gav mig mulighed for direkte at dumpe den faktiske PIN-kode.
$ ./psoc.py
syncing: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9
"Kommunikation" med en mikrocontroller kan betyde forskellige ting: fra "leverandør til leverandør" til interaktion ved hjælp af en seriel protokol (for eksempel ICSP for Microchip's PIC).
Cypress har sin egen proprietære protokol til dette, kaldet ISSP (in-system serial programming protocol), som er delvist beskrevet i teknisk specifikation. Patent US7185162 giver også nogle oplysninger. Der er også en OpenSource-ækvivalent kaldet HSSP (vi bruger den lidt senere). ISSP fungerer som følger:
genstart PSoC;
udlæs det magiske nummer til den serielle datapin på denne PSoC; for at gå ind i ekstern programmeringstilstand;
send kommandoer, som er lange bitstrenge kaldet "vektorer".
ISSP-dokumentationen definerer disse vektorer for kun en lille håndfuld kommandoer:
Initialiser-1
Initialiser-2
Initialize-3 (3V og 5V muligheder)
ID-OPSÆTNING
LÆS-ID-ORD
SET-BLOCK-NUM: 10011111010dddddddd111, hvor ddddddd=blok #
BULK SLETTNING
PROGRAM-BLOK
VERIFICER OPSÆTNING
READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, hvor DDDDDDDD = data ud, aaaaaa = adresse (6 bit)
WRITE-BYTE: 10010aaaaaaddddddd111, hvor dddddddd = data ind, aaaaaa = adresse (6 bit)
SIKKER
CHECKSUM-OPSÆTNING
READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, hvor DDDDDDDDDDDDDDDDDD = data ud: enhedskontrolsum
Alle vektorer har samme længde: 22 bit. HSSP-dokumentationen har nogle yderligere oplysninger om ISSP: "En ISSP-vektor er intet mere end en bitsekvens, der repræsenterer et sæt instruktioner."
5.2. Afmystificerende vektorer
Lad os finde ud af, hvad der foregår her. Til at begynde med antog jeg, at de samme vektorer var råversioner af M8C-instruktioner, men efter at have kontrolleret denne hypotese, opdagede jeg, at opkoderne for operationerne ikke stemte overens.
Så googlede jeg vektoren ovenfor og stødte på det her en undersøgelse, hvor forfatteren, selvom han ikke går i detaljer, giver nogle nyttige tips: ”Hver instruktion begynder med tre bits, der svarer til en af fire mnemonics (læs fra RAM, skriv til RAM, læs register, skriv register). Så er der 8 adressebit, efterfulgt af 8 databit (læs eller skriv) og til sidst tre stopbits.”
Så var jeg i stand til at hente nogle meget nyttige oplysninger fra sektionen Supervisory ROM (SROM). teknisk manual. SROM er en hårdkodet ROM i PSoC'en, der giver hjælpefunktioner (på lignende måde som Syscall) til programkode, der kører i brugerrummet:
00h:SWBootReset
01h: Læseblok
02h: WriteBlock
03h: EraseBlock
06h: BordLæs
07h: Checksum
08h: Kalibrer0
09h: Kalibrer1
Ved at sammenligne vektornavne med SROM-funktioner kan vi kortlægge de forskellige operationer, der understøttes af denne protokol, til de forventede SROM-parametre. Takket være dette kan vi afkode de første tre bits af ISSP-vektorer:
100 => "wrem"
101 => "rdmem"
110 => "wrreg"
111 => "rdreg"
En fuldstændig forståelse af on-chip processer kan dog kun opnås gennem direkte kommunikation med PSoC.
5.3. Kommunikation med PSoC
Da Dirk Petrautsky allerede har porteret Cypress's HSSP-kode på Arduino, jeg brugte Arduino Uno til at forbinde til ISSP-stikket på tastaturet.
Bemærk venligst, at jeg i løbet af min research ændrede Dirks kode en del. Du kan finde min modifikation på GitHub: her og det tilsvarende Python-script til kommunikation med Arduino, i mit lager cypress_psoc_tools.
Så ved at bruge Arduino brugte jeg først kun de "officielle" vektorer til "kommunikation". Jeg forsøgte at læse den interne ROM ved hjælp af VERIFY-kommandoen. Som forventet kunne jeg ikke gøre dette. Sandsynligvis på grund af det faktum, at læsebeskyttelsesbits er aktiveret inde i flashdrevet.
Derefter lavede jeg et par af mine egne simple vektorer til at skrive og læse hukommelse/registre. Bemærk venligst, at vi kan læse hele SROM'en, selvom flashdrevet er beskyttet!
5.4. Identifikation af on-chip registre
Efter at have set på de "adskilte" vektorer opdagede jeg, at enheden bruger udokumenterede registre (0xF8-0xFA) til at specificere M8C-opkoder, som udføres direkte, uden at beskyttelsen. Dette tillod mig at køre forskellige opcodes såsom "ADD", "MOV A, X", "PUSH" eller "JMP". Takket være dem (ved at se på de bivirkninger, de har på registre) kunne jeg afgøre, hvilke af de udokumenterede registre, der faktisk var almindelige registre (A, X, SP og PC).
Som et resultat ser den "afmonterede" kode, der genereres af HSSP_disas.rb-værktøjet, sådan ud (jeg tilføjede kommentarer for klarhedens skyld):
På dette stadium kan jeg allerede kommunikere med PSoC, men jeg har stadig ikke pålidelige oplysninger om sikkerhedsbits på flashdrevet. Jeg var meget overrasket over, at Cypress ikke giver brugeren af enheden nogen midler til at kontrollere, om beskyttelsen er aktiveret. Jeg gravede dybere ned i Google for endelig at forstå, at HSSP-koden leveret af Cypress blev opdateret efter Dirk udgav sin modifikation. Også! Denne nye vektor er dukket op:
Ved at bruge denne vektor (se read_security_data i psoc.py) får vi alle sikkerhedsbits i SRAM ved 0x80, hvor der er to bits pr. beskyttet blok.
Resultatet er deprimerende: alt er beskyttet i tilstanden "deaktiver ekstern læsning og skrivning". Derfor kan vi ikke kun læse noget fra et flashdrev, men vi kan heller ikke skrive noget (for eksempel for at installere en ROM-dumper der). Og den eneste måde at deaktivere beskyttelsen på er fuldstændig at slette hele chippen. 🙁
6. Første (mislykkede) angreb: ROMX
Vi kan dog prøve følgende trick: da vi har evnen til at udføre vilkårlige opkoder, hvorfor så ikke køre ROMX, som bruges til at læse flashhukommelse? Denne tilgang har en god chance for succes. Fordi ReadBlock-funktionen, der læser data fra SROM'en (som bruges af vektorer) kontrollerer, om den kaldes fra ISSP'en. Imidlertid kan ROMX-opkoden muligvis ikke have en sådan kontrol. Så her er Python-koden (efter at have tilføjet et par hjælperklasser til Arduino-koden):
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
Desværre virker denne kode ikke. 🙁 Eller rettere det virker, men vi får vores egne opkoder ved udgangen (0x28 0x30 0x40)! Jeg tror ikke, at enhedens tilsvarende funktionalitet er et element af læsebeskyttelse. Dette er mere som et teknisk trick: Når eksterne opkoder udføres, omdirigeres ROM-bussen til en midlertidig buffer.
Dette kalder i det væsentlige SROM-funktionen 0x07, som præsenteret i dokumentationen (kursiv min):
Denne funktion kontrolsum verifikation. Den beregner en 16-bit kontrolsum af antallet af brugerspecificerede blokke i én flashbank, startende fra nul. BLOCKID-parameteren bruges til at videregive antallet af blokke, der vil blive brugt ved beregning af kontrolsummen. En værdi på "1" vil kun beregne kontrolsummen for blok nul; hvorimod "0" vil medføre, at den samlede kontrolsum af alle 256 blokke i flashbanken beregnes. 16-bit kontrolsummen returneres via KEY1 og KEY2. KEY1-parameteren gemmer de 8 bits af lav orden af kontrolsummen, og KEY2-parameteren gemmer de 8 bits af høj orden. For enheder med flere flashbanker kaldes kontrolsum-funktionen for hver enkelt separat. Banknummeret, som det vil arbejde med, indstilles af FLS_PR1-registret (ved at indstille den bit i det, der svarer til målflashbanken).
Bemærk, at dette er en simpel kontrolsum: bytes tilføjes ganske enkelt efter hinanden; ingen fancy CRC særheder. Derudover, da jeg vidste, at M8C-kernen har et meget lille sæt registre, antog jeg, at når jeg beregnede kontrolsummen, vil mellemværdier blive registreret i de samme variabler, der i sidste ende vil gå til output: KEY1 (0xF8) / KEY2 ( 0xF9).
Så i teorien ser mit angreb således ud:
Vi forbinder via ISSP.
Vi starter checksum-beregningen ved hjælp af CHECKSUM-SETUP vektoren.
Vi genstarter processoren efter en specificeret tid T.
Vi læser RAM for at få den aktuelle kontrolsum C.
Gentag trin 3 og 4, og øg T lidt hver gang.
Vi gendanner data fra et flashdrev ved at trække den tidligere kontrolsum C fra den nuværende.
Der er dog et problem: Initialize-1 vektoren, som vi skal sende efter genstart, overskriver KEY1 og KEY2:
Denne kode overskriver vores dyrebare kontrolsum ved at kalde Calibrate1 (SROM funktion 9)... Måske kan vi bare sende det magiske nummer (fra begyndelsen af koden ovenfor) for at gå ind i programmeringstilstand og derefter læse SRAM? Og ja, det virker! Arduino-koden, der implementerer dette angreb, er ret simpel:
Vent i en bestemt periode; under hensyntagen til følgende faldgruber:
Jeg spildte en masse tid, indtil jeg fandt ud af, hvad det viser sig forsinkelse Mikrosekunder fungerer kun korrekt med forsinkelser på højst 16383 μs;
og så igen dræbt den samme mængde tid, indtil jeg opdagede, at delayMicroseconds, hvis 0 sendes til det som input, virker fuldstændig forkert!
Genstart PSoC'en i programmeringstilstand (vi sender bare det magiske nummer uden at sende initialiseringsvektorer).
Endelig kode i 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))
I en nøddeskal, hvad denne kode gør:
Genstarter PSoC'en (og sender den et magisk nummer).
Sender fulde initialiseringsvektorer.
Kalder Arduino-funktionen Cmnd_STK_START_CSUM (0x85), hvor forsinkelsen i mikrosekunder sendes som en parameter.
Læser kontrolsummen (0xF8 og 0xF9) og det udokumenterede register 0xF1.
Denne kode udføres 10 gange på 1 mikrosekund. 0xF1 er inkluderet her, fordi det var det eneste register, der ændrede sig ved beregning af kontrolsummen. Måske er det en slags midlertidig variabel, der bruges af den aritmetiske logiske enhed. Bemærk det grimme hack, jeg bruger til at nulstille Arduino ved hjælp af picocom, når Arduino holder op med at vise tegn på liv (ingen anelse om hvorfor).
7.2. Læser resultatet
Resultatet af Python-scriptet ser sådan ud (forenklet for læsbarhed):
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
Når det er sagt, har vi et problem: da vi arbejder med en faktisk kontrolsum, ændrer en null-byte ikke den læste værdi. Men da hele beregningsproceduren (8192 bytes) tager 0,1478 sekunder (med små variationer hver gang den køres), hvilket svarer til cirka 18,04 μs pr. byte, kan vi bruge denne tid til at kontrollere kontrolsumværdien på passende tidspunkter. For de første kørsler aflæses alt ganske let, da varigheden af beregningsproceduren altid er næsten den samme. Afslutningen på dette dump er dog mindre præcis, fordi de "mindre timingafvigelser" på hver kørsel bliver betydelige:
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
Det er 10 dumps for hvert mikrosekunds forsinkelse. Den samlede driftstid for at dumpe alle 8192 bytes af et flashdrev er omkring 48 timer.
7.3. Flash binær rekonstruktion
Jeg er endnu ikke færdig med at skrive koden, der fuldstændigt rekonstruerer programkoden på flashdrevet under hensyntagen til alle tidsafvigelser. Jeg har dog allerede gendannet begyndelsen af denne kode. For at sikre mig, at jeg gjorde det korrekt, adskilte jeg det ved hjælp af 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
Ser ret plausibelt ud!
7.4. Finder PIN-kodens lageradresse
Nu hvor vi kan læse kontrolsummen på de tidspunkter, vi har brug for, kan vi nemt tjekke, hvordan og hvor det ændrer sig, når vi:
indtast den forkerte PIN-kode;
ændre pinkoden.
For det første, for at finde den omtrentlige lageradresse, tog jeg et checksum-dump i intervaller på 10 ms efter en genstart. Så indtastede jeg den forkerte PIN-kode og gjorde det samme.
Resultatet var ikke særlig behageligt, da der var mange ændringer. Men til sidst var jeg i stand til at fastslå, at kontrolsummen ændrede sig et sted mellem 120000 µs og 140000 µs forsinkelse. Men "pinkoden", som jeg viste der, var fuldstændig forkert - på grund af en artefakt af delayMicroseconds-proceduren, som gør mærkelige ting, når 0 sendes til den.
Derefter, efter at have brugt næsten 3 timer, huskede jeg, at SROM-systemet kaldet CheckSum modtager et argument som input, der specificerer antallet af blokke for kontrolsummen! At. vi kan nemt lokalisere lagringsadressen for PIN-koden og tælleren for "forkerte forsøg" med en nøjagtighed på op til en 64-byte blok.
Mine første løb gav følgende resultat:
Så ændrede jeg PIN-koden fra "123456" til "1234567" og fik:
PIN-koden og tælleren for forkerte forsøg ser således ud til at være gemt i blok nr. 126.
7.5. Tager en dump af blok nr. 126
Blok #126 burde være placeret et sted omkring 125x64x18 = 144000μs, fra starten af checksum-beregningen, i mit fulde dump, og det ser ret plausibelt ud. Derefter, efter manuelt at frasortere adskillige ugyldige dumps (på grund af akkumulering af "mindre timingafvigelser"), endte jeg med at få disse bytes (med en latenstid på 145527 μs):
Det er helt indlysende, at PIN-koden er gemt i ukrypteret form! Disse værdier er selvfølgelig ikke skrevet i ASCII-koder, men som det viser sig, afspejler de aflæsningerne taget fra det kapacitive tastatur.
Til sidst kørte jeg nogle flere tests for at finde ud af, hvor tælleren for dårlige forsøg var gemt. Her er resultatet:
0xFF - betyder "15 forsøg", og det falder for hvert mislykket forsøg.
7.6. Gendannelse af PIN-kode
Her er min grimme kode, der sætter ovenstående sammen:
Bemærk venligst, at de latensværdier, jeg brugte, sandsynligvis er relevante for en specifik PSoC - den, jeg brugte.
8. Hvad er det næste?
Så lad os opsummere på PSoC-siden i forbindelse med vores Aigo-drev:
vi kan læse SRAM, selvom det er læsebeskyttet;
Vi kan omgå anti-swipe-beskyttelsen ved at bruge et koldstartsporingsangreb og direkte læse PIN-koden.
Vores angreb har dog nogle mangler på grund af synkroniseringsproblemer. Det kan forbedres som følger:
skrive et hjælpeprogram til korrekt at afkode outputdataene, der opnås som et resultat af et "cold boot trace"-angreb;
brug en FPGA-gadget til at skabe mere præcise tidsforsinkelser (eller brug Arduino hardware-timere);
prøv et andet angreb: Indtast en bevidst forkert PIN-kode, genstart og dump RAM, i håb om, at den korrekte PIN-kode bliver gemt i RAM til sammenligning. Det er dog ikke så nemt at gøre på Arduino, da Arduino signalniveauet er 5 volt, mens det board vi undersøger fungerer med 3,3 volt signaler.
En interessant ting, der kunne prøves, er at lege med spændingsniveauet for at omgå læsebeskyttelsen. Hvis denne tilgang virkede, ville vi være i stand til at få absolut nøjagtige data fra flashdrevet - i stedet for at stole på at læse en kontrolsum med upræcise timingforsinkelser.
Da SROM'en sandsynligvis læser guard-bits via ReadBlock-systemkaldet, kunne vi gøre det samme som beskrevet på Dmitry Nedospasovs blog - en genimplementering af Chris Gerlinskis angreb, annonceret på konferencen "REcon Bruxelles 2017".
En anden sjov ting, der kunne gøres, er at slibe sagen af chippen: at tage et SRAM-dump, identificere udokumenterede systemkald og sårbarheder.
9. Konklusion
Så beskyttelsen af dette drev lader meget tilbage at ønske, fordi det bruger en almindelig (ikke "hærdet") mikrocontroller til at gemme PIN-koden... Desuden har jeg ikke set (endnu) på, hvordan det går med data kryptering på denne enhed!
Hvad kan du anbefale til Aigo? Efter at have analyseret et par modeller af krypterede HDD-drev, lavede jeg i 2015 præsentation på SyScan, hvor han undersøgte sikkerhedsproblemerne på flere eksterne HDD-drev og kom med anbefalinger til, hvad der kunne forbedres i dem. 🙂
Jeg brugte to weekender og flere aftener på at lave denne undersøgelse. I alt omkring 40 timer. Tæller fra begyndelsen (da jeg åbnede disken) til slutningen (PIN-kode dump). De samme 40 timer inkluderer den tid, jeg brugte på at skrive denne artikel. Det var en meget spændende tur.