Кремень FMZ Реклама
Kremen FMHM Реклама

MKS Robin. Часть вторая - укрощение строптивой прошивки.

jmz
Идет загрузка
Загрузка
13.12.2018
17053
34
Техничка

Подпишитесь на автора

Подпишитесь на автора, если вам нравятся его публикации. Тогда вы будете получать уведомления о его новых статьях.

Отписаться от уведомлений вы всегда сможете в профиле автора.

78
MKS Robin. Часть вторая - укрощение строптивой прошивки.
Написание альтернативной прошивки или экстремальное мигание светодиодом.

Штурм и натиск

Не удовлетворившись диагностикой по фотографии, проведенной в первой части я заказал себе MKS Robin для более близкого знакомства.

Для обновления прошивки на MKS Robin используется предустановленный загрузчик, который MKS никогда и никому не дает. Чтение памяти микроконтроллера внешним отладчиком заблокировано, а разблокировка вызывает очистку всей флеш-памяти, необратимо превращая MKS Robin в тыкву дорогую отладочную плату. Таким образом, единственный вариант добраться до загрузчика - это изнутри самой прошивки. Ограничения на чтение памяти не распространяются на программу внутри микроконтроллера.

Тренировка на кошках, роль которых выполняла отладочная плата на STM32F103ZET6, дала свои результаты и к моменту приезда MKS Robin у меня уже была пара загрузчик/прошивка, которые я собирался использовать для насильственного вывода Robin из сумрака.

Разумеется ничего из запланированного не заработало. Загрузчик охотно принимал альтернативную прошивку, но после обновления микроконтроллер наглухо зависал. Перепробовав несколько вариантов альтернативных прошивок и не сумев даже зажечь светодиод (пин PB2) я был вынужден расписаться в собственной криворукости и отказаться от идеи штурма в лоб.

В процессе работы прошивка MKS Rоbin пишет диагностическую информацию в COM порт (UART3), поэтому альтернативным вариантом проникновения на охраняемую территорию стала модификация оригинальной прошивки. Анализ и модификация адресов текстовых строк подтвердил правильность изначальной оценки расположения прошивки во флеш памяти, что позволило загрузить код в online disassembler.

Ручная декомпиляция с незнакомого ассемблера, а с ARM ассемблером я никогда раньше не встречался, была весьма познавательной и эмоционально насыщенной. Результатом же стало очередное подтверждение собственной криворукости и интеллектуальной немощи - инициализация микроконтроллера в моей прошивке такая же, как в оригинальной, но оригинальная работает, а моя - нет.

С другой стороны, в процессе изучения ARM ассемблера я понял, что могу собрать небольшую переносимую функцию, которая будет сохранять работоспособность независимо от расположения в памяти. Осталось только найти место в оригинальной прошивке подходящее для размещения троянского кода. Поскольку с инициализацией микроконтроллера у меня не сложилось, я стал искать функции, вызываемые после того, как вся необходимая периферия уже сконфигурирована и работает. Тут очень кстати пришлась диагностическая информация, выводимая на COM порт - периферия в этот момент уже сконфигурирована, иначе я ничего не увидел бы в терминале, а текстовые строки позволяют локализовать участок прошивки, который этот текст выводит.

После отбраковки нескольких вариантов была найдена функция достаточно большая, чтобы в нее можно было поместить полезную нагрузку. Для проверки работоспособности троянского метода мне потребовался внешний светодиод, подключенный к линии DIR драйвера E0. Эта нога микроконтроллера гарантированно использовалась в нужном режиме (GPIO Output).

В моргающем свете диода проблема кривых рук стала выглядеть несколько менее безнадежной и уступила место проблеме снятия данных с закрытого устройства. Наиболее простым выходом виделся шестнадцатеричный дамп интересующей меня области памяти на COM порт с последующей конвертацией в бинарный формат.

