Как отобрать у микроконтроллера QN902x его прошивку

Введение

Когда у меня появился умный чайник Xiaomi Mi Smart Kettle Pro — у меня возник тот же вопрос, что и у многих его пользователей: почему его нельзя включить удаленно? Чайник позволяет подключиться к нему через блютуз, задать температуру которую он будет поддерживать и еще пару незначительных параметров, но его невозможно удаленно включить, что нивелируют его ценность как компонента умного дома.

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

Аппаратная часть

Благо (для меня), чайники мрут как мухи, и на барахолке несложно найти мертвый экземпляр за копейки. Внутри чайника всего две платы: плата с силовой частью и плата с логикой. Подвергнув мертвечинку вивисекции можно добыть из него блютуз-модуль с микроконтроллером, зачастую переживающий ТЭН.

Вокруг микроконтроллера нет ни капли силикона и виднеется окисьВокруг микроконтроллера нет ни капли силикона и виднеется окись

Блютуз-модуль должен быть намертво залит силиконом, но китайцы часто экономят и на плате можно встретить всего пару капель силикона, из-за чего модули довольно быстро коррозируют при активном использовании чайника. После нескольких циклов отмачивания в растворителе и грубой чистке щеткой с платы снимаются хлопья силикона, и она становится пригодна для анализа. Платка компактная, состоит из трех основных компонентов (МК, флешка и предположительно микроконтроллер сенсорных кнопок). На платке достаточно много пятачков для доступа к ключевым цепям для тестирования и прошивки. Микроконтроллер достаточно большой, и, слава богу, не BGA, что позволяет отследить дорожки на плате. Вооружившись скальпелем и мультиметром можно выяснить какие ноги микроконтроллера к каким точкам подключены (добрые китайцы оставили на плате шелкографию и у точек есть имена для их идентификации). После прозвона выходит примерно следующая картина:

Часть схемы с точками и разьемамиЧасть схемы с точками и разьемамиРасположение точек (CAD MS Paint)Расположение точек (CAD MS Paint)

На самой плате не так и много свободного места, к тому же мне попадались платы как минимум двух видов (различия незначительные), было необходимо как-то зарисовать расположение точек, а заодно и назначение разъемов. Всего два разъема, каждый с тремя проводами, все разных цветов. Назначение всех проводов кроме одного удалось выяснить сняв маску со второй, силовой платы, однако назначение одного из контактов так и осталось для меня загадкой. На одной версии силовой платы он не подключен вовсе, на другой через резистор посажен на землю. Вероятно, он был нужен когда-то раньше, а затем от него отказались, но для совместимости решили форм-фактор плат не изменять. Назначение U3 для меня остается не совсем ясным, но похоже что это контроллер сенсорных кнопок. U1 это явно SPI-флешка, но никакой документации о нет по нанесенным на корпус кодам я найти не смог.

QN9020

У использованной тут модели чипа (QN9022) память для программ не интегрирована внутрь самого чипа, но используется внешняя внешняя флеш-память с интерфейсом SPI. Казалось бы — бери и читай, но зачастую в таких случаях используется шифрование данных на флешке, с ключем уникальным для конкретного микроконтроллера. И этот чип оказался не исключением, в даташите явно сказано:

Защита флешиЗащита флеши

Стоит обратить внимание на вторую заметку: код, выполняющийся внутри микроконтроллера, всегда может вычитывать из флеши данные в их декодированном виде. И даже SWD блокируется после прошивки. У этого чипа есть два брата-близнеца (QN9020 и QN9021) с важным отличием от QN9022: у них флешка интегрирована внутрь чипа. Однако, структура и способ доступа к ней никак не отличается, это все еще отдельный кристалл с интерфейсом SPI.

