
В рамках встречи 0x0A DC7831 16 февраля мы представили доклад о базовых принципах эмуляции бинарного кода и собственной разработке — эмуляторе аппаратных платформ .
В статье мы приведём описание запуска прошивки устройства в эмуляторе, продемонстрируем взаимодействие с отладчиком и выполним небольшой динамический анализ прошивки.
Historia e mëparshme
A long time ago in a galaxy far far away
Пару лет назад в нашей лаборатории возникла необходимость исследовать прошивку устройства. Прошивка была сжата, распаковывалась bootloader’ом. Делал он это весьма замороченным способом, несколько раз перекладывая данные в памяти. Да и сама прошивка потом активно взаимодействовала с периферией. И всё это на ядре MIPS.
Имеющиеся эмуляторы по объективным причинам нас не устроили, а хотелось всё-таки запустить код. Тогда решили сделать свой эмулятор, который сделает минимум и позволит распаковать основную прошивку. Попробовали — получилось. Подумали, а что если добавить периферию, чтобы еще и основную прошивку выполнять. Было не очень больно — и тоже получилось. Снова подумали и решили делать полноценный эмулятор.
В итоге получился эмулятор вычислительных систем .

Почему Kopycat?
Имеет место игра слов.
- copycat (англ., сущ. [ˈkɒpɪkæt]) — подражатель, имитатор
- cat (англ., сущ. [ˈkæt]) — кошка, кот — любимое животное одного из создателей проекта
- Буква «K» — от языка программирования Kotlin
Kopycat
При создании эмулятора ставились совершенно определённые цели:
- возможность достаточно быстро создать новую периферию, модуль, процессорное ядро;
- возможность собрать виртуальное устройство из различных модулей;
- возможность загрузить в память виртуального устройства любые двоичные данные (прошивку);
- возможность работы со снапшотами (снимки состояния системы);
- возможность взаимодействия с эмулятором через встроенный отладчик;
- приятный современный язык для разработки.
В итоге, для реализации был выбран Kotlin, шинная архитектура (это когда модули связываются между собой через виртуальные шины данных), JSON — в качестве формата описания устройства, и GDB RSP — в качестве протокола взаимодействия с отладчиком.
Разработка идёт на протяжении чуть больше двух лет и активно продолжается. За это время были реализованы процессорные ядра MIPS, x86, V850ES, ARM, PowerPC.
Проект растет, и пришло время представить его широкой общественности. Подробное описание проекта сделаем позже, а сейчас сосредоточимся на использовании Kopycat.
Для самых нетерпеливых — промо-версию эмулятора можно скачать по .
Носорог в эмуляторе
Напомним, ранее для конференции SMARTRHINO-2018 было создано тестовое устройство «Носорог» для обучения навыкам реверс-инжиниринга. Процесс статического анализа прошивки был описан в .
Теперь же попробуем добавить «динамики» и запустим прошивку в эмуляторе.
Нам понадобятся:
1) Java 1.8
2) Python и модуль для использования Python внутри эмулятора. WHL-cборку модуля Jep под Windows можно .
Për Windows:
1)
2)
Për Linux:
1) socat
В качестве GDB-клиента можно использовать Eclipse, IDA Pro или radare2.
Si funksionon kjo?
Для того, чтобы выполнять прошивку в эмуляторе, необходимо «собрать» виртуальное устройство, которое представляет собой аналог реального устройства.
Реальное устройство («носорог») можно показать на структурной схеме:

Эмулятор имеет модульную структуру и конечное виртуальное устройство можно описать в JSON-файле.
JSON на 105 строк
{
"top": true,
// Plugin name should be the same as file name (or full path from library start)
"plugin": "rhino",
// Directory where plugin places
"library": "user",
// Plugin parameters (constructor parameters if jar-plugin version)
"params": [
{ "name": "tty_dbg", "type": "String"},
{ "name": "tty_bt", "type": "String"},
{ "name": "firmware", "type": "String", "default": "NUL"}
],
// Plugin outer ports
"ports": [ ],
// Plugin internal buses
"buses": [
{ "name": "mem", "size": "BUS30" },
{ "name": "nand", "size": "4" },
{ "name": "gpio", "size": "BUS32" }
],
// Plugin internal components
"modules": [
{
"name": "u1_stm32",
"plugin": "STM32F042",
"library": "mcu",
"params": {
"firmware:String": "params.firmware"
}
},
{
"name": "usart_debug",
"plugin": "UartSerialTerminal",
"library": "terminals",
"params": {
"tty": "params.tty_dbg"
}
},
{
"name": "term_bt",
"plugin": "UartSerialTerminal",
"library": "terminals",
"params": {
"tty": "params.tty_bt"
}
},
{
"name": "bluetooth",
"plugin": "BT",
"library": "mcu"
},
{ "name": "led_0", "plugin": "LED", "library": "mcu" },
{ "name": "led_1", "plugin": "LED", "library": "mcu" },
{ "name": "led_2", "plugin": "LED", "library": "mcu" },
{ "name": "led_3", "plugin": "LED", "library": "mcu" },
{ "name": "led_4", "plugin": "LED", "library": "mcu" },
{ "name": "led_5", "plugin": "LED", "library": "mcu" },
{ "name": "led_6", "plugin": "LED", "library": "mcu" },
{ "name": "led_7", "plugin": "LED", "library": "mcu" },
{ "name": "led_8", "plugin": "LED", "library": "mcu" },
{ "name": "led_9", "plugin": "LED", "library": "mcu" },
{ "name": "led_10", "plugin": "LED", "library": "mcu" },
{ "name": "led_11", "plugin": "LED", "library": "mcu" },
{ "name": "led_12", "plugin": "LED", "library": "mcu" },
{ "name": "led_13", "plugin": "LED", "library": "mcu" },
{ "name": "led_14", "plugin": "LED", "library": "mcu" },
{ "name": "led_15", "plugin": "LED", "library": "mcu" }
],
// Plugin connection between components
"connections": [
[ "u1_stm32.ports.usart1_m", "usart_debug.ports.term_s"],
[ "u1_stm32.ports.usart1_s", "usart_debug.ports.term_m"],
[ "u1_stm32.ports.usart2_m", "bluetooth.ports.usart_m"],
[ "u1_stm32.ports.usart2_s", "bluetooth.ports.usart_s"],
[ "bluetooth.ports.bt_s", "term_bt.ports.term_m"],
[ "bluetooth.ports.bt_m", "term_bt.ports.term_s"],
[ "led_0.ports.pin", "u1_stm32.buses.pin_output_a", "0x00"],
[ "led_1.ports.pin", "u1_stm32.buses.pin_output_a", "0x01"],
[ "led_2.ports.pin", "u1_stm32.buses.pin_output_a", "0x02"],
[ "led_3.ports.pin", "u1_stm32.buses.pin_output_a", "0x03"],
[ "led_4.ports.pin", "u1_stm32.buses.pin_output_a", "0x04"],
[ "led_5.ports.pin", "u1_stm32.buses.pin_output_a", "0x05"],
[ "led_6.ports.pin", "u1_stm32.buses.pin_output_a", "0x06"],
[ "led_7.ports.pin", "u1_stm32.buses.pin_output_a", "0x07"],
[ "led_8.ports.pin", "u1_stm32.buses.pin_output_a", "0x08"],
[ "led_9.ports.pin", "u1_stm32.buses.pin_output_a", "0x09"],
[ "led_10.ports.pin", "u1_stm32.buses.pin_output_a", "0x0A"],
[ "led_11.ports.pin", "u1_stm32.buses.pin_output_a", "0x0B"],
[ "led_12.ports.pin", "u1_stm32.buses.pin_output_a", "0x0C"],
[ "led_13.ports.pin", "u1_stm32.buses.pin_output_a", "0x0D"],
[ "led_14.ports.pin", "u1_stm32.buses.pin_output_a", "0x0E"],
[ "led_15.ports.pin", "u1_stm32.buses.pin_output_a", "0x0F"]
]
}Обратите внимание на параметр firmware в разделе params — это имя файла, который можно загружать в виртуальное устройство в качестве прошивки.
Виртуальное устройство и его взаимодействие с основной операционной системой можно представить вот такой схемой:

Текущий тестовый экземпляр эмулятора подразумевает взаимодействие с COM-портами основной ОС (отладочный UART и UART для Bluetooth-модуля). Это могут быть реальные порты, к которым подключены устройства или же виртуальные COM-порты (для этого как раз нужен com0com / socat).
Для взаимодействия с эмулятором извне на данный момент существует два основных способа:
- протокол GDB RSP (соответственно, поддерживающие этот протокол, инструменты — Eclipse / IDA / radare2);
- внутренняя командная строка эмулятора (Argparse или Python).
Виртуальные COM-порты
Для того чтобы взаимодействовать с UART-ом виртуального устройства на локальной машине через терминал, необходимо создать пару связанных виртуальных COM-портов. В нашем случае один порт задействует эмулятор, а второй — программа-терминал (PuTTY или screen):

Использование com0com
Виртуальные COM-порты настраиваются setup-утилитой из комплекта com0com (консольная версия — C:Program Files (x86)com0comsetupс.exe, или GUI-версия — C:Program Files (x86)com0comsetupg.exe):

Следует установить галочки enable buffer overrun для всех созданных виртуальных портов, иначе эмулятор будет ожидать отклика от COM-порта.
Использование socat
На UNIX-системах виртуальные COM-порты автоматически создаются эмулятором при помощи утилиты socat, для этого достаточно при запуске эмулятора в имени порта указать префикс socat:.
Внутренний интерфейс командной строки (Argparse или Python)
Поскольку Kopycat представляет собой консольное приложение, для взаимодействия со своими объектами и переменными эмулятор предоставляет два варианта интерфейса командной строки: Argparse и Python.
Argparse — это CLI, встроенный в Kopycat, он доступен всегда и всем.
Альтернативный CLI — интерпретатор Python. Для его использования необходимо установить Python-модуль Jep и настроить эмулятор для работы с Python (будет использоваться интерпретатор Python, установленный в основной системе пользователя).
Установка Python-модуля Jep
Под Linux Jep может быть установлен через pip:
pip install jepДля установки Jep под Windows необходимо предварительно установить Windows SDK и соответствующую Microsoft Visual Studio. Мы немного упростили вам задачу и сделали JEP под актуальные версии Python для Windows, поэтому модуль можно установить из файла:
pip install jep-3.8.2-cp27-cp27m-win_amd64.whlДля проверки установки Jep, необходимо выполнить в командной строке:
python -c "import jep"В ответ должно быть получено сообщение:
ImportError: Jep is not supported in standalone Python, it must be embedded in Java.В командном файле эмулятора для вашей системы (kopycat.bat — для Windows, kopycat — для Linux) к списку параметров DEFAULT_JVM_OPTS добавьте дополнительный параметр Djava.library.path — он должен содержать путь до установленного модуля Jep.
В результате для Windows должна получиться строка следующего вида:
set DEFAULT_JVM_OPTS="-XX:MaxMetaspaceSize=256m" "-XX:+UseParallelGC" "-XX:SurvivorRatio=6" "-XX:-UseGCOverheadLimit" "-Djava.library.path=C:/Python27/Lib/site-packages/jep"Запуск Kopycat
Эмулятор представляет собой консольное JVM-приложение. Запуск осуществляется через сценарий командной строки операционной системы (sh/cmd).
Команда для запуска под Windows:
binkopycat -g 23946 -n rhino -l user -y library -p firmware=firmwarerhino_pass.bin,tty_dbg=COM26,tty_bt=COM28Команда для запуска под Linux с использованием утилиты socat:
./bin/kopycat -g 23946 -n rhino -l user -y library -p firmware=./firmware/rhino_pass.bin, tty_dbg=socat:./COM26,tty_bt=socat:./COM28-g 23646— TCP-порт, который будет открыт для доступа к GDB-серверу;-n rhino— имя основного модуля системы (устройство в сборе);-l user— имя библиотеки для поиска основного модуля;-y library— путь для поиска модулей, входящих в устройство;firmwarerhino_pass.bin— путь к файлу прошивки;- COM26 и COM28 — виртуальные COM-порты.
В результате будет выведено приглашение Python > (ose Argparse >):
18:07:59 INFO [eFactoryBuilder.create ]: Module top successfully created as top
18:07:59 INFO [ Module.initializeAndRes]: Setup core to top.u1_stm32.cortexm0.arm for top
18:07:59 INFO [ Module.initializeAndRes]: Setup debugger to top.u1_stm32.dbg for top
18:07:59 WARN [ Module.initializeAndRes]: Tracer wasn't found in top...
18:07:59 INFO [ Module.initializeAndRes]: Initializing ports and buses...
18:07:59 WARN [ Module.initializePortsA]: ATTENTION: Some ports has warning use printModulesPortsWarnings to see it...
18:07:59 FINE [ ARMv6CPU.reset ]: Set entry point address to 08006A75
18:07:59 INFO [ Module.initializeAndRes]: Module top is successfully initialized and reset as a top cell!
18:07:59 INFO [ Kopycat.open ]: Starting virtualization of board top[rhino] with arm[ARMv6Core]
18:07:59 INFO [ GDBServer.debuggerModule ]: Set new debugger module top.u1_stm32.dbg for GDB_SERVER(port=23946,alive=true)
Python >Взаимодействие с IDA Pro
В качестве исходного файла для анализа в IDA для упрощения тестирования используем прошивку «Носорога» в виде (там сохранена метаинформация).
Вы также можете использовать основную прошивку без метаинформации.
После запуска Kopycat в IDA Pro в меню Debugger идём в пункт «Switch debugger…» и выбираем «Remote GDB debugger«. Далее настраиваем подключение: меню Debugger — Process options…
Устанавливаем значения:
- Application — любое значение
- Hostname: 127.0.0.1 (или IP-адрес удаленной машины, где запущен Kopycat)
- Port: 23946