Полезная нагрузка в виде переносимый код для форматированного вывода содержимого флеш памяти в шестнадцатеричном виде на UART3:

[CODE] volatile uint32_t i;

uint8_t data;

uint8_t hex;

uint32_t pointer;

GPIOA->BSRR = (uint32_t)GPIO_PIN_15 << 16U;

for (pointer = 0x08000000 ; pointer < 0x08080000; pointer ++) {

if ((pointer & 0x0000000f) == 0) {

while ((USART3->SR & UART_FLAG_TXE) != UART_FLAG_TXE) {}

hex = ((uint8_t)((pointer >> 28) & 0x0F)) + 48; if (hex > 57) hex += 7;

USART3->DR = (uint16_t) hex;

while ((USART3->SR & UART_FLAG_TXE) != UART_FLAG_TXE) {}

hex = ((uint8_t)((pointer >> 24) & 0x0F)) + 48; if (hex > 57) hex += 7;

USART3->DR = (uint16_t) hex;

while ((USART3->SR & UART_FLAG_TXE) != UART_FLAG_TXE) {}

hex = ((uint8_t)((pointer >> 20) & 0x0F)) + 48; if (hex > 57) hex += 7;

USART3->DR = (uint16_t) hex;

while ((USART3->SR & UART_FLAG_TXE) != UART_FLAG_TXE) {}

hex = ((uint8_t)((pointer >> 16) & 0x0F)) + 48; if (hex > 57) hex += 7;

USART3->DR = (uint16_t) hex;

while ((USART3->SR & UART_FLAG_TXE) != UART_FLAG_TXE) {}

hex = ((uint8_t)((pointer >> 12) & 0x0F)) + 48; if (hex > 57) hex += 7;

USART3->DR = (uint16_t) hex;

while ((USART3->SR & UART_FLAG_TXE) != UART_FLAG_TXE) {}

hex = ((uint8_t)((pointer >> 8) & 0x0F)) + 48; if (hex > 57) hex += 7;

USART3->DR = (uint16_t) hex;

while ((USART3->SR & UART_FLAG_TXE) != UART_FLAG_TXE) {}

hex = ((uint8_t)((pointer >> 4) & 0x0F)) + 48; if (hex > 57) hex += 7;

USART3->DR = (uint16_t) hex;

while ((USART3->SR & UART_FLAG_TXE) != UART_FLAG_TXE) {}

hex = ((uint8_t)( pointer & 0x0F)) + 48; if (hex > 57) hex += 7;

USART3->DR = (uint16_t) hex;

while ((USART3->SR & UART_FLAG_TXE) != UART_FLAG_TXE) {}

USART3->DR = 58;

while ((USART3->SR & UART_FLAG_TXE) != UART_FLAG_TXE) {}

USART3->DR = 32;

}

data = *((uint8_t *)pointer);

while ((USART3->SR & UART_FLAG_TXE) != UART_FLAG_TXE) {}

hex = (data >> 4) + 48; if (hex > 57) hex += 7;

USART3->DR = (uint16_t) hex;

while ((USART3->SR & UART_FLAG_TXE) != UART_FLAG_TXE) {}

hex = (data & 0x0f) + 48; if (hex > 57) hex += 7;

USART3->DR = (uint16_t) hex;

while ((USART3->SR & UART_FLAG_TXE) != UART_FLAG_TXE) {}

if ((pointer & 0x0000000f) == 0x0000000f) {

USART3->DR = 10;

} else {

USART3->DR = 32;

}

}

for (;;) {

GPIOA->BSRR = GPIO_PIN_15;

for (i = 0; i < 3000000; i++) __NOP();

GPIOA->BSRR = (uint32_t)GPIO_PIN_15 << 16U;

for (i = 0; i < 3000000; i++) __NOP();

}

[/CODE]

В скомпилированном виде этот ужас занимает 476 байт и, при правильном размещении, не торопясь выдает почти два мегабайта текста, после чего переходит к бесконечному миганию светодиодом.

