Badak di dalam kucing - jalankan firmware di emulator Kopycat


Dalam kerangka pertemuan 0x0A DC7831 DEF CON Nizhny Novgorod pada 16 Februari, kami menyajikan laporan tentang prinsip-prinsip dasar emulasi kode biner dan pengembangan kami sendiri - sebuah emulator platform perangkat keras Kopycat .


Dalam artikel ini, kami akan menjelaskan peluncuran firmware perangkat di emulator, menunjukkan interaksi dengan debugger dan melakukan analisis dinamis kecil dari firmware.


Latar belakang


Dahulu kala di galaksi jauh sekali


Beberapa tahun yang lalu di laboratorium kami ada kebutuhan untuk mempelajari firmware perangkat. Firmware dikompresi, dibongkar oleh bootloader. Dia melakukan ini dengan cara yang sangat membingungkan, beberapa kali menggeser data dalam memori. Ya, dan firmware itu sendiri kemudian secara aktif berinteraksi dengan periferal. Dan semua ini pada inti MIPS.


Untuk alasan obyektif, emulator yang ada tidak cocok untuk kita, tetapi saya masih ingin menjalankan kode. Kemudian kami memutuskan untuk membuat emulator kami sendiri, yang akan membuat minimum dan memungkinkan untuk membongkar firmware utama. Kami mencoba - ternyata. Kami pikir, bagaimana jika kami menambah periferal agar juga melakukan firmware utama. Itu tidak terlalu menyakitkan - dan itu berhasil juga. Kami berpikir lagi dan memutuskan untuk membuat emulator lengkap.


Hasilnya adalah emulator sistem komputasi Kopycat .



Kenapa Kopycat?

Ada permainan kata-kata.


  1. copycat (Bahasa Inggris, n. [ˈkɒpɪkæt]) - copycat, peniru
  2. cat (Bahasa Inggris, n. [ˈkæt]) - kucing, kucing - hewan favorit salah satu pencipta proyek
  3. Huruf "K" - dari bahasa pemrograman Kotlin

Kopycat


Saat membuat emulator, tujuan yang sangat spesifik ditetapkan:


  • kemampuan untuk dengan cepat membuat periferal, modul, inti prosesor baru;
  • kemampuan untuk merakit perangkat virtual dari berbagai modul;
  • kemampuan untuk memuat data biner (firmware) apa pun ke dalam memori perangkat virtual;
  • kemampuan untuk bekerja dengan snapshot (snapshot dari keadaan sistem);
  • kemampuan untuk berinteraksi dengan emulator melalui debugger bawaan;
  • bahasa modern yang bagus untuk dikembangkan.

Akibatnya, Kotlin dipilih untuk implementasi, arsitektur bus (inilah saat modul berkomunikasi satu sama lain melalui bus data virtual), JSON sebagai format deskripsi perangkat, dan GDB RSP sebagai protokol untuk berinteraksi dengan debugger.


Pembangunan telah berlangsung selama lebih dari dua tahun dan secara aktif sedang berlangsung. Selama waktu ini, inti prosesor MIPS, x86, V850ES, ARM, PowerPC diimplementasikan.


Proyek ini berkembang, dan sekarang saatnya untuk memperkenalkannya kepada masyarakat umum. Kami akan membuat deskripsi rinci tentang proyek nanti, tetapi sekarang kami akan fokus menggunakan Kopycat.


Untuk yang paling tidak sabar - versi promo dari emulator dapat diunduh di sini .


Badak di emulator


Ingatlah bahwa sebelumnya untuk konferensi SMARTRHINO-2018, alat uji "Badak" diciptakan untuk pelatihan keterampilan rekayasa terbalik. Proses analisis firmware statis dijelaskan dalam artikel ini .


Sekarang mari kita coba menambahkan "speaker" dan menjalankan firmware di emulator.


Kami akan membutuhkan:
1) Jawa 1.8
2) Python dan modul Jep untuk menggunakan Python di dalam emulator. Perakitan WHL dari modul Jep untuk Windows dapat diunduh di sini .


Untuk Windows:
1) com0com
2) Putty


