"Dan yang tidak mungkin adalah mungkin": kita mengubah kotak hitam menjadi putih menggunakan analisis biner

gambar

Saat ini, ada dua pendekatan utama untuk mencari kerentanan dalam aplikasi - analisis statis dan dinamis. Kedua pendekatan memiliki pro dan kontra mereka. Pasar sampai pada kesimpulan bahwa kedua pendekatan harus digunakan - mereka menyelesaikan masalah yang sedikit berbeda dengan hasil yang berbeda. Namun, dalam beberapa kasus, penggunaan analisis statis terbatas - misalnya, ketika tidak ada kode sumber. Pada artikel ini, kita akan berbicara tentang teknologi yang agak jarang, tetapi sangat berguna yang memungkinkan Anda untuk menggabungkan keunggulan pendekatan statis dan dinamis - analisis statis kode yang dapat dieksekusi.

Ayo pergi dari jauh
Menurut perusahaan antivirus McAfee, kerusakan global dari kejahatan dunia maya pada 2017 berjumlah sekitar $ 600 miliar, yang setara dengan 0,8% dari PDB global. Kita hidup di zaman teknologi informasi, yang spesifik di antaranya adalah integrasi cepat jaringan global dan teknologi Internet di semua bidang aktivitas manusia. Sekarang kejahatan dunia maya tidak lagi di luar kebiasaan. Statistik menunjukkan peningkatan kejahatan dunia maya secara eksponensial.

Kerentanan aplikasi telah menjadi masalah serius: menurut Departemen Keamanan Dalam Negeri AS, lebih dari 90% serangan dunia maya berhasil dilakukan dengan menggunakan berbagai kerentanan dalam aplikasi. Metode eksploitasi kerentanan yang paling terkenal adalah:

  • Injeksi SQL
  • buffer overflow
  • skrip lintas situs
  • Menggunakan konfigurasi yang tidak aman.


Analisis perangkat lunak (software) untuk keberadaan kemampuan yang tidak dideklarasikan (NDV) dan kerentanan adalah teknologi utama untuk memastikan keamanan aplikasi.
Berbicara tentang teknologi klasik dan mapan untuk menganalisis perangkat lunak untuk kerentanan dan NDV (untuk kepatuhan dengan persyaratan keamanan informasi), kita dapat membedakan:

  • analisis kode statis (Pengujian Keamanan Aplikasi Statis);
  • analisis kode dinamis (Pengujian Keamanan Aplikasi Dinamis).

Ada IAST (analisis interaktif), namun pada dasarnya dinamis (dalam proses analisis, agen tambahan mengamati apa yang terjadi selama eksekusi aplikasi). RASP (Runtime Application Self-Defense), yang juga kadang-kadang disebutkan dalam sejumlah alat analisis, lebih mungkin merupakan alat perlindungan.

Analisis dinamis (metode "Kotak Hitam") adalah pemeriksaan program selama pelaksanaannya. Keuntungan berikut dapat dibedakan dari pendekatan ini.

  1. Karena kerentanan dalam program yang dapat dieksekusi, dan kesalahan terdeteksi menggunakan operasinya, generasi positif palsu kurang dari analisis statis.
  2. Tidak diperlukan kode sumber untuk melakukan analisis.

Namun ada juga kekurangannya.

  1. Cakupan kode yang tidak lengkap, dan oleh karena itu ada risiko kerentanan yang hilang. Misalnya, analisis dinamis tidak dapat menemukan kerentanan yang terkait dengan penggunaan kriptografi atau bookmark yang lemah seperti "bom sementara".
  2. Kebutuhan untuk menjalankan aplikasi, yang dalam beberapa kasus bisa sulit. Meluncurkan aplikasi mungkin memerlukan konfigurasi kompleks dan konfigurasi berbagai integrasi. Selain itu, agar hasilnya seakurat mungkin, perlu untuk mereproduksi "lingkungan tempur", tetapi untuk sepenuhnya menyadari hal ini tanpa merusak perangkat lunak itu sulit.

Analisis statis (metode "Kotak Putih") adalah jenis pengujian program yang tidak dijalankan oleh program.

Kami daftar manfaatnya.

  1. Cakupan penuh kode, yang mengarah pada pencarian lebih banyak kerentanan.
  2. Tidak ada ketergantungan pada lingkungan di mana program akan dieksekusi.
  3. Kemampuan untuk mengimplementasikan pengujian pada tahap awal penulisan kode untuk modul atau program tanpa adanya file yang dapat dieksekusi. Ini memungkinkan Anda untuk secara fleksibel mengintegrasikan solusi serupa ke dalam SDLC (Siklus Hidup Pengembangan Perangkat Lunak siklus hidup pengembangan perangkat lunak) pada awal pengembangan.

