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

Подопытным оказался M306N5FCTFP. Это микроконтроллер группы M16C/6N5. Ядро M16C/60 разработано Mitsubishi, а т.к. преемником этой компании по части МК с 2003 года является Renesas, то сейчас эти микроконтроллеры известны именно под этим брендом.

Немного о самом микроконтроллере


Камешек представляет собой 16-разрядный микроконтроллер в 100-выводном QFP корпусе. Ядро имеет 1 МБайт адресного пространства, тактовая частота 20МГц для автомобильного  исполнения. Набор периферии так же весьма обширный: два 16-разрядных таймера и возможность генерации 3-фазного ШИМ для управления моторами, всякие UART, SPI, I2C естественно, 2 канала DMA, имеется встроенный CAN2.0B контроллер, а также PLL. На мой взгляд очень неплохо для старичка. Вот обзорная схемка из документации:



Так как моя задача выдрать ПО, то так же весьма интересует память. Данный МК выпускался в двух вариантах: масочном и Flash. Ко мне попал, как выше уже упоминалось, M306N5FCTFP. Про него в описании сказано следующее:

  • Flash memory version
  • 128 KBytes + 4K (дополнительные 4K — так называемый блок А в подарок пользователю для хранения данных, но может хранить и программу)
  • V-ver. (автомобильное исполнение с диапазоном +125°C)

Как вытащить из устройства то, что разработчики втащили


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



Как видно из картинки выше, память разбита на 2 части: пользовательская область, и область загрузчика. Во второй как раз с завода залит загрузчик по умолчанию, который умеет писать, читать, стирать пользовательскую память и общается через асинхронный, синхронный, либо CAN-интерфейс. Указано, что он может быть переписан на свой, а может быть и не переписан. В конце концов это легко проверяется попыткой постучаться к стандартному загрузчику хотя-бы через UART… Забегая вперед: производитель отопителя не стал заморачиваться своим загрузчиком, поэтом копать дальше можно в этом направлении. Сразу оговорюсь, что есть еще способ параллельного программирования, но т.к. программатора для этого у меня не было, я не рассматривал этот вариант.

Вход в режим работы загрузчика обеспечивается определенной комбинацией на входах CNVSS, P5_0, P5_5 во время аппаратного сброса. Дальше либо написать свою утилиту для копирования содержимого памяти, либо использовать готовую. Renesas предоставляет свою утилиту, которая называется «M16C Flash Starter», но функция чтения у нее урезана. Она не сохраняет прочитанное на диск, а сравнивает его с файлом с диска. Т.е. по сути это не чтение, а верификация. Однако есть немецкая свободная утилитка с названием M16C-Flasher, которая вычитывать прошивку умеет. В общем начальный инструментарий подобрался.

О защите от считывания




Все бы было совсем просто, если бы в загрузчике не была предусмотрена защита от несанкционированного доступа. Я просто приведу очень вольный перевод из мануала.

Функция проверки идентификатора

Используется в последовательном и CAN режимах обмена. Идентификатор, переданный программатором, сравнивается с идентификатором, записанным во flash памяти. Если идентификаторы не совпадают, команды, отправляемые программатором, не принимаются. Однако, если 4 байта вектора сброса равны FFFFFFFFh, идентификаторы не сравниваются, позволяя всем командам выполняться. Идентификатор — это 7 байт, сохраненных последовательно, начиная с первого байта, по адресам 0FFFDFh, 0FFFE3h, 0FFFEBh, 0FFFEFh, 0FFFF3h, 0FFFF7h, и 0FFFFBh.


Таким образом, чтобы получить доступ к программе, нужно знать заветные 7 байт. Опять же, забегая вперед, я подключился к МК, используя тот же «M16C Flash Starter» и убедился, что комбинации из нулей и FF не проходят и этот вопрос придется как то решать. Здесь сразу же всплыла мысль с атакой по сторонним каналам. Уже начал прикидывать в голове платку, позволяющую измерять ток в цепи питания, но решил, что интернет большой и большинство велосипедов уже изобретено. Вбив несколько поисковых запросов, довольно быстро нашел на hackaday.io проект Serge 'q3k' Bazanski, с названием «Reverse engineering Toshiba R100 BIOS». И в рамках этого проекта автор решал по сути точно такую же задачу: добыча встроенного ПО из МК M306K9FCLR. Более того — на тот момент задача им была уже успешно решена. С одной стороны я немного расстроился — интересная загадка разгадана не мной. С другой — задача превратилась из поиска уязвимости, в ее эксплуатацию, что обещало гораздо более скорое решение.

