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


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

2594e8c1c29ed56624f25b1317924951.png


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

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


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

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

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

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


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

10c0d4c6cf9cae263e68734d4855670d.jpg


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

0f9cc3e69a7fe891d01ebfcde7a6e674.jpg


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

eb015480f0a07a60a2b98d12f815e451.jpg


На этой компоненте написано 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 и внешней ОЗУ. Однако мы пока не знаем тип энергонезависимой памяти. Поэтому далее давайте исследуем компонент, выделенный на изображении розовым.

4b327fe4b17049b74d146a2f14cc0c57.jpg


Этот компонент имеет маркировку 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


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

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


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

a73ad4c26060e31706a29f4299ad29dc.png


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

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

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


8a35e882af8be11bc91bb8a68191e37d.png


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

d6b1af13d0a791699169990f212dc8ad.png


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

d3b522ddeaac6248a18c79d84f01dc0c.png


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

5320b7276403612624a10373782fe247.png


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

1c0d4a96c327a8cc90d8ace114540995.png


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

6b02289e9321192c131751c6d381b068.png


5fee58028210adec3997afc08f086dd9.jpg


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

3e5dbad97f18c6cb4e1187a94ba18f59.png


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

22bc79cae4b8c66551268e27f06c9f01.png


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

66e041a04e05b1a5acc5344e365018fc.png


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

a58cc6de02d7120ac72aa5cdd3d978f3.png


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

b71ba06c8c9e48f4be032970b73f6e42.png



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


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

f668816de3235924a1928f9b7d684094.png


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

79fa13d9e72f48d2ccfc414b57fcd1a1.gif


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

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

079a4238385e814dea1164510e306e65.jpg


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

792ac62d7df55adda440ae6f066eea09.png


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

a3181592a38bf37049e62d3a3fa34d58.png


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

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


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

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


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

1af5025c76192f491e4ad86bb58d6ea5.png


128a8b9d0ae7fdb69d42871993d531d5.png


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

1124c692a99248bf1211981f3e44a8a1.png


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

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

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


dea358b25c460535a8e03dce363ee13e.png


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

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

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

69c183c2096eb7ab5df415fd8ceb46d6.png


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

46a600314dbca5330ea42cd16e18b172.jpg


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


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

lxthuxqkfrhokk6vop5ruq4fkdq.png


Чтобы включить этот 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

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

22f0e5e4b809422889968e7317e6aa57.png


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

22f0e5e4b809422889968e7317e6aa57.png


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

  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:

8760d8578ce1c8b6790dc7617dd65618.png


Инструменты 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. При включении питания автомата мы видим следующее:

bb3629f518ae18aaccb329f9e363dc86.png


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

1f5ec076630a80e77ca26ce3fa1d0c6f.png


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

[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 — это загрузчик в 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
=>

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


Если на этом этапе мы сопоставим это с информацией, полученной при анализе оборудования, то получим согласованные результаты. После анализа оборудования мы предположили, что основным накопителем является 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: 0×0308 Rev: V1.00 Prod: rkflash-SpiNand
Type: Hard Disk
Capacity: 107.7 MB = 0.1 GB (220672×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 0×00002000 0×00003fff «uboot»
attrs: 0×0000000000000000
type: ea450000–0000–424f-8000–0cd500004c0a
guid: 325b0000–0000–4d21–8000–6e10000051c5
2 0×00004000 0×00004fff «trust»
attrs: 0×0000000000000000
type: b44a0000–0000–4121–8000–4e1600002902
guid: 62500000–0000–4f7f-8000–4a7800006ca1
3 0×00005000 0×000097ff «boot»
attrs: 0×0000000000000000
type: 6c1e0000–0000–4833–8000–5d07000065c4
guid: 442c0000–0000–4c4c-8000–2ed400005ecb
4 0×00009800 0×00031dff «rootfs»
attrs: 0×0000000000000000
type: 9b050000–0000–4e44–8000–5f3000007157
guid: 614e0000–0000–4b53–8000–1d28000054a9
5 0×00031e00 0×00035dde «userdata»
attrs: 0×0000000000000000
type: c8050000–0000–4b18–8000–3b1a000041c3
guid: 40780000–0000–493e-8000–688900001525
=>

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

  • 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
us

© Habrahabr.ru