Satu-satunya kelemahan dari metode ini adalah adanya false positive: kebutuhan untuk mengevaluasi apakah analyzer mengindikasikan kesalahan nyata, atau apakah ini false positive.

Seperti yang dapat kita lihat, kedua metode analisis ini memiliki kelebihan dan kekurangan. Namun, apakah mungkin dengan cara apa pun menggunakan kelebihan metode ini, sambil meminimalkan kerugiannya? Ya, jika Anda menerapkan analisis biner - pencarian kerentanan dalam file yang dapat dieksekusi oleh analisis statis.

Analisis biner atau teknologi analisis file yang dapat dieksekusi


gambar

Analisis biner memungkinkan analisis statis tanpa kode sumber, misalnya, dalam kasus kontraktor pihak ketiga. Selain itu, cakupan kode akan lengkap, berbeda dengan penerapan metode analisis dinamis. Menggunakan analisis biner, Anda dapat memverifikasi perpustakaan pihak ketiga yang digunakan dalam proses pengembangan yang tidak ada kode sumber. Juga, menggunakan analisis biner, Anda dapat melakukan pemeriksaan kontrol terhadap rilis, membandingkan hasil analisis kode sumber dari repositori dan kode yang dapat dieksekusi dari server tempur.

Dalam proses analisis biner, gambar biner diubah menjadi representasi perantara (representasi internal atau model kode) untuk analisis lebih lanjut. Setelah itu, algoritma analisis statis diterapkan pada representasi internal. Akibatnya, model saat ini dilengkapi dengan informasi yang diperlukan untuk deteksi lebih lanjut kerentanan dan NDV. Pada tahap selanjutnya, penerapan aturan untuk mencari kerentanan dan NDV.

Kami menulis lebih banyak tentang skema analisis statis di artikel sebelumnya . Tidak seperti analisis kode sumber, yang menggunakan elemen teori kompilasi (leksikal, analisis sintaksis) untuk membangun model, analisis biner menggunakan teori terjemahan terbalik - pembongkaran, dekompilasi, deobfusiasi - untuk membangun model.

Sedikit tentang ketentuannya


Kita berbicara tentang menganalisis file yang dapat dieksekusi yang tidak memiliki info debug. Dengan info debug, tugas ini sangat disederhanakan, tetapi jika ada info debug, maka kode sumber yang paling mungkin adalah, dan tugas menjadi tidak relevan.

Dalam artikel ini, kami menyebut analisis bytecode Java juga analisis biner, meskipun ini tidak sepenuhnya benar. Kami melakukan ini untuk menyederhanakan teks. Tentu saja, tugas menganalisis bytecode JVM lebih sederhana daripada menganalisis kode biner C / C ++ dan Objective-C / Swift. Tetapi skema analisis umum serupa dalam kasus bytecode dan kode biner. Kesulitan utama yang dijelaskan dalam artikel ini berkaitan khusus dengan analisis kode biner.

Dekompilasi adalah proses memulihkan kode sumber dari kode biner. Anda dapat berbicara tentang unsur-unsur terjemahan terbalik - pembongkaran (memperoleh kode assembler dari gambar biner), menerjemahkan assembler menjadi kode tiga-alamat atau representasi lainnya, mengembalikan konstruksi tingkat kode sumber.

Kebingungan - transformasi yang menjaga fungsi kode sumber, tetapi membuatnya sulit untuk mendekompilasi dan memahami gambar biner yang dihasilkan. Deobfusiasi adalah transformasi terbalik. Kebingungan dapat diterapkan baik pada level kode sumber dan pada level kode biner.

Bagaimana cara menonton hasilnya?


Mari kita mulai dari awal, tetapi pertanyaan untuk melihat hasil analisis biner biasanya ditanyakan terlebih dahulu.

Penting bagi seorang spesialis menganalisis kode biner untuk memetakan kerentanan dan NDV ke kode sumber. Untuk melakukan ini, pada tahap akhir, proses deobfuscation (unraveling) dimulai jika konversi membingungkan diterapkan, dan kode biner didekompilasi ke sumber. Artinya, kerentanan dapat ditunjukkan pada kode yang didekompilasi.

Dalam proses dekompilasi, bahkan jika kita mendekompilasi bytecode JVM, beberapa informasi tidak dipulihkan dengan benar, sehingga analisis itu sendiri terjadi pada representasi yang dekat dengan kode biner. Dengan demikian, muncul pertanyaan: bagaimana, menemukan kerentanan dalam kode biner, melokalkannya di sumbernya? Solusi untuk masalah bytecode JVM dijelaskan dalam artikel kami tentang pencarian kerentanan di bytecode Java . Solusi untuk kode biner serupa, yaitu pertanyaan teknis.