В двух словах, q3k точно по такой же логике начал изучение с анализа потребляемого тока, в этом плане он был в гораздо более выгодных условиях, т.к. у него был ChipWhisperer, этой штукой я до сих пор не обзавелся. Но т.к. его первый зонд для снятия тока потребления оказался неподходящим и вычленить из шумов что-то полезное у него не получилось, он решил попробовать простенькую атаку на время отклика. Дело в том, что загрузчик во время выполнения команды дергает вывод BUSY, чтобы проинформировать хост о том, занят он, или готов выполнять следующую команду. Вот, по предположению q3k, замер времени от передачи последнего бита идентификатора до снятия флага занятости мог послужить источником информации при переборе. При проверке этого предположения перебором первого байта ключа действительно было обнаружено отклонение по времени только в одном случае — когда первый байт был равен FFh. Для удобства измерения времени автор даже замедлил МК, отключив кварцевый резонатор и подав на тактовый вход меандр 666кГц, для упрощения процедуры измерений. После чего идентификатор был успешно подобран и ПО было извлечено.

Первый блин — граблями


Ха! Подумал я… Сейчас я быстренько наклепаю программку к имевшейся у меня STM32VLDiscovery c STM32F100 на борту, которая будет отправлять код и измерять время отклика, а в терминал выплевывать результаты измерений. Т.к. макетная плата с целевым контроллером до этого подключалась к ПК через переходник USB-UART, то, дабы ничего не менять на макетке, работать будем в асинхронном режиме.



Когда при старте загрузчика вход CLK1 притянут к земле, он понимает, что от него хотят асинхронного общения. Собственно потому я его и использовал — подтяжка была уже припаяна и я просто соединил проводами две платы: Discovery и макетку с целевым M306.

Заметка по согласованию уровней:

Т.к. M16 имеет TTL-уровни на выводах, а STM32 — LVTTL (упрощенно, в даташите подробнее), то необходимо согласование уровней. Т.к. это не устройство, которое, как известная батарейка, должно работать, работать и работать, а по сути подключается разок на столе, то с трансляторами уровней я не заморачивался: выходные уровни от STM32 пятивольтовый МК переварил, в смысле 3 вольта как «1» воспринимает, выходы от М16 подаем на 5V tolerant входы STM32 дабы ему не поплохело, а ногу, которая дергает RESET M16 не забываем перевести в режим выхода с открытым стоком. Я вот забыл, и это еще +2ч в копилку упущенного времени.
Этого минимума достаточно, чтобы железки друг друга поняли.


Логика атакующего ПО следующая:

  1. Устанавливаем соединение с контроллером. Для этого необходимо дождаться, пока завершится сброс, затем передать 16 нулевых символов с интервалом более, чем 20 мс. Это для того, чтобы отработал алгоритм автоопределения скорости обмена, т.к. интерфейс асинхронный, а МК о своей частоте ничего не знает. Стартовая скорость передатчика должна быть 9600 бод, именно на эту скорость рассчитывает загрузчик. После этого при желании можно запросить другую скорость обмена из пяти доступных в диапазоне 9600-115200 (правда в моем случае на 115200 загрузчик работать отказался). Мне скорость менять не нужно, поэтому я для контроля синхронизации просто запрашивал версию загрузчика. Передаем FBh, загрузчик отвечает строкой вроде «VER.1.01».
  2. Отправляем команду «unlock», которая содержит текущую итерацию ключа, и замеряем время до снятия флага занятости.

    Команда состоит из кода F5h, трех байт адреса, где начинается область идентификатора (в моем случае, для ядра M16C, это 0FFFDFh), длина (07h), и сам идентификатор.
  3. Измеряем время между передачей последнего бита идентификатора и снятием флага занятости.
  4. Увеличиваем перебираемый байт ключа (KEY1 на начальном этапе), возвращаемся к шагу 2 до тех пор, пока не переберем все 255 значений текущего байта.
  5. Сбрасываем статистику на терминал (ну или выполняем анализ «на борту»).

Для общения с целевым МК я использовал USART в STM32, для измерения времени — таймер в режиме Input Capture. Единственное, для простоты я измерял время не между последним битом ключа и снятием флага, а между началом передачи и флагом. Причиной было то, что последний бит может меняться, а в асинхронном режиме прицепить вход захвата особо не к чему. В то же время UART аппаратный и время передачи в принципе идентично и ощутимых погрешностей набегать не должно.

