Дайте две или уязвимость защиты многостраничных PIC18

Эта статья не руководство к действию хакеров, это подсказка, как правильно используя предоставленные MICROCHIP инструменты защитить прошивку внутри чипа.

Не помню уже сколько лет назад это было, натолкнулся я на статью «Heart of darkness — exploring the uncharted backwaters of hid iСlass security by Milosch Meriac». Суть статьи в проблемах безопасности iCLASS card. В общем-то я по быстренькому стал «пробегать» статью, пока не натолкнулся на: «Copy Protection? You«re kidding me!». И меня «скрючило от восторга»…, а чё так можно было!!?!

Пардон, ничего не понятно, сейчас объясню.

Не помню какой PIC тогда был под рукой, сейчас есть PIC18F26K20.

Суть уязвимости.

Запускаем PICKIT и попутно открываем даташит по программированию PIC18F*K*.

15283aef999d928659f8cfcc858e99a1.png

Смотрим, зеленым биты защиты EEPROM, красным — биты защиты BOOT блока, синим — биты защиты кода, блоки 0–3.

CPB/WRTB, CP[3:0]/WRT[3:0] — защита от чтения/записи кода, сперва BOOT затем блоки с 0–3.

CPD/WRTD — Защита от чтения/записи EEP данных.

А вот самые интересные биты защиты: EBTRB/EBTR[3:0] — защита от чтения секторов из других блоков.

Смотрим на конфигурацию «защищенного» чипа (защита установлена):

87e5046d3ed520a3030c021997f3b277.png

Смотрим на конфигурацию, — всё защищено!

Читаем:

bfad4ac716b4c52a33a0119dd1c3b3ae.png

Ноль по вдоль! Всё защищено!

И вроде проблем нет.

А они есть, читаем в даташите:

ace44e64db07caab8fcddde08032cd1d.png

Написано, — «Биты защиты можно записать только из 1 в 0, наоборот нельзя, чтобы записать в бит защиты 1 нужно стереть все блоки или блок соотв. биту защиты чтобы установить соотв. бит защиты в 1». И опа, оно!

Стираем BOOT блок, записываем в него свой код, который будет для нас читать прошивку и отдавать её по EUSART.

7b7d225ccb36834d1df2f91fa36e5197.png

Да, но МЫ ПОТЕРЯЛИ ТО ЧТО БЫЛО В BOOT блоке, и тут появляется тема «ДАЙТЕ ДВЕ», берем второй чип с такой же прошивкой и проделываем то же самое только например с блоком 0, причем код помещаем в самый конец блока, всё остальное заполняем NOP’ами, чтобы с определенной вероятностью наш код начался не «с середины». Вот так:

add54175c7aa938b13da2a44d09495ad.png

Затем склеиваем блоки в любом HEX редакторе, и вуаля! Прошивка на руках.

b9ac3e3a220d3dfe977f0443cf3ca016.png

Код есть, вытаскиваем EEP. Точно так же:

6d62f8d68a98c43d17fffa54f61ac5c4.png

Выглядит довольно просто. Однако проверим так ли это на самом деле.

Первая задача, как затереть сектор. Читаем даташит:

22cba9e89dfefe848e126166e4fecb3b.png

Стереть весь чип: 0×3F8F, BOOT: 0×0084, Block0: 0×0180.

Только вот вопрос чем тереть то?

Берем PICKIT3 (а он «из коробки» трет весь чип, нам не подходит):

5300dc359b239323a768f43f186b44bd.jpg

Ни для кого не секрет что исходный код этого программатора открыт. Идем на Microchip и качаем исходники прошивки (PICkit3 Programmer Application v3.10).

Изучаем исходники… понимаем что это провал, внутри программатора интерпретатор команд, ага, значит сами команды в исходном коде приложения PIC KIT Programmer…

Изучаем исходники (они в том же архиве)… понимаем что и тут интерпретатор!!! Да чтож такое!!! А команды то где?

А они заботливо сложены в базе данных PK2DeviceFile.dat

Первая мысль написать небольшой скрипт править базу данных, собственно давным давно я так и сделал, но теперь в поиске используя PK2DeviceFile.dat неожиданно натолкнулся на редактор этой базы (pickit2-editor, только прежде чем получить результат мне пришлось исправить несколько багов ибо при попытке сохранить подправленный файл базы прога «вылетала» неисправимо файл базы запортив).

Запускаем PicKit2 Editor и находим имя скрипта стирания нашего чипа:

89c430c19bfbf9ac24e928e9479bfc2f.png

Переходим в скрипты, находим наш, и правим соотв (0×3F8F → 0×0084).

d6388a0d0d6644cdba46ab3a29424cc6.png

Правим 0×3F на 0×00, а 0×8F на 0×84. Теперь при нажатии на кнопку ERASE, PicKit Programmer будет стирать не весь чип, а только BOOT блок.