Untuk Linux:
1) socat


Anda dapat menggunakan Eclipse, IDA Pro, atau radare2 sebagai klien GDB.


Bagaimana cara kerjanya?


Untuk melakukan firmware pada emulator, Anda harus "merakit" perangkat virtual, yang merupakan analog dari perangkat nyata.


Perangkat nyata ("badak") dapat ditampilkan dalam diagram blok:


Sirkuit perangkat nyata

Emulator memiliki struktur modular dan perangkat virtual final dapat dijelaskan dalam file JSON.


JSON di 105 baris
{ "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"] ] } 

Perhatikan parameter firmware di bagian params - ini adalah nama file yang dapat diunduh ke perangkat virtual sebagai firmware.


Perangkat virtual dan interaksinya dengan sistem operasi utama dapat direpresentasikan sebagai berikut:


Perangkat yang diemulasi sirkuit

Contoh pengujian emulator saat ini melibatkan interaksi dengan port COM pada OS utama (debug UART dan UART untuk modul Bluetooth). Ini bisa menjadi port nyata yang terhubung dengan perangkat atau port COM virtual ( com0com / socat hanya untuk ini ) .


Saat ini ada dua cara utama untuk berinteraksi dengan emulator dari luar:


  • Protokol GDB RSP (masing-masing, mendukung protokol ini, alat - Eclipse / IDA / radare2);
  • baris perintah internal emulator (Argparse atau Python).

Port COM Virtual


Untuk berinteraksi dengan UART perangkat virtual pada mesin lokal melalui terminal, Anda perlu membuat beberapa port COM virtual yang terhubung. Dalam kasus kami, satu port menggunakan emulator, dan yang kedua program terminal (Putty atau layar):


Port COM Virtual

Menggunakan com0com


Port COM virtual dikonfigurasi dengan utilitas pengaturan dari kit com0com (versi konsol adalah C: \ Program Files (x86) \ com0com \ setup.exe, atau versi GUI adalah C: \ Program Files (x86) \ com0com \ setupg.exe ) :


Konfigurasikan port COM virtual

Periksa kotak centang aktifkan buffer overrun untuk semua port virtual yang dibuat, jika tidak emulator akan menunggu respons dari port COM.


Menggunakan socat


Pada sistem UNIX, port COM virtual secara otomatis dibuat oleh emulator menggunakan utilitas socat, untuk ini cukup untuk menentukan awalan socat: pada nama port ketika memulai emulator.


Antarmuka baris perintah internal (Argparse atau Python)


Karena Kopycat adalah aplikasi konsol, emulator menyediakan dua opsi untuk antarmuka baris perintah untuk berinteraksi dengan objek dan variabelnya: Argparse dan Python.


Argparse adalah CLI yang dibangun di Kopycat, selalu tersedia untuk semua orang.


CLI alternatif adalah juru bahasa Python. Untuk menggunakannya, Anda perlu menginstal modul Jep Python dan mengkonfigurasi emulator agar berfungsi dengan Python (interpreter Python yang diinstal pada sistem utama pengguna akan digunakan).


Instal Modul Python Jep


Di Linux, Jep dapat diinstal melalui pip:


 pip install jep 

Untuk menginstal Jep di Windows, Anda harus terlebih dahulu menginstal Windows SDK dan Microsoft Visual Studio yang sesuai. Kami sedikit menyederhanakan tugas Anda dan membuat rakitan WHL JEP untuk versi Python untuk Windows saat ini, sehingga modul dapat diinstal dari sebuah file:


 pip install jep-3.8.2-cp27-cp27m-win_amd64.whl 

Untuk memverifikasi instalasi Jep, Anda harus menjalankan baris perintah:


 python -c "import jep" 

Sebagai tanggapan, sebuah pesan harus diterima:


 ImportError: Jep is not supported in standalone Python, it must be embedded in Java. 

Dalam file batch emulator untuk sistem Anda ( kopycat.bat untuk Windows, kopycat untuk Linux) tambahkan parameter tambahan Djava.library.path ke daftar parameter DEFAULT_JVM_OPTS - itu harus berisi path ke modul Jep yang diinstal.


