Чтение прошивки из устройств Xiaomi/Aqara.

_config.yml
2 способа прочитать залоченный JN5169.

Эта статья носит образовательный характер. Если что-то сломаете, сами будете чинить.

Способ первый. OTA exploit
Способ второй. Wire exploit
Заключение

Мне известны два производителя Zigbee устройств использующих чипы JN5169 - это Heiman и Xiaomi (Aqara). Далее просто Aqara.
У Heiman чипы открыты, так что нет никакого интереса их рассматривать. Xiaomi же наоборот, более распространены, имеют больший ассортимент датчиков и закрывают свои чипы для обратного чтения прошивки.

У такого подхода есть преимущества и недостатки. Начну с последнего. Большинство устройств не имеют функции OTA обновления. То что в них зашили на заводе, с тем они и будут, в свое время, утилизированы. Справедливости ради стоит заметить, что даже первые версии устройств 2015-2016 годов выпуска, нормально работают по сей день и нет особой нужды их обновлять. Но замечено, что эти же модели датчиков, купленные позже, имеют версию прошивки повыше. Списка изменений нет, но что-то исправляют.

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

Помимо обновления ПО, есть несколько причин зачем нужен файл с прошивкой:

  1. Замена микроконтроллера вышедшего из строя. Самопроизвольный выход из строя - история достаточно редкая, но не все просто смотрят на тот же “геркон”, некоторые в него еще и залезают;
  2. Сбой EEPROM. А это случается чаще. Суть в том, что при выходе из zigbee сети, в память может записаться некорректная информация и устройство больше не сможет вернуться в сеть. Решить проблему возможно только стиранием EEPROM. Особенностью JN5169 является то, что доступа к EEPROM из bootloader нет. Для его стирания прошивальщиком используется загрузка и исполнение кода в RAM. При уровне блокировки CRP=1 - загружать данные в RAM запрещено;
  3. Исследование. В прошивках можно найти много интересного. Большой объем flash памяти позволяет оставить в коде различные отладочные функции, либо сервисный режим, который может расширить функционал давно знакомого устройства;

Способ первый. OTA exploit

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

1. Bootloader

1.1 Code read protection

Все чипы JN5169 имеют заводской bootloader. Для его запуска нужно установить на выводе 22 (DO1) низкий уровень и перезагрузить микроконтроллер. Информацию об этом можно найти на странице 77 в datasheet и страница 6 Application Note: JN-AN-1003 На странице 6 приведена диаграмма. По ней видно какие условия необходимо соблюсти, чтобы попасть в процедуру “Run UART-Flash serial programmer”, которая позволит производить операции с внутренней flash памятью. Для операции чтения нужно чтобы “Code read protection level” (CRP) равнялся 0. Об уровнях защиты написано на странице 10 Application Note: JN-AN-1003 и странице 12

Bit Field Description Default
5:4 CRP_LEVEL Code Read Protection (CRP) level: 0x3
    00 = CRP level 2 (no programming allowed via UART)  
    10 = CRP level 2 (no programming allowed via UART)  
    01 = CRP level 1 (Flash read protection)  
    11 = CRP level 0 (no protection)  

Посмотрим что там с уровнем CRP в aqara реле. Для этого понадобится UART преобразователь с уровнями 3.3V и утилита JN51xx Production Flash Programmer Мануал по утилите здесь.

Преобразователь подключить согласно схеме. RX-TX, TX-RX. Перед подачей питания на плату реле, замкните вывод /BOOT на GND. Запустите утилиту JN51xx Production Flash Programmer, передав ей следующие параметры: JN51xxProgrammer.exe -V 0 -s COM5 -P 115200 --deviceconfig.
Если все правильно настроено, то ответ будет следующим:

COM5: Detected JN5169 with MAC address 00:15:8D:00:03:00:FF:9F
COM5: Device configuration: JTAG_DISABLE_CPU,VBO_200,CRP_LEVEL1,EXTERNAL_FLASH_NOT_ENCRYPTED,EXTERNAL_FLASH_LOAD_ENABLE