Секретный загрузчик находится в первых 28-ми килобайтах флеш памяти, поэтому оставалось только перевести полученные данные в бинарный формат и отрезать все лишнее.

Четырехдневный марафон успешно завершился заливкой загрузчика на reprap.org, после чего можно было со спокойной совестью паковать чемодан и ехать в командировку.

Шифровка из китая

Трофейный загрузчик прекрасно разместился на отладочной плате, не высказывая никакого неудовольствия от отсутствия привычных LCD экрана и SPI флеш-памяти. Наличие подключенного ST-Link так же не вызвало у загрузчика никаких возражений, поэтому разблокировка микроконтроллера MKS Rоbin была отложена на неопределенный срок и вся работа велась на отладочной плате.

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

Поворотным моментом стала попытка запустить альтернативную прошивку на MKS Robin. Загрузчик отрапортовал об успешном обновлении прошивки, после чего микроконтроллер наглухо завис, в точности повторив свое поведение на ранней стадии знакомства. Причина столь разного поведения абсолютно одинаковых микроконтроллеров нашлась очень быстро, ей оказалось отличие методов заливки прошивки. MKS Robin обновлялась с SD карты с использованием штатного загрузчика, в то время как отладочная плата, для ускорения процесса, прошивалась через ST-Link. Прошивка отладочной платы с SD карты полностью воспроизвела поведение MKS Robin, а подключенный отладчик тактично намекнул, что микроконтроллер поймал HardFault exception.

Сравнение содержимого памяти микроконтроллера и файла с прошивкой показало, что совпадают только первые 320 байт. Заливка оригинальной прошивки дала еще более интересную картину - первые 320 байт заливаются без изменений, следующие 30 килобайт меняются, а начиная с 31041-го байта все опять заливается как есть. Таким образом MKS Robin использует частичное шифрование прошивки (30КБ из ~430КБ). Все мои альтернативные прошивки были короче 30 килобайт и полностью убивались в процессе заливки. Модифицированная же прошивка имела полезную нагрузку в нешифрованной области, что и позволило сделать дамп содержимого флеш памяти. Причиной повышенной криворукости и многодневной фрустрации оказался альтернативно-одаренный подход китайских разработчиков к шифрованию прошивки.

Поскольку загрузчик корректно работал на отладочной плате, у меня была возможность подавать на вход произвольные данные и сравнивать исходные данные с их расшифровкой. Через несколько минут стало очевидно, что данные шифруются 32-х байтным ключем с использованием национального китайского алгоритма шифрования, так же известного как XOR. Приятной особенностью этого алгоритма является то, что двойное шифрование восстанавливает исходный данные. Таким образом залив прошивку через загрузчик на отладочную плату и сделав дамп памяти через отладчик я получаю готовую к использованию шифрованную прошивку.
Секретный китайский ключ

A3BDAD0D4111BB8DDC802DD0D2C49B1E26EBE3334A15E40AB3B13C93BBAFF73E
Пару минут спустя MKS Robin бодро мигал распаянным на плате светодиодом, который я безуспешно пытался зажечь в самом начале нашего знакомства.

Дальше - больше. К мигающему светодиоду добавились:
  • поддержка COM порта для вывода отладочной информации
  • поддержка SD карты, для создания резервной копии загрузчика
  • PlatformIO проект со скриптами для автоматического шифрования прошивки в ‘Robin.bin’ пригодный для обновления с SD карты штатного загрузчика

В результате получился полный набор инструментов, позволяющий запустить на MKS Robin произвольный код (Marlin) используя штатные средства обновления прошивки. Код традиционно доступен на GitHub

Подпишитесь на автора

Подпишитесь на автора, если вам нравятся его публикации. Тогда вы будете получать уведомления о его новых статьях.

Отписаться от уведомлений вы всегда сможете в профиле автора.

78
Комментарии к статье