Так, как стереть поняли.

Теперь код ридера, предлагается использовать стандартный интерфейс EUSART установленный в чипе, будем читать код из памяти и отдавать его по EUASRT, подключим его к любому конвертеру интерфейсов RS232→USB (убедившись что конвертер соотв. напряжению питания чипа). Смотрим опять в даташит:

4a8bb650e5f8f8dea74c40ccdf40cf79.png

Отлично, 18-ый пин соединяем с конвертером интерфейсов (незабываем объединить земли, у меня всё соединено через общий хаб):

5b493289dd545efc021aeb505c5bd4dd.jpg

Всё мы готовы. Напишем код ридера:

data_reader.c

#include "pic18fregs.h"

/* CONFIG1L */
        #pragma config FOSC     = INTIO67
        #pragma config FCMEN    = OFF
        #pragma config IESO     = OFF
/* CONFIG2L */
        #pragma config PWRT     = OFF
        #pragma config BOREN    = NOSLP
        #pragma config BORV     = 18
/* CONFIG2H */
        #pragma config WDTEN      = ON
        #pragma config WDTPS    = 128
/* CONFIG3H */
          #pragma config CCP2MX   = PORTC
        #pragma config PBADEN   = OFF
        #pragma config LPT1OSC  = OFF
        #pragma config HFOFST   = OFF
        #pragma config MCLRE    = OFF
/* CONFIG4L */
        #pragma config STVREN   = ON
        #pragma config LVP      = OFF
        #pragma config XINST    = OFF
        #pragma config DEBUG    = OFF
/* CONFIG5L */
        #pragma config CP0      = ON
        #pragma config CP1      = ON
        #pragma config CP2      = ON
        #pragma config CP3      = ON
/* CONFIG5H */
        #pragma config CPB      = ON
        #pragma config CPD      = OFF
/* CONFIG6L */
        #pragma config WRT0     = OFF
        #pragma config WRT1     = OFF
        #pragma config WRT2     = OFF
        #pragma config WRT3     = OFF
/* CONFIG6H */
        #pragma config WRTD     = OFF
        #pragma config WRTB     = OFF
        #pragma config WRTC     = OFF
/* CONFIG7L */
        #pragma config EBTR0    = OFF
        #pragma config EBTR1    = OFF
        #pragma config EBTR2    = OFF
        #pragma config EBTR3    = OFF
/* CONFIG7H */
        #pragma config EBTRB    = OFF

typedef __code unsigned char *CODEPTR;

void main()
{
    unsigned int uaddr = 0;
    CODEPTR c;
    TRISA = 0;
    TRISB = 0;
    TRISC = 0;
  /* Set Default State of OSC */
  OSCCON = 0b00110000;
  PIR2 = PIE2 = OSCTUNE = 0;
  IPR2 = 0xFF;

    /* Disable IRQs */
    INTCONbits.GIE = 0;

    /* enable EUSART */
    RCSTAbits.SPEN = 1;
    /* baud rate to 2400 Baud */
    SPBRG = 25;
    /* enable TX + only HI byte divisor */
    TXSTA = 0b00100100;

    c = 0x0;
    do
    {
        TXREG = *c++;
        while (!TXSTAbits.TRMT);
        ClrWdt();
    } while (c != (CODEPTR)0x10000);

    while (1)
  {
    /* Recharge WDT */
        ClrWdt();
  }
}

Компилируем и получаем:

data_reader.hex

:020000040000FA:10000000926A936A946A300ED36E9B6AA06AA16A60:10001000FF0EA26EF29EAB8E190EAF6E240EAC6E6A:10002000006A016A026A00C0F6FF01C0F7FF02C061:10003000F8FF0900F5CFADFF002A02E3014A022ACA:10004000ACA2FED70400005005E1015003E10250CC:0C005000010A01E0E8D70400FED712000E:020000040030CA:03000100081D0FC8:02000500018177:0600080000C00FE00F40F4:00000001FF

Записываем в чип и видим что байтики поехали:

208bdf3d47b5307b13e63514e30118b9.jpg

Далее если действовать по описанному выше алгоритму получаем всю прошивку целиком.

В общем то и всё. Время для резюме.

Применимость уязвимости:

  1. EBTRB/EBTR[3:0] — защита от чтения секторов из других блоков не установлена.

  2. У вас есть два идентичных многостраничных PIC18 чипа с идентичными прошивками.

Как защититься от уязвимости:

  1. EBTRB/EBTR[3:0] — устанавливать защиту от чтения секторов или хотя бы одного сектора, таким образом получить доступ к «чистой прошивке» будет затруднительно.

Однако необходимо помнить, — если вы устанавливаете защиту чтения из других блоков, Вам необходимо убедиться что данные для одного блока компилятор не будет собирать в другом! А он так может.

За сим разрешите откланяться.

Приятного дня и бодрости духа.

© Habrahabr.ru