Tangkapan Layar Antarmuka IDA Pro DisassemblerIDA Pro adalah disassembler terkenal yang telah digunakan oleh para peneliti keamanan informasi di seluruh dunia selama bertahun-tahun. Kami di Positive Technologies juga menggunakan alat ini. Selain itu, kami dapat mengembangkan
modul prosesor disassembler kami sendiri
untuk arsitektur mikroprosesor NIOS II , yang meningkatkan kecepatan dan kenyamanan analisis kode.
Hari ini saya akan menceritakan tentang sejarah proyek ini dan menunjukkan apa yang terjadi pada akhirnya.
Latar belakang
Semuanya dimulai pada tahun 2016, ketika kami harus mengembangkan modul prosesor kami sendiri untuk menganalisis firmware dalam satu tugas. Pengembangan dilakukan dari awal pada manual
Panduan Referensi Prosesor Klasik Nios II , yang saat itu paling relevan. Secara total, pekerjaan ini memakan waktu sekitar dua minggu.
Modul prosesor dikembangkan untuk versi IDA 6.9. Untuk kecepatan, IDA Python dipilih. Di tempat modul prosesor berada - subdirektori procs di dalam direktori instalasi IDA Pro - ada tiga modul Python: msp430, ebc, spu. Di dalamnya, Anda dapat melihat bagaimana modul disusun dan bagaimana fungsionalitas pembongkaran dasar dapat diimplementasikan:
- instruksi parsing dan operan,
- penyederhanaan dan tampilan mereka,
- membuat offset, referensi silang, serta kode dan data yang mereka rujuk
- pemrosesan saklar konstruksi,
- menangani manipulasi dengan variabel stack dan stack.
Kira-kira fungsi seperti itu diterapkan pada waktu itu. Untungnya, alat ini berguna dalam proses mengerjakan tugas lain, di mana, setahun kemudian, alat itu secara aktif digunakan dan disempurnakan.
Saya memutuskan untuk berbagi pengalaman membuat modul prosesor dengan komunitas di PHDays 8. Presentasi membangkitkan minat (laporan video
diterbitkan di situs web PHDays), bahkan pencipta IDA Pro Ilfak Gilfanov hadir. Salah satu pertanyaannya adalah apakah dukungan untuk IDA Pro versi 7. Diimplementasikan pada saat itu tidak ada, tetapi setelah kinerja saya berjanji untuk membuat rilis modul yang sesuai. Di sinilah kesenangan dimulai.
Sekarang
manual terbaru
dari Intel , yang digunakan untuk memverifikasi dan memeriksa kesalahan. Saya secara signifikan merevisi modul, menambahkan sejumlah fitur baru, termasuk menyelesaikan masalah-masalah yang tidak dapat dikalahkan sebelumnya. Yah, tentu saja, saya menambahkan dukungan untuk versi 7 IDA Pro. Inilah yang terjadi.
Model Perangkat Lunak NIOS II
NIOS II adalah prosesor perangkat lunak yang dikembangkan untuk FPGA Altera (sekarang bagian dari Intel). Dari sudut pandang program, ia memiliki fitur berikut: urutan byte little endian, ruang alamat 32-bit, set instruksi 32-bit, yaitu, 4 byte, 32 umum dan 32 register tujuan khusus digunakan untuk menyandikan setiap perintah.
Pembongkaran dan referensi kode
Jadi, kami membuka file baru di IDA Pro, dengan firmware untuk prosesor NIOS II. Setelah menginstal modul, kita akan melihatnya dalam daftar prosesor Pro IDA. Pilihan prosesor ditunjukkan pada gambar.

Misalkan modul belum mengimplementasikan bahkan analisis dasar perintah. Mengingat bahwa setiap perintah membutuhkan 4 byte, kita mengelompokkan byte menjadi empat, maka semuanya akan terlihat seperti ini.

Setelah menerapkan fungsionalitas dasar instruksi dan operasi decoding, menampilkannya di layar, dan menganalisis instruksi transfer kontrol, byte yang ditetapkan dari contoh di atas dikonversi ke kode berikut.

