Введение
Когда у меня появился умный чайник Xiaomi Mi Smart Kettle Pro - у меня возник тот же вопрос, что и у многих его пользователей: почему его нельзя включить удаленно? Чайник позволяет подключиться к нему через блютуз, задать температуру которую он будет поддерживать и еще пару незначительных параметров, но его невозможно удаленно включить, что нивелируют его ценность как компонента умного дома.
Первая мысль - найти стороннее приложение, которое позволим мне подключиться к чайнику и сжечь его, удаленно включив без воды заставить включаться по утрам, но оказалось что этого до сих пор никто не сделал. При гуглении протокола нашлось его описание, выдранное из приложения для андроида. Само собой, ничего о удаленном включении там не было, зато была заметка что чайник работает на чипе QN9022. QN9022 - чип с открытой документацией, что позволяет проанализировать прошивку малой кровью или написать свою.
Аппаратная часть
Благо (для меня), чайники мрут как мухи, и на барахолке несложно найти мертвый экземпляр за копейки. Внутри чайника всего две платы: плата с силовой частью и плата с логикой. Подвергнув мертвечинку вивисекции можно добыть из него блютуз-модуль с микроконтроллером, зачастую переживающий ТЭН.
![Вокруг микроконтроллера нет ни капли силикона и виднеется окись Вокруг микроконтроллера нет ни капли силикона и виднеется окись](https://habrastorage.org/getpro/habr/upload_files/20c/b48/bd0/20cb48bd09d7269a943d8b885cb80720.png)
Блютуз-модуль должен быть намертво залит силиконом, но китайцы часто экономят и на плате можно встретить всего пару капель силикона, из-за чего модули довольно быстро коррозируют при активном использовании чайника. После нескольких циклов отмачивания в растворителе и грубой чистке щеткой с платы снимаются хлопья силикона, и она становится пригодна для анализа. Платка компактная, состоит из трех основных компонентов (МК, флешка и предположительно микроконтроллер сенсорных кнопок). На платке достаточно много пятачков для доступа к ключевым цепям для тестирования и прошивки. Микроконтроллер достаточно большой, и, слава богу, не BGA, что позволяет отследить дорожки на плате. Вооружившись скальпелем и мультиметром можно выяснить какие ноги микроконтроллера к каким точкам подключены (добрые китайцы оставили на плате шелкографию и у точек есть имена для их идентификации). После прозвона выходит примерно следующая картина:
![Часть схемы с точками и разьемами Часть схемы с точками и разьемами](https://habrastorage.org/getpro/habr/upload_files/f12/74c/434/f1274c434fe0ef4021b7d1dcc77398cd.png)
![Расположение точек (CAD MS Paint) Расположение точек (CAD MS Paint)](https://habrastorage.org/getpro/habr/upload_files/a9d/476/23e/a9d47623ee6618e3391eb42e826b1847.png)
На самой плате не так и много свободного места, к тому же мне попадались платы как минимум двух видов (различия незначительные), было необходимо как-то зарисовать расположение точек, а заодно и назначение разъемов. Всего два разъема, каждый с тремя проводами, все разных цветов. Назначение всех проводов кроме одного удалось выяснить сняв маску со второй, силовой платы, однако назначение одного из контактов так и осталось для меня загадкой. На одной версии силовой платы он не подключен вовсе, на другой через резистор посажен на землю. Вероятно, он был нужен когда-то раньше, а затем от него отказались, но для совместимости решили форм-фактор плат не изменять. Назначение 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, и остается подпаять их к USB-UART преобразователю.
![Блютуз-модуль спаянный с USB-UART преобразователем Блютуз-модуль спаянный с USB-UART преобразователем](https://habrastorage.org/getpro/habr/upload_files/de2/a9f/627/de2a9f627def25307007718a6d94ec9c.jpg)
Рассмотрим команды загрузчика:
CMD Code | UART Commands | Functions |
---|---|---|
0x33 | B_C_CMD | Build connection with bootloader. |
0x34 | SET_BR_CMD | Set UART baud rate used in ISP mode. |
0x35 | SET_FLASH_CLK_CMD | Set clock frequency used by QN902x’s flash. |
0x36 | RD_BL_VER_CMD | Read bootloader version. |
0x37 | RD_CHIP_ID_CMD | Read the chip number of QN902x. |
0x38 | RD_FLASH_ID_CMD | Read flash ID of QN902x. |
0x39 | SET_APP_LOC_CMD | Set application routine download location, internal SRAM or Flash. |
0x3A | SETUP_FLASH_CMD | Set the flash operation commands. |
0x3B | SET_ST_ADDR_CMD | Set the start address of Read, Program, Erase and Verify commands. |
0x3C | SET_APP_SIZE_CMD | Set the application size. |
0x3E | SET_APP_CRC_CMD | Set the CRC result of verifying application. |
0x40 | SET_APP_IN_FLASH_ADDR_CMD | Set the starting address of application storage location. |
0x42 | SE_FLASH_CMD | Sector erase flash |
0x43 | BE_FLASH_CMD | Block erase flash |
0x44 | CE_FLASH_CMD | Chip erase flash |
0x45 | PROGRAM_CMD | Download. |
0x46 | RD_CMD | Read NVDS. |
0x47 | VERIFY_CMD | Verify the application. |
0x48 | PROTECT_CMD | Enter into protect mode. |
0x49 | RUN_APP_CMD | Run application. |
0x4A | REBOOT_CMD | Reboot system. (software reset) |
0x4B | WR_RANDOM_DATA_CMD | Write a random number to Bootloader. |
0x4C | SET_APP_IN_RAM_ADDR_CMD | Set the starting address of application location in the SRAM. |
0x4D | SET_APP_RESET_ADDR_CMD | Set the address of application entry point. |
И там же, рядом, карта разметки флеши:
В глаза бросается команда чтения RD_CMD, которая позволяет прочитать флешку по указанному адресу. Казалось бы - задача решена, но на практике область действия RD_CMD ограничена первыми 0x1000 байтами, в которых располагается NVDS (non-volatile data storage, набор хранимых параметров в виде словаря).
Однако, обнаружилось что загрузчик позволяет загружать программу не только во флешку, но и в ОЗУ. Новый план - загрузить программу в ОЗУ, запустить ее, и используя ее как мост между компьютером, посылающем команды, и микроконтроллером, вычитать содержимое флеши. Последовательность действий для загрузки кода в ОЗУ указана в том же документе:
Накидав простую прошивку, которая через простой UART-интерфейс позволяет читать и писать данные по указанным адресам, и спустя много попыток все же запустив ее, я смог вычитать флешку. Однако, вместо ожидаемого образа была получена пустая область NVDS и очень странные данные в области программы:
Явно виднелась какая-то последовательность, повторяющаяся каждые 0x100 байт, но это отдельная тема. А вот плата перестала запускаться, хотя до этого исправно мигала диодами и сообщала о себе через блютуз. Чтож, видимо придется копать глубже. Посмотрим на карту памяти микроконтроллера:
Нас интересуют области помеченные как "BootLoader" и "ROM". Хотя прошивка и погибла в процессе считывания, код загрузчика никуда из области ПЗУ деться не мог, а значит был доступен для чтения. Анализ, как ожидалось, мог открыть какие-то ошибки реализации командного интерфейса, или проявить скрытые команды (например, показать куда делись команды с пропавшими из списка команд кодами, например 0x3C, вдруг там окажется UNPROTECT_CMD). Дальше уже ничего сложного: загружаем в ОЗУ свой код, через него вычитываем загрузчик, сохраняем его и анализируем дизассемблером. Анализ кода показал следующее:
При загрузке программы (первая команда PROGRAM_CMD) загрузчик стирает флешку. Разумный ход, хотя в документации об этом не было ни слова.
Присутствуют несколько скрытых команд (хотя и не во всех ревизиях чипа), однако мои надежды они не оправдали. Недокументированные команды всего лишь позволяют включить режим быстрой загрузки и сократить время нахождения загрузчика в режиме ожидания активации.
Обнаружилась довольно неожиданная реализация 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 байт присланные с этой командой, однако это снова отдельная тема. Вторая команда куда интереснее:
SPI-флешки имеют командный интерфейс, и хотя формат команд у разных флешек почти всегда одинаковый, то их код может сильно отличаться. Разработчики чипов QN902x приняли славное решение, позволив пользователю самому задавать код команд и тем самым сделав QN9022 почти универсальным чипом в плане совместимости с разными SPI-флешками. В том числе, внимание, код команды для стирания прошивки. И несмотря на слова When the internal Flash is not existent, очень похоже что у чипов с интегрированной флешой этот процесс ничем не отличается. А значит нужно просто-напросто отправить набор команд, в котором коды стирания будут подменена на что-то безобидное, вроде кода чтения или кода выведения флеши из режима сна.
Конец
И... Это работает!
Достаточно после установления связи с загрузчиком отправить первой командой альтернативный набор команд для работы с флешью, затем загрузить в ОЗУ прошивку с реализацией моста-считывателя, вернуть на законное место оригинальный набор команд и запустить прошивку из ОЗУ. После этого можно сдампить флешку, а после перезагрузки плата вернется в рабочее состояние. Разработчики не предусмотрели банальную верификацию очистки флеши, что заняло бы едва ли десяток инструкций и защитило от подобного рода атаки.
Отдельные вопросы вызвал алгоритм шифрования, подозрительно напоминающий XOR с меняющейся маской. А так же мне теперь предстоит длительный процесс разборки сдампленной прошивки, но и это совершенно другая история.
Мою реализацию вышеописанного метода можете посмотреть тут: https://github.com/a-sakharov/qn902x-dump