8 апреля 2015 в 15:40

Reverse Engineering ESP8266 — часть 2 tutorial

Продолжаем исследование модуля ESP8266. В этот раз рассмотрим процесс загрузки прошивки для дизассемблирования.

Первая часть статьи здесь.


Содержание


  1. Введение
  2. Архитектура ESP8266
    • Карта памяти (адресного пространства)
    • Формат прошивки
    • Процесс запуска
  3. Инструменты
  4. Загрузка прошивки для исследования
  5. Ассемблер Xtensa
    • Регистры
    • Базовые операторы
    • Функции
    • Условные переходы
  6. Заключение
  7. Ссылки


4. Загрузка прошивки для исследования


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

ELF

Начнем с самого простого – загрузки файла app.out – прошивки в формате ELF, созданной с помощью SDK.

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

После компиляции и сборки в папке build у нас появится файл app.out, который представляет собой скомпилированный пользовательский код, данные, библиотеки и отладочную информацию. В таком виде прошивку загрузить в модуль нельзя, поэтому после сборки ELF-файла SDK преобразует app.out в один или два файла в папке firmware – 0x00000.bin и 0x40000.bin, которые можно непосредственно прошить.

Открыв app.out в HIEW и посмотрев на таблицу сегментов (enter, F8, F6) мы увидем следующую картину:


Колонка VirtAddr содержит адреса начала сегментов в адресном пространстве Xtensa. Обратите внимание, что три сегмента (.data, .rodata и .bss) будут загружены в область оперативной памяти, сегмент .text будет записан по адресу пользовательского исполняемого кода, а сегмент .irom0.text – по адресу кода библиотек SDK. Остальные сегменты, имеющие начальный адрес, равный нулю, содержат служебную информацию и в прошивку, готовую к заливке в модуль добавлены не будут.

Забегая вперед скажу, что сегмент .irom0.text будет в исходном виде скопирован в файл 0x40000, а сегменты .data, .rodata, .bss и .text будут собраны в файл 0x00000.bin с учетом формата, который был рассмотрен выше.

Для загрузки app.out в IDA необходимо проделать следующую последовательность действий:

1. Открываем IDA Pro 6.6 или выше
2. Нажимаем «Go» — никаких файлов пока открывать не будем
3. Открываем пункт меню File – Script file и выбираем скрипт определения процессора xtensa.py
4. Загружаем файл app.out, здесь необходимо выбрать тип процессора и нажать «Set»:


5. В следующих окнах с предупреждением о неизвестном типе машины и предложениями загрузки отладочной информации нажать «Yes»
6. В результате мы получим готовый к исследованию файл:


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

Системная прошивка модуля

Мы рассмотрели довольно простую загрузку в IDA прошивки в виде ELF-файла. Однако на практике зачастую требуется изучить уже готовые прошивки, извлеченные из flash модуля (методом подключения к flash напрямую) или распространяемые в виде файлов 0x00000.bin и 0x40000.bin. Здесь придется проделать немного ручной работы. Начнем с загрузки образа системного ROM. В первой части я давал ссылку на архив с файлом 40000000.bin – это он и есть. Последовательность действий такая:

1. Открываем IDA Pro 6.6 или выше
2. Нажимаем «Go»
3. Открываем пункт меню File – Script file и выбираем скрипт определения процессора xtensa.py
4. Открываем файл 40000000.bin
5. Выбираем тип процессора Tensilica Xtensa [xtensa] и нажимаем «Set»
6. Далее необходимо указать организацию памяти для правильной загрузки двоичного файла. Здесь мы создаем сегмент кода по адресу 0x40000000 и загружаем в него наш файл:


7. Образ ROM загружен, но он плохо читаем из-за отсутствия названий функций. Теперь загрузим скрипт 40000000.idc, который произведет дополнительную работу – определит имена функций и создаст дополнительные сегменты в адресном пространстве: File – Script file – 40000000.idc. Вот результат:


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

А вот, кстати, функция, которая копирует пользовательскую прошивку из flash в память SoC:


Такой функции в SDK нет, поэтому название я ей дал произвольное.

Но прошивка не полна без загрузки пользовательской части – файлов 0x00000.bin и 0x40000.bin. Поэтому подгрузим эти файлы к системному ROM.

Пользовательская прошивка

Мы загрузили в IDA системный ROM модуля, а скрипт подготовил нам несколько сегментов для загрузки остальных частей. Начнем с простого – загрузки кода библиотек.