Seperti dapat dilihat dari contoh, referensi silang juga dihasilkan dari perintah transfer kontrol (dalam hal ini, Anda dapat melihat lompatan bersyarat dan panggilan prosedur).
Salah satu properti berguna yang dapat diimplementasikan dalam modul prosesor adalah komentar perintah. Jika Anda menonaktifkan output nilai byte dan mengaktifkan output komentar, bagian kode yang sama sudah akan terlihat seperti ini.

Di sini, jika Anda pertama kali menemukan kode assembler dari arsitektur baru untuk Anda, menggunakan komentar Anda dapat memahami apa yang terjadi. Selanjutnya, contoh kode akan berada dalam bentuk yang sama - dengan komentar, agar tidak melihat manual NIOS II, tetapi untuk segera memahami apa yang terjadi di bagian kode, yang diberikan sebagai contoh.
Instruksi semu dan penyederhanaan perintah
Beberapa perintah NIOS II adalah instruksi palsu. Tidak ada opcode yang terpisah untuk tim semacam itu, dan mereka sendiri dimodelkan sebagai kasus khusus dari tim lain. Dalam proses pembongkaran, penyederhanaan instruksi dilakukan - penggantian kombinasi tertentu dengan instruksi semu. Instruksi semu dalam NIOS II secara umum dapat dibagi menjadi empat jenis:
- ketika salah satu sumber adalah nol (r0) dan dapat dihapus dari pertimbangan,
- ketika tim memiliki nilai negatif dan tim digantikan oleh yang sebaliknya,
- ketika kondisinya terbalik,
- ketika offset 32-bit dimasukkan dalam dua tim di bagian (yang termuda dan tertua) dan ini digantikan oleh satu perintah.
Dua tipe pertama diimplementasikan, karena mengganti kondisi tidak memberikan sesuatu yang istimewa, dan offset 32-bit memiliki lebih banyak pilihan daripada yang disajikan dalam manual.
Misalnya, untuk tampilan pertama, perhatikan kodenya.

Terlihat bahwa penggunaan daftar nol dalam perhitungan sering ditemukan di sini. Jika Anda memperhatikan contoh ini dengan seksama, Anda akan melihat bahwa semua perintah kecuali transfer kontrol adalah opsi untuk hanya memasukkan nilai ke register spesifik.
Setelah mengimplementasikan pemrosesan instruksi semu, kami mendapatkan bagian kode yang sama, tetapi sekarang tampilannya lebih mudah dibaca, dan alih-alih variasi dari atau dan menambahkan perintah, kami mendapatkan variasi dari perintah mov.

Variabel tumpukan
Arsitektur NIOS II mendukung stack, dan di samping stack pointer sp, ada juga pointer ke frame stack fp. Pertimbangkan contoh prosedur kecil yang menggunakan tumpukan.

Jelas, ruang disediakan untuk variabel lokal di stack. Dapat diasumsikan bahwa register ra disimpan dalam variabel stack dan kemudian dikembalikan darinya.
Setelah menambahkan fungsionalitas ke modul yang melacak perubahan dalam penunjuk tumpukan dan membuat variabel tumpukan, contoh yang sama akan terlihat seperti ini.

Sekarang kode terlihat sedikit lebih jelas, dan Anda sudah dapat memberi nama variabel stack dan menguraikan tujuannya dengan mengikuti referensi silang. Fungsi dalam contoh ini adalah tipe __fastcall dan argumennya di register r4 dan r5 didorong ke stack untuk memanggil subprocedure yang bertipe _stdcall.
Angka dan offset 32-bit
Keunikan NIOS II adalah bahwa dalam satu operasi, yaitu, ketika menjalankan satu perintah, dimungkinkan untuk mendaftarkan paling banyak nilai langsung ukuran 2 byte (16 bit). Di sisi lain, register prosesor dan ruang alamat adalah 32-bit, yaitu, untuk pengalamatan, 4 byte harus dimasukkan ke dalam register.
Untuk mengatasi masalah ini, perpindahan dua bagian digunakan. Mekanisme serupa digunakan dalam prosesor di PowerPC: offset terdiri dari dua bagian, yang tertua dan yang termuda, dan dimasukkan ke dalam register dengan dua perintah. Di PowerPC, ini adalah sebagai berikut.

