
Penulis Artikel: 0x64rem
Entri
Setahun setengah yang lalu, saya memiliki ide untuk mewujudkan phaser saya sebagai bagian dari tesis di universitas. Saya mulai mempelajari materi tentang grafik aliran kontrol, grafik aliran data, eksekusi simbolis, dll. Selanjutnya datang pencarian alat, contoh perpustakaan yang berbeda (Angr, Triton, Pin, Z3). Tidak ada yang konkrit terjadi pada akhirnya, sampai musim panas ini saya pergi ke program Digital Security 's Summer of Hack 2019, di mana saya ditawari perpanjangan Clang Static Analyzer sebagai tema untuk proyek tersebut. Tampaknya bagi saya bahwa topik ini akan membantu saya meletakkan pengetahuan teoretis saya di rak, mulai menerapkan sesuatu yang substansial dan mendapatkan rekomendasi dari mentor yang berpengalaman. Selanjutnya, saya akan memberi tahu Anda bagaimana proses penulisan plug-in berjalan dan menjelaskan jalan pikiran saya selama bulan magang.
Bunyi analisa statis
Untuk pengembangan, Clang menyediakan tiga opsi antarmuka untuk interaksi:
- LibClang adalah antarmuka C tingkat tinggi yang memungkinkan Anda berinteraksi dengan AST, tetapi tidak sepenuhnya. Pilihan yang baik jika Anda memerlukan interaksi dengan bahasa lain (misalnya, implementasi binding ) atau antarmuka yang stabil.
- Dentang Plugin - perpustakaan dinamis dipanggil pada waktu kompilasi. Memungkinkan Anda memanipulasi AST sepenuhnya.
- LibTooling - perpustakaan untuk membuat alat terpisah berdasarkan Dentang. Juga memberikan akses penuh untuk berinteraksi dengan AST. Kode yang dihasilkan dapat dijalankan di luar lingkungan build dari proyek yang diperiksa.
Karena kami akan memperluas kemampuan Clang Static Analyzer, kami memilih implementasi plugin. Anda dapat menulis kode untuk plugin dalam C ++ atau Python.
Untuk yang terakhir, ada pengikat yang memungkinkan Anda untuk mem-parsing kode sumber, beralih di atas simpul dari pohon sintaksis abstrak yang dihasilkan, juga memiliki akses ke properti dari node dan dapat memetakan node ke garis kode sumber. Set seperti itu cocok untuk pemeriksa sederhana. Lihat repositori llvm untuk lebih jelasnya.
Tugas saya memerlukan analisis rinci kode, sehingga C ++ dipilih untuk pengembangan. Berikutnya adalah pengantar alat.
Clang Staic Analyzer (selanjutnya disebut CSA) adalah alat untuk analisis statis kode C / C ++ / Objective-C, berdasarkan pada eksekusi simbolik. Analyzer dapat dipanggil melalui frontend Dentang dengan menambahkan flag -cc1 dan -analyze ke perintah build, atau melalui binar scan-build yang terpisah. Selain analisis itu sendiri, CSA memungkinkan untuk menghasilkan laporan html visual.