Теперь становится доступна кнопка запуска отладки (клавиша F9):
![]()
Нажимаем её — происходит подключение к модулю отладчика в эмуляторе. IDA переходит в режим отладки, становятся доступны дополнительные окна: информация о регистрах, о стеке.
Теперь мы можем использовать все стандартные возможности работы с отладчиком:
- пошаговое выполнение инструкций (Step into dhe Step over — клавиши F7 и F8, соответственно);
- запуск и приостановка выполнения;
- создание точек останова как на код, так и на данные (клавиша F2).
Подключение к отладчику не означает запуска кода прошивки. Текущей позицией для выполнения должен быть адрес 0x08006A74 — начало функции Reset_Handler. Если прокрутить листинг ниже, то можно увидеть вызов функции main. Можно установить курсор на этой строке (адрес 0x08006ABE) и выполнить операцию Run until cursor (клавиша F4).

Далее можно нажать F7, чтобы зайти в функцию main.
Если выполнить команду Continue process (клавиша F9), то появится окно «Please wait» с единственной кнопкой Suspend:

При нажатии Suspend выполнение кода прошивки приостанавливается и может быть продолжено с того же адреса в коде, где было прервано.
Если продолжить выполнение кода, то в терминалах, подключенных к виртуальным COM-портам, можно увидеть следующие строки:


Наличие строки «state bypass» говорит о том, что виртуальный Bluetooth-модуль перешёл в режим приёма данных от COM-порта пользователя.
Теперь в Bluetooth-терминале (на рисунке — COM29) можно вводить команды в соответствии с протоколом «Носорога». Например, на команду «MEOW» в Bluetooth-терминал вернётся строка «mur-mur»:

Эмулируй меня не полностью
При построении эмулятора можно выбирать степень детализации/эмуляции того или иного устройства. Так, например, модуль Bluetooth можно эмулировать по-разному:
- эмулируется полностью устройство с полным набором команд;
- эмулируются AT-команды, а поток данных принимается с COM-порта основной системы;
- виртуальное устройство обеспечивает полное перенаправление данных на реальное устройство;
- в виде простой заглушки, которая всегда возвращает «OK».
В текущей версии эмулятора используется второй подход — виртуальный Bluetooth-модуль выполняет конфигурирование, после чего переходит в режим «проксирования» данных из COM-порта основной системы в UART-порт эмулятора.

Рассмотрим возможность простой инструментации кода в случае, если не реализована какая-то часть периферии. Например, если не создан таймер, отвечающий за контроль передачи данных в DMA (проверка выполняется в функции ws2812b_wait, расположенной по адресу 0x08006840), то прошивка будет всегда ждать сброса флага busy, расположенного по адресу 0x200004C4, который показывает занятость линии данных DMA:

Мы можем обойти такую ситуацию путём «ручного» сброса флага busy сразу после его установки. В IDA Pro можно создать Python-функцию и вызывать её в breakpoint’е, при этом сам breakpoint поставить в коде после записи значения 1 во флаг busy.
Breakpoint-обработчик
Сначала создадим Python-функцию в IDA. Меню File — Script command…
Добавляем новый сниппет в списке слева, даём ему имя (например, BPT),
в текстовом поле справа вводим код функции:
def skip_dma():
print "Skipping wait ws2812..."
value = Byte(0x200004C4)
if value == 1:
PatchDbgByte(0x200004C4, 0)
return False 
После этого нажимаем Ekzekuto и закрываем окно скриптов.
Теперь перейдём в код по адресу 0x0800688A, установим breakpoint (клавиша F2), отредактируем его (контекстное меню Edit breakpoint…), не забудем установить тип скрипта – Python:


Если текущее значение флага busy равно 1, то следует выполнить функцию skip_dma в строке скриптов:

Если запустить прошивку на выполнение, то срабатывание кода breakpoint-обработчика можно увидеть в IDA в окне Output по строке Skipping wait ws2812.... Теперь прошивка не будет ожидать сброс флага busy.
Взаимодействие с эмулятором
Эмуляция ради эмуляции вряд ли вызовет восторг и радость. Гораздо интереснее, если эмулятор поможет исследователю увидеть данные в памяти или установить взаимодействие потоков.
Покажем, как в динамике установить взаимодействие RTOS-тасков. Предварительно следует приостановить выполнение кода, если оно запущено. Если перейти в функцию bluetooth_task_entry в ветку обработки команды «LED » (адрес 0x080057B8), то можно увидеть, что сначала создается, а потом отправляется в системную очередь ledControlQueueHandle некоторое сообщение.

Следует установить breakpoint на обращение к переменной ledControlQueueHandle, расположенной по адресу 0x20000624 и продолжить выполнение кода:

В результате сначала произойдет останов по адресу 0x080057CA перед вызовом функции osMailAlloc, далее — по адресу 0x08005806 перед вызовом функции osMailPut, потом через некоторое время — по адресу 0x08005BD4 (перед вызовом функции osMailGet), который принадлежит функции leds_task_entry (LED-таск), то есть произошло переключение тасков, и теперь управление получил LED-таск.