Dalam pendekatan ini, cross-link dibentuk dari kedua tim, meskipun pada kenyataannya, alamat dikonfigurasi dalam perintah kedua. Ini terkadang bisa menjadi gangguan saat menghitung jumlah referensi silang.
Properti offset untuk bagian yang lebih lama menggunakan tipe HIGHA16 yang tidak standar, terkadang tipe HIGH16 digunakan, untuk bagian yang lebih muda - LOW16.

Tidak ada yang rumit dalam perhitungan angka dua bagian 32-bit. Kesulitan muncul dalam pembentukan operan sebagai offset untuk dua tim yang terpisah. Semua pemrosesan ini jatuh pada modul prosesor. Tidak ada contoh bagaimana mengimplementasikan ini (terutama dengan Python) di IDA SDK.
Dalam laporan PHDays, bias berdiri sebagai masalah yang belum terselesaikan. Untuk mengatasi masalah, kami menipu: 32-bit offset hanya dari bagian termuda - di pangkalan. Basis dihitung sebagai bagian tertua, bergeser ke kiri sebanyak 16 bit.

Dengan pendekatan ini, referensi silang dibentuk hanya dengan perintah untuk masuk ke bagian bawah offset 32-bit.
Basis terlihat di properti offset dan properti ditandai untuk menganggapnya sebagai angka, sehingga sejumlah besar referensi silang ke alamat itu sendiri tidak terbentuk, yang kami ambil sebagai basis.

Dalam kode untuk NIOS II, mekanisme berikut ditemukan untuk memasukkan angka 32-bit ke dalam register. Pertama, bagian tertua dari offset dimasukkan ke dalam register dengan perintah movhi. Kemudian bagian yang lebih muda bergabung. Ini dapat dilakukan dengan tiga cara (dengan perintah): menambahkan addi, mengurangi subi, logika OR ori.
Sebagai contoh, pada bagian kode berikutnya, register diatur ke angka 32-bit, yang kemudian dimasukkan ke register - argumen sebelum memanggil fungsi.

Setelah menambahkan perhitungan offset, kami mendapatkan representasi berikut dari blok kode ini.

32-bit offset yang dihasilkan ditampilkan di sebelah perintah untuk masuk ke bagian bawahnya. Contoh ini cukup ilustratif, dan kita bahkan dapat dengan mudah menghitung semua angka 32-bit dalam pikiran hanya dengan menambahkan bagian minor dan tertinggi. Dilihat dari nilai-nilai, mereka kemungkinan besar tidak bias.
Pertimbangkan kasus ketika pengurangan digunakan ketika memasuki bagian yang lebih muda. Dalam contoh ini, tidak akan mungkin untuk menentukan angka 32-bit terakhir (offset) saat bepergian.

Setelah menerapkan perhitungan angka 32-bit, kami mendapatkan formulir berikut.

Di sini kita melihat bahwa sekarang, jika alamat berada di ruang alamat, offset terbentuk di atasnya, dan nilai yang terbentuk sebagai akibat dari koneksi bagian minor dan senior tidak lagi ditampilkan di dekatnya. Di sini mereka mendapat offset dengan garis "10/22/08". Agar sisa offset menunjuk ke alamat yang valid, mari tambah sedikit segmennya.

Setelah meningkatkan segmen, kami mendapatkan bahwa sekarang semua angka 32-bit yang dihitung adalah offset dan menunjukkan alamat yang valid.
Disebutkan di atas bahwa ada opsi lain untuk menghitung offset ketika perintah ATAU logis digunakan. Berikut adalah contoh kode di mana dua offset dihitung dengan cara ini.

