
Na sastanku 0x0A DC7831 16. veljače predstavili smo izlaganje o osnovnim principima emulacije binarnog koda i našem vlastitom razvoju — emulatoru hardverskih platformi .
U članku ćemo opisati pokretanje firmvera uređaja u emulatoru, demonstrirati interakciju s debagerom i provesti malu dinamičku analizu firmvera.
Povijest
Dugo, dugo vremena u galaksiji daleko, daleko
Prije nekoliko godina u našoj laboratoriji javila se potreba za istraživanjem firmvera uređaja. Firmver je bio komprimiran, otvarao ga je bootloader. Učinilo je to prilično kompliciranim načinom, nekoliko puta premeštajući podatke u memoriji. Također, sam firmver aktivno je interagirao s periferijom. A sve to na MIPS jezgru.
Postojeći emulatori nas nisu zadovoljili iz objektivnih razloga, a željeli smo pokrenuti kod. Onda smo odlučili napraviti vlastiti emulator koji će učiniti minimum i omogućiti dekompresiju glavnog firmvera. Pokušali smo — uspjelo je. Razmislili smo, a što ako dodamo periferiju, kako bismo mogli izvršiti i glavni firmver. Nije bilo preteško — i također je uspjelo. Ponovno smo razmislili i odlučili izrađivati potpuni emulator.
Na kraju smo dobili emulator računalnih sustava .

Zašto Kopycat?
Igra riječi.
- copycat (eng., sâ., [ˈkɒpɪkæt]) — imitator, podražavatelj
- cat (eng., sâ., [ˈkæt]) — mačka, mačak — omiljena životinja jednog od kreatora projekta
- Slovo „K“ — od programskog jezika Kotlin
Kopycat
Prilikom izrade emulatora postavljeni su sasvim određeni ciljevi:
- mogućnost dovoljno brzo stvoriti novu periferiju, modul, procesorsku jezgru;
- mogućnost sastavljanja virtualnog uređaja iz raznih modula;
- mogućnost učitavanja bilo kakvih binarnih podataka (firmvera) u memoriju virtualnog uređaja;
- mogućnost rada sa snapshotovima (snimkama stanja sustava);
- mogućnost interakcije s emulatorom putem ugrađenog debagera;
- prijatan suvremen jezik za razvoj.
Na kraju, za realizaciju odabran je Kotlin, sabirna arhitektura (to jest, kada se moduli povezuju jedni s drugima preko virtualnih sabirnih vodiča), JSON — kao format opisa uređaja, i GDB RSP — kao protokol interakcije s debagerom.
Razvoj traje nešto više od dvije godine i aktivno se nastavlja. Tijekom tog vremena implementirana su procesorska jezgra MIPS, x86, V850ES, ARM, PowerPC.
Projekt raste i došlo je vrijeme da ga predstavimo široj javnosti. Detaljan opis projekta napravit ćemo kasnije, a sada ćemo se fokusirati na korištenje Kopycat.
Za najnestrpljivije — promo verziju emulatora možete preuzeti putem .
Nosorog u emulatoru
Podsjetimo, ranije je za konferenciju SMARTRHINO-2018 napravljeno testno uređaj "Nosorog" za obuku vještinama reverznog inženjerstva. Proces statičke analize firmvera opisan je u .
Sada ćemo pokušati dodati "dinamiku" i pokrenuti firmver u emulatoru.
Bit će nam potrebni:
1) Java 1.8
2) Python i modul za korištenje Pythona unutar emulatora. WHL paket modula Jep za Windows možete .
Za Windows:
1)
2)
Za Linux:
1) socat
Kao GDB klijent možete koristiti Eclipse, IDA Pro ili radare2.
Kako to radi?
Za izvođenje firmvera u emulatoru, potrebno je "sagraditi" virtualni uređaj, koji predstavlja analog stvarnog uređaja.
Stvarni uređaj ("nosorog") može se prikazati na strukturnoj shemi:

Emulator ima modularnu strukturu i konačni virtualni uređaj može se opisati u JSON datoteci.
JSON sadrži 105 redaka
{
"top": true,
// Ime plugina treba biti isto kao i ime datoteke (ili puni put od početka biblioteke)
"plugin": "rhino",
// Direktorij u kojem se plugin pohranjuje
"library": "user",
// Parametri plugina (parametri konstruktora ako je verzija jar-plugin)
"params": [
{ "name": "tty_dbg", "type": "String"},
{ "name": "tty_bt", "type": "String"},
{ "name": "firmware", "type": "String", "default": "NUL"}
],
// Vanjske portove plugina
"ports": [ ],
// Unutarnje sabirnice plugina
"buses": [
{ "name": "mem", "size": "BUS30" },
{ "name": "nand", "size": "4" },
{ "name": "gpio", "size": "BUS32" }
],
// Unutarnje komponente plugina
"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" }
],
// Povezivanje plugina između komponenti
"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"]
]
}Obratite pažnju na parametar firmware u odjeljku params — to je ime datoteke koju možete učitati u virtualni uređaj kao firmware.
Virtualni uređaj i njegovo interakcija s osnovnim operativnim sustavom može se prikazati ovom shemom:

Trenutni testni primjer emulatora podrazumijeva interakciju s COM portovima osnovnog OS-a (debug UART i UART za Bluetooth modul). Mogu biti stvarni portovi kojima su priključeni uređaji ili virtualni COM portovi (za to je potreban com0com / socat).
Za interakciju s emulatorom izvana trenutno postoji dva osnovna načina:
- GDB RSP protokol (odgovarajući alati koji podržavaju ovaj protokol — Eclipse / IDA / radare2);
- unutarnja komandna linija emulatora (Argparse ili Python).
Virtualni COM portovi
Da biste komunicirali s UART-om virtualnog uređaja na lokalnoj mašini putem terminala, potrebno je stvoriti par povezanih virtualnih COM portova. U našem slučaju jedan port koristi emulator, a drugi — terminalski program (PuTTY ili screen):

Korištenje com0com
Virtualni COM portovi se postavljaju pomoću instalacijskog alata iz kompleta com0com (konzolna verzija — C:Program Files (x86)com0comsetupc.exe, ili GUI verzija — C:Program Files (x86)com0comsetupg.exe):

