[Перевод] Реверс-инжиниринг железа: находим UART и извлекаем прошивку при помощи UBoot

[*]

Введение

В этом посте мы расскажем об UART, UBoot и USB, а нашей целью станет игровой автомат Arcade 1UP Marvel. Серия автоматов Arcade 1Up предоставляет возможность за приемлемую цену приобрести домашнюю аркадную машину. С момента выпуска этих автоматов появилось множество модов, демонстрирующих, как заменить внутренние компоненты автомата для запуска стандартного ПО MAME. В посте мы исследуем оборудование автомата и узнаем, как извлечь его прошивку.

[Перевод] Реверс-инжиниринг железа: находим UART и извлекаем прошивку при помощи UBoot

Задачи

В этом посте мы раскроем следующие темы:

  • Разборка встроенной системы
  • Идентификация компонентов по маркировкам на печатных платах
  • Измерение напряжений на ножках при помощи мультиметра
  • Использование и настройка логического анализатора
  • Анализ и просмотр UBoot
  • Скриптинг взаимодействий с UBoot при помощи depthcharge

Цель этой статьи заключается в том, чтобы познакомить читателей с процессом нахождения активного UART в системе, работой с консолью UBoot и в конечном итоге использованием этих компонентов для извлечения флэш-памяти из системы. Прочитав статью, вы познакомитесь с утилитой screen из библиотеки depthcharge python3.

Краткое описание оборудования

При изучении новой платформы одна из первых задач заключается в анализе доступных интерфейсов. В случае этого аркадного автомата поначалу кажется, что набор интерфейсов очень ограничен. Пользователи взаимодействуют с устройством посредством джойстика/кнопок и USB-разъёмом на боковой панели автомата. Похоже, относительно USB-разъёма существует довольно мало информации. Стоит заметить, что даже на фотографиях с сайта производителя USB-разъёма нет. Однако наличие разъёма для USB-устройств подразумевает поддержку внешних контроллеров. На другой стороне устройства есть стандартный разъём для наушников. Эти два периферийных разъёма ведут себя как положено — к USB-разъёму можно подключить внешний контроллер, да и разъём наушников работает в соответствии с описанием.

Интересный факт: на некоторых старых мобильных телефонах можно было настроить аудиоразъём так, чтобы при запуске он работал в качестве последовательного терминала. Подробнее об этом можно прочитать здесь. К сожалению, подобные модификации не работают на нашей платформе.

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

Разборка автомата

Внутри автомат практически пуст, если не считать прикреплённого к экрану металлического корпуса.

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

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

На этой компоненте написано Rockchip RK3128. Если поискать этот артикул онлайн, мы найдём очень много информации; информация ниже взята из вики Rockchip

  • CPU
    • четырёхъядерный процессор ARM Cortex-A7MP Core, высокопроизводительный, с низким энергопотреблением и кэшированием приложений
    • Полная реализация набора команд v7-A архитектуры ARM, поддержка ARM Neon Advanced SIMD (single instruction, multiple data) для ускорения медиавычислений и обработки сигналов
  • GPU
    • ARM Mali400 MP2
    • высокопроизводительный, с поддержкой OpenGL ES1.1, 2.0, OpenVG1.1 и т. д.
  • Память
    • 8 КБ внутренней SRAM
    • Dynamic Memory Interface (DDR3/DDR3L/LPDDR2):совместим с DDR3-1066/DDR3L-1066/LPDDR2-800 SDRAM стандарта JEDEC. Поддерживает 32-битную ширину данных, 2 ранга (выбирается чипом), общее адресное пространство размером 2 ГБ (максимум).
    • Интерфейс Nand Flash: поддерживает 8-битные async/toggle/syncnandflash, до 4 банков, 16-битную, 24-битную, 40-битную, 60-битную аппаратную ECC
    • Интерфейс eMMC: совместимый со стандартным интерфейсом eMMC, поддерживает протокол MMC4.5
  • Видео
    • Декодер видео MPEG-1, MPEG-2, MPEG-4,H.263, H.264, H.265, VC-1, VP8, MVC в реальном времени
  • Аудио
    • I2S/PCM с 8 каналами: до 8 каналов (8xTX, 2xRX). Разрешение аудио от 16 бит до 32 бит. Частота сэмплирования до 192 кГц
    • I2S/PCM с 2 каналами: до 2 каналов (2xTX, 2xRX). Разрешение аудио от 16 бит до 32 бит. Частота сэмплирования до 192 кГц
  • Разъёмы
    • Контроллер SPI: один встроенный контроллер SPI на чипе
    • Контроллер UART: 3 контроллера UART на чипе
    • Контроллер I2C: 4 контроллера I2C на чипе
    • USB Host2.0: встроенные интерфейсы 1 USB Host 2.0
    • USB OTG2.0: совместимый со спецификацией USB OTG2.0. Поддерживает режимы high-speed (480 Мбит/с), full-speed (12 Мбит/с) и low-speed (1,5 Мбит/с).

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

Непосредственно рядом с CPU есть ещё один компонент, на изображении ниже выделенный оранжевым.

SRAM

Этот компонент имеет маркировку SEC931 K4B2G1646F-BYMA, и нам повезло найти этот артикул в результатах поиска на веб-странице Samsung. Информация на этой странице сообщает нам, что это чип DDR3 SDRAM на 2 ГБ. На той же странице можно скачать даташит; при подобной работе всегда стоит собирать даташиты, если они доступны. Этот чип отвечает за расширение доступной для CPU памяти и обеспечение источника энергозависимой памяти (ОЗУ).

Пока мы выявили то, что скорее всего является основным CPU и внешней ОЗУ. Однако мы пока не знаем тип энергонезависимой памяти. Поэтому далее давайте исследуем компонент, выделенный на изображении розовым.

Этот компонент имеет маркировку Winbond 25N01GVZEIG, поиск такого артикула приводит нас к этому даташиту. Это устройство является последовательным чипом флэш-памяти SLC NAND на 1 Гбит. Согласно даташиту, этот чип использует Serial Peripheral Interface и совместим с диапазоном напряжений от 2,6 В до 3,3 В. С большой вероятностью в этом чипе хранится основной массив данных, используемых автоматом, и он будет нашей основной целью для извлечения прошивки.

Последний компонент находится рядом с линиями GPIO и имеет маркировку MIX2018A. Он непохож на другие тем, что про него я не смог найти почти никакой информации. Однако, судя по информации на нескольких сайтах, эта интегральная схема является звукоусилителем. Во всех источниках про это устройство, которые мне удалось найти онлайн, оно называется именно так, поэтому будем пока считать, что в этом и заключается его функция.

Итак, повторим компоненты, которые мы обнаружили:

  1. ARM CPU Rockchip RK3128
  2. Чип Samsung SRAM
  3. Флэш-память Winbond NAND на 1 Гбит
  4. Звукоусилитель MIX2018A

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

Анализ разъёмов