Yang dievaluasi dalam register r8 kemudian didorong ke stack.
Setelah konversi, dapat dilihat bahwa dalam kasus ini register dikonfigurasikan ke alamat awal prosedur, yaitu alamat prosedur didorong ke stack.

Membaca dan menulis relatif terhadap basis
Sebelum itu, kami mempertimbangkan kasus-kasus ketika angka 32-bit yang dimasukkan menggunakan dua perintah bisa berupa angka dan juga offset. Dalam contoh berikut, pangkalan dimasukkan di bagian atas register, kemudian membaca atau menulis terjadi relatif terhadap itu.

Setelah memproses situasi seperti itu, kami mendapatkan offset ke variabel dari perintah baca dan tulis sendiri. Selain itu, tergantung pada dimensi operasi, ukuran variabel itu sendiri diatur.

Ganti konstruksi
Konstruk saklar yang ditemukan dalam file biner dapat memfasilitasi analisis. Misalnya, dengan jumlah kasus pemilihan di dalam switch switch, Anda dapat melokalkan switch yang bertanggung jawab untuk memproses protokol atau sistem perintah tertentu. Oleh karena itu, tugas muncul untuk mengenali sakelar itu sendiri dan parameternya. Pertimbangkan bagian kode berikut.

Utas eksekusi berhenti pada transisi register jmp r2. Selanjutnya ada blok kode yang ada tautannya dari data, dan di akhir setiap blok ada lompatan ke label yang sama. Jelas, ini adalah saklar konstruksi dan blok-blok individual ini menangani kasus-kasus tertentu darinya. Di atas Anda juga dapat melihat pemeriksaan jumlah kasus dan lompatan default.
Setelah menambahkan pemrosesan switch, kode ini akan terlihat seperti ini.

Sekarang lompatan itu sendiri ditunjukkan, alamat meja dengan offset, jumlah kasing, serta setiap kasing dengan nomor yang sesuai.
Tabel itu sendiri dengan offset ke opsi adalah sebagai berikut. Untuk menghemat ruang, lima elemen pertama diberikan.

Bahkan, pemrosesan switch terdiri dari kembali melalui kode dan mencari semua komponennya. Yaitu, beberapa skema organisasi sakelar dijelaskan. Terkadang mungkin ada pengecualian dalam skema. Ini mungkin menjadi alasan untuk kasus ketika sakelar yang tampaknya jernih tidak dikenali dalam modul prosesor yang ada. Ternyata saklar yang sebenarnya tidak termasuk dalam skema yang didefinisikan di dalam modul prosesor. Masih ada opsi yang memungkinkan ketika sirkuit tampaknya ada, tetapi ada tim lain di dalamnya yang tidak terlibat dalam sirkuit, atau tim utama disusun ulang, atau dipecah oleh transisi.
Modul prosesor NIOS II mengenali sakelar dengan instruksi "asing" di antara perintah-perintah utama, serta dengan tempat-tempat yang disusun kembali dari perintah-perintah utama dan dengan pemutusan sirkuit. Jalur balik digunakan di sepanjang jalur eksekusi, dengan mempertimbangkan kemungkinan transisi yang memutus sirkuit, dengan pemasangan variabel internal yang memberi sinyal berbagai keadaan pengenal. Hasilnya, sekitar 10 opsi organisasi sakelar berbeda yang ditemukan dalam firmware dikenali.
Instruksi kustom
Ada fitur yang menarik dalam arsitektur NIOS II - instruksi khusus. Ini memberikan akses ke 256 instruksi yang ditentukan pengguna yang dimungkinkan dalam arsitektur NIOS II. Dalam pekerjaannya, selain register tujuan umum, instruksi khusus dapat mengakses satu set khusus 32 register kustom. Setelah menerapkan logika untuk parsing perintah kustom, kami mendapatkan formulir berikut.

