Эта статья не руководство к действию хакеров, это подсказка, как правильно используя предоставленные 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*.
Смотрим, зеленым биты защиты EEPROM, красным - биты защиты BOOT блока, синим - биты защиты кода, блоки 0-3.
CPB/WRTB, CP[3:0]/WRT[3:0] - защита от чтения/записи кода, сперва BOOT затем блоки с 0-3.
CPD/WRTD - Защита от чтения/записи EEP данных.
А вот самые интересные биты защиты: EBTRB/EBTR[3:0] - защита от чтения секторов из других блоков.
Смотрим на конфигурацию "защищенного" чипа (защита установлена):
Смотрим на конфигурацию, - всё защищено!
Читаем:
Ноль по вдоль! Всё защищено!
И вроде проблем нет.
А они есть, читаем в даташите:
Написано, - "Биты защиты можно записать только из 1 в 0, наоборот нельзя, чтобы записать в бит защиты 1 нужно стереть все блоки или блок соотв. биту защиты чтобы установить соотв. бит защиты в 1". И опа, оно!
Стираем BOOT блок, записываем в него свой код, который будет для нас читать прошивку и отдавать её по EUSART.
Да, но МЫ ПОТЕРЯЛИ ТО ЧТО БЫЛО В BOOT блоке, и тут появляется тема "ДАЙТЕ ДВЕ", берем второй чип с такой же прошивкой и проделываем то же самое только например с блоком 0, причем код помещаем в самый конец блока, всё остальное заполняем NOP'ами, чтобы с определенной вероятностью наш код начался не "с середины". Вот так:
Затем склеиваем блоки в любом HEX редакторе, и вуаля! Прошивка на руках.
Код есть, вытаскиваем EEP. Точно так же:
Выглядит довольно просто. Однако проверим так ли это на самом деле.
Первая задача, как затереть сектор. Читаем даташит:
Стереть весь чип: 0x3F8F, BOOT: 0x0084, Block0: 0x0180.
Только вот вопрос чем тереть то?
Берем PICKIT3 (а он "из коробки" трет весь чип, нам не подходит):
Ни для кого не секрет что исходный код этого программатора открыт. Идем на Microchip и качаем исходники прошивки (PICkit3 Programmer Application v3.10).
Изучаем исходники... понимаем что это провал, внутри программатора интерпретатор команд, ага, значит сами команды в исходном коде приложения PIC KIT Programmer...
Изучаем исходники (они в том же архиве)... понимаем что и тут интерпретатор!!! Да чтож такое!!! А команды то где?
А они заботливо сложены в базе данных PK2DeviceFile.dat
Первая мысль написать небольшой скрипт править базу данных, собственно давным давно я так и сделал, но теперь в поиске используя PK2DeviceFile.dat неожиданно натолкнулся на редактор этой базы (pickit2-editor, только прежде чем получить результат мне пришлось исправить несколько багов ибо при попытке сохранить подправленный файл базы прога "вылетала" неисправимо файл базы запортив).
Запускаем PicKit2 Editor и находим имя скрипта стирания нашего чипа:
Переходим в скрипты, находим наш, и правим соотв (0x3F8F -> 0x0084).
Правим 0x3F на 0x00, а 0x8F на 0x84. Теперь при нажатии на кнопку ERASE, PicKit Programmer будет стирать не весь чип, а только BOOT блок.
Так, как стереть поняли.
Теперь код ридера, предлагается использовать стандартный интерфейс EUSART установленный в чипе, будем читать код из памяти и отдавать его по EUASRT, подключим его к любому конвертеру интерфейсов RS232->USB (убедившись что конвертер соотв. напряжению питания чипа). Смотрим опять в даташит:
Отлично, 18-ый пин соединяем с конвертером интерфейсов (не забываем объединить земли, у меня всё соединено через общий хаб):
Всё мы готовы. Напишем код ридера:
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
Записываем в чип и видим что байтики поехали:
Далее если действовать по описанному выше алгоритму получаем всю прошивку целиком.
В общем то и всё. Время для резюме.
Применимость уязвимости:
EBTRB/EBTR[3:0] - защита от чтения секторов из других блоков не установлена.
У вас есть два идентичных многостраничных PIC18 чипа с идентичными прошивками.
Как защититься от уязвимости:
EBTRB/EBTR[3:0] - устанавливать защиту от чтения секторов или хотя бы одного сектора, таким образом получить доступ к "чистой прошивке" будет затруднительно.
Однако необходимо помнить, - если вы устанавливаете защиту чтения из других блоков, Вам необходимо убедиться что данные для одного блока компилятор не будет собирать в другом! А он так может.
За сим разрешите откланяться.
Приятного дня и бодрости духа.
MichaelBorisov
Спасибо за подробное описание и анализ!!!