In parte di a riunione 0x0A DC7831
In questu articulu, descriveremu cumu eseguisce u firmware di u dispositivu in l'emulatore, dimustrà l'interazzione cù u debugger, è eseguisce una piccula analisi dinamica di u firmware.
Pristoria
Un bellu tempu fà in una galassia assai luntanu
Un paru d'anni fà in u nostru laboratoriu ci era bisognu di investigà u firmware di un dispositivu. U firmware hè stata cumpressu è unpacked cù un bootloader. Hà fattu questu in una manera assai cumplicata, trasfurmendu i dati in memoria parechje volte. È u firmware stessu hà attivamente interagitu cù i periferichi. È tuttu questu nantu à u core MIPS.
Per ragioni oggettive, l'emulatori dispunibuli ùn ci cunvene micca, ma avemu sempre vulutu eseguisce u codice. Allora avemu decisu di fà u nostru propiu emulatore, chì faria u minimu è ci permette di unpack u firmware principale. Avemu pruvatu è hà travagliatu. Avemu pensatu, chì si aghjunghje periferiche per fà ancu u firmware principale. Ùn hà micca male assai - è hà ancu travagliatu. Avemu pensatu di novu è decisu di fà un emulatore cumpletu.
U risultatu era un emulatore di sistemi di computer
Perchè Kopycat?
Ci hè un ghjocu di parolle.
- copycat (Inglese, noun [ˈkɒpɪkæt]) - imitatore, imitatore
- gattu (Inglese, noun [ˈkæt]) - cat, cat - l'animali prediletti di unu di i creatori di u prugettu
- A lettera "K" hè da a lingua di prugrammazione Kotlin
Copycat
Quandu si creanu l'emulatore, sò stati stabiliti scopi assai specifichi:
- a capacità di creà rapidamente novi periferiche, moduli, core di processore;
- a capacità di assemblà un dispositivu virtuale da diversi moduli;
- a capacità di carricà ogni dati binari (firmware) in a memoria di un dispositivu virtuale;
- capacità di travaglià cù snapshots (snapshots di u statu di u sistema);
- a capacità di interagisce cù l'emulatore attraversu u debugger integratu;
- bella lingua muderna per u sviluppu.
In u risultatu, Kotlin hè statu sceltu per l'implementazione, l'architettura di bus (questu hè quandu i moduli cumunicanu cù l'altri via buse di dati virtuali), JSON cum'è u formatu di descrizzione di u dispositivu, è GDB RSP cum'è u protokollu per l'interazzione cù u debugger.
U sviluppu hè andatu per pocu più di dui anni è hè attivamente in corso. Duranti stu tempu, i core di processori MIPS, x86, V850ES, ARM è PowerPC sò stati implementati.
U prugettu cresce è hè ora di prisentà lu à u publicu più largu. Faremu una descrizzione dettagliata di u prugettu più tardi, ma per ora ci focalizeremu nantu à l'usu di Kopycat.
Per i più impazienti, una versione promo di l'emulatore pò esse scaricata da
Rhino in l'emulatore
Ricordemu chì prima per a cunferenza SMARTRHINO-2018, un dispositivu di prova "Rhinoceros" hè statu creatu per l'insignamentu di e cumpetenze di ingegneria inversa. U prucessu di l'analisi di u firmware staticu hè statu descrittu in
Avà pruvemu à aghjunghje "parlanti" è eseguite u firmware in l'emulatore.
Averemu bisognu:
1) Java 1.8
2) Python è modulu
Per Windows:
1)
2)
Per Linux:
1) socat
Pudete aduprà Eclipse, IDA Pro o radare2 cum'è cliente GDB.
Cumu viaghja?
Per fà u firmware in l'emulatore, hè necessariu "assemble" un dispositivu virtuale, chì hè un analogu di un dispositivu reale.
U veru dispusitivu ("rinoceronte") pò esse mostratu in u schema di bloccu:
L'emulatore hà una struttura modulare è u dispositivu virtuale finali pò esse descrittu in un schedariu JSON.
JSON 105 linee
{
"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"]
]
}
Attenti à u paràmetru firmware rùbbrica paràmetri hè u nome di un schedariu chì pò esse caricatu in un dispositivu virtuale cum'è firmware.
U dispusitivu virtuale è a so interazzione cù u sistema operatore principale pò esse rapprisintatu da u schema seguente:
L'istanza di prova attuale di l'emulatore implica l'interazzione cù i porti COM di u SO principale (debug UART è UART per u modulu Bluetooth). Questi ponu esse porti veri à quale i dispositi sò cunnessi o porti COM virtuali (per questu avete solu bisognu com0com/socat).
Ci hè attualmente dui modi principali per interagisce cù l'emulatore da l'esternu:
- Protokollu GDB RSP (per quessa, i strumenti chì sustenenu stu protokollu sò Eclipse / IDA / radare2);
- linea di cummanda di emulatore internu (Argparse o Python).
Porti COM virtuali
Per interagisce cù l'UART di un dispositivu virtuale nantu à a macchina lucale via un terminal, avete bisognu di creà un paru di porti COM virtuali associati. In u nostru casu, un portu hè utilizatu da l'emulatore, è u sicondu da un prugramma di terminal (PuTTY o schermu):
Utilizà com0com
I porti COM virtuali sò cunfigurati cù l'utilità di setup da u kit com0com (versione di cunsola - C: Programmi (x86) com0comsetupс.exe, o versione GUI - C: File di prugramma (x86) com0comsetupg.exe):
Verificate e caselle attivà u buffer overrun per tutti i porti virtuali creati, altrimenti l'emulatore aspetta una risposta da u portu COM.
Utilizà u socat
In i sistemi UNIX, i porti COM virtuali sò creati automaticamente da l'emulatore utilizendu l'utilità socat per fà questu, solu specificate u prefissu in u nome di u portu quandu principia l'emulatore socat:
.
Interfaccia di linea di cummanda interna (Argparse o Python)
Siccomu Kopycat hè una applicazione di cunsola, l'emulatore furnisce duie opzioni d'interfaccia di linea di cumanda per interagisce cù i so oggetti è variabili: Argparse è Python.
Argparse hè una CLI integrata in Kopycat è hè sempre dispunibule per tutti.
Un CLI alternativu hè l'interprete Python. Per aduprà, avete bisognu di installà u modulu Jep Python è cunfigurà l'emulatore per travaglià cù Python (l'interprete Python installatu nantu à u sistema principale di l'utilizatore serà utilizatu).
Installazione di u modulu Python Jep
Sottu Linux, Jep pò esse installatu via pip:
pip install jep
Per installà Jep in Windows, prima deve installà u Windows SDK è u Microsoft Visual Studio currispundente. Avemu fattu un pocu più faciule per voi è
pip install jep-3.8.2-cp27-cp27m-win_amd64.whl
Per verificà a stallazione di Jep, avete bisognu di eseguisce nantu à a linea di cummanda:
python -c "import jep"
U missaghju seguente deve esse ricevutu in risposta:
ImportError: Jep is not supported in standalone Python, it must be embedded in Java.
In u schedariu batch di emulatore per u vostru sistema (copycat.bat - per Windows, imitatore - per Linux) à a lista di parametri DEFAULT_JVM_OPTS
aghjunghje un paràmetru supplementu Djava.library.path
- deve cuntene u percorsu à u modulu Jep installatu.
U risultatu per Windows deve esse una linea cum'è questu:
set DEFAULT_JVM_OPTS="-XX:MaxMetaspaceSize=256m" "-XX:+UseParallelGC" "-XX:SurvivorRatio=6" "-XX:-UseGCOverheadLimit" "-Djava.library.path=C:/Python27/Lib/site-packages/jep"
Lanciamentu di Kopycat
L'emulatore hè una applicazione JVM di cunsola. U lanciamentu hè realizatu attraversu u script di linea di cumanda di u sistema operatore (sh/cmd).
Cumanda per eseguisce sottu Windows:
binkopycat -g 23946 -n rhino -l user -y library -p firmware=firmwarerhino_pass.bin,tty_dbg=COM26,tty_bt=COM28
Cumanda per eseguisce sottu Linux cù l'utilità 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
- U portu TCP chì serà apertu per l'accessu à u servitore GDB;-n rhino
- nome di u modulu principale di u sistema (dispositivu assemblatu);-l user
- nome di a biblioteca per circà u modulu principale;-y library
- percorsu per circà i moduli inclusi in u dispusitivu;firmwarerhino_pass.bin
- percorso à u schedariu firmware;- COM26 è COM28 sò porti COM virtuali.
In u risultatu, un promptatu serà visualizatu Python >
(o 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 >
Interazione cù IDA Pro
Per simplificà a prova, usemu u firmware Rhino cum'è u schedariu fonte per l'analisi in IDA in a forma
Tù dinù ponu aduprà u firmware principali senza meta infurmazione.
Dopu avè lanciatu Kopycat in IDA Pro, in u menù Debugger andate à l'elementu "Cambia u debugger..." è selezziunate "Debugger GDB remoto". Dopu, stallate a cunnessione: menu Debugger - Opzioni di prucessu...
Pone i valori:
- Applicazione - ogni valore
- Hostname: 127.0.0.1 (o l'indirizzu IP di a macchina remota induve Kopycat hè in esecuzione)
- Port: 23946
Avà u buttone di debugging diventa dispunibule (key F9):
Cliccate per cunnette à u modulu di debugger in l'emulatore. IDA entra in modu di debugging, i finestri supplementari sò dispunibili: infurmazione nantu à i registri, nantu à a pila.
Avà pudemu aduprà tutte e funzioni standard di u debugger:
- esecuzione passo-passo di istruzioni (Passu in и Passa sopra - tasti F7 è F8, rispettivamente);
- principià è mette in pausa l'esecuzione;
- creendu punti di rottura per u codice è per i dati (key F2).
Cunnessu à un debugger ùn significa micca chì eseguisce u codice di firmware. A pusizione di esecuzione attuale deve esse l'indirizzu 0x08006A74
- principiu di funzione Reset_Handler. Sè vo scroll down u listinu, pudete vede a funzione chjama principale. Pudete mette u cursore nantu à sta linea (indirizzu 0x08006ABE
) è fà l'operazione Corri finu à u cursore (tasti F4).
Dopu, pudete appughjà F7 per entre in a funzione principale.
Sè vo eseguite u cumandamentu Cuntinuà u prucessu (Tasti F9), allora a finestra "Per piacè aspettate" cumparisce cù un solu buttone Sospendi:
Quandu pressu Sospendi l'esekzione di u codice firmware hè sospesa è pò esse continuata da u listessu indirizzu in u codice induve hè stata interrotta.
Se continuate à eseguisce u codice, vi vede e seguenti linee in i terminali cunnessi à i porti COM virtuali:
A prisenza di a linea "state bypass" indica chì u modulu Bluetooth virtuale hà cambiatu à u modu di riceve dati da u portu COM di l'utilizatore.
Avà in u terminal Bluetooth (COM29 in a stampa) pudete inserisce cumandamenti in cunfurmità cù u protocolu Rhino. Per esempiu, u cumandamentu "MEOW" restituverà a stringa "mur-mur" à u terminal Bluetooth:
Emulate micca cumplettamente
Quandu custruisce un emulatore, pudete sceglie u livellu di dettagliu / emulazione di un dispositivu particulari. Per esempiu, u modulu Bluetooth pò esse emulatu in diverse manere:
- u dispusitivu hè cumplettamente emulatu cù un inseme cumpletu di cumandamenti;
- I cumandamenti AT sò emulati, è u flussu di dati hè ricevutu da u portu COM di u sistema principale;
- u dispusitivu virtuale furnisce redirezzione cumpleta di dati à u dispusitivu reale;
- cum'è un stub simplice chì sempre torna "OK".
A versione attuale di l'emulatore usa u sicondu approcciu - u modulu Bluetooth virtuale realiza a cunfigurazione, dopu chì cambia à u modu di "proxying" dati da u portu COM di u sistema principale à u portu UART di l'emulatore.
Cunsideremu a pussibilità di una strumentazione simplice di u codice in casu chì una parte di a periferia ùn hè micca implementata. Per esempiu, se ùn hè micca creatu un timer rispunsevule per u cuntrollu di u trasferimentu di dati à DMA (u cuntrollu hè realizatu in a funzione ws2812b_wait, raspolojennoy po adresu 0x08006840
), allura u firmware aspittà sempre chì a bandiera sia resettata occupatosituatu à 0x200004C4
chì mostra l'occupazione di a linea di dati DMA:
Pudemu attornu à sta situazione resettendu manualmente a bandiera occupato subitu dopu à stallà lu. In IDA Pro, pudete creà una funzione Python è chjamà in un breakpoint, è mette u breakpoint stessu in u codice dopu avè scrittu u valore 1 à a bandiera. occupato.
Gestore di breakpoint
Prima, creemu una funzione Python in IDA. Menu File - Cumandamentu di script...
Aghjunghjite un novu snippet in a lista di a manca, dà un nome (per esempiu, BPT),
In u campu di testu à a diritta, entre u codice di funzione:
def skip_dma():
print "Skipping wait ws2812..."
value = Byte(0x200004C4)
if value == 1:
PatchDbgByte(0x200004C4, 0)
return False
Dopu quì, cliccate Run è chjude a finestra di scrittura.
Avà andemu à u codice à 0x0800688A
, stabilisce un breakpoint (tasti F2), editallu (menu di cuntestu Edite u breakpoint...), ùn vi scurdate di stabilisce u tipu di script in Python:
Se u valore di bandiera attuale occupato uguali à 1, allora duvete eseguisce a funzione skip_dma in a linea di script:
Se eseguite u firmware per l'esekzione, pudete vede l'attivazione di u codice di gestore di breakpoint in a finestra IDA radicali avrìanu pututu per linea Skipping wait ws2812...
. Avà u firmware ùn aspittà micca chì a bandiera sia resettata occupato.
Interazione cù l'emulatore
L'emulazione per l'emulazione hè improbabile di causà piacè è gioia. Hè assai più interessante se l'emulatore aiuta à l'investigatore per vede e dati in memoria o stabilisce l'interazzione di filamenti.
Vi mustraremu cumu stabilisce dinamicamente l'interazzione trà e attività RTOS. Duvete prima pause l'esekzione di u codice s'ellu hè in esecuzione. Se vai à a funzione entrata_task_bluetooth à u ramu di trasfurmazioni di u cumandamentu "LED" (indirizzu 0x080057B8
), allora pudete vede ciò chì hè creatu prima è dopu mandatu à a fila di u sistema ledControlQueueHandle qualchì missaghju.
Duvete stabilisce un breakpoint per accede à a variabile ledControlQueueHandle, raspolojennoy po adresu 0x20000624
è cuntinuà à esecutà u codice:
Per via di u risultatu, a fermata serà prima à l'indirizzu 0x080057CA
prima di chjamà a funzione osMailAlloc, dopu à l'indirizzu 0x08005806
prima di chjamà a funzione osMailPut, dopu un pocu tempu - à l'indirizzu 0x08005BD4
(prima di chjamà a funzione osMailGet), chì appartene à a funzione leds_task_entry (LED-task), vale à dì, i compiti cambiatu, è avà u LED-task ricevutu u cuntrollu.
In questu modu simplice pudete stabilisce cumu e funzioni RTOS interagiscenu cù l'altri.
Di sicuru, in a realità, l'interazzione di i travaglii pò esse più complicata, ma cù un emulatore, seguità sta interazzione diventa menu laboriosa.
Lanciate cù Radare2
Ùn pudete micca ignurà un strumentu universale cum'è Radare2.
Per cunnette à l'emulatore cù r2, u cumandimu pare cusì:
radare2 -A -a arm -b 16 -d gdb://localhost:23946 rhino_fw42k6.elf
Lanciamentu dispunibule avà (dc
) è mette in pausa l'esecuzione (Ctrl+C).
Sfurtunatamente, in u mumentu, r2 hà prublemi quandu travaglia cù u servitore gdb hardware è u layout di memoria per quessa, i punti di rottura è i Passi ùn funzionanu (cumandamentu; ds
). Speremu chì questu serà riparatu prestu.
Corsa cù Eclipse
Una di l'opzioni per utilizà l'emulatore hè di debug u firmware di u dispusitivu chì hè sviluppatu. Per a chjarità, avemu ancu aduprà u firmware Rhino. Pudete scaricà i fonti di firmware
Avemu aduprà Eclipse da u settore cum'è IDE
Per chì l'emulatore carcà u firmware direttamente compilatu in Eclipse, avete bisognu di aghjunghje u paràmetru firmware=null
à u cumandimu di lanciu di l'emulatore:
binkopycat -g 23946 -n rhino -l user -y modules -p firmware=null,tty_dbg=COM26,tty_bt=COM28
Configurazione di a cunfigurazione di debug
In Eclipse, selezziunate u menu Run - Debug Configurations... In a finestra chì apre, in a rùbbrica Debugging Hardware GDB avete bisognu di aghjunghje una nova cunfigurazione, dopu in a tabulazione "Principale" specificate u prughjettu attuale è l'applicazione per debugging:
In a tabulazione "Debugger" avete bisognu di specificà u cumandimu GDB:
${openstm32_compiler_path}arm-none-eabi-gdb
È inserite ancu i paràmetri per cunnette à u servitore GDB (ospite è portu):
In a tabulazione "Startup", deve specificà i seguenti parametri:
- attivà a casella di cuntrollu Caricà l'imagine (per chì l'imaghjina di firmware assemblata hè caricata in l'emulatore);
- attivà a casella di cuntrollu Caricà simboli;
- aghjunghje cumandamentu di lanciamentu:
set $pc = *0x08000004
(imposta u registru di u PC à u valore da a memoria à l'indirizzu0x08000004
- l'indirizzu hè guardatu quì ResetHandler).
Attenti, Se ùn vulete micca scaricà u schedariu firmware da Eclipse, allora l'opzioni Caricà l'imagine и Eseguite cumandamenti senza bisognu di indicà.
Dopu à cliccà Debug, pudete travaglià in modu debugger:
- esecuzione di codice passu à passu
- interagisce cù i breakpoints
Vita. Eclipse hà, hmm... alcune stranezze... è duvete campà cun elli. Per esempiu, se quandu u debugger principia u missaghju "Nisuna fonte dispunibule per "0x0"" appare, allora eseguite u cumandamentu Step (F5)
Inveci di 'na cunchiusioni
Emulà u codice nativu hè una cosa assai interessante. Diventa pussibule per un sviluppatore di u dispositivu per debug u firmware senza un veru dispositivu. Per un investigatore, hè una occasione di fà analisi di codice dinamica, chì ùn hè micca sempre pussibule ancu cù un dispositivu.
Vulemu furnisce i specialisti cun un strumentu cunvene, moderatu simplice è ùn piglia micca assai sforzu è tempu per stallà è correre.
Scrivite in i cumenti nantu à a vostra sperienza cù emulatori di hardware. Vi invitemu à discutiri è saremu felici di risponde à e dumande.
Solu l'utilizatori registrati ponu participà à l'indagine.
Chì avete aduprà l'emulatore?
-
Sviluppu (debug) firmware
-
Sò in ricerca di firmware
-
Je lance des jeux (Dendi, Sega, PSP)
-
qualcos'altro (scrivite in i cumenti)
7 utilizatori anu vutatu. 2 utilizatori si sò astenuti.
Chì software utilizate per emulà u codice nativu?
-
QEMU
-
U mutore di l'unicornu
-
Proteus
-
qualcos'altro (scrivite in i cumenti)
6 utilizatori anu vutatu. 2 utilizatori si sò astenuti.
Chì vulete migliurà in l'emulatore chì utilizate?
-
Vogliu a velocità
-
Vogliu facilità di installazione / lanciamentu
-
Vogliu più opzioni per interagisce cù l'emulatore (API, ganci)
-
Sò cuntentu di tuttu
-
qualcos'altro (scrivite in i cumenti)
8 utilizatori anu vutatu. 1 utilizatore s'hè astenutu.
Source: www.habr.com