Mari kita ulangi peringatan penting - kita berbicara tentang analisis kode biner tanpa info debug. Di hadapan info debug, tugas ini sangat disederhanakan.

Pertanyaan utama yang kami tanyakan tentang menampilkan hasil adalah apakah kode yang didekompilasi cukup untuk memahami dan melokalisasi kerentanan.

gambar

Di bawah ini adalah beberapa pemikiran tentang hal ini.

  1. Jika kita berbicara tentang bytecode JVM, maka secara umum jawabannya adalah "ya" - kualitas dekompilasi untuk bytecode itu hebat. Hampir selalu Anda bisa mengetahui apa itu kerentanan.
  2. Apa yang dapat mengganggu lokalisasi kualitatif kerentanan adalah kebingungan sederhana seperti mengganti nama nama dan fungsi kelas. Namun, dalam praktiknya seringkali ternyata lebih penting untuk memahami kerentanan daripada menentukan di mana file itu. Pelokalan diperlukan ketika seseorang dapat memperbaiki kerentanan, tetapi dalam kasus ini, pengembang juga akan memahami di mana kerentanan tersebut berasal dari kode yang di-decompile.
  3. Ketika kita berbicara tentang analisis kode biner (misalnya, C ++), tentu saja, semuanya jauh lebih rumit. Tidak ada alat yang sepenuhnya memulihkan kode C ++ acak. Namun, kekhasan kasus kami adalah bahwa kami tidak perlu mengkompilasi kode nanti: kami membutuhkan kualitas yang cukup untuk memahami kerentanan.
  4. Paling sering, Anda dapat mencapai kualitas dekompilasi yang cukup untuk memahami kerentanan yang ditemukan. Untuk melakukan ini, Anda harus menyelesaikan banyak masalah sulit, tetapi Anda dapat menyelesaikannya (di bawah ini kita akan membicarakannya secara singkat).
  5. Untuk C / C ++, bahkan lebih sulit untuk melokalisasi kerentanan - nama-nama karakter hilang dalam banyak cara selama proses kompilasi, Anda tidak dapat mengembalikannya.
  6. Situasi di Objective-C sedikit lebih baik - ada nama fungsi di sana, dan lebih mudah untuk melokalisasi kerentanan.
  7. Masalah kebingungan berdiri terpisah. Ada sejumlah transformasi kompleks yang dapat memperumit dekompilasi dan pemetaan kerentanan. Dalam praktiknya, ternyata dekompiler yang baik dapat menangani sebagian besar konversi yang membingungkan (ingat bahwa kita memerlukan kualitas kode yang cukup untuk memahami kerentanan).

Sebagai kesimpulan - paling sering ternyata menunjukkan kerentanan sehingga dapat dipahami dan diverifikasi.

Kompleksitas dan detail analisis biner


Di sini kita tidak akan berbicara tentang bytecode: semua hal menarik tentang itu sudah dikatakan di atas. Hal yang paling menarik adalah analisis kode biner nyata. Di sini kita akan berbicara tentang analisis C / C ++, Objective-C dan Swift sebagai contoh.

Kesulitan yang signifikan muncul bahkan ketika pembongkaran. Tahap yang paling penting adalah pembagian gambar biner ke dalam subprogram. Selanjutnya, pilih instruksi assembler di subrutin - masalah teknis. Kami menulis tentang ini secara terperinci dalam sebuah artikel untuk jurnal β€œIssues of Cybersecurity No. 1 (14) - 2016” , di sini kami akan menjelaskan secara singkat.

Sebagai contoh, kita akan berbicara tentang arsitektur x86. Instruksi di dalamnya tidak memiliki panjang yang tetap. Dalam gambar biner, tidak ada pembagian yang jelas ke dalam bagian-bagian kode dan data: tabel impor, tabel fungsi virtual dapat berada di bagian kode, tabel transisi dapat dalam interval antara blok fungsi dasar di bagian kode. Dengan demikian, Anda harus dapat memisahkan kode dari data dan memahami di mana rutinitas dimulai dan di mana rutinitas berakhir.

Yang paling umum adalah dua metode untuk memecahkan masalah menentukan alamat awal subprogram. Dalam metode pertama, alamat subprogram ditentukan oleh prolog standar (untuk arsitektur x86 itu adalah push ebp; mov ebp, esp). Dalam metode kedua, bagian kode secara rekursif dilalui dari titik masuk dengan pengakuan instruksi panggilan subrutin. Memotong dilakukan dengan mengenali instruksi cabang. Kombinasi dari metode yang dijelaskan juga digunakan ketika traversal rekursif dimulai dari alamat awal yang ditemukan oleh prolog.