В итоге, для всех значений результаты были идентичны. Полностью идентичны. Тактовая частота таймера у меня была 24Мгц, соответственно разрешение по времени — 41,6 нс. Ну ок, попробовал замедлить целевой МК. Ничего не поменялось. Здесь в голове родился вопрос: что я делаю не так, как это делал q3k? После сравнения разница нашлась: он использует синхронный интерфейс обмена (SPI), а я асинхронный (UART). И где-то вот здесь я обратил внимание на тот момент, который упустил вначале. Даже на схемах подключения для синхронного и асинхронного режимов загрузчика вывод готовности назван по-разному:



В синхронном это «BUSY», в асинхронном это «Monitor». Смотрим в таблицу «Функции выводов в режиме Standart Serial I/O»:


«Семён Семёныч…»

Упущенная вначале мелочь завела не туда. Собственно, если в синхронном режиме это именно флаг занятости загрузчика, то в асинхронном (тот, который serial I/O mode 2) — просто «мигалка» для индикации работы. Возможно вообще аппаратный сигнал готовности приемопередатчика, оттого и удивительная точность его поднятия.

В общем перепаиваем резистор на выводе SCLK с земли на VCC, припаиваем туда провод, цепляем все это к SPI и начинаем сначала…

Успех!




В синхронном режиме все почти так же, только не требуется никакой предварительной процедуры установки соединения, упрощается синхронизация и захват времени можно выполнить точнее. Если бы сразу выбрал этот режим сохранил бы время… Я снова не стал усложнять и измерять время именно от последнего бита, а запускал таймер перед началом передачи последнего байта ключа, т.е. включаем таймер и отправляем в передатчик KEY7 (на скриншоте выше, из логического анализатора, видно расстояние между курсорами. Это и есть отсчитываемый отрезок времени).

Этого оказалось более чем достаточно для успешной идентификации. Вот так выглядит перебор одного байта:



По оси абсцисс у нас количество дискрет счетчика, по оси ординат, соответственно, передаваемое значение ключа. Отношение сигнал/шум такое, что даже никаких фильтров не требуется, прямо как в школе на уроке информатики: находим максимум в массиве и переходим в подбору следующего байта. Первые 6 байт подбираются легко и быстро, чуть сложнее с последним: там просто наглый перебор не проходит, нужен сброс «жертвы» перед каждой попыткой. В итоге на каждую попытку уходит что-то около 400 мс,  и перебор идет в худшем случае в районе полутора минут. Но это в худшем. После каждой попытки запрашиваем статус и, как только угадали, останавливаемся. Я вначале вообще просто быстренько ручками перебрал идентификатор, вставляя в excel вывод консоли и строя график, тем более, что это была разовая задача, но уже для статьи решил дописать автоматический перебор, ради красивой консольки…



Конечно, если бы разработчик затер загрузчик (заменил своим), так просто выкрутиться не получилось бы, но в автомобильной электронике частенько МК вообще не закрыты. В частности в блоке управления с другого отопителя, в котором был установлен V850 того же Renesas все решилось подпайкой пары проводов и копированием прошивки штатной утилитой. Это в мире ЭБУ двигателем целые криптовойны. Видимо не нравится производителям явление чип-тюнинга и других видов вмешательства… Хотя это как гонка брони и снаряда — железки круче, дороже, но победителя нет…

Ссылки:

  1. https://www.dataman.com/media/datasheet/Renesas/M16C6N5Group.pdf
  2. https://hackaday.io/project/723-reverse-engineering-toshiba-r100-bios/log/51302-ec-firmware-dumped
  3. https://q3k.org/slides-recon-2018.pdf

