
В рамките на срещата 0x0A DC7831 На 16 февруари представихме доклад за основните принципи на емулиране на бинарен код и собствената ни разработка – емулация на хардуерни платформи .
В статията ще представим описание на стартирането на фърмуера на устройството в емулацията, ще демонстрираме взаимодействието с отладчика и ще извършим малък динамичен анализ на фърмуера.
Предистория
Преди много време в галактика далеч далеч
Преди няколко години в нашата лаборатория се появи необходимостта да изследваме фърмуера на устройството. Фърмуерът беше компресиран и се разопаковаше от bootloader. Направи го по доста сложен начин, няколко пъти прехвърляйки данните в паметта. А и самият фърмуер активно взаимодействаше с периферията. И всичко това на ядро MIPS.
Съществуващите емулятори по обективни причини не ни удовлетвориха, но искахме да стартираме кода. Тогава решихме да направим свой емулятор, който да направи минимално и да позволи да се разопакова основният фърмуер. Опитахме – стана. Помислихме, а какво, ако добавим периферия, за да можем да изпълняваме основния фърмуер. Не беше много трудно – и стана. Пак помислихме и решихме да направим пълноценен емулятор.
В крайна сметка се получи емулятор на изчислителни системи .

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

Емулаторът има модулна структура и крайното виртуално устройство може да бъде описано в JSON файл.
JSON с 105 реда
{
"top": true,
// Името на плъгина трябва да бъде същото като името на файла (или пълния път от началото на библиотеката)
"plugin": "rhino",
// Директория, където плъгинът поставя
"library": "user",
// Параметри на плъгина (конструкторски параметри, ако е версия на jar-плъгин)
"params": [
{ "name": "tty_dbg", "type": "String"},
{ "name": "tty_bt", "type": "String"},
{ "name": "firmware", "type": "String", "default": "NUL"}
],
// Външни портове на плъгина
"ports": [ ],
// Вътрешни шини на плъгина
"buses": [
{ "name": "mem", "size": "BUS30" },
{ "name": "nand", "size": "4" },
{ "name": "gpio", "size": "BUS32" }
],
// Вътрешни компоненти на плъгина
"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" }
],
// Свързване между компоненти на плъгина
"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"]
]
}Обърнете внимание на параметъра прошивка в раздела параметри — това е името на файла, който може да бъде зареждан в виртуалното устройство като прошивка.
Виртуалното устройство и взаимодействието му с основната операционна система могат да бъдат представени с тази схема:

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

Използване на com0com
Виртуалните COM портове се конфигурират с настройващата утилита от комплекта 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 не се поддържа в самостоятелен Python, трябва да бъде внедрен в 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 > (или Argparse >):
18:07:59 INFO [eFactoryBuilder.create ]: Модулът top е създаден успешно като top
18:07:59 INFO [ Module.initializeAndRes]: Настройка на ядрото на top.u1_stm32.cortexm0.arm за top
18:07:59 INFO [ Module.initializeAndRes]: Настройка на дебъгера на top.u1_stm32.dbg за top
18:07:59 WARN [ Module.initializeAndRes]: Трейсърът не беше намерен в top...
18:07:59 INFO [ Module.initializeAndRes]: Инициализиране на портове и шини...
18:07:59 WARN [ Module.initializePortsA]: ВНИМАНИЕ: Някои портове имат предупреждение, използвайте printModulesPortsWarnings, за да ги видите...
18:07:59 FINE [ ARMv6CPU.reset ]: Задайте адреса на входната точка на 08006A75
18:07:59 INFO [ Module.initializeAndRes]: Модулът top е успешно инициализиран и нулиран като основна клетка!
18:07:59 INFO [ Kopycat.open ]: Стартиране на виртуализация на дъската top[rhino] с arm[ARMv6Core]
18:07:59 INFO [ GDBServer.debuggerModule ]: Задайте нов модул за дебъгер top.u1_stm32.dbg за GDB_SERVER(port=23946,alive=true)
Python >Взаимодействие с IDA Pro
Като изходен файл за анализ в IDA за опростяване на тестването, използваме фърмуера "Носорог" под формата на (там е запазена метаинформация).
Можете също да използвате основния фърмуер без метаинформация.
След стартиране на Kopycat в IDA Pro в менюто Debugger отиваме в пункта "Switch debugger…и избираме "Отдалечен GDB отладчик«. Следва настройка на свързване: меню Отладчик — Опции на процеса…
Задаваме стойностите:
- Приложение — произволна стойност
- Име на хост: 127.0.0.1 (или IP адрес на отдалечената машина, където е стартиран Kopycat)
- Порт: 23946

