В февралe 2020 года на Хабре появилась увлекательная статья про преобразование программатора ST-Link v2 в аппаратный ключ шифрования. Уже тогда в комментариях появились жалобы на то, что результат не удаётся повторить, но они остались без ответа.
За прошедшее время до меня дошла пара посылок с Aliexpress и теперь, самостоятельно пройдя весь путь, я попытаюсь представить более или менее полную инструкцию с комментариями, которая поможет неспециалисту перепрошить микропроцессор в китайском клоне ST-Link v2. Постараюсь не повторять уже известное, поэтому про пайку контактов и использование GPG с аппаратным ключом смотрите в исходной статье.
Аппаратная часть
Первое, на что нужно обратить внимание ещё до покупки, — это комментарии к товару на Aliexpress. Скорее всего, кто-нибудь уже написал про марку микропроцессора из серии STM32***, который установлен в изделие у данного конкретного продавца. Впрочем, мой опыт показал, что комментарии не помогают, и даже у одного продавца могут попадаться программаторы с разной начинкой.
Главное отличие микропроцессоров с нашей точки зрения — это объём флэш-памяти размещённой на кристалле. На одном из форумов попадалось мнение, что китайцы могут выпустить программатор на дешёвом процессоре без аппаратной поддержки USB и эмулировать USB программно, дёргая одну из ножек процессора, но это, маловероятно, поскольку требует существенной доработки исходного кода программатора.
Оригинальный ST-Link v2 от фирмы STMicroelectronics базировался на чипе STM32F103 той же фирмы, имеющего 128 Кбайт флэш-памяти.
В первой посылке мне пришёл программатор на китайском клоне STM32F103 — CKS32F103C8*. В интернете не очень много информации по этому чипу. Его обзывают то CS32F то CKS32F. Даже даташит на китайском куда-то пропал, так что сообщаю, что на моём чипе всего 64 Kбайт флэш-памяти. Вроде бы, процессоры CKS32F103CB* имеют 128 Kбайт флэша, но это только слухи из интернета.
Во второй посылке оказались изделия на основе STM32GC102CB. Что это такое, похоже, не знает никто, но в нём опять 64 Kбайт флэш-памяти (и, к счастью, есть аппаратный USB).
Фокус в том, что прошивка программатора в минимальной конфигурации укладывается в эти 64 Кбайт и китайцы явно пытаются сэкономить копеечку. Про обновление прошивки ST-Link v2 в качестве программатора можно почитать в статье на хабре.
Программное обеспечение
Проще всего собрать комплект ПО под каким-нибудь Linux, основанном на Debian. В моём случае это был antiX Linux и репозитории Debian 9 Stretch.
make и git у меня были, так что мне хватило установки специфических пакетов:
sudo apt install gcc-arm-none-eabi
sudo apt install libnewlib-arm-none-eabi
sudo apt install openocd
Плюс к этому, установилось несколько зависимостей.
Почему-то мне не удалось найти пакеты c Arm Embedded Toolchain в стандартных репозиториях CentOS. Может искал не по тем ключевым словам.
Под Windows всё придётся настраивать самостоятельно:
- GNU Arm Embedded Toolchain
- OpenOCD
- GNUWin32 для вспомогательных утилит
Сам так и не попробовал, но вдруг кому пригодится.
Проверка и настройка программатора и целевой платы
Ещё раз напоминаю, что для дальнейших шагов вам потребуется подпаять проводки от программатора на целевую плату, взятую из другого программатора. Смотрим оригинальную статью.
Хочу обратить внимание на то, что у разных программаторов разная разводка контактов. Получив вторую посылку я почти убедил себя, что пришли бракованные программаторы, просто потому, что переткнул пучок проводов из одного программатора в другой, не меняя их раскладку. В интернете попадаются жалобы, что на плате идёт одна маркировка выводов, а на корпусе нарисована другая. Маркировка на плате более достоверна.
Будем считать, что нашим основным инструментом управления программатором является OpenOCD.
Для начала нам необходимо разблокировать доступ к флэш-памяти на целевой плате, поскольку изначально он закрыт и на запись и на чтение (для сохранения в тайне содержимого текущей прошивки).
Ситуация 1: целевая плата с микросхемой ST32F1** (и STM32GC102CB)
$ openocd -f interface/stlink-v2.cfg -f target/st32f1x.cfg -c 'init; reset halt; stm32f1x unlock 0; reset halt;exit'
...
Info : device id = 0x20036410
Info : flash size = 64kbytes
stm32x unlocked.
INFO: a reset or power cycle is required for the new settings to take effect.
Ситуация 2: целевая плата с микросхемой CKS32F1**
$ openocd -f interface/stlink-v2.cfg -f target/st32f1x.cfg -c 'init; reset halt; stm32f1x unlock 0; reset halt;exit'
...
Info : STLINK v2 JTAG v29 API v2 SWIM v7 VID 0x0483 PID 0x3748
Info : using stlink api v2
Info : Target voltage: 3.174295
Warn : UNEXPECTED idcode: 0x2ba01477
Error: expected 1 of 1: 0x1ba01477
Файлы interface/stlink-v2.cfg и target/st32f1x.cfg — это скрипты на языке OpenOCD, настраивающие параметры программатора и целевой платы. Разработчики китайского клона назначили ID своего процессора 0x2ba01477, а у STM321F1*** было 0x1ba01477. В принципе, в стандартной поставке OpenOCD есть несколько конфигураций с нужным ID, но я решил, что во всём остальном клон должен быть похож именно на STM321F1**, и просто скопировал файл конфигурации к себе и поправил ID в команде set _CPUTAPID 0x1ba01477.
mkdir ~/.openocd/{target}
cp /usr/share/openocd/scripts/target/stm32f1x.cfg ~/.openocd/target/cks32f1x.cfg
sed -i 's/0x1ba01477/0x2ba01477/' /.openocd/target/cks32f1x.cfg
В дальнейшем, при прошивке плат с процессором CKS32F1** я просто указываю опцию -f target/cks32f1x.cfg.
Теперь можно проверить объём памяти. Можно, например, сгенерировать случайный файл размером 128 Кбайт и попытаться записать его во флэш-память. Тогда OpenOCD выдаст ошибки примерно такого вида:
Error: checksum mismatch - attempting binary compare
diff 0 address 0x08010000. Was 0x00 instead of 0x7d
diff 1 address 0x08010001. Was 0x50 instead of 0x40
Я просто попытался разблокировать второй банк памяти:
$ openocd -f interface/stlink-v2.cfg -f target/cks32f1x.cfg -c 'init; reset halt; stm32f1x unlock 1; reset halt;exit'
...
Error: flash bank 1 does not exist
...
Если моя методика неверна — пишите, перепроверю чипы другим способом.
Компиляция прошивки GNUK
Когда мы определились с объёмом флэш-памяти на чипе, можно выбрать версию прошивки GNUK, которая и обеспечивает функциональность нашей платы как аппаратного ключа шифрования.
Если удариться в историю, то в 2003 году компания g10code, созданная ведущим разработчиком GnuPG, написала спецификации протокола для взаимодействия программы, соответствующей спецификации OpenPGP, с аппаратным ключом шифрования, реализованным по стандарту смарткарты ISO/IEC 7816. Ещё одна компания — ZeitControl написала проприетарный код, реализующий необходимые алгоритмы, для своей Card OS (и своей смарт-карты). Нынче получившуюся игрушку для гиков можно купить в интернет магазине FLOSS-Shop под названием OpenPGP Smart Card по несколько завышенной цене в 17,90 €. Не забудьте, что кроме смарт-карты, вам потребуется купить ещё и считыватель для неё.
Несколько позже в игру вступила некоммерческая организация Free Software Initiative of Japan — этакая национальная ячейка FSF. Именно они разработали прошивку GNUK для процессоров STM32*, которая эмулирует считыватель смарт-карт со вставленной в него OpenPGP Smart Card.
На настоящий момент существует две стабильные версии GNUK — 1.0 и 1.2, отличающиеся базовой ОС и набором алгоритмов шифрования. Я бы порекомендовал брать их из дебиановского репозитория, где они находятся в бранчах STABLE-BRANCH-1-0 и STABLE-BRANCH-1-2 соответственно.
В части шифрования обе версии базируются на библиотеке PolarSSL, а в аппаратной части версия 1.0 базируется на ChibiOS_2.0.8, а версия 1.2 на библиотеке chopstx.
Для нас важно, что версия 1.0 компилируется в исполняемый код объёмом 62 Кбайт, а версия 1.2 — в 112 Кбайт.
Итак, если у нас есть 128 Кбайт флэша, то компилируем версию 1.2
git clone https://salsa.debian.org/gnuk-team/gnuk/gnuk.git
cd gnuk
git checkout STABLE-BRANCH-1-2
# подтягиваем библиотеку 'chopstx'
git submodule update --init
cd src
# --target определяет ножки процессора, на которые привязан светодиод и управление USB
# Значение --target по умолчанию - FST_01 - не совместимо с ST-LINK v2
# --vidpid="234b:0000" соответствует USB считывателю смарт-карт
# --enable-factory-reset - возможность программного обнуления ключа при забытом PIN
./configure --target=ST_DONGLE --vidpid="234b:0000" --enable-factory-reset
make
Если у нас есть только 64 Кбайт флэша, то мы компилируем версию 1.0
git clone https://salsa.debian.org/gnuk-team/gnuk/gnuk.git
cd gnuk
git checkout STABLE-BRANCH-1-0
cd src
# На удивление, значение по умолчанию в этой версии - OLIMEX_STM32_H103 - совместимо с ST-LINK v2
# --enable-keygen - включает внутреннюю генерацию секретного ключа
./configure --vidpid="234b:0000" --enable-keygen
make
Возможно, в этот момент вы получите множество однотипных сообщений об ошибках сборки:
usb_lld.o: In function `usb_lld_set_data_to_recv':
usb_lld.c:(.text.usb_lld_set_data_to_recv+0x0): multiple definition of `usb_lld_set_data_to_recv'
main.o:/mnt/f/DocSasha/devzone/gnuk/src/usb_lld.h:136: first defined here
...
Проблема в том, что компилятор по непонятной причине игнорирует директиву inline в заголовочном файле usb_lld.h и компилирует по экземпляру функции usb_lld_set_data_to_recv в каждом модуле, в котором этот заголовочный файл использовался.
Для решения проблемы достаточно заменить inline-функцию на эквивалентный макрос #define и снова запустить make. Для правки можно использовать patch, а можно просто удалить строки, помеченные минусом, и добавить строку, помеченную плюсом.
--- a/src/usb_lld.h
+++ b/src/usb_lld.h
@@ -131,10 +131,7 @@ extern void usb_lld_set_feature (uint8_t feature);
extern void usb_lld_set_data_to_send (const void *p, size_t len);
-extern inline void usb_lld_set_data_to_recv (void *p, size_t len)
-{
- usb_lld_set_data_to_send ((const void *)p, len);
-}
+#define usb_lld_set_data_to_recv(p, len) usb_lld_set_data_to_send ((const void *)p, len)
extern void usb_lld_prepare_shutdown (void);
extern void usb_lld_shutdown (void);
Заливаем прошивку в чип. Не забываем выбрать правильный cfg файл и помним, что в версии 1.2 образ прошивки лежит в src/build/gnuk.elf, а в версии 1.0 в src/gnuk.elf
# загружаем прошивку
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c 'program build/gnuk.elf verify reset exit'
# и запрещаем доступ к флэшу
openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c init -c "reset halt" -c "stm32f1x lock 0" -c reset -c exit
Поскольку у меня все процессоры с 64 Кбайт флэш-памяти я не смог проверить, надо ли применять команду stm32f1x lock 1, чтобы заблокировать доступ ко второму банку памяти.
Заключение
Ну вот и всё. У меня получилось. Надеюсь, что и у вас получится.
Swarg64
Совсем отстал от жизни, оказывается GigaDevice не единственные, кто при создании чипов посматривал в сторону AVR/STM32.