Комментарии (33)


  1. hddscan
    26.09.2019 23:50
    +3

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

    Кстати, номера весьма известные, для тех, кто смотрел сериал Lost
    lostpedia.fandom.com/ru/wiki/Числа

    Числа 4 8 15 16 23 42 — номера кандидатов замены Джейкоба в качестве «защитника острова».

    После ввода чисел таймер становится в положение «108:00», указывающее на необходимость вновь ввести числа через 108 минут (4 + 8 + 15 + 16 + 23 + 42 = 108).

    108=0х6с

    Создатели железок любят всякую нумерологию :)
    Я например встречал число Пи, в качестве ключа в одной из железок


    1. Inanity
      27.09.2019 00:21
      +1

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


      1. hddscan
        27.09.2019 00:41

        Т.е. будет просто немного сложнее, разница во времени будет говорить о том, что один из 7 байтов оказался угадан верно/неверно.

        bool weGood=true;
        for (int i=0;i++;i<7)
        {
           if (key[i]!=input[i]) weGood=false;
        }
        if (!weGood) killIt();
        


        как вы здесь определите какой байт верный?
        по количеству присваиваний переменной weGood?
        это вообще будет заметно на анализаторе?


        1. Inanity
          27.09.2019 01:36

          как вы здесь определите какой байт верный?
          Да, в общем-то почти точно так же как в статье. Берём какой-нибудь пароль, замеряем его время. Перебираем первый байт, замеряя время и сравнивая с предыдущим показанием. И так по всем байтам.
          по количеству присваиваний переменной weGood?
          Именно так. Разница будет в длительность исполнения инструкции присваивания weGood.
          это вообще будет заметно на анализаторе?
          От анализатора зависит. Salea logic со своими несчастными 24МГц может и не увидит (хотя частоту атакуемого процессора можно и уменьшить). А вот осциллограф на 4Gsa/s очень даже увидит. Если использовать FPGA, то ещё и дешевле выйдет. Так или иначе цена подобного взлома достаточно низка для практического применения.


          1. Serge78rus
            27.09.2019 09:38

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


            1. 15432
              27.09.2019 09:55

              Есть безопасная реализация memcmp, где введенные байты xor'ятся с образцом, а результаты orr'ятся между собой.


              1. Serge78rus
                27.09.2019 10:16

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


                1. Nookie-Grey
                  27.09.2019 13:55
                  +1

                  в борьбе со случайной задержкой статистика помогает)


                  1. Serge78rus
                    27.09.2019 14:29

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


            1. amartology
              27.09.2019 13:06

              Чтобы взломщики не забывали заземлять вход АЦП перед началом работы? )


              1. Serge78rus
                27.09.2019 14:51

                АЦП может иметь и внутренние входы — например, датчик температуры.


                1. sim2q
                  28.09.2019 01:51

                  пароль проходит только при -15С :)


        1. EviLOne
          27.09.2019 21:39

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


      1. DrGluck07
        27.09.2019 13:34

        Я бы просто добавил рандомную задержку перед ответом. Причём достаточно большую, десятки или сотни миллисекунд.


    1. aivs
      27.09.2019 01:47

      Отличное завершение детектива!


    1. N1X Автор
      27.09.2019 13:16

      По поводу чисел: каюсь, моя небольшая отсылка. Я исправил ключ в памяти контроллера, чтобы не светить настоящий. Это мои тараканы — «Если ты параноик, то это не значит, что за тобой не следят». Для статьи ключ не важен, поэтому я решил отвязаться от чужой интеллектуальной собственности. Ну а т.к. ключик 7 байт — то последовательность почему-то и всплыла в голове.
      По поводу защиты: производитель и не заявлял, стойкость к взлому. А также он предоставил возможность заменить загрузчик на свой, если это необходимо. Здесь все справедливо, как по мне.
      А вот в плане перебора: любое ветвление в коде потенциально выдает по времени ветку, которая выполняется. Предположим, что мы даем ответ вообще по аппаратному таймеру, т.е. заведомо позже завершения алгоритма и всегда с одной задержкой. Тогда всего лишь отпадает атака по времени отклика, но мы начинаем измерять шум переключений вентилей и анализировать все это дело на FPGA и опять же, не достаточно стойкий алгоритм выдаст себя с потрохами. Т.е. стойкость к взлому и сам взлом — это такая гонка вооружений, которая никогда не кончится. Производители контроллеров также применяют различные трюки, к примеру можно посмотреть на МК DS5002 от Dallas (ныне Maxim)…


      1. hddscan
        27.09.2019 18:14
        +1

        По поводу чисел: каюсь, моя небольшая отсылка. Я исправил ключ в памяти контроллера, чтобы не светить настоящий.

        ну все равно пасхалка :)
        было интересно


  1. audiserg
    27.09.2019 00:06
    +1

    Вот так совпадение. Спасибо! Я сегодня тоже неожиданно обнаружил в блоке климата мазда cx5 защиту на r5f2138, в моде 3 контроллер не завелся, а в моде 2 отозвался но 0xff не принимает. Пойду почитаю даташит, может и у меня этот busy есть...


  1. aivs
    27.09.2019 01:49
    +1

    Очень крутое расследование-исследование. Интересно, как Vag защищает свои блоки.


    1. N1X Автор
      27.09.2019 13:23

      По вагу, касательно дизельгейта, есть очень хороший обзор. Вот где действительно высший пилотаж… Видео, правда на английском.
      youtu.be/7t4paclIwuU


      1. emmibox
        27.09.2019 14:26
        +2

        Там не высший пилотаж. Этому человеку «помогли» с инфой которую он бы никогда не получил из открытых источников сам… Дальше он проделал достаточно тривиальную работу зная что сам «эффект» воспроизводится в этом ПО…


  1. iliasam
    27.09.2019 07:40

    Огромное спасибо за статью!
    У меня тоже возникла проблема с прошивкой контроллера R5F6416 в ресивере Yamaha, без знания ключа даже нет возможности стереть прошивку.
    Тоже была мысль, что нужно использовать атаку по времени, но до реальных экспериментов дело у меня не дошло.


  1. SSLHTML
    27.09.2019 13:30

    Большое спасибо за статью. Очень занимательно. Подскажите пожалуйста, каким kjubxtcrbv анализатором сигнала вы пользуетесь и ПО соответственно? Не предполагал, что перезаливкой загрузчика можно сбросить лок биты. Вероятно это справедливо только к подобным МК, но не для ATMEGA…


    1. N1X Автор
      27.09.2019 13:32

      Логический анализатор виден на главном фото в верхнем правом углу: китайский клон Saleae Logic.
      По поводу защиты: механизмы в МК из статьи и ATMega совершенно различны.


  1. DrGluck07
    27.09.2019 14:08

    У меня есть история как удалось сломать протокол обмена с некой железякой потому что нам в руки попала сервисная программа. Жаль только её тут рассказать нельзя.


    1. Paskin
      27.09.2019 19:54

      Работая с китайцами — об этом можно сериалы снимать. Мы, например, у одной железки декомпилировали сервисную программу и исправили баги сначала в ней (например, она падала если у вас нет D:\Temp), потом в драйвере — а потом написали новую прошивку и отправили им чтобы на заводе прошивали… А у другой — выбросили драйвер вообще, так как он только скрывал команды, посылаемые устройству через последовательный интерфейс, за заумным API. Причем тамошние гуру системного программирования почему-то решили что 64-битные указатели можно конвертировать в 32-битные int и обратно…


      1. DrGluck07
        27.09.2019 23:27

        Не, это были не китайцы. Они очень старались не давать никому свой протокол, но кто-то забыл программу на объекте и… Syser Kernel Debugger, всё такое. Обычными отладчиками не получилось.


      1. sim2q
        28.09.2019 01:57

        Вспомнилось что-то сколько ошибок исправили раздебажив U.S.Robotics модем — полуряный в своё время и даже дописали особостойкую к помехам «русскую» версию протокола на 21600 :)


  1. madcatdev
    27.09.2019 17:49
    +1

    Попробуйте вместо Saleae Logic использовать программу PulseView от Sigrok — она гораздо удобнее.


    Статья и подход к решению проблемы понравились. Только не проще ли было заменить этот МК на что-то более распространенное и написать свою прошивку для него? Вряд-ли ведь в отопителе используются какие-то сложные алгоритмы.


    1. N1X Автор
      27.09.2019 20:04

      Спасибо, программку обязательно попробую.
      А по поводу замены МК — в некоторых товарищ в итоге так и сделал, т.к. они шли с управлением по CAN-шине, а у него был спрос на переделку для включения по логическому сигналу, вот он стал PIC16 туда ставить…


    1. sim2q
      28.09.2019 01:55

      PulseView от Sigrok — она гораздо удобнее.
      Но жрет память как не в себя. Хотя может это конкретный плагин (i2c)


  1. pavel-pervomaysk
    28.09.2019 08:28

    Алгоритм давно реализован прибором Martech. И работает он на нескольких версиях загрузчика, к сожалению не на всех.
    Я пошел другими путями, но в итоге пароль вытащен, параллельный режим у его брата M306NBF закрыт. Чтобы не быть голословным WxxxWxx :).
    Уязвимость — дело тонкое.


    1. esaulenka
      29.09.2019 20:18

      В чём отличие этой статьи и готового прибора? В том, что готовая железка стоит каких-то 1к евро. И информацию о том, как именно она работает, авторы железки никому не сольют…