Anda mungkin memperhatikan bahwa dua instruksi terakhir memiliki nomor instruksi yang sama dan tampaknya melakukan tindakan yang sama.
Menurut instruksi khusus, ada
manual terpisah . Menurutnya, salah satu opsi paling komprehensif dan terkini untuk set instruksi kustom adalah set instruksi Komponen 2 Floating Point Hardware (FPH2) NIOS II untuk bekerja dengan floating point. Setelah mengimplementasikan parsing dari perintah FPH2, contohnya akan terlihat seperti ini.

Dari mnemonik dari dua tim terakhir, kami memastikan bahwa mereka benar-benar melakukan tindakan yang sama - perintah fadds.
Transisi berdasarkan nilai register
Dalam firmware yang sedang diselidiki, suatu situasi sering ditemui ketika lompatan dilakukan sesuai dengan nilai register, di mana offset 32-bit, yang menentukan tempat lompatan, dimasukkan sebelumnya.
Pertimbangkan sepotong kode.

Di baris terakhir ada lompatan dalam nilai register, sementara itu jelas bahwa sebelum alamat prosedur dimasukkan dalam register, yang dimulai pada baris pertama dari contoh. Dalam hal ini, jelas bahwa lompatan dibuat pada awalnya.
Setelah menambahkan fungsi pengenalan lompatan, formulir berikut diperoleh.

Di sebelah perintah jmp r8, alamat tempat lompatan terjadi jika dimungkinkan untuk dihitung ditampilkan. Referensi silang juga dibentuk antara tim dan alamat tempat lompatan berlangsung. Dalam hal ini, tautannya terlihat di baris pertama, lompatan itu sendiri dilakukan dari baris terakhir.
Nilai register Gp (global pointer), simpan dan muat
Adalah umum untuk menggunakan penunjuk global yang dikonfigurasikan ke beberapa alamat, dan variabel dialamatkan relatif terhadapnya. NIOS II menggunakan register gp (global pointer) untuk menyimpan global pointer. Pada titik tertentu, sebagai aturan, dalam prosedur inisialisasi firmware, nilai alamat dimasukkan dalam register gp. Modul prosesor menangani situasi ini; Untuk menggambarkan hal ini, berikut ini adalah contoh kode dan jendela output IDA Pro ketika pesan debug diaktifkan di modul prosesor.
Dalam contoh ini, modul prosesor menemukan dan menghitung nilai register gp di database baru. Saat menutup basis data idb, nilai gp disimpan dalam basis data.

Saat memuat basis data idb yang ada dan jika nilai gp telah ditemukan, itu diambil dari basis data, seperti yang ditunjukkan dalam pesan debug dalam contoh berikut.

Membaca dan menulis tentang gp
Operasi umum membaca dan menulis dengan offset relatif terhadap register gp. Sebagai contoh, dalam contoh berikut, tiga pembacaan dan satu catatan dari jenis ini dilakukan.

Karena kita sudah mendapatkan nilai dari alamat yang disimpan dalam register gp, kita dapat mengatasi jenis membaca dan menulis ini.
Setelah menambahkan pemrosesan untuk situasi membaca dan menulis relatif terhadap register gp, kami mendapatkan gambar yang lebih nyaman.

Di sini Anda dapat melihat variabel mana yang sedang diakses, melacak penggunaannya dan mengidentifikasi tujuannya.
Mengatasi relatif ke gp
Ada lagi penggunaan register gp untuk mengatasi variabel.

Sebagai contoh, di sini kita melihat bahwa register dikonfigurasi relatif terhadap register gp ke beberapa variabel atau area data.
Setelah menambahkan fungsionalitas yang mengenali situasi seperti itu, mengonversi ke offset dan menambahkan referensi silang, kami mendapatkan formulir berikut.

Di sini Anda sudah dapat melihat area mana yang relatif terhadap register gp yang dikonfigurasi, dan semakin jelas apa yang terjadi.
Mengatasi relatif ke sp
Demikian pula, dalam contoh berikut, register disetel ke beberapa area memori, kali ini relatif terhadap register sp - pointer ke stack.