Akibatnya, untuk Windows, Anda harus mendapatkan garis seperti ini:


 set DEFAULT_JVM_OPTS="-XX:MaxMetaspaceSize=256m" "-XX:+UseParallelGC" "-XX:SurvivorRatio=6" "-XX:-UseGCOverheadLimit" "-Djava.library.path=C:/Python27/Lib/site-packages/jep" 

Peluncuran Kopycat


Emulator adalah aplikasi konsol JVM. Peluncuran ini dilakukan melalui skrip baris perintah dari sistem operasi (sh / cmd).


Perintah untuk dijalankan di bawah Windows:


 bin\kopycat -g 23946 -n rhino -l user -y library -p firmware=firmware\rhino_pass.bin,tty_dbg=COM26,tty_bt=COM28 

Perintah untuk dijalankan di Linux menggunakan utilitas 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 - Port TCP yang akan terbuka untuk akses ke server GDB;
  • -n rhino - nama modul utama sistem (perakitan perangkat);
  • -l user - nama perpustakaan untuk mencari modul utama;
  • -y library - jalur untuk mencari modul yang termasuk dalam perangkat;
  • firmware\rhino_pass.bin - path ke file firmware;
  • COM26 dan COM28 adalah port COM virtual.

Hasilnya adalah Argparse > Python > (atau 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 > 

Interaksi dengan IDA Pro


Untuk menyederhanakan pengujian, kami menggunakan firmware Badak sebagai file ELF (meta-informasi disimpan di sana) sebagai file sumber untuk analisis dalam IDA.


Anda juga dapat menggunakan firmware utama tanpa meta-informasi.


Setelah memulai Kopycat di IDA Pro, di menu Debugger, buka item " Tukar debugger ... " dan pilih " Remote GDB debugger ". Selanjutnya, konfigurasikan koneksi: Menu Debugger - Opsi proses ...


Tetapkan nilai:


  • Aplikasi - nilai apa pun
  • Hostname: 127.0.0.1 (atau alamat IP mesin jarak jauh tempat Kopycat berjalan)
  • Port: 23946

Mengonfigurasi koneksi ke server GDB

Sekarang tombol mulai debug tersedia (tombol F9):



Tekan itu - itu terhubung ke modul debugger di emulator. IDA masuk ke mode debug, jendela tambahan tersedia: informasi tentang register, tentang stack.


Sekarang kita dapat menggunakan semua fitur standar untuk bekerja dengan debugger:


  • langkah-langkah pelaksanaan instruksi ( Langkah ke dan Langkah - masing-masing kunci F7 dan F8);
  • memulai dan menghentikan eksekusi;
  • membuat breakpoint pada kode dan data (kunci F2).

Menghubungkan ke debugger tidak berarti memulai kode firmware. Posisi saat ini untuk eksekusi harus alamat 0x08006A74 - awal dari fungsi Reset_Handler . Jika Anda menggulir daftar ke bawah, Anda dapat melihat panggilan ke fungsi utama . Anda dapat menempatkan kursor pada baris ini (alamat 0x08006ABE ) dan melakukan operasi Run hingga kursor (tombol F4).


gambar


Selanjutnya, Anda dapat menekan F7 untuk masuk ke fungsi utama .


Jika Anda menjalankan perintah Lanjutkan proses (tombol F9), jendela "Silakan tunggu" akan muncul dengan satu tombol Tangguhkan :



Ketika Ditangguhkan ditekan, eksekusi kode firmware ditangguhkan dan dapat dilanjutkan dari alamat yang sama dalam kode tempat kode itu terputus.


Jika Anda terus mengeksekusi kode, maka di terminal yang terhubung ke port COM virtual, Anda dapat melihat baris berikut:


gambar


gambar


Kehadiran string "state bypass" menunjukkan bahwa modul Bluetooth virtual telah beralih ke mode penerimaan data dari port COM pengguna.


Sekarang di terminal Bluetooth (pada gambar - COM29) Anda dapat memasukkan perintah sesuai dengan protokol Badak. Misalnya, string "mur-mur" kembali ke perintah "MEOW" di terminal Bluetooth:




Meniru saya tidak sepenuhnya


Saat membangun emulator, Anda dapat memilih tingkat detail / emulasi perangkat. Jadi, misalnya, modul Bluetooth dapat ditiru dengan berbagai cara:


  • perangkat yang sepenuhnya ditiru dengan serangkaian perintah;
  • Perintah AT ditiru, dan aliran data diterima dari port COM dari sistem utama;
  • perangkat virtual menyediakan pengalihan data lengkap ke perangkat nyata;
  • sebagai rintisan sederhana yang selalu mengembalikan "OK".

Dalam versi emulator saat ini, pendekatan kedua digunakan - modul Bluetooth virtual melakukan konfigurasi, setelah itu beralih ke mode proxy data dari port COM sistem utama ke port UART emulator.



Pertimbangkan kemungkinan instrumentasi kode yang sederhana jika beberapa bagian periferal tidak diterapkan. Misalnya, jika timer tidak dibuat yang mengontrol transfer data dalam DMA (verifikasi dilakukan di fungsi ws2812b_wait yang terletak di 0x08006840 ), maka firmware akan selalu menunggu bendera sibuk yang terletak di 0x200004C4 untuk mengatur ulang garis data DMA:



Kami dapat menghindari situasi ini dengan menyetel ulang bendera sibuk secara manual segera setelah menyetelnya. Di IDA Pro, Anda dapat membuat fungsi Python dan memanggilnya di breakpoint, dan breakpoint harus diatur dalam kode setelah menuliskan nilai 1 ke flag yang sibuk .


Penangan breakpoint


Pertama, buat fungsi Python di IDA. Menu file - Perintah skrip ...


Tambahkan potongan baru di daftar di sebelah kiri, berikan nama (misalnya, BPT ),
di kotak teks di sebelah kanan kita masukkan kode fungsi:


 def skip_dma(): print "Skipping wait ws2812..." value = Byte(0x200004C4) if value == 1: PatchDbgByte(0x200004C4, 0) return False 


Setelah itu, klik Jalankan dan tutup jendela skrip.


Sekarang mari kita pergi ke kode di 0x0800688A , atur breakpoint (tombol F2), edit ( Edit breakpoint ... menu konteks), jangan lupa atur jenis skrip - Python:




Jika nilai saat ini dari bendera sibuk adalah 1, maka fungsi skip_dma harus dieksekusi di baris skrip:



Jika Anda menjalankan firmware untuk dieksekusi, maka kode breakpoint-handler dapat dilihat di IDA di jendela Output pada baris Skipping wait ws2812... Sekarang firmware tidak akan menunggu untuk mengatur ulang flag yang sibuk .


Interaksi emulator


Persaingan demi persaingan tidak mungkin menyebabkan kegembiraan dan kegembiraan. Akan jauh lebih menarik jika emulator membantu peneliti untuk melihat data dalam memori atau untuk membangun interaksi arus.


Kami menunjukkan cara membangun interaksi tugas RTOS secara dinamis. Pertama, jeda eksekusi kode jika sedang berjalan. Jika Anda beralih ke fungsi bluetooth_task_entry di cabang pemrosesan perintah "LED" (alamat 0x080057B8 ), Anda dapat melihat apa yang pertama kali dibuat, dan kemudian pesan dikirim ke antrian sistem ledControlQueueHandle .


gambar


Anda harus mengatur breakpoint untuk mengakses variabel ledControlQueueHandle yang terletak di 0x20000624 dan terus menjalankan kode:



Akibatnya, pada awalnya ia akan berhenti di alamat 0x080057CA sebelum memanggil fungsi osMailAlloc , kemudian pada 0x08005806 sebelum memanggil fungsi osMailPut , kemudian setelah beberapa saat di alamat 0x08005BD4 (sebelum memanggil fungsi osMailGet ), yang termasuk dalam fungsi leds_task_entry (tugas LED), i.e. ada saklar tugas, dan sekarang kontrol telah menerima tugas LED.


gambar


Sedemikian sederhana, Anda dapat menetapkan bagaimana tugas RTOS berinteraksi satu sama lain.


Tentu saja, dalam kenyataannya, interaksi tugas bisa lebih rumit, tetapi menggunakan emulator untuk melacak interaksi ini menjadi lebih sulit.


Di sini Anda dapat menonton video pendek peluncuran emulator dan interaksi dengan IDA Pro.


Luncurkan dengan Radare2


Anda tidak dapat mengabaikan alat universal seperti Radare2.


Untuk terhubung ke emulator menggunakan r2, perintahnya akan terlihat seperti ini:


 radare2 -A -a arm -b 16 -d gdb://localhost:23946 rhino_fw42k6.elf 

Sekarang mulai ( dc ) dan eksekusi jeda (Ctrl + C) tersedia.


Sayangnya, saat ini di r2 ada masalah ketika bekerja dengan server gdb hardware dan markup memori, karena ini, breakpoints dan Langkah (perintah ds ) tidak berfungsi. Kami berharap ini akan diperbaiki dalam waktu dekat.


Luncurkan dengan Eclipse


Salah satu opsi untuk menggunakan emulator adalah debugging firmware perangkat yang sedang dikembangkan. Untuk kejelasan, kami juga akan menggunakan firmware Badak. Anda dapat mengunduh sumber firmware dari sini .


Kami akan menggunakan Eclipse dari System Workbench untuk STM32 suite sebagai IDE.


Agar firmware yang langsung dikompilasi dalam Eclipse untuk dimuat ke emulator, Anda perlu menambahkan parameter firmware=null ke perintah mulai emulator:


 bin\kopycat -g 23946 -n rhino -l user -y library -p firmware=null,tty_dbg=COM26,tty_bt=COM28 

Konfigurasi debug


Di Eclipse, pilih menu Run - Debug Configurations .... Di jendela yang terbuka, di bagian Debugging Hardware GDB , Anda perlu menambahkan konfigurasi baru, dan kemudian tentukan proyek dan aplikasi saat ini untuk debugging pada tab "Main":



Pada tab Debugger, Anda harus menentukan perintah GDB:
${openstm32_compiler_path}\arm-none-eabi-gdb


Dan juga memasukkan parameter untuk menghubungkan ke server GDB (host dan port):



Parameter berikut harus ditentukan pada tab "Startup":


  • aktifkan kotak centang Muat gambar (sehingga gambar firmware yang dikumpulkan dimuat ke emulator);
  • aktifkan tanda centang Muat simbol ;
  • tambahkan perintah mulai: set $pc = *0x08000004 (set nilai dari memori ke alamat 0x08000004 dalam register PC - alamat 0x08000004 disimpan di sana).

Harap dicatat bahwa jika Anda tidak ingin mengunduh file firmware dari Eclipse, Anda tidak perlu menentukan parameter Load image and Run commands .



Setelah mengklik Debug, Anda dapat bekerja dalam mode debug:


  • eksekusi kode langkah-demi-langkah
  • interaksi dengan breakpoints

Catatan Eclipse memiliki, hmm ... beberapa fitur ... dan Anda harus hidup dengannya. Misalnya, jika pesan "Tidak ada sumber yang tersedia untuk" 0x0 "" muncul ketika memulai debugger, kemudian jalankan perintah Langkah (F5)



Alih-alih sebuah kesimpulan


Emulasi kode asli adalah hal yang sangat menarik. Untuk pengembang perangkat, menjadi mungkin untuk men-debug firmware tanpa perangkat nyata. Untuk peneliti - kemampuan untuk melakukan analisis kode dinamis, yang tidak selalu mungkin bahkan dengan perangkat.


Kami ingin memberikan alat yang akan mudah bagi para spesialis, cukup sederhana dan tidak membutuhkan banyak upaya dan waktu untuk mengonfigurasi dan meluncurkan.


Tulis di komentar tentang pengalaman Anda menggunakan emulator perangkat keras. Kami mengundang Anda untuk berdiskusi dan dengan senang hati akan menjawab pertanyaan.

Source: https://habr.com/ru/post/id445798/


All Articles