Таким нехитрым способом можно установить, как таски RTOS взаимодействуют друг с другом.
Конечно, в действительности взаимодействие тасков может быть устроено сложнее, но с использованием эмулятора отслеживать это взаимодействие становится менее трудоемко.
можно посмотреть небольшое видео запуска эмулятора и взаимодействия с IDA Pro.
Запуск с Radare2
Нельзя обойти стороной такой универсальный инструмент как Radare2.
Для подключения к эмулятору с использованием r2 команда будет выглядеть так:
radare2 -A -a arm -b 16 -d gdb://localhost:23946 rhino_fw42k6.elfСейчас доступны запуск (dc) и приостановка выполнения (Ctrl+C).
К сожалению, на данный момент в r2 есть проблемы при работе с хардварным gdb-сервером и разметкой памяти, из-за этого не работают точки останова и Step’ы (команда ds). Надеемся, в ближайшее время это будет исправлено.
Запуск с Eclipse
Один из вариантов использования эмулятора — отладка прошивки разрабатываемого устройства. Для наглядности будем также использовать прошивку «Носорога». Скачать исходники прошивки можно .
В качестве IDE будем использовать Eclipse из набора .
Для того, чтобы в эмулятор загружалась прошивка непосредственно собранная в Eclipse, необходимо добавить параметр firmware=null в команду запуска эмулятора:
binkopycat -g 23946 -n rhino -l user -y modules -p firmware=null,tty_dbg=COM26,tty_bt=COM28Настройка debug-конфигурации
В Eclipse выбираем меню Run — Debug Configurations… В открывшемся окне в разделе GDB Hardware Debugging необходимо добавить новую конфигурацию, после чего на вкладке «Main» указать текущий проект и приложение для отладки:

На вкладке «Debugger» необходимо указать GDB-команду:
${openstm32_compiler_path}arm-none-eabi-gdb
А также ввести параметры для подключения к GDB-серверу (хост и порт):

На вкладке «Startup» необходимо указать следующие параметры:
- включить галочку Load image (чтобы выполнялась загрузка в эмулятор собранного образа прошивки);
- включить галочку Load symbols;
- добавить команду запуска:
set $pc = *0x08000004(выставить в регистр PC значение из памяти по адресу0x08000004— там хранится адрес ResetHandler’а).
Vini re, если вы не хотите загружать файл прошивки из Eclipse, то параметры Load image dhe Run commands указывать не нужно.

После нажатия Debug можно работать в режиме отладчика:
- пошаговое выполнение кода

- взаимодействие с точками останова

Shënim. В Eclipse есть, хмм… некоторые особенности… и с ними приходится жить. Вот, например, если при запуске отладчика появится сообщение «No source available for «0x0″», то выполните команду Step (F5)

Në përfundim
Эмуляция нативного кода — дело весьма интересное. Для разработчика устройств появляется возможность отлаживать прошивку без реального устройства. Для исследователя — возможность проводить динамический анализ кода, что не всегда возможно даже при наличии устройства.
Мы хотим предоставить специалистам инструмент, который был бы удобен, в меру прост и не отнимал много сил и времени на свою настройку и запуск.
Напишите в комментариях о своём опыте использования аппаратных эмуляторов. Приглашаем к обсуждению и будем рады ответить на вопросы.
Vetëm përdoruesit e regjistruar mund të marrin pjesë në anketë. , ju lutem.
Для чего Вы используете эмулятор?
разрабатываю (отлаживаю) прошивки
исследую прошивки
запускаю игры (Dendi, Sega, PSP)
что-то другое (напишите в комментарии)
Проголосовали 7 пользователей. Воздержались 2 пользователя.
Какой софт Вы используете для эмуляции нативного кода?
QEMU
Unicorn engine
Proteus
что-то другое (напишите в комментарии)
Проголосовали 6 пользователей. Воздержались 2 пользователя.
Что бы Вам хотелось улучшить в используемом эмуляторе?
хочется скорости
хочется удобства настройки/запуска
хочется больше возможностей взаимодействия с эмулятором (API, хуки)
меня всё устраивает
что-то другое (напишите в комментарии)
Të votojnë 8 përdorues. U përgjigj një përdorues.
Burimi: habr.com