Jelas, register disetel ke beberapa variabel lokal. Situasi seperti itu - pengaturan argumen ke buffer lokal sebelum panggilan prosedur - cukup umum.
Setelah menambahkan pemrosesan (mengonversi nilai langsung ke offset), kami mendapatkan formulir berikut.

Sekarang menjadi jelas bahwa setelah pemanggilan prosedur, nilai-nilai diambil dari variabel-variabel yang alamatnya dilewati sebagai parameter sebelum pemanggilan fungsi.
Referensi silang dari kode ke bidang struktur
Mendefinisikan struktur dan menggunakannya dalam IDA Pro dapat memfasilitasi analisis kode.

Melihat bagian kode ini, kita dapat memahami bahwa field__range bertambah dan, mungkin, merupakan counter dari terjadinya suatu peristiwa. Jika bidang membaca dan menulis dipisahkan dalam kode pada jarak yang sangat jauh, referensi silang dapat membantu.
Pertimbangkan struktur itu sendiri.
Meskipun akses ke bidang struktur adalah, seperti yang kita lihat, tidak ada referensi silang dari kode ke elemen struktur.Setelah situasi seperti itu diproses, untuk kasus kami semuanya akan terlihat sebagai berikut.
Sekarang ada referensi silang ke bidang struktur dari tim tertentu yang bekerja dengan bidang ini. Referensi silang maju dan mundur dibuat, dan Anda dapat melacak dengan prosedur yang berbeda di mana nilai-nilai bidang struktur dibaca dan di mana mereka dimasukkan.Perbedaan antara manual dan kenyataan
Dalam manual, ketika mendekodekan beberapa perintah, bit tertentu harus mengambil nilai yang didefinisikan dengan ketat. Sebagai contoh, untuk perintah kembali dari pengecualian eret, bit 22-26 harus 0x1E.
Ini adalah contoh dari perintah ini dari satu firmware.
Membuka firmware lain di tempat dengan konteks yang sama, kami menghadapi situasi yang berbeda.
Bytes ini tidak secara otomatis dikonversi ke perintah, meskipun ada pemrosesan semua perintah. Dilihat oleh lingkungan, dan bahkan alamat yang sama, ini harus tim yang sama. Mari kita perhatikan dengan cermat byte. Ini adalah perintah eret yang sama, dengan pengecualian bahwa bit 22-26 tidak sama dengan 0x1E, tetapi sama dengan nol.Kita harus sedikit memperbaiki analisis dari perintah ini. Sekarang tidak cukup sesuai dengan manual, tetapi sesuai dengan kenyataan.
Dukungan IDA 7
Dimulai dengan IDA 7.0, API yang disediakan oleh Python IDA untuk skrip biasa telah banyak berubah. Sedangkan untuk modul prosesor, perubahannya sangat besar. Meskipun demikian, modul prosesor NIOS II mampu dibuat ulang untuk versi 7, dan berhasil bekerja di dalamnya.
Satu-satunya momen yang tidak dapat dipahami: ketika memuat file biner baru di bawah NIOS II di IDA 7, analisis otomatis awal yang ada di IDA 6.9 tidak terjadi.Kesimpulan
Selain fungsionalitas pembongkaran dasar, contoh-contohnya ada di SDK, modul prosesor mengimplementasikan banyak fitur berbeda yang memudahkan pekerjaan kode explorer. Jelas bahwa semua ini dapat dilakukan secara manual, tetapi, misalnya, ketika ada ribuan dan puluhan ribu offset dari berbagai jenis pada file biner dengan firmware beberapa megabyte, mengapa menghabiskan waktu untuk ini? Biarkan modul prosesor melakukan ini untuk kita. Lagi pula, bagaimana fitur menyenangkan dari navigasi cepat melalui kode yang dipelajari menggunakan referensi silang! Ini menjadikan IDA alat yang nyaman dan menyenangkan seperti yang kita ketahui.Diposting oleh Anton Dorfman, Positive Technologies