Из ответа следует что уровень защиты - CRP_LEVEL1. В таблице 8 перечислено что можно делать при таком уровне.

Ребята из команды alephsecurity.com подробно изучили bootloader, архитекутру JN5169 и попытались обойти его защиту. Весь их опыт подробно описан в этих статьях: раз, два, три. Рекомендую!

1.2 Magic number

Перед тем как передать управление программе, bootloader должен найти ее в памяти. Это необходимо делать из-за возможности расположить прошивку со смещением относительно начального адреса. Чуть ниже я объясню зачем нужен этот механизм.
В Application Note: JN-AN-1003, на странице 7 подробно описан процесс поиска. Если кратко, то bootloader проверяет начало каждого сектора, начиная с нулевого, на наличие Boot Image Record (BIR). Главным признаком BIR является Magic number. Для JN5169 этот номер соответствует значению 0x123456781122334455667788. На странице 17 приведена структура BIR. Если в начале какого-то сектора будет найден корректный Magic number, то производится проверка еще некоторых значений и в случае успеха управление передается программе. Если начало программы находится в отличном от нулевого секторе, то предварительно настраиваются регистры REG_SYS_FLASH_REMAP и REG_SYS_FLASH_REMAP2 страница 8.

2. OTA-обновление

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

2.1 Header

В обновляемом девайсе должна быть реализована клиентская часть OTA кластера с необходимыми командами. Некоторые устройства выдают в ответе на Simple req. то что у них есть клиентская часть OTA, но по факту на все запросы отвечают что функция не поддерживается.

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

  • Manufacturer id;
  • Версия прошивки;
  • Image types;

Значение перечисленных полей легко определить отправив с сервера команду Image Notify. Клиент ответит запросом Query Next Image Request в которой будет содержаться интересующая информация. Для aqara реле значения такие:

  • Manufacturer id = 0x115f;
  • Версия прошивки. Желательно что бы версия была выше текущей (0x23). Можно указать 0xffffffff;
  • Image types = 0x0101;
ZigBee Cluster Library Frame
    Frame Control Field: Cluster-specific (0x01)
    Sequence Number: 199
    Command: Query Next Image Request (0x01)
    Payload
        Field Control: 0x00
        Manufacturer Code: Lumi United Techology, Ltd Shenzhen (0x115f)
        Image Type: Manufacturer Specific (0x0101)
        File Version: 0x00000023
            0000 0000 .... .... .... .... .... .... = Application Release: 0
            .... .... 0000 0000 .... .... .... .... = Application Build: 0
            .... .... .... .... 0000 0000 .... .... = Stack Release: 0
            .... .... .... .... .... .... 0010 0011 = Stack Build: 35

Есть еще один момент. В OTA заголовке есть какая-то строка с указанием зашифрован ли образ. Если она в новой прошивке отличается от того что записано в текущей, то процесс обновления будет прерван после получения нескольких блоков. Для всех обновляемых по OTA девайсов (Aqara) эта строка должна быть DR1175r1v1UNENCRYPTED00000. Если дать поиск по примерам от NXP, то значение DR1175r1v1UNENCRYPTED00000 будет найдено в примере DimmableLight. DR1175 - это маркировка платы Lighting/Sensor Expansion Board. Смею предположить, что ребята из LUMI взяли в качестве базового указанный выше пример, либо с него подтянули работу с OTA, так как значение Image types тоже совпадает.

2.2 Invalidate current image and validate upgrade image

Объем памяти в JN5169 - 512 КБ. Этого достаточно, в большинстве случаев, чтобы использовать внутреннюю flash для хранения новой прошивки получаемой через OTA. Размер прошивки aqara реле примерно 180 КБ.