Как я говорил выше, файл прошивки 0x40000.bin представляет собой образ сегмента кода без всякой служебной информации и напрямую мапируется в адресное пространство процессора по адресу 0x40240000. Чтобы подгрузить его в IDA проделаем следующее:

1. Убедимся, что у нас открыта база данных 40000000.bin и скрипт 40000000.idc создал дополнительные сегменты: RAM, ROM, IRAM, IROM
2. Выбираем в меню File – Load file – Additional binary file, открываем файл прошивки 40000.bin
3. В следующем окне выбираем параметры загрузки. Обратите внимание, что загрузка производится по смещению в параграфах, т.е. вместо адреса указываем значение в 10h раз меньше (отбрасываем последний ноль). Галку создания сегмента можно снять, он у нас уже создан:


4. Файл загружен. После указания начала кода (в данном случае это 4024000Ch) мы получаем примерно следующую картину:


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

Небольшое лирическое отступление
Как исследовать голый ассемблерный код? Как понять, что выполняет та или иная функция? Для многих известных SDK IDA имеет сигнатуры FLIRT, которые определят названия стандартных функций. В нашем случае это не поможет, так как IDA не знает данный SDK. Так что эту работу придется проделать вручную. Для примера приведу несколько методов в порядке возрастания сложности:

1. Найти сигнатуру исследуемой функции в дизассемблере ELF-файла, скомпилированного той же версией SDK. Есть вероятность, что вы ее найдете, и у нее будет имя (из отладочной информации). В том числе для этого я рассматривал загрузку прошивки в ELF.
2. Известные константы – функция может ссылаться на текстовые строки или двоичные данные. С опытом многие такие константы запоминаешь наизусть, если константа незнакома – гуглим. Вот пример:


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


Сравнив реализации алгоритма, можно с уверенностью сказать, что по адресу 40100E70h расположена функция strlen.
Или вот такой кусок кода сразу выдает функцию деления:


3. Собственно, изучение ассемблерного кода и попытка понять, что выполняет функция. Иногда можно определить, что перед нами «местная» реализация знакомого алгоритма, а иногда нет.
В любом случае, навык понимания ассемблерного кода приходит с опытом, так что дерзайте!

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


Сначала идет 8 байт общего заголовка прошивки, в которой определяется количество сегментов и точка входа. Потом идут сами сегменты, также имеющие 8-ми байтовые заголовки с адресом и длиной.
Чтобы правильно загрузить их в IDA я вырезал данные каждого сегмента (без заголовков) в отдельные файлы, назвав их по адресу загрузки:


Теперь остается подгрузить их в IDA. Для каждого файла выполняем последовательность действий, аналогичных загрузке системного ROM:

1. File – Load file – Additional binary file, выбираем файл данных
2. В параметрах загрузки указываем сегмент (по имени файла без последнего нуля), сегмент не создаем.

Все, теперь у нас есть полностью загруженная и готовая к исследованию прошивка!


В этой части статьи мы рассмотрели процедуры загрузки различных видов прошивок ESP8266 для дизассемблирования в IDA Pro. В заключительной части мы рассмотрим особенности процессора Xtensa, отличия от архитектуры x86, набор регистров и команд.
@codezero
карма
22,0
рейтинг 0,0
Похожие публикации
Самое читаемое Разработка

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

  • +2
    Отличная статья, спасибо!
    • +1
      Благодарю! Приятно слышать!
      • 0
        Потрясающе!

        Поглядываю на свой ESP-01 и руки чешутся после вашей статьи)))
  • 0
    После прочтения ваших статей возникли вопросы. В моей картине мира на флеше записаны три вещи: bootloader, который лежит в 0x00000 и два участка кода, user1 и user2. Они, вроде бы, лежат по смещениям 0x01000 и 0x40000. При этом по умолчанию грузится user2, но при каком-то внешнем воздействии грузится user1.
    У вас же как-то всё грузится одновременно и нет отличия загрузчика от остального кода. Может быть я что-то неправильно понимаю?
  • 0
    Хм. Может в radare2 тоже добавить поддержку Xtensa… Есть ли на это спрос?
    • 0
      Спрос есть и еще какой… но кто бы это сделал и добавил.
      • 0
        Ок, буду держать в голове, если появится ближайшая возможность — добавим.
      • 0
        Теперь это в основной ветке github.com/radare/radare2 — пользуйтесь на здоровье!
        • 0
          Спасибо огромное, на выходных потестирую.

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