CSA memiliki pustaka yang sangat baik untuk parsing kode sumber menggunakan AST (Abstract Syntax Tree), CFG (Control Flow Graph). Dari struktur Anda dapat melihat lebih lanjut deklarasi variabel, jenisnya, penggunaan operator biner dan unary, Anda bisa mendapatkan ekspresi simbolik, dll. Plugin saya akan menggunakan fungsionalitas kelas AST, pilihan ini akan dibenarkan lebih lanjut. Berikut ini adalah daftar kelas yang digunakan dalam implementasi plugin, daftar ini akan membantu untuk mendapatkan pemahaman utama tentang fitur CSA:
Stmt - ini termasuk operasi biner.
Deklarasi - deklarasi variabel.
Expr - menyimpan bagian kiri, kanan ekspresi, tipenya.
ASTContext - informasi tentang pohon, simpul saat ini.
Manajer sumber - informasi tentang kode aktual yang sesuai dengan bagian pohon.
RecursiveASTVisitor, ASTMatcher - kelas untuk melintasi pohon.
Saya ulangi bahwa CSA memberi pengembang kesempatan untuk memeriksa secara terperinci struktur kode, dan kelas yang tercantum di atas hanya sebagian kecil dari yang tersedia. Saya merekomendasikan untuk membaca dokumentasi versi Dentang Anda jika Anda tidak tahu cara mengekstrak data apa pun; kemungkinan besar, sesuatu yang cocok telah ditulis.
Pencarian Overflow Integer
Untuk mulai mengimplementasikan plugin, Anda harus memilih tugas yang akan diselesaikan. Untuk kasus ini, situs web llvm menyediakan daftar pemeriksa potensial , Anda juga dapat memodifikasi pemeriksa alpha atau stabil . Selama peninjauan kode checker yang tersedia, menjadi jelas bahwa untuk pengembangan libclang yang lebih sukses, lebih baik menulis checker Anda dari awal, jadi pilihan dibuat dari daftar ide yang belum direalisasi . Akibatnya, opsi dipilih untuk membuat checker untuk deteksi overflow integer. Dentang sudah memiliki fungsi untuk mencegah kerentanan ini (flag -ftrapv, -fwrapv dan sejenisnya ditunjukkan untuk penggunaannya), itu dibangun ke dalam kompiler, dan knalpot seperti itu dituangkan ke dalam peringatan, dan tidak sering terlihat di sana. Masih ada UBSan , tetapi ini adalah pembersih, tidak semua orang menggunakannya, dan metode ini adalah tentang mengidentifikasi masalah saat runtime, dan plug-in CSA bekerja pada waktu kompilasi, menganalisis sumber.
Berikutnya adalah kumpulan materi tentang kerentanan yang dipilih. Integer overflow digunakan untuk sesuatu yang sederhana dan tidak serius. Faktanya, kerentanan itu menghibur dan dapat memiliki konsekuensi yang mengesankan.
Integer overflows adalah jenis kerentanan yang dapat menghasilkan data tipe integer dalam kode yang mengambil nilai yang tidak diharapkan. Overflow - jika variabel menjadi lebih besar dari yang dimaksudkan, Underflow - kurang dari tipe aslinya. Kesalahan tersebut dapat muncul baik karena programmer, dan karena kompiler.
Dalam C ++, selama operasi perbandingan aritmatika, nilai integer dilemparkan ke tipe yang sama, lebih sering ke yang lebih besar dalam hal kedalaman bit. Dan hantu seperti itu terjadi di mana-mana dan terus-menerus, mereka bisa eksplisit atau implisit. Ada beberapa aturan dimana hantu terjadi [1]:
- Mengonversi dari yang sudah ditandatangani ke jenis dengan yang sudah ditandatangani, tetapi lebih besar: cukup tambahkan pesanan tinggi.
- Mengonversi bilangan bulat yang ditandatangani menjadi bilangan bulat yang tidak ditandatangani dengan kapasitas yang sama: negatif dikonversi menjadi positif dan mengambil makna baru. Contoh kesalahan serupa di DirectFB adalah CVE-2014-2977 .
- Mengonversi bilangan bulat yang ditandatangani menjadi bilangan bulat tak bertanda dari kapasitas bit yang lebih besar: pertama, kapasitas bit akan berkembang, kemudian jika jumlahnya negatif, maka akan salah mengubah nilainya. Misalnya: 0xff (-1) menjadi 0xffffffff.
- Integer unsigned dengan tanda kapasitas bit yang sama: angka dapat mengubah nilainya, tergantung pada nilai bit tinggi.
- Integer yang tidak ditandatangani dengan integer dengan tanda kapasitas yang lebih besar: pertama, kapasitas nomor yang tidak ditandatangani meningkat, kemudian konversi ke yang ditandatangani.
- Turun konversi: bit hanya terpotong. Ini dapat membuat nilai yang tidak ditandatangani negatif dan seterusnya. Contoh kerentanan seperti itu di PHP .
Yaitu pemicu untuk kerentanan dapat berupa input pengguna yang tidak aman, aritmatika salah, konversi tipe yang salah yang disebabkan oleh programmer atau kompiler selama optimasi. Opsi bom waktu juga dimungkinkan, ketika sepotong kode tidak berbahaya dengan satu versi kompiler, tetapi dengan dirilisnya algoritma optimasi baru "meledak" dan menyebabkan perilaku yang tidak terduga. Dalam sejarah, sudah ada kasus seperti itu dengan kelas SafeInt (sangat ironis) [5, 6.5.2].
Integer overflows membuka vektor lebar: adalah mungkin untuk memaksa eksekusi untuk mengambil jalur yang berbeda (jika overflow mempengaruhi pernyataan bersyarat), menyebabkan buffer overflow. Untuk kejelasan, Anda dapat membiasakan diri dengan CVE tertentu, lihat penyebabnya, konsekuensinya. Secara alami, lebih baik mencari integer overflow pada produk-produk open source, sehingga Anda tidak hanya membaca deskripsi, tetapi juga melihat kodenya.
- CVE-2019-3560 - Integer overflow di Fizz (sebuah proyek yang mengimplementasikan TLS untuk Facebook) dapat mengeksploitasi kerentanan DoS menggunakan paket jaringan yang sempit.
- CVE-2018-14618 - Buffer overflow di Curl disebabkan oleh integer overflow karena panjang kata sandi.
- CVE-2018-6092 - Pada sistem 32-bit, kerentanan di WebAssembly untuk Chrome memungkinkan RCE diimplementasikan melalui halaman HTML khusus.
Agar tidak menemukan kembali roda, kode untuk mendeteksi bilangan bulat bilangan bulat di penganalisa statis CppCheck dipertimbangkan. Pendekatannya adalah sebagai berikut:
- Menentukan apakah ekspresi adalah operator biner.
- Jika ya, maka periksa untuk melihat apakah kedua argumen bertipe integer.
- Tentukan ukuran jenis.
- Periksa dengan perhitungan apakah nilai dapat melampaui batas maksimum atau minimum.
Tetapi pada tahap ini tidak memberikan kejelasan. Ternyata banyak cerita yang berbeda, dan dari sistematisasi informasi ini menjadi lebih sulit. Segala sesuatu di tempatnya menempatkan daftar CWE . Secara total, ada 9 jenis integer overflow yang dialokasikan di situs:
- 190 - integer oveflow
- 191 - integer underflow
- 192 - galat coertion integer
- 193 - off-by-one
- 194 - Ekstensi Tanda Tidak Terduga
- 195 - Ditandatangani ke Kesalahan Konversi yang Tidak Ditandatangani
- 196 - Tidak ditandatangani ke Kesalahan Konversi yang Ditandatangani
- 197 - Kesalahan Numeric Truncation
- 198 - Penggunaan Pemesanan Byte Salah
Kami mempertimbangkan alasan untuk setiap opsi dan memahami bahwa luapan terjadi dengan gips eksplisit / implisit yang salah. Dan karena setiap gips ditampilkan dalam struktur pohon sintaksis abstrak, kami akan menggunakan AST untuk analisis. Pada gambar di bawah (Gbr. 3), dapat dilihat bahwa setiap operasi yang menyebabkan gips di pohon adalah node yang terpisah, dan, berkeliaran di sekitar pohon, kita dapat memeriksa semua konversi jenis berdasarkan pada tabel dengan transformasi yang dapat menyebabkan kesalahan.