Задокументировав все дискретные компоненты материнской платы, мы попытаемся распознать её внешние разъёмы. Во-первых, у нас есть круглый разъём, показанный на изображении внизу синим цветом:

Этот разъём используется для подачи питания на автомат.

Справа от круглого разъёма есть разъём micro-USB. Это сразу вызывает удивление по двум причинам:

  1. Это разъём не для пользователя
  2. Это не разъём USB-хоста; это разъём micro, намекающий на USB-устройство или, возможно, на контроллер OTG (on the go)

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

После разъёма платы управления есть ещё один четырёхконтактный разъём. Не совсем очевидно, куда он ведёт. Например, этот разъём может вести к USB-разъёму или к разъёму для наушников. Мы можем попробовать определить это при помощи мультиметра и прозвонки цепи. Прозвонка позволяет проверить, может ли течь ток между двумя щупами и обычно обозначается на мультиметре одним из следующих символов:

Мы можем использовать эту модель для проверки того, соединены ли два компонента. Я вставил в разъём для наушников кабель и прижал щуп к одному из металлических колец, чтобы проверить соединение. Другим щупом я касался каждой точки четырёхконтактного разъёма и на одной из линий мультиметр издал громкий писк, сообщая, что между этими двумя точками есть соединение. Каждый из трёх контактов совпадал с кольцом на аудиоразъёме; это разъём для наушников!

Далее у нас находится разъём для дисплея:

Рядом с дисплеем расположены два двухконтактных разъёма, один в правом нижнем углу, он используется для питания подсветки самого автомата, а другой идёт к переключателю «вкл./выкл.», находящемуся снаружи металлического корпуса.

Следующий разъём похож на аудиоразъём; это четырёхконтактный соединитель, кабели которого ведут к панели управления. Остался только один интерфейс, который мы пока не рассмотрели: USB-разъём на боковой панели автомата. Если переключить мультиметр на режим прозвонки цепей и проверить контакты этот разъёма с USB-разъёмом, то мы убедимся, что они действительно связаны. Это наш внешний USB-разъём.

Мы разобрались со всеми разъёмами, которые пришлось отсоединить, чтобы лучше видеть плату. Следовательно, исследовать нам осталось не так много. При изучении печатной платы стоит рассмотреть неиспользуемые тестовые площадки или переходные соединения; на изображении ниже я выделил неиспользуемые контакты/площадки.

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

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

Последняя группа площадок выглядит очень похоже на разъёмы, использованные для USB и аудио. Подобные четырёхконтактные соединения неиспользуемых площадок часто являются кандидатами на отладочную консоль через UART; мы исследуем эти контакты и обсудим UART в следующем разделе.


Исследование отладочных контактов

Изучая неизвестные контакты наподобие описанных в предыдущем разделе, я обычно начинаю с замера напряжения. Это можно сделать при помощи наших мультиметров. Чтобы вычислить напряжение на этих площадках, мы переключим мультиметр в режим измерения постоянного тока и будем касаться щупом интересующих нас точек, держа чёрный щуп на точке заземления. Эти контакты имеют следующие показания:

Контакт Уровень напряжения
1 0v
2 0v
3 0v
4 0v

На этих линиях нет напряжения; хоть это нас и разочаровало, но неожиданностью не стало. Если бы передавался активный UART или другой цифровой сигнал, мы бы ожидали увидеть активность в виде колебаний напряжения. Давайте перейдём к другому трёхконтактному разъёму.

Контакт Уровень напряжения Цвет
1 2.7v Розовый
2 1.4-3.3v Жёлтый
3 GND Чёрный

При замерах на этом разъёме начинаются резкие колебания на втором контакте, постепенно сводящиеся к 3,3 В; пример того, как выглядят эти колебания, см. в показанном ниже gif:

Примечание: при поиске последовательного разъёма вы не всегда сможете увидеть колебания напряжения такой величины. Колебания напрямую коррелируют с уровнем активности сигнала, то есть если трафик небольшой, то колебания будут небольшими или их вообще не будет. Если вы подозреваете, что нашли контакт UART или какой-то цифровой интерфейс, то всегда стоит проверить его логическим анализатором.

Мы видим то, что может походить на активность сигнала (на основании колебаний напряжения). Далее нам нужно исследовать этот трафик логическим анализатором. Логические анализаторы помогают нам преобразовать эти колебания напряжения в человекочитаемую последовательность единиц и нулей. Для этого мы соединим логический анализатор с двумя интересующими нас точками при помощи показанного ниже переходника «мама-мама»:

Подключив анализатор, мы запустим Pulseview и выберем его в раскрывающемся меню; в приложении это устройство отображается как «Saleae Logic». Для этого анализатора максимальная частота захвата составляет 24 МГц, и именно её мы будем использовать для анализа. Также нам нужно указать количество сэмплов. Я выбрал значение 500G (500 миллиардов).

Мы запустим захват нажатием на Run, а затем включим питание автомата с этими параметрами.

Успех! Мы перехватили какой-то трафик; прежде чем двигаться дальше в Pulseview, давайте подробнее поговорим о том, как работает UART на уровне сигналов. Мы убедились в том, что по этим линиям передаётся некий трафик; далее нам нужно чуть больше узнать о трафике UART и о том, как его анализировать.

UART

UART расшифровывается как Universal Asynchronous Receiver Transmitter. UART — это двухпроводной асинхронный последовательный протокол, позволяющий общаться двум устройствам. Обеим сторонам требуется две линии: Transmit (Tx) и receive (Rx). Во встроенной системе UART может использоваться для множества задач, в том числе для общения с другими процессорами, передачи данных датчиков и доступа к отладке. UART — асинхронный протокол, то есть ему не требуется тактовый сигнал. Вместо этого обе общающиеся стороны настраиваются на общение с определённой скоростью, называемой baud rate. Baud rate измеряется в битах в секунду.

Пакет/передача UART состоит из следующих полей:

Позиция бита Имя Описание
0:1 Начальный бит Используется для обозначения начала пакета
1:9 Биты данных (на самом деле им можно задать любое значение, но обычно 8) Передаваемые/считываемые данные; стоит заметить, что обычно первым передаётся самый младший бит
9:10 Бит чётности 1, если биты данных содержат нечётное количество единиц, 0 в противном случае
10:12 Биты останова Обозначают, что пакет закончился

Даже зная представленное выше описание пакета, нам сложно определить содержимое перехвата логики. К счастью, в Pulseview есть декодер UART, которым мы можем воспользоваться.

Декодируем трафик UART

С помощью pulseview мы можем попытаться декодировать этот трафик и посмотреть, действительно ли это активный UART. Для подготовки декодера нужно нажать на показанные ниже зелёный и жёлтый символы. Откроется окно выбора декодера, введите в панели поиска uart и выберите декодер UART.

Далее нужно настроить декодер UART. Нам нужно выбрать соответствующие каналы и задать все параметры протокола, необходимые этому декодеру. См. настраиваемые параметры ниже:

Сначала выберем линию Rx как содержащий трафик канал; в нашем случае это будет D1. Во всех остальных полях оставим стандартные значения: 8-битная ширина данных, отключенный контроль чётности и т. п.

Нам нужно определить и указать самостоятельно один параметр: baud rate. Не забывайте, что стороны должны заранее согласовать baud rate; нет никакого этапа согласования/запуска. Нам нужно определить этот baud rate самостоятельно; в противном случае, декодер не будет знать, как правильно парсить эти сигналы. Для определения baud rate мы можем сделать следующее.

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

Герц измеряется в тактах на секунду; вспомним, что baud rate измеряется в битах на секунду. Следовательно, если мы выделили один бит, передаваемый по проводнику, и частоту этого импульса, то у нас есть и baud rate.

Согласно Pulseview, вычисленная частота равна 115,384 кГц, что эквивалентно baud rate в 115385 бит/с. Те, кто знаком с консолями отладки, должны заметить, что это очень близко к стандартно используемому baud rate, равному 115200. Так что давайте вставим это значение в декодер и посмотрим, что произойдёт.

На скриншоте ниже мы видим настоящий лог отладки.

У нас есть активный UART и мы наем его baud rate, но теперь нам нужно найти способ взаимодействия с ним. Для этого мы воспользуемся Raspberry Pi. Дополненная схема расположения выводов автомата имеет следующий вид:

Конфигурируем Raspberry Pi

Raspberry Pi имеет множество UART; тот, который мы будем использовать, выделен на изображении ниже:

Чтобы включить этот UART, нам нужно убедиться, что включен блоб дерева соответствующего устройства. Блоб дерева устройства (device tree blob, DTB) необходим для того, чтобы ядро понимало доступную аппаратную периферию. Ядро считывает эту двоичную информацию при запуске и перечисляет указанную периферию. При реверс-инжиниринге встроенной системы на Linux извлечение этой информации может пойти на пользу, потому что её можно декомпилировать и наметить, где в памяти находятся разные устройства.

Все нужные нам блобы дерева устройств в raspberry pi могут находиться в /boot/overlays/. Найдите в этой папке двоичные объекты дерева устройств для различных аппаратных конфигураций, некоторые для конкретных HAT (специализированных печатных плат, спроектированных для Pi), подключаемых к Pi, и другие для включения различной периферии ввода-вывода. Мы можем включить соответствующий DTB для периферии UART при помощи инструмента raspi-config.

Используем Raspi Config

raspi-config — это инструмент пользовательского пространства, позволяющий нам конфигурировать различные аспекты Raspberry Pi, одним из которых является включение различных внешних интерфейсов. Мы воспользуемся raspi-config для включения интерфейса UART; начнём с запуска инструмента:

sudo raspi-config

При этом появится следующий экран:

Далее мы выберем Interface Options, а затем Serial Port, как показано на изображении ниже:

После выбора этой опции отобразится два вопроса:

  1. Would you like a login shell to be accessible over serial? (Сделать login shell доступной через последовательный порт?)
    • No (Нет)
  2. Would you like the serial port hardware to be enabled? (Включить оборудование последовательного порта?)
    • Yes (Да)

Так мы включили UART на Raspberry Pi. Далее нужно подключить его к автомату. Мы подключим Tx автомата к Rx устройства Pi, а Rx автомата — к Tx устройства Pi:

Инструменты UART

При помощи интерфейса UART на Raspberry Pi мы можем попытаться подключиться к этому последовательному порту нашей цели. Для взаимодействия с этим последовательным портом мы будем использовать утилиту screen. При взаимодействии с UART утилите Screen нужно передать устройство и baud rate; так как мы знаем baud rate из предыдущего раздела, то мы запустим screen следующим образом:

sudo screen -L -Logfile cabinet-bootup.log /dev/ttyS0 115200

  • -L -Logfile cabinet-bootup.log — записываем сессию в файл cabinet-bootup.log
  • /dev/ttyS0 — использовать это последовательное устройство
  • 115200 — Baud rate

Теперь мы можем включить питание автомата, сконфигурировав UART и запустив screen. При включении питания автомата мы видим следующее:

И наконец мы оказываемся в консоли:

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

[root@rk3128:/]# mount
/dev/root on / type squashfs (ro,relatime)
devtmpfs on /dev type devtmpfs (rw,relatime,size=103544k,nr_inodes=25886,mode=755)
proc on /proc type proc (rw,relatime)
devpts on /dev/pts type devpts (rw,relatime,gid=5,mode=620,ptmxmode=000)
tmpfs on /dev/shm type tmpfs (rw,relatime,size=112248k,nr_inodes=28062,mode=777)
tmpfs on /tmp type tmpfs (rw,relatime,size=112248k,nr_inodes=28062)
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,relatime,size=112248k,nr_inodes=28062,mode=755)
sysfs on /sys type sysfs (rw,relatime)
debug on /sys/kernel/debug type debugfs (rw,relatime)
pstore on /sys/fs/pstore type pstore (rw,relatime)
/dev/root on /var type squashfs (ro,relatime)
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,relatime,size=112248k,nr_inodes=28062,mode=755)
/dev/rkflash0p5 on /userdata type ext2 (rw,relatime)
none on /sys/kernel/config type configfs (rw,relatime)
adb on /dev/usb-ffs/adb type functionfs (rw,relatime)

Наша файловая система рута смонтирована как read-only и использует формат squashfs. Кроме того, смонтирован ещё один раздел с меткой userdata. Если мы исследуем доступные блочные устройства, то увидим следующее:

[root@rk3128:/]# ls -lathr /dev/block/by-name/
lrwxrwxrwx 1 root root 16 Jan 1 00:00 userdata -> ../../rkflash0p5
lrwxrwxrwx 1 root root 16 Jan 1 00:00 uboot -> ../../rkflash0p1
lrwxrwxrwx 1 root root 16 Jan 1 00:00 trust -> ../../rkflash0p2
lrwxrwxrwx 1 root root 16 Jan 1 00:00 rootfs -> ../../rkflash0p4
lrwxrwxrwx 1 root root 16 Jan 1 00:00 boot -> ../../rkflash0p3
drwxr-xr-x 3 root root 380 Jan 1 00:00 ..
drwxr-xr-x 2 root root 140 Jan 1 00:00 .

Мы видим, что устройство SPI flash предположительно находится в /dev/rkflash0. Чтобы получить образ этого блочного устройства, мы можем подключить USB-накопитель к USB-разъёму автомата и использовать утилиту dd. После вставки USB-накопителя он регистрируется как /dev/sda и мы можем создать на USB-накопитель образ содержимого SPI flash с помощью следующей команды:

sudo dd if=/dev/rkflash0 of=/dev/sda status=progress

Если мы подключим USB-накопитель к Pi и исследуем таблицу разделов, то увидим, что образы соответствующих разделов записались на накопитель!

pi@voidstar:~ $ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 1 57.8G 0 disk
├─sda1 8:1 1 4M 0 part
├─sda2 8:2 1 2M 0 part
├─sda3 8:3 1 9M 0 part
├─sda4 8:4 1 80.8M 0 part
└─sda5 8:5 1 8M 0 part

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