Dalam prakteknya, ternyata pendekatan semacam itu memberikan persentase yang cukup rendah dari kode yang dikenali, karena tidak semua fungsi memiliki prolog standar, dan ada panggilan dan transisi tidak langsung.

Algoritma dasar dapat ditingkatkan dengan heuristik berikut.

  1. Pada basis uji gambar yang besar, temukan daftar prolog yang lebih akurat (prolog baru atau variasi yang standar).
  2. Anda dapat secara otomatis menemukan tabel fungsi virtual, dan dari mereka untuk mengambil alamat awal subprogram.
  3. Alamat awal subprogram dan beberapa konstruksi lainnya dapat ditemukan berdasarkan bagian dari kode biner yang terkait dengan mekanisme penanganan pengecualian.
  4. Anda dapat memverifikasi alamat mulai dengan mencari alamat-alamat ini dalam gambar dan dengan mengenali instruksi panggilan.
  5. Untuk mencari batas, Anda dapat melakukan traversal rekursif dari subrutin dengan pengenalan instruksi dari alamat awal. Ada kesulitan dengan transisi tidak langsung dan fungsi tidak-kembali. Analisis tabel impor dan pengakuan switch-konstruksi dapat membantu.

Hal penting lain yang perlu dilakukan selama penerjemahan terbalik, agar dapat secara normal mencari kerentanan nanti, adalah mengenali fungsi standar dalam gambar biner. Fungsi standar dapat dihubungkan secara statis ke gambar, atau bahkan bisa sejajar. Algoritma pengenalan utama adalah pencarian berdasarkan tanda tangan dengan variasi, untuk solusinya, Anda dapat menawarkan algoritma Aho-Korasik yang disesuaikan. Untuk mengumpulkan tanda tangan, Anda perlu menganalisis pra-pustaka gambar yang dikumpulkan dengan kondisi yang berbeda, dan memilihnya sebagai byte yang tidak berubah.

Apa selanjutnya


Pada bagian sebelumnya, kami memeriksa tahap awal dari terjemahan terbalik dari gambar biner - pembongkaran. Panggung memang memang awal, tetapi menentukan. Pada tahap ini, Anda dapat kehilangan beberapa kode, yang kemudian akan memiliki efek dramatis pada hasil analisis.

gambar

Kemudian banyak hal menarik terjadi. Katakan secara singkat tentang tugas utama. Kami tidak akan berbicara secara terperinci: entah bagaimana caranya, yang tidak bisa kami tulis secara eksplisit di sini, atau tidak ada solusi teknis dan teknik yang sangat menarik ada di detailnya.

  1. Mengubah kode rakitan menjadi representasi perantara yang dapat digunakan analisis. Anda dapat menggunakan berbagai bytecode. Untuk bahasa C, LLVM tampaknya menjadi pilihan yang baik. LLVM secara aktif didukung dan dikembangkan oleh masyarakat, infrastruktur, termasuk berguna untuk analisis statis, saat ini mengesankan. Pada tahap ini, ada sejumlah besar detail yang perlu Anda perhatikan. Misalnya, Anda perlu mendeteksi variabel mana yang dialamatkan pada tumpukan agar tidak menggandakan entitas dalam tampilan yang dihasilkan. Anda perlu mengonfigurasi tampilan optimal kumpulan instruksi assembler dalam instruksi bytecode.
  2. Kembalikan struktur tingkat tinggi (mis. Loop, cabang). Semakin akurat memungkinkan untuk mengembalikan konstruksi asli dari kode assembler, semakin baik kualitas analisisnya. Pemulihan konstruksi semacam itu terjadi menggunakan elemen-elemen teori grafik pada CFG (grafik aliran kendali) dan beberapa representasi grafis lainnya dari program.
  3. Melakukan algoritma analisis statis. Ada detailnya. Secara umum, tidak terlalu penting apakah kita mendapatkan representasi internal dari sumber atau dari biner - kita semua juga perlu membangun CFG, menerapkan algoritma analisis aliran data dan algoritma lain yang khas dari statika. Ada beberapa fitur ketika menganalisis tampilan yang diperoleh dari biner, tetapi mereka lebih teknis.

Kesimpulan


Kami berbicara tentang bagaimana Anda dapat melakukan analisis statis ketika tidak ada kode sumber. Menurut pengalaman komunikasi dengan pelanggan, ternyata teknologinya sangat laris. Namun, teknologinya jarang: masalah analisis biner tidak sepele, solusinya membutuhkan algoritma berteknologi tinggi yang rumit dari analisis statis dan terjemahan terbalik.

Artikel ini ditulis bekerja sama dengan Anton Prokofiev, analis Solar appScreener

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


All Articles