Lebih khusus, algoritma terdengar seperti ini: kita berkeliling Cast dan melihat IntegralCast (konversi integer). Jika Anda menemukan simpul yang cocok, lihat keturunan mencari operasi biner atau Decl (deklarasi variabel). Dalam kasus pertama, Anda perlu memeriksa tanda dan kedalaman bit yang digunakan operasi biner. Dalam kasus kedua, bandingkan hanya jenis deklarasi.
Implementasi pemeriksa
Mari kita mulai implementasi. Kami membutuhkan kerangka untuk pemeriksa, yang bisa berupa perpustakaan yang berdiri sendiri, atau dapat dirakit sebagai bagian dari Dentang. Dalam kode, perbedaannya akan kecil. Jika Anda sudah berencana untuk menulis plugin Anda sendiri, saya sarankan Anda segera membaca pdf kecil: "Dentang Analisis Statis: Panduan Pengembang Pemeriksa" , hal-hal dasar dijelaskan dengan baik di sana, meskipun sesuatu mungkin tidak relevan lagi, perpustakaan diperbarui secara teratur, tetapi Anda raih segera.
Jika Anda ingin menambahkan checker ke rakitan dentang Anda, maka Anda perlu:
Tulis pemeriksa itu sendiri dengan kira-kira konten berikut:
namespace { class SuperChecker : public Checker<check::PreStmt<BinaryOperator>> {
Kemudian, dalam kode sumber Dentang, Anda perlu mengubah file CMakeLists.txt
dan Checkers.td
. Tinggal di sekitar sini ${llvm-source-path}/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
dan di sini ${llvm-source-path}/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
.
Di bagian pertama, Anda hanya perlu menambahkan nama file dengan kode, di bagian kedua Anda perlu menambahkan deskripsi struktural:
#Checkers.td def SuperChecker : Checker<"SuperChecker">, HelpText<"test checker">, Documentation<HasDocumentation>;
Jika tidak jelas, maka dalam file Checkers.td
ada cukup banyak contoh bagaimana dan apa yang harus dilakukan.
Kemungkinan besar Anda tidak akan ingin membangun kembali Dentang, dan Anda akan menggunakan opsi dengan perakitan perpustakaan (jadi / dll). Maka dalam kode pemeriksa haruslah sesuatu seperti ini:
namespace { class SuperChecker : public Checker<check::PreStmt<BinaryOperator>> {
Selanjutnya, kumpulkan kode Anda, Anda dapat menulis skrip Anda sendiri untuk perakitan, tetapi jika Anda memiliki masalah dengan ini (seperti penulis :)), maka Anda dapat menggunakan Makefile dalam kode sumber dentang dan membuat perintah clangStaticAnalyzerCheckers dengan cara yang aneh.
Selanjutnya, hubungi pemeriksa:
untuk checker bawaan
clang++ -cc1 -analyze -analyzer-checker=core.DivideZero test.cpp
untuk eksternal
clang++ -cc1 -load ${PATH_TO_CHECKER}/SuperChecker.so -analyze -analyzer-checker=test.Me -analyzer-config test.Me:UsrInp1="foo" test.Me:Inp1="bar" -analyzer-config test.Me:Inp2=123 test.cpp
Pada tahap ini, kita sudah memiliki semacam hasil (Gbr. 4), tetapi kode tertulis hanya dapat mendeteksi potensi luapan. Dan itu berarti sejumlah besar positif palsu.

Untuk memperbaikinya, kita dapat:
- Berkeliling grafik bolak-balik dan memeriksa nilai-nilai spesifik dari variabel untuk kasus-kasus di mana kita memiliki potensi meluap.
- Selama AST traversal, segera simpan nilai-nilai spesifik untuk variabel dan periksa bila perlu.
- Gunakan analisis noda.
Untuk memperkuat argumen lebih lanjut, perlu disebutkan bahwa ketika menganalisis Dentang, semua file yang ditentukan dalam arahan #include
juga menguraikan, sebagai akibatnya, ukuran AST yang dihasilkan meningkat. Akibatnya, dari opsi yang diusulkan, hanya satu yang rasional mengenai tugas tertentu:
- Pertama, dibutuhkan banyak waktu untuk menyelesaikannya. Berjalan di pohon, mencari dan menghitung semua yang Anda butuhkan akan memakan waktu lama, akan sulit untuk menganalisis proyek besar dengan kode seperti itu. Untuk berjalan pohon dalam kode, kita akan menggunakan
clang::RecursiveASTVisitor
kelas , yang melakukan pencarian kedalaman rekursif. Perkiraan waktu dari pendekatan ini adalah
, di mana V adalah himpunan simpul, dan E adalah himpunan tepi grafik. - Yang kedua - Anda tentu dapat menyimpan, tetapi kami tidak tahu apa yang akan kami butuhkan dan apa yang tidak. Selain itu, struktur pohon itu sendiri, yang kami gunakan dalam analisis, membutuhkan banyak memori, jadi menghabiskan sumber daya semacam itu untuk hal lain adalah ide yang buruk.
- Ketiga adalah ide yang bagus, untuk metode ini Anda dapat menemukan cukup banyak penelitian dan contoh. Namun dalam CSA tidak ada noda yang siap. Ada pemeriksa , yang kemudian ditambahkan ke daftar pemeriksa alfa (alpha.security.taint.TaintPropagation) dalam sumber, dijelaskan dalam file
GenericTaintChecker.cpp
. Pemeriksa itu baik, tetapi hanya cocok untuk fungsi I / O tidak aman yang diketahui dari C, itu hanya "menandai" variabel yang merupakan argumen atau hasil dari fungsi berbahaya. Selain opsi yang dijelaskan, ada baiknya mempertimbangkan variabel global, bidang kelas, dll, untuk mengembalikan model "distribusi" dengan benar.
Waktu yang tersisa untuk magang dihabiskan dengan membaca GenericTaintChecker.cpp
dan mencoba membuatnya kembali sesuai dengan kebutuhan Anda. Itu tidak berhasil dengan sukses pada akhir istilah, tetapi tetap tugas untuk perbaikan sudah di luar lingkup pelatihan di DSec. Juga selama pengembangan menjadi jelas bahwa mengidentifikasi fungsi berbahaya adalah tugas yang terpisah, tidak selalu tempat berbahaya dalam proyek berasal dari beberapa fungsi standar, oleh karena itu bendera ditambahkan ke pemeriksa untuk menunjukkan daftar fungsi yang akan dianggap "diracuni" / "ditandai" selama analisis noda.
Selain itu, pemeriksaan ditambahkan untuk menentukan apakah variabel adalah bidang bit. Dengan alat CSA standar, ukuran ditentukan berdasarkan tipe, dan jika kita bekerja dengan bidang bit, maka ukurannya akan memiliki nilai tipe bit dari seluruh bidang, dan bukan jumlah bit yang ditentukan dalam deklarasi variabel.
Apa hasilnya?
Saat ini, checker sederhana telah diterapkan yang hanya dapat memperingatkan potensi kelebihan integer. Kelas yang dimodifikasi untuk analisis noda, yang masih memiliki banyak pekerjaan yang harus dilakukan. Setelah itu, Anda perlu menggunakan SMT untuk menentukan luapan. Untuk ini, pemecah SMT Z3 cocok, yang ditambahkan ke rakitan Dentang dalam versi 5.0.0 (dilihat dari catatan rilis ). Untuk menggunakan solver, Clang perlu dibangun dengan opsi CLANG_ANALYZER_BUILD_Z3=ON
, dan ketika plug-in CSA dipanggil langsung, -Xanalyzer -analyzer-constraints=z3
dikirimkan.
Repositori Hasil GitHub
Referensi:
Howard M., Leblanc D., Viega J. "24 Dosa Keamanan Komputer"
Cara Menulis Checker dalam 24 Jam
Dentang Analisis Statis: Panduan Pengembang Pemeriksa
Manual pengembangan pemeriksa CSA
Dietz W. et al. Memahami integer overflow dalam C / C ++