CLK: (uboot. arm: enter 600000 KHz, init 600000 KHz, kernel 0N/A)
apll 600000 KHz
dpll 600000 KHz
cpll 650000 KHz
gpll 594000 KHz
armclk 600000 KHz
aclk_cpu 148500 KHz
hclk_cpu 74250 KHz
pclk_cpu 74250 KHz
aclk_peri 148500 KHz
hclk_peri 74250 KHz
pclk_peri 74250 KHz
Net: Net Initialization Skipped
No ethernet found.
Hit key to stop autoboot('CTRL+C'): 0

Если зажать Ctrl-c в командной строке screen при включении питания автомата, то мы увидим следующее:

Hit key to stop autoboot('CTRL+C'): 0
=>

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


UBoot

UBoot — это загрузчик в open-source, часто используемый во встроенных системах. Он имеет поддержку широкого ассортимента архитектур и типов CPU. Однако задача UBoot обычно заключается в загрузке ядра операционной системы или основного приложения встроенной системы.

Также UBoot содержит утилиты, полезные в процессе реверс-инжиниринга; самым примечательным является командная строка UBoot.

Команды UBoot

Консоль UBoot может содержать большое количество встроенных утилит. Эти утилиты можно использовать в процессе стандартной загрузки (часто с помощью переменных среды) или в командной строке UBoot. Набор команд зависит от того как был собран образ UBoot.

Теперь, когда мы обнаружили консоль UBoot, давайте начнём с того, что посмотрим, какие команды публично доступны нам, выполнив help command

android_print_hdr base bdinfo bidram_dump BMP boot boot_android bootavb
bootd bootm bootp bootrkp bootz charge cmp coninfo cp crc32 ...
=>
? - alias for 'help'
android_print_hdr- print android image header
base - print or set address offset
bdinfo - print Board Infostructure
bidram_dump- Dump bidram layout
BMP - manipulate BMP image data
boot - boot default, i.e., run 'bootcmd'
boot_android- Execute the Android Bootloader flow.
bootavb - Execute the Android avb a/b boot flow.
bootd - boot default, i.e., run 'bootcmd'
bootm - boot application image from memory
bootp - boot image via network using BOOTP/TFTP protocol
bootrkp - Boot Linux Image from rockchip image type
Bootz - boot Linux zImage image from memory
charge - Charge display
CMP - memory compare
coninfo - print console devices and information
cp - memory copy
crc32 - checksum calculation
DHCP - boot image via network using DHCP/TFTP protocol
dm - Driver model low level access
download- enter rockusb/bootrom download mode
dtimg - manipulate dtb/dtbo Android image
dump_atags- Dump the content of the atags
dump_irqs- Dump IRQs
echo - echo args to console
editenv - edit environment variable
env - environment handling commands
exit - exit script
ext2load- load binary file from a Ext2 filesystem
ext2ls - list files in a directory (default /)
ext4load- load binary file from a Ext4 filesystem
ext4ls - list files in a directory (default /)
ext4size- determine a file's size
false - do nothing, unsuccessfully
fastboot- use USB or UDP Fastboot protocol
fatinfo - print information about filesystem
fatload - load binary file from a dos filesystem
fatls - list files in a directory (default /)
fantasize - determine a file's size
fatwrite- write file into a dos filesystem
fdt - flattened device tree utility commands
fstype - Lookup a filesystem type
go - start application at address 'addr'
gpt - GUID Partition Table
help - print command description/usage
iomem - Show iomem data by device compatible(high priority) or node name
lcdputs - print string on video framebuffer
load - load binary file from a filesystem
loop - infinite loop on address range
ls - list files in a directory (default /)
MD - memory display
mii - MII utility commands
mm - memory modify (auto-incrementing address)
mmc - MMC subsystem
mmcinfo - display MMC info
MW - memory write (fill)
NFS - boot image via network using NFS protocol
nm - memory modify (constant address)
part - disk partition related commands
ping - Send ICMP ECHO_REQUEST to network host
printenv- print environment variables
pxe - commands to get and boot from pxe files
rbrom - Perform RESET of the CPU
reboot - Perform RESET of the CPU, alias of 'reset'
reset - Perform RESET of the CPU
rkimgtest- Test if storage media have rockchip image
rknand - rockchip nand flash sub-system
rksfc - rockchip SFC sub-system
rktest - Rockchip board modules test
rockchip_show_bmp- load and display BMP from resource partition
rockchip_show_logo- load and display log from resource partition
rocks - Use the rockusb Protocol
run - run commands in an environment variable
save - save file to a filesystem
setcurs - set cursor position within screen
setenv - set environment variables
showvar - print local hushshell variables
size - determine a file's size
source - run script from memory
sysboot - command to get and boot from syslinux files
sysmem_dump- Dump system layout
sysmem_search- Search a available system region
test - minimal test like /bin/sh
TFTP - download image via network using TFTP protocol
true - do nothing, successfully
ums - Use the UMS [USB Mass Storage] USB - USB sub-system
usbboot - boot from USB device
version - print monitor, compiler, and linker version
=>

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

download- enter rockusb/bootrom download mode
dtimg - manipulate dtb/dtbo Android image
dump_atags- Dump the content of the atags
ext2load- load binary file from a Ext2 filesystem
ext2ls - list files in a directory (default /)
ext4load- load binary file from a Ext4 filesystem
ext4ls - list files in a directory (default /)
ext4size- determine a file's size
fastboot- use USB or UDP Fastboot protocol
fatinfo - print information about filesystem
fatload - load binary file from a dos filesystem
fatls - list files in a directory (default /)
fantasize - determine a file's size
fatwrite- write file into a dos filesystem
mm - memory modify (auto-incrementing address)
rknand - rockchip nand flash sub-system
rksfc - rockchip SFC sub-system
ums - Use the UMS [USB Mass Storage] USB - USB sub-system

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

Переменные среды UBoot

При сборке или конфигурировании образа UBoot для устройства могут быть настроены различные переменные среды. Эти переменные управляют тем, какие операции выполняются при запуске. Такие переменные могут храниться различным образом. Иногда они жёстко прописаны в самом двоичном файле; также они могут находиться на разделе флэш-памяти, что позволяет пользователям изменять их через командную строку UBoot.

Мы можем исследовать переменные среды при помощи команды printenv:

=> printenv
arch=arm
baudrate=115200
board=evb_rk3128
board_name=evb_rk3128
boot_a_script=load ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} ${prefix}${script}; source ${scriptaddr}
boot_extlinux=sysboot ${devtype} ${devnum}:${distro_bootpart} any ${scriptaddr} ${prefix}extlinux/extlinux.conf
boot_net_usb_start=usb start
boot_prefixes=/ /boot/
boot_script_dhcp=boot.scr.uimg
boot_scripts=boot.scr.uimg boot.scr
boot_targets=mmc1 mmc0 rknand0 usb0 pxe dhcp
bootargs=storagemedia=nand androidboot.storagemedia=nand androidboot.mode=normal
bootcmd=boot_android ${devtype} ${devnum};bootrkp;run distro_bootcmd;
bootcmd_dhcp=run boot_net_usb_start; if dhcp ${scriptaddr} ${boot_script_dhcp}; then source ${scriptaddr}; fi;
bootcmd_mmc0=setenv devnum 0; run mmc_boot
bootcmd_mmc1=setenv devnum 1; run mmc_boot
bootcmd_pxe=run boot_net_usb_start; dhcp; if pxe get; then pxe boot; fi
bootcmd_rknand0=setenv devnum 0; run rknand_boot
bootcmd_usb0=setenv devnum 0; run usb_boot
bootdelay=0
cpu=armv7
devnum=0
devtype=spinand
distro_bootcmd=for target in ${boot_targets}; do run bootcmd_${target}; done
ethaddr=d2:79:07:fc:f7:89
fdt_addr1_r=0x61700000
fdt_addr_r=0x68300000
kernel_addr1_r=0x62008000
kernel_addr_r=0x62008000
mmc_boot=if mmc dev ${devnum}; then setenv devtype mmc; run scan_dev_for_boot_part; fi
pxefile_addr1_r=0x60600000
pxefile_addr_r=0x60600000
ramdisk_addr1_r=0x63000000
ramdisk_addr_r=0x6a200000
rkimg_bootdev=if mmc dev 1 && rkimgtest mmc 1; then setenv devtype mmc; setenv devnum 1; echo Boot from SDcard;elif mmc dev 0; then setenv devtype mmc; setenv devnum 0;elif mtd_blk dev 0; then setenv devtype mtd; setenv devnum 0;elif mtd_blk dev 1; then setenv devtype mtd; setenv devnum 1;elif mtd_blk dev 2; then setenv devtype mtd; setenv devnum 2;elif rknand dev 0; then setenv devtype rknand; setenv devnum 0;elif rksfc dev 0; then setenv devtype spinand; setenv devnum 0;elif rksfc dev 1; then setenv devtype spinor; setenv devnum 1;fi;
scan_dev_for_boot=echo Scanning ${devtype} ${devnum}:${distro_bootpart}...; for prefix in ${boot_prefixes}; do run scan_dev_for_extlinux; run scan_dev_for_scripts; done;
scan_dev_for_boot_part=part list ${devtype} ${devnum} -bootable devplist; env exists devplist || setenv devplist 1; for distro_bootpart in ${devplist}; do if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype; then run scan_dev_for_boot; fi; done
scan_dev_for_extlinux=if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}extlinux/extlinux.conf; then echo Found ${prefix}extlinux/extlinux.conf; run boot_extlinux; echo SCRIPT FAILED: continuing...; fi
scan_dev_for_scripts=for script in ${boot_scripts}; do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${script}; then echo Found U-Boot script ${prefix}${script}; run boot_a_script; echo SCRIPT FAILED: continuing...; fi; done
scriptaddr=0x60500000
scriptaddr1=0x60500000
serial#=c3d9b8674f4b94f6
soc=rockchip
stderr=serial,vidconsole
stdout=serial,vidconsole
usb_boot=usb start; if usb dev ${devnum}; then setenv devtype usb; run scan_dev_for_boot_part; fi
vendor=rockchip

Environment size: 3477/32764 bytes
=>

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

Переменная Смысл
bootcmd Эта команда используется для задания стандартного поведения при запуске
board=evb_rk3128 Эта команда определяет используемый CPU / плату разработки
devtype=spinand Эта переменная определяет используемое флэш-устройство

Если на этом этапе мы сопоставим это с информацией, полученной при анализе оборудования, то получим согласованные результаты. После анализа оборудования мы предположили, что основным накопителем является SPI flash, и это предположение подтверждается переменными среды UBoot, а также доступными командами.

Давайте начнём с исследования команд rksfc; гугление подсказывает нам, что это инструмент интерфейса RockChip SPI SFC (serial flash controller). Эта команда имеет следующие подкоманды:

=> rksfc
rksfc - rockchip sfc sub-system

Usage:
rksfc scan - scan Sfc devices
rksfc info - show all available Sfc devices
rksfc device [dev] - show or set current Sfc device
dev 0 - spinand
dev 1 - spinor
rksfc part [dev] - print partition table of one or all Sfc devices
rksfc read addr blk# cnt - read `cnt' blocks starting at block
`blk#' to memory address `addr'
rksfc write addr blk# cnt - write `cnt' blocks starting at block
`blk#' from memory address `addr'

Получить информацию о SPI flash можно при помощи следующих команд:

=> rksfc scan
=> rksfc info
Device 0: Vendor: 0x0308 Rev: V1.00 Prod: rkflash-SpiNand
Type: Hard Disk
Capacity: 107.7 MB = 0.1 GB (220672 x 512)
=> rksfc device 0

Device 0: Vendor: 0x0308 Rev: V1.00 Prod: rkflash-SpiNand
Type: Hard Disk
Capacity: 107.7 MB = 0.1 GB (220672 x 512)
... is now current device
=> rksfc part 0

Partition Map for SPINAND device 0 -- Partition Type: EFI

Part Start LBA End LBA Name
Attributes
Type GUID
Partition GUID
1 0x00002000 0x00003fff "uboot"
attrs: 0x0000000000000000
type: ea450000-0000-424f-8000-0cd500004c0a
guid: 325b0000-0000-4d21-8000-6e10000051c5
2 0x00004000 0x00004fff "trust"
attrs: 0x0000000000000000
type: b44a0000-0000-4121-8000-4e1600002902
guid: 62500000-0000-4f7f-8000-4a7800006ca1
3 0x00005000 0x000097ff "boot"
attrs: 0x0000000000000000
type: 6c1e0000-0000-4833-8000-5d07000065c4
guid: 442c0000-0000-4c4c-8000-2ed400005ecb
4 0x00009800 0x00031dff "rootfs"
attrs: 0x0000000000000000
type: 9b050000-0000-4e44-8000-5f3000007157
guid: 614e0000-0000-4b53-8000-1d28000054a9
5 0x00031e00 0x00035dde "userdata"
attrs: 0x0000000000000000
type: c8050000-0000-4b18-8000-3b1a000041c3
guid: 40780000-0000-493e-8000-688900001525
=>

При помощи этих команд мы можем узнать больше о SPI flash. Мы видим, что его размер блока равен 512 и что чип суммарно содержит 220672 (0x35E00) блока, разбитые на пять разделов:

  • uboot — вероятно, содержит образ UBoot / загрузчик первого этапа
  • trust — образ Trusted execution environment
  • boot — образ ядра / ramdisk
  • rootfs — самый большой раздел, файловая система рута ядра
  • user data — пользовательские данные, скорее всего, используется для записи рекордов, пользовательских настроек и пр.