Сега бутонът за стартиране на отладка (клавиш F9) става активен:
![]()
Натискаме го — свързване с модула на отладчика в емулатора. IDA преминава в режим на отладка, стават достъпни допълнителни прозорци: информация за регистрите, за стека.
Сега можем да използваме всички стандартни функции на отладчика:
- поетапно изпълнение на инструкции (Стъпка в и Стъпка над — клавиши F7 и F8, съответно);
- стартиране и спиране на изпълнението;
- създаване на точки за спиране както за код, така и за данни (клавиш F2).
Свързването с отладчика не означава стартиране на кода за фърмуера. Текущата позиция за изпълнение трябва да бъде адрес 0x08006A74 — начало на функцията Reset_Handler. Ако превъртите листинга надолу, можете да видите извикването на функцията main. Можете да поставите курсора на този ред (адрес 0x08006ABE) и да извършите операция Изпълни до курсора (клавиш F4).

След това можете да натиснете F7, за да влезете в функцията main.
Ако изпълните командата Продължи процеса (клавиш F9), ще се появи прозорец „Моля, изчакайте“ с единствен бутон Спиране:

При натискане Спиране изпълнението на кода за фърмуера спира и може да бъде продължено от същия адрес в кода, където е било прекъснато.
Ако продължите изпълнението на кода, в терминалите, свързани към виртуалните COM-порти, можете да видите следните редове:


Наличието на реда „състояние за заобикаляне“ показва, че виртуалният 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…
Добавяме нов snippet в списъка отляво, даваме му име (например, BPT),
в текстовото поле отдясно с въвеждаме кода на функцията:
def skip_dma():
print "Skipping wait ws2812..."
value = Byte(0x200004C4)
if value == 1:
PatchDbgByte(0x200004C4, 0)
return False 
След това натискаме Run и затваряме прозореца на скриптовете.
Сега да преминем към кода на адрес 0x0800688A, поставяме breakpoint (клавиш F2), редактираме го (контекстно меню Edit breakpoint…), не забравяйте да зададете типа на скрипта – Python:


Ако текущата стойност на флага busy е равна на 1, то следва да изпълним функцията skip_dma в реда на скриптовете:

Ако стартираме фърмуера за изпълнение, то задействането на кода на обработчика на breakpoint може да се види в IDA в прозореца Регистри за посока, регистра за изход. В текущия проект те няма да са ни нужни. по реда 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 сървър и разпределението на паметта, поради което не работят точките на спиране и стъпките (командата 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Настройка на конфигурацията за отстраняване на грешки
В 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’а).
Обърнете внимание, ако не искате да зареждате файла с фърмуер от Eclipse, то параметрите Load image и Run commands не е необходимо да се посочват.

След натискане на Debug можете да работите в режим на отладчик:
- стъпково изпълнение на кода

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

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

Вместо заключение
Емулцията на нативен код е доста интересна работа. За разработчика на устройства се появява възможност да отлажда фърмуер без реално устройство. За изследователя — възможност за динамичен анализ на кода, което не винаги е възможно дори при наличие на устройство.
Искаме да предоставим на специалистите инструмент, който да е удобен, сравнително прост и да не отнема много усилия и време за настройка и стартиране.
Напишете в коментарите за вашия опит с хардуерни емулатори. Каним ви на дискусия и ще се радваме да отговорим на въпросите.
Само регистрирани потребители могат да участват в анкетата. , моля.
За какво използвате емулатора?
разработвам (отлаждам) фърмуери
изследвам фърмуери
стартирам игри (Dendi, Sega, PSP)
нещо друго (напишете в коментара)
Гласували 7 потребители. Въздържали се 2 потребителя.
Какъв софтуер използвате за емулация на нативен код?
QEMU
Unicorn engine
Proteus
нещо друго (напишете в коментара)
Гласували 6 потребители. Въздържали се 2 потребителя.
Какво бихте искали да подобрите в използвания емулатор?
искам скорост
искам удобство при настройка/стартиране
искам повече възможности за взаимодействие с емулатора (API, хукове)
всичко ми е наред
нещо друго (напишете в коментара)
Гласуваха 8 потребители. 1 потребител се въздържа.
Източник: habr.com