Treba označiti enable buffer overrun za sve stvorene virtualne portove, inače će emulator čekati odgovor s COM porta.
Korištenje socat
Na UNIX sustavima virtualni COM portovi automatski se stvaraju od strane emulatora koristeći alat socat, dovoljno je prilikom pokretanja emulatora u imenu porta navesti prefiks socat:.
Unutarnji sučelje komandne linije (Argparse ili Python)
Budući da je Kopycat konzolna aplikacija, za interakciju sa svojim objektima i varijablama emulator nudi dvije opcije sučelja komandne linije: Argparse i Python.
Argparse — ovo je CLI integriran u Kopycat, dostupan je uvijek i svima.
Alternativni CLI — Python interpretor. Da bi se koristio, potrebno je instalirati Python modul Jep i postaviti emulator za rad s Python-om (bit će korišten interpreter Python-a instaliran u osnovnom sustavu korisnika).
Instalacija Python modula Jep
Na Linuxu, Jep se može instalirati putem pipa:
pip install jepZa instalaciju Jep-a na Windowsu potrebno je prethodno instalirati Windows SDK i odgovarajući Microsoft Visual Studio. Malo smo vam olakšali zadatak i napravili JEP za aktualne verzije Pythona za Windows, stoga se modul može instalirati iz datoteke:
pip install jep-3.8.2-cp27-cp27m-win_amd64.whlZa provjeru instalacije Jep, potrebno je izvršiti u komandnoj liniji:
python -c "import jep"Kao odgovor trebali biste dobiti poruku:
ImportError: Jep nije podržan u samostalnom Python-u, mora biti ugrađen u Java.U komandnoj datoteci emulatore za vaš sustav (kopycat.bat — za Windows, kopycat — za Linux) dodajte dodatni parametar na popis opcija DEFAULT_JVM_OPTS dodajte dodatni parametar Djava.library.path — on mora sadržavati put do instaliranog modula Jep.
Kao rezultat za Windows trebala bi nastati sljedeća linija:
set DEFAULT_JVM_OPTS="-XX:MaxMetaspaceSize=256m" "-XX:+UseParallelGC" "-XX:SurvivorRatio=6" "-XX:-UseGCOverheadLimit" "-Djava.library.path=C:/Python27/Lib/site-packages/jep"Pokretanje Kopycat
Emulator predstavlja konzolnu JVM-aplikaciju. Pokretanje se vrši putem skripte komandne linije operativnog sustava (sh/cmd).
Komanda za pokretanje pod Windowsom:
binkopycat -g 23946 -n rhino -l user -y library -p firmware=firmwarerhino_pass.bin,tty_dbg=COM26,tty_bt=COM28Komanda za pokretanje pod Linuxom koristeći alat 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-port koji će biti otvoren za pristup GDB-serveru;-n rhino— ime glavnog modula sustava (uređaj u sklopu);-l user— ime biblioteke za pretraživanje glavnog modula;-y library— put za pretraživanje modula koji ulaze u uređaj;firmwarerhino_pass.bin— put do datoteke s firmverom;- COM26 i COM28 — virtualni COM-portovi.
Kao rezultat bit će prikazan upit Python > (ili Argparse >):
18:07:59 INFO [eFactoryBuilder.create ]: Modul top uspješno kreiran kao top
18:07:59 INFO [ Module.initializeAndRes]: Postavljanje jezgre na top.u1_stm32.cortexm0.arm za top
18:07:59 INFO [ Module.initializeAndRes]: Postavljanje debugggera na top.u1_stm32.dbg za top
18:07:59 WARN [ Module.initializeAndRes]: Tracer nije pronađen u top...
18:07:59 INFO [ Module.initializeAndRes]: Inicijalizacija portova i sabirnica...
18:07:59 WARN [ Module.initializePortsA]: PAŽNJA: Neki portovi imaju upozorenje, koristite printModulesPortsWarnings da ih vidite...
18:07:59 FINE [ ARMv6CPU.reset ]: Postavite adresu ulazne točke na 08006A75
18:07:59 INFO [ Module.initializeAndRes]: Modul top je uspješno inicijaliziran i resetiran kao glavni ćelija!
18:07:59 INFO [ Kopycat.open ]: Pokretanje virtualizacije ploče top[rhino] s arm[ARMv6Core]
18:07:59 INFO [ GDBServer.debuggerModule ]: Postavljen novi debugger modul top.u1_stm32.dbg za GDB_SERVER(port=23946,alive=true)
Python >Interakcija s IDA Pro
Kao izvorna datoteka za analizu u IDA za olakšavanje testiranja koristit ćemo firmver „Nosoroga“ u obliku (tamo je spremljena meta-informacija).
Možete također koristiti osnovni firmver bez meta-informacije.
Nakon pokretanja Kopycat-a u IDA Pro, u izborniku Debugger idemo do stavke „Switch debugger…” i odabiremo „Daljinski GDB debager«. Dalje postavljamo vezu: izbornik Debager — Opcije procesa…
Postavljamo vrijednosti:
- Aplikacija — bilo koja vrijednost
- Hostname: 127.0.0.1 (ili IP adresa udaljenog računala na kojem je pokrenut Kopycat)
- Port: 23946

Sada je dostupna tipka za pokretanje debagiranja (tipka F9):
![]()
Pritiskom na nju događa se povezivanje s modulom debagera u emulatoru. IDA prelazi u način debagiranja, postaju dostupni dodaci prozori: informacije o registrima, o steku.
Sada možemo koristiti sve standardne mogućnosti rada s debagerom:
- korak po korak izvršavanje instrukcija (Uđi u i Preskoči — tipke F7 i F8, redom);
- pokretanje i pauziranje izvršavanja;
- stvaranje točaka zaustavljanja na kodu i podacima (tipka F2).
Povezivanje s debagerom ne znači pokretanje koda firmvera. Trenutna pozicija za izvršavanje mora biti adresa 0x08006A74 — početak funkcije Reset_Handler. Ako se pomaknete dolje u ispisu, možete vidjeti poziv funkcije main. Možete postaviti kursor na tu liniju (adresa 0x08006ABE) i izvršiti operaciju Pokreni do kursora (tipka F4).

Zatim možete pritisnuti F7 kako biste ušli u funkciju main.
Ako izvršite naredbu Nastavi proces (tipka F9), pojavit će se prozor «Molimo pričekajte» s jedinom tipkom Pauziraj:

Pritiskom na Pauziraj izvršavanje koda firmvera se pauzira i može se nastaviti s iste adrese u kodu gdje je prekinuto.
Ako nastavite izvršavanje koda, u terminalima povezanim s virtualnim COM-portovima možete vidjeti sljedeće linije:


Prisutnost linije «state bypass» označava da je virtualni Bluetooth modul prešao u način primanja podataka s korisničkog COM-port.
Sada u Bluetooth terminalu (na slici — COM29) možete unositi naredbe u skladu s protokolom «Nosorog». Na primjer, nakon naredbe «MEOW» Bluetooth terminal će vratiti liniju «mur-mur»:

Emuliraj me ne potpuno
Prilikom izrade emulatora može se odabrati stepen detaljnosti/emulacije određenog uređaja. Na primjer, Bluetooth modul se može emulirati na razne načine:
- emulira se cijeli uređaj s punim setom naredbi;
- emuliraju se AT-naredbe, a protok podataka se prima s COM-porta glavnog sustava;
- virtualni uređaj osigurava potpuno preusmjeravanje podataka na stvarni uređaj;
- u obliku jednostavne zamjenske vrijednosti koja uvijek vraća «OK».
U trenutnoj verziji emulatora koristi se drugi pristup — virtualni Bluetooth modul vrši konfiguraciju, nakon čega prelazi u režim „proksiranja” podataka iz COM porta glavnog sistema u UART port emulatora.

Razmotrite mogućnost jednostavne instrumentacije koda u slučaju da neki deo periferije nije implementiran. Na primer, ako tajmer koji je odgovoran za kontrolu prenosa podataka u DMA nije kreiran (provera se vrši u funkciji ws2812b_wait, koja se nalazi na adresi 0x08006840), then the firmware will always wait for the flag to be reset busy, which is located at 0x200004C4, which indicates the status of the DMA data line:

Možemo zaobići ovu situaciju „ručnim” resetovanjem zastavice busy odmah nakon što je postavljena. U IDA Pro možete kreirati Python funkciju i pozvati je u breakpointu, postavljajući sam breakpoint u kodu nakon što se vrednost 1 zapiše u zastavicu busy.
Handler breakpoint
Prvo kreiramo Python funkciju u IDA. Meniju File — Script command…
Dodajemo novi snippett na levoj strani liste, dajemo mu ime (na primer, BPT),
u desnom tekstualnom polju unosimo kod funkcije:
def skip_dma():
print "Skipping wait ws2812..."
value = Byte(0x200004C4)
if value == 1:
PatchDbgByte(0x200004C4, 0)
return False 
Nakon toga pritisnemo Run i zatvaramo prozor skripti.
Sada ćemo preći na kod na adresi 0x0800688A, postavimo breakpoint (tipka F2), uredimo ga (kontekstni meni Edit breakpoint…), ne zaboravite postaviti tip skripte – Python:


Ako je trenutno stanje zastavice busy jednako 1, treba izvršiti funkciju skip_dma u liniji skripti:

Ako pokrenete firmver, aktivaciju koda breakpoint handler-a možete videti u IDA u prozoru Izlaz na liniji Skipping wait ws2812.... Sada firmware neće čekati na resetovanje zastavice. busy.
Interakcija s emulatorom
Emulacija radi emulacije verovatno neće izazvati oduševljenje i radost. Mnogo je zanimljivije ako emulator pomogne istraživaču da vidi podatke u memoriji ili uspostavi međuzavisnost tokova.
Pokazaćemo kako u dinamički postaviti interakciju RTOS zadataka. Pre toga treba obustaviti izvršavanje koda ako je pokrenut. Ako se pređe u funkciju bluetooth_task_entry u grani obrade komande „LED” (adresa 0x080057B8), može se videti da se prvo stvara, a zatim šalje u sistemsku red čekanja ledControlQueueHandle neka poruka.

Treba postaviti breakpoint na pristup varijabli ledControlQueueHandle, koja se nalazi na adresi 0x20000624 i nastaviti sa izvršavanjem koda:

Kao rezultat, prvo će doći do zaustavljanja na adresi 0x080057CA pre poziva funkcije osMailAlloc, zatim — na adresi 0x08005806 pre poziva funkcije osMailPut, a zatim nakon nekog vremena — na adresi 0x08005BD4 (pre poziva funkcije osMailGet), koja pripada funkciji leds_task_entry (LED zadatak), što znači da je došlo do prebacivanja između zadataka, i sada je kontrolu preuzeo LED zadatak.