Тем не менее, никто не гарантирует что процесс прошивки был выполнен в соответствии с рекомендациями и интерфейс SWD действительно отключен, так что стоит проверить этот вариант в первую очередь. Китайцы оставили на плате точки для подключения, видимо использовавшиеся для отладки, однако SWCLK соединено с линией питания резистором с нулевым сопротивлением R4. Зачем это может быть сделано? У этого микроконтроллера, как и у многих других, одни и те же ноги могут быть сконфигурированы для разных целей, и нога P0_7 может быть как SWCLK, так и AIN3.

Мультиплексирование ногМультиплексирование ног

Зачем нам тут AIN3 к подключать к питанию? На это нам ответит схема устройства АЦП.

Схема АЦПСхема АЦП

AIN3 может быть сконфигурирован как опорное напряжение для АЦП. Учитывая, что у нас аналоговый температурный сенсор, это ожидаемый ход. Значит, для работы SWD надо выпаять R4, и подпаявшись к пятачкам T9-T12 можно попробовать подключиться к микроконтроллеру, для чего я попытался использовать J-Link. Однако, результат немного предсказуем, контроллер не отвечает и наиболее простой путь оказался недоступен.

Загрузчик

Значит, нужно копать дальше, и в первую очередь изучить как происходит процесс прошивки. А происходит он через загрузчик, что хороший знак: чем больше кода, тем больше шансов встретить баг сюрприз-фичу. Загрузчик может работать через UART или SPI и имеет простой командный интерфейс. В нашем случае, необходимые для прошивки пины через UART вынесены на точки T1-T3, и остается подпаять их к UAB-UART преобразователю.

Блютуз-модуль спаянный с USB-UART преобразователемБлютуз-модуль спаянный с USB-UART преобразователем

Рассмотрим команды загрузчика:

CMD Code

UART Commands

Functions

0×33

B_C_CMD

Build connection with bootloader.

0×34

SET_BR_CMD

Set UART baud rate used in ISP mode.

0×35

SET_FLASH_CLK_CMD

Set clock frequency used by QN902x«s flash.

0×36

RD_BL_VER_CMD

Read bootloader version.

0×37

RD_CHIP_ID_CMD

Read the chip number of QN902x.

0×38

RD_FLASH_ID_CMD

Read flash ID of QN902x.

0×39

SET_APP_LOC_CMD

Set application routine download location, internal SRAM or Flash.

0×3A

SETUP_FLASH_CMD

Set the flash operation commands.

0×3B

SET_ST_ADDR_CMD

Set the start address of Read, Program, Erase and Verify commands.

0×3C

SET_APP_SIZE_CMD

Set the application size.

0×3E

SET_APP_CRC_CMD

Set the CRC result of verifying application.

0×40

SET_APP_IN_FLASH_ADDR_CMD

Set the starting address of application storage location.

0×42

SE_FLASH_CMD

Sector erase flash

0×43

BE_FLASH_CMD

Block erase flash

0×44

CE_FLASH_CMD

Chip erase flash

0×45

PROGRAM_CMD

Download.

0×46

RD_CMD

Read NVDS.

0×47

VERIFY_CMD

Verify the application.

0×48

PROTECT_CMD

Enter into protect mode.

0×49

RUN_APP_CMD

Run application.

0×4A

REBOOT_CMD

Reboot system. (software reset)

0×4B

WR_RANDOM_DATA_CMD

Write a random number to Bootloader.

0×4C

SET_APP_IN_RAM_ADDR_CMD

Set the starting address of application location in the SRAM.

0×4D

SET_APP_RESET_ADDR_CMD

Set the address of application entry point.

И там же, рядом, карта разметки флеши:

Карта разметки флешиКарта разметки флеши

В глаза бросается команда чтения RD_CMD, которая позволяет прочитать флешку по указанному адресу. Казалось бы — задача решена, но на практике область действия RD_CMD ограничена первыми 0×1000 байтами, в которых располагается NVDS (non-volatile data storage, набор хранимых параметров в виде словаря).

Однако, обнаружилось что загрузчик позволяет загружать программу не только во флешку, но и в ОЗУ. Новый план — загрузить программу в ОЗУ, запустить ее, и используя ее как мост между компьютером, посылающем команды, и микроконтроллером, вычитать содержимое флеши. Последовательность действий для загрузки кода в ОЗУ указана в том же документе:

Диаграмма загрузки программы в ОЗУДиаграмма загрузки программы в ОЗУ

Накидав простую прошивку, которая через простой UART-интерфейс позволяет читать и писать данные по указанным адресам, и спустя много попыток все же запустив ее, я смог вычитать флешку. Однако, вместо ожидаемого образа была получена пустая область NVDS и очень странные данные в области программы:

Странные данныеСтранные данные

Явно виднелась какая-то последовательность, повторяющаяся каждые 0×100 байт, но это отдельная тема. А вот плата перестала запускаться, хотя до этого исправно мигала диодами и сообщала о себе через блютуз. Чтож, видимо придется копать глубже. Посмотрим на карту памяти микроконтроллера:

Карта памятиКарта памяти

Нас интересует область помеченная как «BootLoader». Хотя прошивка и погибла в процессе считывания, код загрузчика никуда из области ПЗУ деться не мог, а значит был доступен для чтения. Анализ, как ожидалось, мог открыть какие-то ошибки реализации командного интерфейсы, или проявить скрытые команды (например, показать куда делись команды с пропавшими из списка команд кодами, например 0×3C, вдруг там окажется UNPROTECT_CMD). Дальше уже ничего сложного: загружаем в ОЗУ свой код, через него вычитываем загрузчик, сохраняем его и анализируем дизассемблером. Анализ кода показал следующее:

  1. При загрузке программы (первая команда PROGRAM_CMD) загрузчик стирает флешку. Разумный ход, хотя в документации об этом не было ни слова.

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

  3. Обнаружилась довольно неожиданная реализация switch-case, поначалу поставившая меня в ступор: вычисление адреса команды выполнялось в подфункции, которая вместо возврата выполняла переход на вычисленный адрес исходя из переданного кода, адреса возврата и массива констант сохраненных по адресу возврата.

После этих новостей у меня появилась новая идея: закоротить флешке ноги MOSI/MISO (или другим способам аппаратно защитить ее от стирания) на время выполнения первой команды PROGRAM_CMD, однако мне не нравился этот способ своей неэлегантностью и неприменимостью к чипам QN9020/QN9021, и я отложил его на случай если ничего другое не сработает. Продолжив ковырять загрузчик просто из интереса, внезапно мне бросились в глаза команды WR_RANDOM_DATA_CMD и SETUP_FLASH_CMD. Про WR_RANDOM_DATA_CMD в документации сказано кратко: The procedure of writing a 32bit random number to bootloader. Зачем, куда? Не ясно, особенно много вопросов вызывает тот факт, что реально загрузчик пишет не 4 байта, а 12 байт присланные с этой командой, однако это снова отдельная тема. Вторая команда куда интереснее:

Описание SETUP_FLASH_CMDОписание SETUP_FLASH_CMD

SPI-флешки имеют командный интерфейс, и хотя формат команд у разных флешек почти всегда одинаковый, то их код может сильно отличаться. Разработчики чипов QN902x приняли славное решениепозволив пользователю самому задавать код команд и тем самым сделав QN9022 почти универсальным чипом в плане совместимости с разными SPI-флешками. В том числе, внимание, код команды для стирания прошивки. И несмотря на слова When the internal Flash is not existent, очень похоже что у чипов с интегрированной флешой этот процесс ничем не отличается. А значит нужно просто-напросто отправить набор команд, в котором коды стирания будут подменена на что-то безобидное, вроде кода чтения или кода выведения флеши из режима сна.

Конец

И… Это работает!

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

Отдельные вопросы вызвал алгоритм шифрования, подозрительно напоминающий XOR с меняющейся маской. А так же мне теперь предстоит длительный процесс разборки сдампленной прошивки, но и это совершенно другая история.

Мою реализацию вышеописанного метода можете посмотреть тут: https://github.com/aleaksah/qn902x-dump

© Habrahabr.ru