Som en del av mötet 0x0A DC7831
I den här artikeln kommer vi att beskriva hur du kör enhetens firmware i emulatorn, demonstrerar interaktion med debuggern och utför en liten dynamisk analys av firmware.
förhistoria
För länge sedan i en galax långt långt borta
För ett par år sedan fanns det ett behov i vårt laboratorium att undersöka en enhets firmware. Den fasta programvaran komprimerades och packades upp med en bootloader. Han gjorde detta på ett mycket komplicerat sätt och flyttade data i minnet flera gånger. Och själva firmwaren interagerade sedan aktivt med kringutrustningen. Och allt detta på MIPS-kärnan.
Av objektiva skäl passade inte de tillgängliga emulatorerna oss, men vi ville ändå köra koden. Sedan bestämde vi oss för att göra vår egen emulator, som skulle göra det minsta och tillåta oss att packa upp den huvudsakliga firmwaren. Vi provade det och det fungerade. Vi tänkte, tänk om vi lägger till kringutrustning för att också utföra den huvudsakliga firmware. Det gjorde inte särskilt ont - och det löste sig också. Vi tänkte om och bestämde oss för att göra en fullfjädrad emulator.
Resultatet blev en datorsystememulator
Varför Kopycat?
Det finns en lek med ord.
- copycat (engelska, substantiv [ˈkɒpɪkæt]) - imitator, imitator
- hur (engelska, substantiv [ˈkæt]) - katt, katt - favoritdjuret till en av skaparna av projektet
- Bokstaven "K" kommer från programmeringsspråket Kotlin
Härmapa
När du skapade emulatorn sattes mycket specifika mål upp:
- förmågan att snabbt skapa nya kringutrustning, moduler, processorkärnor;
- förmågan att montera en virtuell enhet från olika moduler;
- förmågan att ladda alla binära data (firmware) i minnet på en virtuell enhet;
- förmåga att arbeta med ögonblicksbilder (ögonblicksbilder av systemtillståndet);
- förmågan att interagera med emulatorn genom den inbyggda debuggern;
- trevligt modernt språk för utveckling.
Som ett resultat valdes Kotlin för implementering, bussarkitekturen (detta är när moduler kommunicerar med varandra via virtuella databussar), JSON som enhetsbeskrivningsformat och GDB RSP som protokoll för interaktion med debuggern.
Utvecklingen har pågått i drygt två år och pågår aktivt. Under denna tid implementerades MIPS-, x86-, V850ES-, ARM- och PowerPC-processorkärnor.
Projektet växer och det är dags att presentera det för en bredare publik. Vi kommer att göra en detaljerad beskrivning av projektet senare, men för nu kommer vi att fokusera på att använda Kopycat.
För de mest otåliga kan en promoversion av emulatorn laddas ner från
Rhino i emulatorn
Låt oss komma ihåg att tidigare för SMARTRHINO-2018-konferensen skapades en testanordning "Rhinoceros" för att lära ut reverse engineering-färdigheter. Processen med statisk firmwareanalys beskrevs i
Låt oss nu försöka lägga till "högtalare" och köra firmware i emulatorn.
Vi behöver:
1) Java 1.8
2) Python och modul
För Windows:
1)
2)
För Linux:
1) socat
Du kan använda Eclipse, IDA Pro eller radare2 som en GDB-klient.
Hur fungerar det?
För att utföra firmware i emulatorn är det nödvändigt att "montera" en virtuell enhet, som är en analog till en riktig enhet.
Den verkliga enheten ("noshörning") kan visas i blockschemat:
Emulatorn har en modulär struktur och den slutliga virtuella enheten kan beskrivas i en JSON-fil.
JSON 105 linjer
{
"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"]
]
}
Var uppmärksam på parametern firmware avsnitt params är namnet på en fil som kan laddas in i en virtuell enhet som firmware.
Den virtuella enheten och dess interaktion med huvudoperativsystemet kan representeras av följande diagram:
Den aktuella testinstansen av emulatorn involverar interaktion med COM-portarna på huvudoperativsystemet (felsöka UART och UART för Bluetooth-modulen). Dessa kan vara riktiga portar som enheter är anslutna till eller virtuella COM-portar (för detta behöver du bara com0com/socat).
Det finns för närvarande två huvudsakliga sätt att interagera med emulatorn utifrån:
- GDB RSP-protokoll (i enlighet med detta är verktygen som stöder detta protokoll Eclipse / IDA / radare2);
- intern emulatorkommandorad (Argparse eller Python).
Virtuella COM-portar
För att interagera med UART för en virtuell enhet på den lokala maskinen via en terminal måste du skapa ett par associerade virtuella COM-portar. I vårt fall används en port av emulatorn, och den andra används av ett terminalprogram (PuTTY eller skärm):
Använder com0com
Virtuella COM-portar konfigureras med hjälp av installationsverktyget från com0com-paketet (konsolversion - C:Program Files (x86)com0comsetupс.exe, eller GUI-version - C:Program Files (x86)com0comsetupg.exe):
Markera rutorna aktivera buffertöverskridande för alla skapade virtuella portar, annars väntar emulatorn på ett svar från COM-porten.
Använder socat
På UNIX-system skapas virtuella COM-portar automatiskt av emulatorn med hjälp av socat-verktyget; för att göra detta, ange bara prefixet i portnamnet när du startar emulatorn socat:
.
Internt kommandoradsgränssnitt (Argparse eller Python)
Eftersom Kopycat är en konsolapplikation ger emulatorn två kommandoradsgränssnittsalternativ för att interagera med dess objekt och variabler: Argparse och Python.
Argparse är ett CLI inbyggt i Kopycat och är alltid tillgängligt för alla.
En alternativ CLI är Python-tolken. För att använda den måste du installera Jep Python-modulen och konfigurera emulatorn för att fungera med Python (Python-tolken installerad på användarens huvudsystem kommer att användas).
Installera Python-modulen Jep
Under Linux kan Jep installeras via pip:
pip install jep
För att installera Jep på Windows måste du först installera Windows SDK och motsvarande Microsoft Visual Studio. Vi har gjort det lite lättare för dig och
pip install jep-3.8.2-cp27-cp27m-win_amd64.whl
För att kontrollera installationen av Jep måste du köra på kommandoraden:
python -c "import jep"
Följande meddelande bör tas emot som svar:
ImportError: Jep is not supported in standalone Python, it must be embedded in Java.
I emulatorns batchfil för ditt system (copycat.bat - för Windows, härmapa - för Linux) till listan över parametrar DEFAULT_JVM_OPTS
lägga till en extra parameter Djava.library.path
— den måste innehålla sökvägen till den installerade Jep-modulen.
Resultatet för Windows bör vara en rad så här:
set DEFAULT_JVM_OPTS="-XX:MaxMetaspaceSize=256m" "-XX:+UseParallelGC" "-XX:SurvivorRatio=6" "-XX:-UseGCOverheadLimit" "-Djava.library.path=C:/Python27/Lib/site-packages/jep"
Startar Kopycat
Emulatorn är en konsol JVM-applikation. Lanseringen utförs genom operativsystemets kommandoradsskript (sh/cmd).
Kommando att köra under Windows:
binkopycat -g 23946 -n rhino -l user -y library -p firmware=firmwarerhino_pass.bin,tty_dbg=COM26,tty_bt=COM28
Kommando för att köra under Linux med hjälp av socat-verktyget:
./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-port som kommer att vara öppen för åtkomst till GDB-servern;-n rhino
— Namnet på huvudsystemmodulen (monterad enhet).-l user
— namnet på biblioteket för att söka efter huvudmodulen;-y library
— sökväg för att söka efter moduler som ingår i enheten.firmwarerhino_pass.bin
— sökväg till firmwarefilen;- COM26 och COM28 är virtuella COM-portar.
Som ett resultat kommer en prompt att visas Python >
(eller 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 >
Interaktion med IDA Pro
För att förenkla testning använder vi Rhino firmware som källfil för analys i IDA i formuläret
Du kan också använda den huvudsakliga firmware utan metainformation.
Efter att ha startat Kopycat i IDA Pro, i Debugger-menyn gå till objektet "Byt felsökning..." och välj "Fjärrfelsökning av GDB". Ställ sedan in menyn för anslutning: Debugger - Processalternativ...
Ställ in värdena:
- Applikation - vilket värde som helst
- Värdnamn: 127.0.0.1 (eller IP-adressen för fjärrmaskinen där Kopycat körs)
- Port: 23946
Nu blir felsökningsknappen tillgänglig (F9-tangenten):
Klicka på den för att ansluta till debuggermodulen i emulatorn. IDA går in i felsökningsläge, ytterligare fönster blir tillgängliga: information om register, om stacken.
Nu kan vi använda alla standardfunktioner i felsökaren:
- steg-för-steg exekvering av instruktioner (Stiga in i и Kliva över — tangenterna F7 respektive F8);
- starta och pausa exekvering;
- skapa brytpunkter för både kod och data (F2-tangenten).
Att ansluta till en debugger betyder inte att den fasta programvaran körs. Den aktuella exekveringspositionen måste vara adressen 0x08006A74
— start av funktion Reset_Handler. Om du scrollar ner i listan kan du se funktionsanropet huvudsakliga. Du kan placera markören på denna rad (adress 0x08006ABE
) och utför operationen Kör tills markören (knapp F4).
Därefter kan du trycka på F7 för att gå in i funktionen huvudsakliga.
Om du kör kommandot Fortsätt processen (F9-tangenten), då visas fönstret "Var god vänta" med en enda knapp Avbryta:
När du trycker på Avbryta exekveringen av firmwarekoden avbryts och kan fortsätta från samma adress i koden där den avbröts.
Om du fortsätter att köra koden kommer du att se följande rader i terminalerna som är anslutna till de virtuella COM-portarna:
Närvaron av "state bypass"-linjen indikerar att den virtuella Bluetooth-modulen har växlat till läget för att ta emot data från användarens COM-port.
Nu i Bluetooth-terminalen (COM29 på bilden) kan du ange kommandon i enlighet med Rhino-protokollet. Till exempel kommer "MEOW"-kommandot att returnera strängen "mur-mur" till Bluetooth-terminalen:
Emulera mig inte helt
När du bygger en emulator kan du välja detaljnivå/emulering för en viss enhet. Till exempel kan Bluetooth-modulen emuleras på olika sätt:
- enheten är helt emulerad med en komplett uppsättning kommandon;
- AT-kommandon emuleras och dataströmmen tas emot från COM-porten på huvudsystemet;
- den virtuella enheten tillhandahåller fullständig dataomdirigering till den verkliga enheten;
- som en enkel stubb som alltid returnerar "OK".
Den nuvarande versionen av emulatorn använder det andra tillvägagångssättet - den virtuella Bluetooth-modulen utför konfigurationen, varefter den växlar till läget för "proxy" av data från COM-porten på huvudsystemet till UART-porten på emulatorn.
Låt oss överväga möjligheten till enkel instrumentering av koden om någon del av periferin inte implementeras. Till exempel, om en timer som ansvarar för att kontrollera dataöverföringen till DMA inte har skapats (kontrollen utförs i funktionen ws2812b_waitbelägen vid 0x08006840
), kommer den fasta programvaran alltid att vänta på att flaggan återställs upptagenbelägen vid 0x200004C4
som visar beläggningen av DMA-datalinjen:
Vi kan komma runt denna situation genom att manuellt återställa flaggan upptagen direkt efter installationen. I IDA Pro kan du skapa en Python-funktion och anropa den i en brytpunkt, och lägga själva brytpunkten i koden efter att ha skrivit värdet 1 till flaggan upptagen.
Brytpunktshanterare
Låt oss först skapa en Python-funktion i IDA. Meny Arkiv - skriptkommando...
Lägg till ett nytt utdrag i listan till vänster, ge det ett namn (t.ex. BPT),
I textfältet till höger anger du funktionskoden:
def skip_dma():
print "Skipping wait ws2812..."
value = Byte(0x200004C4)
if value == 1:
PatchDbgByte(0x200004C4, 0)
return False
Efter det trycker vi Körning och stäng skriptfönstret.
Låt oss nu gå till koden kl 0x0800688A
, ställ in en brytpunkt (F2-tangenten), redigera den (sammanhangsmeny Redigera brytpunkt...), glöm inte att ställa in skripttypen till Python:
Om det aktuella flaggvärdet upptagen är lika med 1, då bör du köra funktionen skip_dma i skriptraden:
Om du kör den fasta programvaran för exekvering kan utlösningen av brytpunktshanterarkoden ses i IDA-fönstret Produktion per rad Skipping wait ws2812...
. Nu väntar inte firmware på att flaggan återställs upptagen.
Interaktion med emulatorn
Emulering för emuleringens skull är osannolikt att orsaka glädje och glädje. Det är mycket mer intressant om emulatorn hjälper forskaren att se data i minnet eller etablera interaktionen mellan trådar.
Vi kommer att visa dig hur du dynamiskt etablerar interaktion mellan RTOS-uppgifter. Du bör först pausa exekveringen av koden om den körs. Om du går till funktionen bluetooth_task_entry till bearbetningsgrenen för "LED"-kommandot (adress 0x080057B8
), då kan du se vad som först skapas och sedan skickas till systemkön ledControlQueueHandle något meddelande.
Du bör ställa in en brytpunkt för att komma åt variabeln ledControlQueueHandlebelägen vid 0x20000624
och fortsätt exekvera koden:
Som ett resultat kommer stoppet först att ske på adressen 0x080057CA
innan du anropar funktionen osMailAlloc, sedan på adressen 0x08005806
innan du anropar funktionen osMailPut, sedan efter ett tag - till adressen 0x08005BD4
(innan du anropar funktionen osMailGet), som hör till funktionen leds_task_entry (LED-uppgift), det vill säga uppgifterna byttes, och nu fick LED-uppgiften kontroll.
På detta enkla sätt kan du fastställa hur RTOS-uppgifter interagerar med varandra.
Naturligtvis, i verkligheten, kan interaktionen mellan uppgifter vara mer komplicerad, men med en emulator blir det mindre mödosamt att spåra denna interaktion.
Starta med Radare2
Du kan inte ignorera ett sådant universellt verktyg som Radare2.
För att ansluta till emulatorn med r2 skulle kommandot se ut så här:
radare2 -A -a arm -b 16 -d gdb://localhost:23946 rhino_fw42k6.elf
Lansering tillgänglig nu (dc
) och pausa körningen (Ctrl+C).
Tyvärr har r2 för tillfället problem när man arbetar med hårdvaru-gdb-servern och minneslayouten; på grund av detta fungerar inte brytpunkter och steg (kommando ds
). Vi hoppas att detta åtgärdas snart.
Kör med Eclipse
Ett av alternativen för att använda emulatorn är att felsöka den fasta programvaran för enheten som utvecklas. För tydlighetens skull kommer vi också att använda Rhino firmware. Du kan ladda ner firmwarekällorna
Vi kommer att använda Eclipse från setet som en IDE
För att emulatorn ska ladda firmware direkt kompilerad i Eclipse måste du lägga till parametern firmware=null
till emulatorns startkommando:
binkopycat -g 23946 -n rhino -l user -y modules -p firmware=null,tty_dbg=COM26,tty_bt=COM28
Konfigurera felsökningskonfiguration
I Eclipse väljer du menyn Kör - Felsökningskonfigurationer... I fönstret som öppnas, i avsnittet GDB hårdvarufelsökning du måste lägga till en ny konfiguration och sedan på fliken "Huvud" ange det aktuella projektet och applikationen för felsökning:
På fliken "Debugger" måste du ange GDB-kommandot:
${openstm32_compiler_path}arm-none-eabi-gdb
Och ange också parametrarna för att ansluta till GDB-servern (värd och port):
På fliken "Startup" måste du ange följande parametrar:
- kryssrutan aktivera Ladda bilden (så att den sammansatta firmwarebilden laddas in i emulatorn);
- kryssrutan aktivera Ladda symboler;
- add launch kommando:
set $pc = *0x08000004
(ställ in PC-registret till värdet från minnet vid adress0x08000004
- adressen är lagrad där ResetHandler).
Observera, om du inte vill ladda ner firmware-filen från Eclipse, då alternativen Ladda bilden и Kör kommandon inget behov av att ange.
Efter att ha klickat på Debug kan du arbeta i felsökningsläge:
- steg för steg exekvering av kod
- interagerar med brytpunkter
Notera. Eclipse har, hmm... några egenheter... och du måste leva med dem. Till exempel, om meddelandet "Ingen källa tillgänglig för "0x0″" visas när du startar felsökningen, kör sedan stegkommandot (F5)
I stället för en slutsats
Att emulera inbyggd kod är en mycket intressant sak. För en enhetsutvecklare blir det möjligt att felsöka den fasta programvaran utan en riktig enhet. För en forskare är det en möjlighet att genomföra dynamisk kodanalys, vilket inte alltid är möjligt även med en enhet.
Vi vill förse specialister med ett verktyg som är bekvämt, måttligt enkelt och som inte tar mycket ansträngning och tid att installera och köra.
Skriv i kommentarerna om din erfarenhet av att använda hårdvaruemulatorer. Vi inbjuder dig att diskutera och svarar gärna på frågor.
Endast registrerade användare kan delta i undersökningen.
Vad använder du emulatorn till?
-
Jag utvecklar (felsöker) firmware
-
Jag undersöker firmware
-
Jag lanserar spel (Dendi, Sega, PSP)
-
något annat (skriv i kommentarerna)
7 användare röstade. 2 användare avstod från att rösta.
Vilken programvara använder du för att emulera inbyggd kod?
-
QEMU
-
Enhörningsmotor
-
Proteus
-
något annat (skriv i kommentarerna)
6 användare röstade. 2 användare avstod från att rösta.
Vad skulle du vilja förbättra i emulatorn du använder?
-
Jag vill ha fart
-
Jag vill ha enkel installation/start
-
Jag vill ha fler alternativ för att interagera med emulatorn (API, krokar)
-
Jag är nöjd med allt
-
något annat (skriv i kommentarerna)
8 användare röstade. 1 användare avstod från att rösta.
Källa: will.com