Na ovaj jednostavan način može se utvrditi kako zadaci RTOS međusobno komuniciraju.
Naravno, u stvarnosti komunikacija zadataka može biti složenija, ali korištenjem emulatora praćenje te interakcije postaje manje naporno.
možete pogledati kratki video o pokretanju emulatora i interakciji s IDA Pro.
Pokretanje s Radare2
Ne možemo zaobići tako univerzalni alat kao Radare2.
Za povezivanje s emulatorom koristeći r2, komanda će izgledati ovako:
radare2 -A -a arm -b 16 -d gdb://localhost:23946 rhino_fw42k6.elfSada su dostupni pokretanje (dc) i pauza izvršenja (Ctrl+C).
Nažalost, trenutno u r2 postoje problemi prilikom rada s hardverskim gdb poslužiteljem i razdjeljivanjem memorije, zbog čega ne rade točke prekida i koraci (komanda ds). Nadamo se da će to uskoro biti ispravljeno.
Pokretanje s Eclipse
Jedna od opcija korištenja emulatora je debagiranje firmware-a razvijanog uređaja. Za ilustraciju ćemo koristi firmware "Nosorog". Izvorne kodove firmware-a možete preuzeti .
Kao IDE koristit ćemo Eclipse iz paketa .
Da bi se firmware učitao u emulator direktno iz Eclipse-a, potrebno je dodati parametar firmware=null u komandu za pokretanje emulatora:
binkopycat -g 23946 -n rhino -l user -y modules -p firmware=null,tty_dbg=COM26,tty_bt=COM28Postavljanje debug konfiguracije
U Eclipse-u odabiremo izbornik Run — Debug Configurations… U otvorenom prozoru, u odjeljku GDB Hardware Debugging potrebno je dodati novu konfiguraciju, nakon čega na kartici „Main“ odabrati trenutni projekt i aplikaciju za debagiranje:

Na kartici „Debugger“ potrebno je navesti GDB komandu:
${openstm32_compiler_path}arm-none-eabi-gdb
Također, unesite parametre za povezivanje s GDB poslužiteljem (host i port):

Na kartici „Startup“ potrebno je navesti sljedeće parametre:
- označiti kvačicu Load image (da bi se izvršilo učitavanje u emulator odabranog firmware-a);
- označiti kvačicu Load symbols;
- dodajte komandu pokretanja:
set $pc = *0x08000004(izdati u registar PC vrijednost iz memorije na adresi0x08000004— tamo se pohranjuje adresa ResetHandlera).
Napominjemo, ako ne želite učitati datoteku firmvera iz Eclipsea, tada parametre Load image i Pokreni naredbe ne treba navoditi.

Nakon pritiska na Debug možete raditi u režimu ispravljanja:
- korak po korak izvršenje koda

- interakcija s točkama prekida

Napomena. U Eclipseu postoje, hmm… neke posebnosti… i s njima se mora živjeti. Na primjer, ako prilikom pokretanja ispravljača dobijete poruku «Nemaju izvore dostupne za «0x0»», izvršite naredbu Step (F5)

Umjesto zaključka
Emulacija nativnog koda je vrlo zanimljiv zadatak. Za programere uređaja otvara se mogućnost ispravljanja firmvera bez stvarnog uređaja. Za istraživače — mogućnost provođenja dinamičke analize koda, što nije uvijek moguće čak ni u prisustvu uređaja.
Želimo pružiti stručnjacima alat koji bi bio prikladan, dovoljno jednostavan i ne bi oduzimao previše truda i vremena za postavljanje i pokretanje.
Napišite u komentarima o svom iskustvu korištenja hardverskih emulatora. Pozivamo vas na raspravu i bit ćemo sretni odgovoriti na pitanja.
Samo registrirani korisnici mogu sudjelovati u anketi. , molimo.
Za što koristite emulator?
razvijam (ispravljam) firmvere
istražujem firmvere
pokrećem igre (Dendi, Sega, PSP)
nešto drugo (napišite u komentar)
Glasovalo je 7 korisnika. 2 korisnika su se suzdržala.
Koji softver koristite za emulaciju nativnog koda?
QEMU
Unicorn engine
Proteus
nešto drugo (napišite u komentar)
Glasovalo je 6 korisnika. Suzdržano 2 korisnika.
Što biste željeli poboljšati u korištenom emulatoru?
želim brzinu
želim jednostavnost postavljanja/pokretanja
želim više mogućnosti interakcije s emulatorom (API, hookovi)
sve me zadovoljava
nešto drugo (napišite u komentar)
Glasovalo je 8 korisnika. 1 korisnik se suzdržao.
Izvor: habr.com