Обратите внимание, что эти данные совпадают с тем, что мы видели ранее в командной строке консоли рута. Теперь мы знаем, как разбита флэш-память, и какие данные могут быть доступны, но можно ли считывать/записывать эти данные без припаивания дополнительных линий к плате? Изучив команду usb, мы увидим следующее:

=> usb
usb - USB sub-system

Usage:
usb start - start (scan) USB controller
usb reset - reset (rescan) USB controller
usb stop [f] - stop USB [f]=force stop
usb tree - show USB device tree
usb info [dev] - show available USB devices
usb test [dev] [port] [mode] - set USB 2.0 test mode
(specify port 0 to indicate the device's upstream port)
Available modes: J, K, S[E0_NAK], P[acket], F[orce_Enable] usb storage - show details of USB storage devices
usb dev [dev] - show or set current USB storage device
usb part [dev] - print partition table of one or all USB storage devices
usb read addr blk# cnt - read `cnt' blocks starting at block `blk#'
to memory address `addr'
usb write addr blk# cnt - write `cnt' blocks starting at block `blk#'
from memory address `addr'

Если мы вставим устройство в USB-разъём на боковой панели автомата и выполним USB start, а затем USB info, то получим следующий результат:

=> usb start
starting USB...
Bus usb@10180000: Bus usb@101c0000: USB EHCI 1.00
Bus usb@101e0000: USB OHCI 1.0
scanning bus usb@10180000 for devices... 1 USB Device(s) found
scanning bus usb@101c0000 for devices... RKPARM: Invalid parameter part table
2 USB Device(s) found
scanning bus usb@101e0000 for devices... 1 USB Device(s) found
scanning usb for storage devices... 1 Storage Device(s) found
=> usb info
1: Hub, USB Revision 1.10
- U-Boot Root Hub
- Class: Hub
- PacketSize: 8 Configurations: 1
- Vendor: 0x0000 Product 0x0000 Version 0.0
Configuration: 1
- Interfaces: 1 Self Powered 0mA
Interface: 0
- Alternate Setting 0, Endpoints: 1
- Class Hub
- Endpoint 1 In Interrupt MaxPacket 2 Interval 255ms

1: Hub, USB Revision 2.0
- u-boot EHCI Host Controller
- Class: Hub
- PacketSize: 64 Configurations: 1
- Vendor: 0x0000 Product 0x0000 Version 1.0
Configuration: 1
- Interfaces: 1 Self Powered 0mA
Interface: 0
- Alternate Setting 0, Endpoints: 1
- Class Hub
- Endpoint 1 In Interrupt MaxPacket 8 Interval 255ms

2: Mass Storage, USB Revision 2.10
- USB DISK 3.0 0719146D1CBF9257
- Class: (from Interface) Mass Storage
- PacketSize: 64 Configurations: 1
- Vendor: 0x13fe Product 0x6300 Version 1.0
Configuration: 1
- Interfaces: 1 Bus Powered 498mA
Interface: 0
- Alternate Setting 0, Endpoints: 2
- Class Mass Storage, Transp. SCSI, Bulk only
- Endpoint 1 In Bulk MaxPacket 512
- Endpoint 2 Out Bulk MaxPacket 512

1: Hub, USB Revision 1.10
- U-Boot Root Hub
- Class: Hub
- PacketSize: 8 Configurations: 1
- Vendor: 0x0000 Product 0x0000 Version 0.0
Configuration: 1
- Interfaces: 1 Self Powered 0mA
Interface: 0
- Alternate Setting 0, Endpoints: 1
- Class Hub
- Endpoint 1 In Interrupt MaxPacket 2 Interval 255ms

Превосходно, мы видим, что стек USB успешно регистрируется и распознаёт наш накопитель mass storage device.

Прежде чем двигаться дальше, давайте перечислим то, что мы знаем о нашей среде UBoot:

  1. Изучение переменных среды дало нам используемые адреса в ОЗУ
  2. При помощи утилиты rksfc read мы можем считывать секторы SPI flash в ОЗУ
  3. При помощи команд USB мы можем регистрировать USB-устройство и выполнять на него запись

Можно считать SPI flash в ОЗУ, подключить USB-устройство, а затем записать данные с SPI flash data на USB-устройство при помощи команды USB write. Если это сработает, то мы сможем и восстанавливать образы, выполнив эти операции в обратном порядке: считывать данные с USB-накопителя и записывать их на флэш-память с помощью rksfc write. Давайте начнём с проверки записи.

Сначала мы можем попробовать считать всю SPI flash в ОЗУ при помощи следующей команды. В качестве адреса назначения можно попробовать адрес, сохранённый в $ramdisk_addr_r, то есть 0x6a200000:

=> rksfc read $ramdisk_addr_r 0 0x35E00

spinand read: device 0 block # 0, count 220672 ... undefined instruction
pc : ce695528 lr : 1fadca4d
sp : 6be17bd8 ip : 00000020 fp : 60093204
r10: 00004254 r9 : 6be1bdf8 r8 : ad758c77
r7 : ebaa79cb r6 : b052b720 r5 : 36395b84 r4 : f3a911be
r3 : 780fb750 r2 : 00000000 r1 : 600a62fc r0 : 200a226c
Flags: nZcv IRQs on FIQs off Mode SVC_32

Call trace:
PC: [< ce695528 >] LR: [< 1fadca4d >]

Stack:
[< ce695528 >]

Copy info from "Call trace..." to a file(eg. dump.txt), and run
command in your U-Boot project: ./scripts/stacktrace.sh dump.txt

Resetting CPU ...

### ERROR ### Please RESET the board ###

Не сработало; каким-то образом мы вызвали undefined instruction exception. Скорее всего, мы поломали что-то, что используется UBoot; давайте посмотрим, что получится, если попробовать другой адрес ниже в памяти:

=> rksfc read $scriptaddr 0 0x35E00

spinand read: device 0 block # 0, count 220672 ... 220672 blocks read: OK

Переход на более низкий адрес в ОЗУ позволил считать всё полностью, ничего не сломав; давайте посмотрим, сможем ли мы теперь записать эти данные на USB-накопитель:

usb write addr blk# cnt - write `cnt' blocks starting at block `blk#'
from memory address `addr'
=> usb write $scriptaddr 0 0x35E00

usb write: device 0 block # 0, count 220672 ... 220672 blocks written: OK
=>

Давайте теперь посмотрим на содержимое этого накопителя, вставив его в Raspberry Pi:

pi@voidstar:~ $ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 1 57.8G 0 disk
├─sda1 8:1 1 4M 0 part
├─sda2 8:2 1 2M 0 part
├─sda3 8:3 1 9M 0 part
├─sda4 8:4 1 80.8M 0 part
└─sda5 8:5 1 8M 0 part

Теперь мы видим, что таблица разделов в USB-накопителе совпадает с результатами, выведенными командой rksfc part 0. Далее мы используем утилиту dd, чтобы извлечь отдельные разделы для анализа.

pi@voidstar:~/marvel-cab/parts $ sudo dd if=/dev/sda1 of=part1.bin
4194304 bytes (4.2 MB, 4.0 MiB) copied, 0.18225 s, 23.0 MB/s
pi@voidstar:~/marvel-cab/parts $ sudo dd if=/dev/sda2 of=part2.bin
2097152 bytes (2.1 MB, 2.0 MiB) copied, 0.109297 s, 19.2 MB/s
pi@voidstar:~/marvel-cab/parts $ sudo dd if=/dev/sda3 of=part3.bin
9437184 bytes (9.4 MB, 9.0 MiB) copied, 0.386968 s, 24.4 MB/s
pi@voidstar:~/marvel-cab/parts $ sudo dd if=/dev/sda4 of=part4.bin
84672512 bytes (85 MB, 81 MiB) copied, 2.96481 s, 28.6 MB/s
pi@voidstar:~/marvel-cab/parts $ sudo dd if=/dev/sda5 of=part5.bin
8371712 bytes (8.4 MB, 8.0 MiB) copied, 0.314125 s, 26.7 MB/s
pi@voidstar:~/marvel-cab/parts $ file *
part1.bin: data
part2.bin: data
part3.bin: Android bootimg, kernel (0x10008000), second stage (0x10f00000), page size: 2048
part4.bin: Squashfs filesystem, little endian, version 4.0, xz compressed, 71663599 bytes, 1185 inodes, blocksize: 131072 bytes, created: Mon May 31 09:06:53 2021
part5.bin: Linux rev 1.0 ext2 filesystem data (mounted or unclean), UUID=42185cbc-b698-4af6-8a47-e444e5635787, volume name "userdata" (large files)

Пока данные совпадают и с выводом на запущенной системе, и в таблице разделов из меню UBoot. Следовательно, мы можем извлечь раздел squashfs с помощью unsquashfs и попробовать смонтировать раздел ext2, чтобы убедиться в их правильности:

pi@voidstar:~/marvel-cab/parts $ unsquashfs part4.bin
Parallel unsquashfs: Using four processors
1029 inodes (1792 blocks) to write
create_inode: could not create character device squashfs-root/dev/console, because you're not superuser!
created 596 files
created 157 directories
created 431 symlinks
created 0 devices
created 0 fifos
pi@voidstar:~/marvel-cab/parts $ ls squashfs-root/
bin busybox.config data dev etc lib lib32 linuxrc media misc mnt moo OEM opt proc root run sbin sdcard sys timestamp tmp udisk userdata usr var
pi@voidstar:~/marvel-cab/parts $ ls squashfs-root/moo/
docs logo.mp4 MOO MOO-Ship-MIME_CCADE_MSH_2P-BRK01 SKUShell.MIME_CCADE_SF2_2P.19.exe start.sh _ui assets

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

Убедившись, что мы можем считывать флэш-память, давайте проверим, сможем ли мы записать этот образ обратно при помощи описанного выше способа:

=> usb read $scriptaddr 0 0x35E00
usb read: device 0 block # 0, count 220672 ... 220672 blocks read: OK
=> rksfc write $scriptaddr 0x35E00 0
spinand write: device 0 block # 0, count 220672 ... 220672 blocks written: OK

Перезагрузимся и будем надеяться, что перезаписанный образ будет работать.

Успех! Теперь мы можем считывать/записывать SPI flash из UBoot при помощи USB-накопителя; это будет полезно для тестирования патчей и модификаций прошивки!

Теперь, когда мы научились считывать/записывать флэш-память автомата при помощи UBoot, было бы здорово научиться затирать отдельные разделы и сегменты флэш-памяти автоматически, без необходимости каждый раз вводить интервалы вручную. Для этого мы воспользуемся утилитой Depthcharge, позволяющей оптимизировать взаимодействие с UBoot!

Скриптинг UBoot при помощи Depthcharge

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

  1. Подключаться к UART и обнаруживать командную строку UBoot
  2. Выполнять считывание и запись в SPI flash с помощю команд rksfc read/write
  3. Выполнять считывание и запись на USB-накопитель при помощи команд USB read/write

Сначала нужно установить модуль; мы можем установить depthcharge на Pi, выполнив sudo pip install depthcharge.

Подключение к UART и обнаружение командной строки UBoot

Мы можем подключиться к командной строке UBoot при помощи следующего кода на python:

def console_setup():
    console=Console("/dev/ttyS0",baudrate=115200)
    ctx = Depthcharge(console,arch="arm")
    return ctx

В представленной выше функции и мы создаём объект Console, требующий, чтобы мы указали путь к последовательному порту и baud rate. Этот объект консоли затем используется для создания контекста Depthcharge, которым мы будем пользоваться для доступа к возможностям Depthcharge. В документации depthcharge есть хорошо описанный пример этого и подробно излагается процесс настройки.

Считывание и запись во флэш-память при помощи depthcharge

Теперь, когда мы подключились к интерфейсу, нам нужно реализовать команды read и write rksfc. Мы можем это сделать при помощи API send_command() утилиты depthcharge. Этот вызов API позволяет нам генерировать и передавать команду UBoot в командную строку и возвращать ответ. В примере ниже мы создаём команду чтения в переменной cmd_str и проверяем правильность форматирования аргументов, а затем отправляем команду при помощи API send_command().

def rksfc_read(ctx,dest_addr,src_addr,size):
    cmd_str = f"rksfc read  0x{dest_addr:02x} 0x{src_addr:02x} 0x{size:02x}"
    resp = ctx.send_command(cmd_str)
    return resp

def rksfc_write(ctx,dest_addr,src_addr,size):
    cmd_str = f"rksfc write 0x{dest_addr:02x} 0x{src_addr:02x} 0x{size:02x}"
    resp = ctx.send_command(cmd_str)
    time.sleep(10)
    return resp

Реализовав считывание и запись флэш-памяти, мы должны теперь зарегистрировать стек USB, а затем выполнять считывание/запись с накопителя.

Считывание и запись на USB при помощи depthcharge

Аналогично тому, как мы реализовали команды rksfc, далее нам нужно реализовать команды usb. Этот процесс будет схож с тем, который мы использовали для команд rksfc:

'''
usb_setup
This script is used to enumerate and set up the USB port
'''
def usb_setup(ctx,reset=False):
    resps = []
    if not reset:
        resp = ctx.send_command("usb start")
    else:
        resp = ctx.send_command("usb reset")
    resps.append(resp)
    resp = ctx.send_command("usb storage")
    resps.append(resp)
    resp = ctx.send_command("usb dev 0")
    resps.append(resp)
    return resps
  
'''
USB write addr blk# cnt - write `cnt' blocks starting at block `blk#'
    from memory address `addr'
'''
def usb_raw_write(ctx,source_addr,block,size):
    cmd = f"usb write 0x{source_addr:x} 0x{block:x} 0x{size:x}"
    resp = ctx.send_command(cmd)
    return resp

'''
USB read addr blk# cnt - read `cnt' blocks starting at block `blk#'
    to memory address `addr'
'''
def usb_raw_read(ctx,source_addr,block,size):
    cmd = f"usb read 0x{source_addr:x} 0x{block:x} 0x{size:x}"
    resp = ctx.send_command(cmd)
    return resp

Дамп флэш-памяти при помощи Depthcharge

Теперь, когда мы подготовили соответствующие функции, попробуем сделать следующее:

if __name__ == "__main__":
    log.info("Marvel Super Heroes Depthcharge Test...")
    ctx = console_setup()
    usb_setup(ctx,reset=False)
    # Read the SPI flash into RAM
    rksfc_read(ctx,TARGET_RAM_ADDR,0,0x35E00)
    log.info("Flash read into RAM")
    # Write the data from RAM to a USB drive
    usb_raw_write(ctx,TARGET_RAM_ADDR,0,0x35E00)
    log.info("Flash written to USB")

После запуска этого скрипта получим следующий результат:

pi@voidstar:~/marvel-cab/scripts $ python3 mvc.py
[+] Marvel Super Heroes Deptcharge Test...
[*] Using default payload base address: ${loadaddr} + 32MiB
[*] No user-specified prompt provided. Attempting to determine this.
[*] Identified prompt: =>
[*] Retrieving command list via "help"
[*] Reading environment via "printenv"
[!] Disabling payload deployment and execution due to error(s).
[*] Version: U-Boot 2017.09-g4857df5-dirty #lzy (Mar 24 2021 - 16:18:22 +0800)
[*] Enumerating available MemoryWriter implementations...
[*] Available: CpMemoryWriter
[*] Available: CRC32MemoryWriter
[*] Excluded: I2CMemoryWriter - Command "i2c" required but not detected.
[*] Excluded: LoadbMemoryWriter - Command "loadb" required but not detected.
[*] Excluded: LoadxMemoryWriter - Command "loadx" required but not detected.
[*] Excluded: LoadyMemoryWriter - Command "loady" required but not detected.
[*] Available: MmMemoryWriter
[*] Available: MwMemoryWriter
[*] Available: NmMemoryWriter
[*] Enumerating available MemoryReader implementations...
[!] Excluded: CpCrashMemoryReader - Operation requires crash or reboot, but opt-in not specified.
[*] Available: CRC32MemoryReader
[!] Excluded: GoMemoryReader - Payload deployment+execution opt-in not specified
[*] Excluded: I2CMemoryReader - Command "i2c" required but not detected.
[*] Excluded: ItestMemoryReader - Command "itest" required but not detected.
[*] Available: MdMemoryReader
[*] Available: MmMemoryReader
[*] Excluded: SetexprMemoryReader - Command "setexpr" required but not detected.
[*] Enumerating available Executor implementations...
[!] Excluded: GoExecutor - Payload deployment+execution opt-in not specified
[*] Enumerating available RegisterReader implementations...
[!] Excluded: CpCrashRegisterReader - Operation requires crash or reboot, but opt-in not specified.
[!] Excluded: CRC32CrashRegisterReader - Operation requires crash or reboot, but opt-in not specified.
[!] Excluded: FDTCrashRegisterReader - Operation requires crash or reboot, but opt-in not specified.
[!] Excluded: ItestCrashRegisterReader - Operation requires crash or reboot, but opt-in not specified.
[!] Excluded: MdCrashRegisterReader - Operation requires crash or reboot, but opt-in not specified.
[!] Excluded: MmCrashRegisterReader - Operation requires crash or reboot, but opt-in not specified.
[!] Excluded: NmCrashRegisterReader - Operation requires crash or reboot, but opt-in not specified.
[!] Excluded: SetexprCrashRegisterReader - Operation requires crash or reboot, but opt-in not specified.
[!] No default RegisterReader available.
[+] spinand read: device 0 block # 0, count 220672 ...
[+] Flash read into RAM
[+] => usb write 0x61700000 0x0 0x35e00

usb write: device 0 block # 0, count 220672 ...
[+] Flash written to USB

Вставив USB-накопитель, увидим следующие разделы:

pi@voidstar:~/marvel-cab/scripts $ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 1 57.8G 0 disk
├─sda1 8:1 1 4M 0 part
├─sda2 8:2 1 2M 0 part
├─sda3 8:3 1 9M 0 part
├─sda4 8:4 1 80.8M 0 part
└─sda5 8:5 1 8M 0 part

Победа! Мы извлекли SPI flash на USB-устройство при помощи Depthcharge!

Содержимое файловой системы

Теперь, когда мы надёжным образом можем считывать и записывать флэш-память, давайте вкратце изучим её содержимое. Интересные файлы расположены в папке /moo. В этой папке находится эмулятор и его ресурсы. Moo — это специализированный эмулятор, использующий собственный формат ROM; в 2020 году исследователи тщательно изучили другую версию этого эмулятора. Однако если посмотреть на содержимое папки, в глаза бросается нечто интересное:

pi@voidstar:~/marvel-cab/parts/squashfs-root/moo $ file *
docs: symbolic link to ../userdata
logo.mp4: ISO Media, MP4 Base Media v1 [IS0 14496-12:2003] MOO: symbolic link to MOO-Ship-MIME_CCADE_MSH_2P-BRK01
MOO-Ship-MIME_CCADE_MSH_2P-BRK01: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 4.4.0, stripped
SKUShell.MIME_CCADE_SF2_2P.19.exe: PE32+ executable (GUI) x86-64, for MS Windows
start.sh: POSIX shell script, ASCII text executable
_ui: directory
zassets: directory

Это ведь не может исполняемый файл Windows PE32? Ну, если мы скопируем его на машину с и попробуем его запустить, то получим следующее:

MOO Running

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

Благодаря описанным в статье способам мы можем выполнять считывание и запись между SPI flash и USB-накопителем при помощи UBoot. Мы извлекли файловую систему рута и идентифицировали основные компоненты эмуляции. Далее нам нужно будет выполнить реверс-инжиниринг части двоичных файлов на этой целевой платформе, чтобы определить, насколько сложно будет запускать собственные прошивки.

Выводы и дальнейшая работа

В этом посте мы рассказали о том, как подходить к разборке встроенного устройства и идентифицировать потенциальные отладочные разъёмы при помощи мультиметров/логических анализаторов. Затем мы рассказали о том, как анализировать неизвестный трафик UART и подключаться к последовательному порту при помощи screen на Raspberry Pi. Подключившись к последовательному порту, мы обнаружили, что доступ к консоли UBoot можно получить нажатием Ctrl-C. Изучив консоль UBoot, мы написали скрипт depthcharge для извлечения каждого из разделов SPI flash на внешний флэш-накопитель. В следующем посте мы подробно рассмотрим двоичный файл UBoot и узнаем, как создавать и модифицировать схемы распределения памяти при помощи Ghidra; затем мы попробуем прошить устройство собственным ядром и установить свою прошивку.

Все использованные скрипты и инструменты можно найти на github.


 

Источник

Читайте также