Теперь в чем вся суть. Например, текущая прошивка располагается в младшей половине памяти (Блок 0 = 0x00-0x3ffff). При OTA обновлении обновленная прошивка сохраняется в старшую половину памяти (Блок 1 - 0x40000-0x7ffff). После получения всего образа прошивки и проверки CRC происходит следующее:

  1. Обновленная прошивка, записанная в блок 1, не имеет в своем заголовке Magic number (все байты 0xff). Если в данный момент процессор перезапустится, то загрузится текущая версия прошивки из блока 0;
  2. Текущая версия прошивки из блока 0 записывает Magic number в заголовок обновленной прошивки из блока 1. Если в данный момент проц перезапустится, то загрузится прошивка из блока 0 (в данном примере текущая версия);
  3. Текущая версия прошивки портит в своем заголовке Magic number (все 0x0) и программно ресетит процессор;
  4. Bootloader проверяет все сектора в поисках Magic number и находит его в начале блока 1, где располагается обновленная версия прошивки. Далее настраиваются регистры ремапа и происходит запуск программы;
  5. Предыдущая версия прошивки в блоке 0, с испорченным Magic number, будет хранится во flash памяти до следующего обновления. Bootloader будет ее пропускать;

А что, если в качестве OTA обновления передать программу, которая инициализирует UART и сделает кое что для нас изнутри процессора?

3. Действуем

Нам понадобится:

  1. OTA-сервер. Я буду использовать координатор на чипе JN5169 и управляющую программу ZGWUI. Инструкцию по созданию координатора рекомендую посмотреть здесь. При создании статьи я прошил версию ZiGate. В тамошней версия ZigbeeNodeControlBridge почему-то сломан OTA, хотя в оригинальной от NXP нормально работает. Скорость выбирайте 115200, на 1M не все USB-UART адаптеры корректно работают. ZGWUI.exe найдете в примере от NXP.
    Можно использовать zigbee2mqtt c любым поддерживаемым стиком, только необходимо перенести OTA репозиторий к себе на компьютер, чтобы зазря не беспокоить Koenkk;
  2. Прошивка которая будет “подсунута” девайсу в качестве обновления. Скачать можно отсюда.
  3. Терминал PuTTY или любой другой умеющий писать в логи непечатные символы;
  4. Редактор WinHex или аналогичный;
  5. JN51xx Production Flash Programmer;
  6. USB-UART адаптер;
  7. Aqara реле (либо привод штор, розетка);

Запустить Zigbee сеть

_config.yml

  1. Settings: port, 115200, 8n1, none flow control.
  2. Open port
  3. Нажмите Erase PD
  4. Установите желаемый канал. По умолчанию 11
  5. Нажмите Start NWK
  6. Нажмите Permit Join указав параметры: Address 0, Interval 0xFF

Лог при выполнении пунктов 4-6. У вас должно быть что-то подобное

Type: 0x8000
 (Status)
  Length: 5
  Status: 0x00 (Success)
  SQN: 0x00
  Message: 
Type: 0x8000
 (Status)
  Length: 5
  Status: 0x00 (Success)
  SQN: 0x00
  Message: 
Type: 0x8024
 (Network Up)
  Status: 0x01
  Short Address: 0x0000
  Extended Address: 0x158D00028C036A
  Channel: 11
Type: 0x8000
 (Status)
  Length: 5
  Status: 0x00 (Success)
  SQN: 0x00
  Message: 

После выполнения пункта 6, ваш координатор готов принять новые устройства. Активируйте на реле режим сопряжения.

После успешного добавления в сеть, в логах найдите анонс с коротким адресом реле. Скопируйте адрес без 0x. У меня это B0AC

 (End Device Announce)
  Short Address: 0xB0AC

Перейдите на вкладку OTA Cluster (номер 7 на картинке с ZGWUI). Нажмите кнопку Load Image и выберите файл с обновлением. Для кнопки Image Notify укажите следующие данные: Short, короткий адрес(B0AC),1,1,JITTER,1,1,1,1

_config.yml

Нажмите Image Notify. Если все сделано правильно, то начнет процесс передачи прошивки. Размер прошивки, конечно, великоват для своего функционала, нужно будет оптимизировать. Подождать придется 4 минуты 30 сек, если между реле и координатором 1 hop и в сети нет спамящих нодов.

_config.yml

Пока передается прошивка, можете запустить PuTTY. Укажите номер порта, скорость 115200 8n1. Логирование пока не включайте.

После завершения передачи файла (+ 5 секунд) устройство переключится на новую прошивку. В терминал будет отправлено приветствие с подсказкой.

_config.yml

  • f - чтение всей flash памяти (0x00080000 - 0x000FFFFF);
  • w - программа сформирует файл прошивки (ID файла + корректный MAGIC_NUMBER + все данные прошивки) и выдаст его в терминал;

Включите логирование и нажмите ‘w’. После прекращения передачи данных (оценить визуально в окне терминала) вам необходимо удалить из лога возможный заголовок, который формируют некоторые терминальные программы. В PuTTY это выглядит так:

_config.yml

0d 0a - это последние символы заголовка PuTTY, далее идут данные прошивки (0f 03 00 0b…)

_config.yml

Полученный файл прошивки можно сразу же проверить, прошив его утилитой JN51xx Production Flash Programmer

JN51xxProgrammer.exe -V 1 -s COM5 -P 115200 -f putty.bin

Устройство (реле) должно снова заработать как и прежде. Если этого не произошло, и вы не находите ошибки в своих действиях, просьба обратиться ко мне. Возможно потребуется доработать алгоритм. Контакты в разделе About

Способ второй. Wire exploit

Здесь информации будет поменьше, так как практически все что нужно представлено в описании первого способа.

Открываем описание на bootloader и смотрим таблицу 8

В последнем столбце указано какие действия можно совершать при CRP = 1. Среди всего прочего, есть такая операция как Flash program request. Запись до 128 байт по указанному адресу. Вспомните как bootloader выполняет поиск валидного образа прошивки в памяти и каким образом OTA клиент переходит на новую прошивку. Собрав эти знания в кучу, можно предположить такой подход:

  1. Испортить MAGIC_NUMBER в текущей прошивке с помощью Flash program request;
  2. Записать свою прошивку куда-нибудь повыше, с корректным MAGIC_NUMBER, используя всё ту же Flash program request;

Какой функционал заложить в свою прошивку, думаю, объяснять не стоит :)

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

Здесь вы сможете найти прошивку и скрипт который все сделает. Функционал прошивки тот же что для OTA.

В этот раз предлагаю взять другое устройство, например датчик дверей и окон (геркон). Схема подключения здесь

  1. Перейдите в режим bootloader;
  2. Укажите в скрипте нужный порт;
  3. Запустите скрипт;
  4. Перезапустите JN5169 предварительно подключившись к нему терминальной программой (115200 8n1);

Результат работы скрипта: _config.yml

Успешно запущенная программа: _config.yml

Со всеми неудачами и вопросами - пишите. Контакты в разделе About

Заключение

Теперь о том, что можно предпринять для повышения защиты.

В первом случае стоит подписывать свои прошивки или шифровать их, механизмы в функционале OTA это предусматривают. Запрещать обратное чтение прошивки.

Во втором случае. По началу я подумал про способ, когда все свободное пространство заполняется нулями, но потом посмотрел внимательнее таблицу 8 и нашел что команда Sector erase request доступна в CRP1.

Привязка прошивки к MAC адресу чипа, который лежит в OTP. Но это такое, просто повышается стоимость реверса и стоимость производства.

Исправить bootloader. Не проверял можно ли переписывать эту область flash.

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

У меня больше нет адекватных вариантов, кроме как установка CRP = 2. В виду того что биты CRP хранятся в OTP, то JN5169 из микроконтроллера с flash памятью, превращается в микроконтроллер с PROM.


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

10.01.2022