Institut Teknologi Massachusetts. Kursus Kuliah # 6.858. "Keamanan sistem komputer." Nikolai Zeldovich, James Mickens. Tahun 2014
Keamanan Sistem Komputer adalah kursus tentang pengembangan dan implementasi sistem komputer yang aman. Ceramah mencakup model ancaman, serangan yang membahayakan keamanan, dan teknik keamanan berdasarkan pada karya ilmiah baru-baru ini. Topik meliputi keamanan sistem operasi (OS), fitur, manajemen aliran informasi, keamanan bahasa, protokol jaringan, keamanan perangkat keras, dan keamanan aplikasi web.
Kuliah 1: “Pendahuluan: model ancaman”
Bagian 1 /
Bagian 2 /
Bagian 3Kuliah 2: "Kontrol serangan hacker"
Bagian 1 /
Bagian 2 /
Bagian 3 James Mickens: Dari kuliah sebelumnya, kami mempelajari semua tentang serangan buffer overflow, dan hari ini kami akan terus mendiskusikan beberapa metode untuk meluncurkan serangan ini. Ide dasar di balik serangan buffer overflow adalah sebagai berikut.

Pertama-tama, saya perhatikan bahwa serangan ini mempengaruhi beberapa keadaan yang berbeda. Keadaan pertama yang mereka gunakan adalah bahwa perangkat lunak sistem sering ditulis dalam C.
Dengan perangkat lunak sistem, maksud saya database, kompiler, server jaringan, dan sejenisnya. Anda dapat mengingat hal itu sebagai shell perintah favorit Anda. Semua "perangkat lunak" ini biasanya ditulis dalam C. Mengapa dalam C? Karena, pertama, lebih cepat, dan kedua, C dianggap sebagai perakit tingkat tinggi yang paling sesuai dengan kebutuhan berbagai platform perangkat keras. Oleh karena itu, semua sistem kritis ditulis dalam bahasa pemrograman tingkat rendah ini. Masalah dengan perangkat lunak yang ditulis dalam C adalah bahwa ia benar-benar menggunakan alamat memori mentah dan tidak memiliki alat atau modul perangkat lunak untuk memeriksanya. Dalam beberapa kasus, ini dapat menyebabkan konsekuensi bencana.
Mengapa tidak ada pemeriksaan indeks array di C, yaitu, tidak ada pemeriksaan perbatasan? Salah satu alasannya adalah perangkat kerasnya tidak. Dan orang-orang yang menulis dalam C biasanya menginginkan kecepatan eksekusi program secepat mungkin. Alasan lain adalah bahwa dalam C, seperti yang akan kita bahas nanti, sebenarnya sangat sulit untuk mendefinisikan semantik dari apa itu pointer dan sejauh mana ia harus bertindak. Oleh karena itu, dalam beberapa kasus akan sangat sulit untuk mengotomatisasi proses perangkat lunak dalam C.
Mari kita bahas beberapa teknologi yang sebenarnya mencoba membuat beberapa jenis manajemen memori otomatis. Tetapi, seperti yang akan kita lihat, tidak satu pun dari metode ini yang sepenuhnya "anti peluru".
Selain itu, serangan buffer overflow menggunakan pengetahuan arsitektur x86, misalnya, ke arah mana stack tumbuh. Apa yang dimaksud dengan konvensi pemanggilan fungsi? Saat Anda mengakses fungsi C, seperti apa tumpukan itu? Dan ketika Anda memilih objek pada heap, seperti apa struktur utama yang dipilih ini?
Mari kita lihat contoh sederhana. Ini sangat mirip dengan apa yang Anda lihat di kuliah terakhir. Jadi, di sini kita memiliki permintaan baca standar, dan kemudian kita mendapatkan buffer, di sini, kemudian muncul
int kanonik, diikuti oleh perintah
mendapat perintah yang terkenal. Dan di bawah ini kami memiliki hal-hal lain yang diperlukan.

Jadi, seperti yang kita diskusikan di kuliah minggu lalu, ini bermasalah, kan? Karena ini
mendapat operasi tidak memeriksa batas buffer. Jika pengguna mengisi buffer dengan data dan kami menggunakan fungsi tidak aman ini di sini, maka kami sebenarnya bisa meluap buffer. Kami dapat menulis ulang seluruh isi tumpukan. Biarkan saya mengingatkan Anda bagaimana tampilannya.
Di bagian paling bawah adalah array "i". Buffer terletak di atasnya, ia memiliki alamat pertama di bagian bawah, dan yang terakhir di bagian atas. Dalam kasus apa pun, di atas buffer, kami memiliki nilai simpanan indikator gap - nilai simpanan EBP. Di atasnya adalah alamat pengirim untuk fungsi, dan bahkan lebih tinggi beberapa hal dari bingkai sebelumnya.
Dan jangan lupa bahwa di sini di bagian bawah, di sebelah kiri "i", kami memiliki penunjuk tumpukan ESP yang ada di sana, dan penunjuk break baru datang di bagian EBP yang disimpan. Alamat kembali termasuk ESP, dan sisa dari frame sebelumnya termasuk titik istirahat.

Biarkan saya mengingatkan Anda bahwa stack overflows adalah data terakumulasi ke atas, ke arah panah ini ke kanan. Ketika operasi mendapat dimulai, kita mulai menulis byte ke buffer, pada akhirnya, itu akan mulai menimpa semua yang terletak di hulu. Pada dasarnya, semuanya akan terlihat akrab bagi Anda.
Apa yang dilakukan penyerang untuk memanfaatkan ini? Pada dasarnya, ia memasuki urutan data yang panjang. Oleh karena itu, ide kuncinya adalah teknik seperti itu dapat digunakan untuk menyerang.
Dan jika alamat pengirim ditangkap oleh penyerang, ia dapat menentukan di mana fungsi akan melompat setelah meluap. Artinya, satu-satunya hal yang dapat dilakukan seorang peretas adalah mencegat alamat pengirim dan melompat ke mana pun ia mau. Kebanyakan penyerang menjalankan kode dengan hak istimewa untuk mengontrol proses intersepsi.
Jadi, jika proses ini adalah prioritas tinggi, misalnya, dijalankan sebagai root atau admin, tidak peduli apa yang kita sebut superuser dari sistem operasi favorit Anda, sekarang program ini, yang dikendalikan oleh penyerang, dapat melakukan apa pun yang diinginkan menggunakan hak istimewa dari prioritas ini. Dengan demikian, seorang hacker dapat membaca file atau mengirim spam jika ia merusak server email. Bahkan dapat mengalahkan firewall, karena ide firewall adalah bahwa ada mesin "baik" di belakangnya, dan yang "buruk" di luarnya. Biasanya, komputer di dalam firewall “saling percaya” satu sama lain, dan jika Anda berhasil meretas setidaknya satu komputer di dalam jaringan yang dilindungi oleh firewall, itu akan bagus. Karena sekarang Anda dapat dengan mudah melewati banyak pemeriksaan yang biasanya dilakukan komputer ini mengenai mesin "alien", karena mereka akan menganggap Anda orang yang tepercaya.
Ada hal yang harus Anda pikirkan dan saya anggap sebagai mahasiswa:
“Hebat, mereka menunjukkan kepada kita bagaimana meluap buffer, tetapi mengapa sistem operasi tidak bisa menghentikan ini? Bukankah dia bertindak sebagai seseorang seperti Penjaga Galaxy, yang melindungi hal-hal baik dari kejahatan terjadi di sekitar? "
Penting untuk dicatat bahwa OS tidak terus-menerus memonitor Anda. Dan perangkat keras mengamati, itu mengekstraksi instruksi dan mendekripsi mereka dan melakukan banyak hal seperti itu. Namun dalam perkiraan pertama, apa yang dilakukan OS? Pada dasarnya ia mengatur tabel halaman yang memungkinkan aplikasi untuk bekerja, dan jika Anda meminta sistem operasi, misalnya, untuk mengirim paket jaringan, atau Anda ingin membuat semacam permintaan IPC, atau hal serupa, Anda akan beralih ke OS untuk meminta bantuan. Tetapi sistem operasi tidak mengikuti setiap instruksi yang dijalankan aplikasi Anda. Dengan kata lain, ketika buffer ini penuh, OS tidak memantau sama sekali bagaimana memori tumpukan ini digunakan. Semua ruang alamat ini milik Anda, sebagai pemrakarsa proses, dan ini tidak berlaku untuk OS. Anda dapat melakukan apa pun yang Anda inginkan dengan ini, dan sistem operasi tidak dapat membantu Anda dengan masalah.
Nanti kita akan membahas beberapa hal yang dapat dilakukan OS mengenai perangkat keras untuk mempertahankan diri terhadap serangan jenis ini. Biarkan saya mengingatkan Anda lagi - pada kenyataannya, hanya perangkat keras yang memantau apa yang Anda lakukan dan bereaksi terhadapnya. Dengan demikian, Anda dapat memanfaatkan beberapa jenis perlindungan khusus, kami akan membahas ini lebih lanjut.
Ini adalah bagaimana buffer overflow terlihat. Bagaimana kita akan memperbaiki semua hal ini?
Salah satu cara untuk mencegah buffer overflows adalah dengan hanya menghindari kesalahan dalam kode C. Ini adalah pendekatan yang konstruktif, karena jika program Anda tidak memiliki kesalahan, maka penyerang tidak dapat menggunakannya. Namun, ini lebih mudah diucapkan daripada dilakukan. Ada beberapa hal yang sangat sederhana yang dapat dilakukan oleh programmer untuk memberikan "kebersihan" keamanan. Sebagai contoh, fitur seperti get, yang sekarang kita tahu dapat disebut "masuk," atau "menangkap sistem operasi," yang merupakan pelanggaran keamanan.
Jadi, ketika Anda mengkompilasi kode Anda menggunakan kompiler modern seperti GCC atau Visual Studio, mereka akan menunjukkan kelemahan dari fungsi-fungsi tersebut. Mereka akan berkata: "Hei, Anda menggunakan hal yang berbahaya, lebih baik pertimbangkan untuk menggunakan fungsi app atau fungsi lain yang benar-benar dapat melacak kepatuhan perbatasan." Ini adalah salah satu hal sederhana yang dapat dilakukan programmer.
Tetapi perhatikan bahwa banyak aplikasi sebenarnya memanipulasi buffer tanpa menggunakan semua fungsi ini. Ini sangat umum di server jaringan yang menetapkan prosedur parsing mereka sendiri dan kemudian memastikan bahwa data diambil dari buffer seperti yang mereka inginkan. Dengan demikian, cukup membatasi diri pada pemilihan fungsi perintah yang benar, tidak akan mungkin untuk sepenuhnya menyelesaikan masalah.
Keadaan lain yang membuat pendekatan ini untuk masalah lebih sulit adalah bahwa tidak selalu jelas bahwa masalah disebabkan oleh kesalahan dalam program yang ditulis dalam C. Jika Anda pernah bekerja pada program skala besar yang ditulis dalam C, Anda tahu , seperti yang terjadi dengan pengidentifikasi fungsi yang memiliki 18 bintang di atas penunjuk void *. Saya pikir hanya Zeus yang tahu apa artinya ini, bukan? Dengan bahasa seperti C, bahkan seorang programmer bisa merasa sangat sulit untuk memahami apakah kesalahan telah terjadi atau tidak.
Secara umum, salah satu topik utama dari kuliah kami adalah bahwa bahasa C adalah produk setan. Dan kami menggunakannya hanya karena kami selalu ingin menjadi lebih cepat daripada orang lain, bukan? Tetapi karena perangkat keras menjadi lebih cepat dan lebih cepat, kami menggunakan bahasa yang lebih maju untuk menulis kode sistem yang banyak. Namun, tidak selalu masuk akal untuk menulis kode C Anda, bahkan jika Anda pikir itu akan lebih cepat. Kami akan membahas masalah ini nanti.

Jadi, pendekatan pertama untuk memecahkan masalah adalah untuk menghindari kesalahan dalam kode program C, dan yang kedua adalah membuat alat yang membantu programmer menemukan kesalahan seperti itu. Contoh alat tersebut adalah analisis kode statis. Nanti kita akan membicarakannya secara rinci, dan sekarang saya akan mengatakan bahwa analisis statis adalah cara menganalisis kode sumber program Anda bahkan sebelum dimulai, dan membantu mendeteksi potensi masalah.
Bayangkan Anda memiliki fungsi seperti itu, sebut saja
void foo (int, * p) , ini berisi data integer dan pointer. Katakanlah itu menyatakan nilai offset integer
int off . Fungsi ini mendeklarasikan pointer lain dan menambahkan offset ke dalamnya:
int * z = p + off . Bahkan sekarang, ketika menulis suatu fungsi, analisis kode statis dapat memberi tahu kita bahwa variabel offset ini tidak diinisialisasi.

Dengan demikian, dengan menganalisis program, dimungkinkan untuk menjawab pertanyaan apakah fungsi kita akan berfungsi dengan baik. Dan dalam contoh ini, sangat sederhana untuk melihat jawaban "tidak, tidak akan" karena tidak ada inisialisasi offset. Analisis statis adalah perangkat lunak, dan ketika Anda menggunakan kompiler populer untuk membangun kode Anda, itu akan memberi tahu Anda: "Hai, sobat, hal ini tidak diinisialisasi. Apakah Anda yakin ingin melakukan hal itu? ” Ini adalah salah satu contoh paling sederhana menggunakan analisis statis.
Contoh lain mempertimbangkan kasus ketika kita memiliki cabang fungsi, yaitu pelaksanaannya dalam kondisi tertentu.

Jadi, jika offset lebih besar dari 8,
jika (off> 8) , maka ini mengarah pada panggilan ke beberapa function
bar (off) . Jadi, kondisi ini memberi tahu kita apa nilai offsetnya. Bahkan mengabaikan fakta bahwa offset tidak diinisialisasi, saat menganalisis cabang fungsi ini, kami masih menemukan bahwa itu bisa lebih besar dari 8. Oleh karena itu, ketika kami mulai melakukan analisis statis bar, kami menemukan bahwa offset hanya dapat mengambil nilai tertentu. Saya perhatikan sekali lagi bahwa ini adalah pengantar yang sangat dangkal untuk analisis statis, nanti kita akan mempertimbangkan alat ini secara lebih rinci. Tetapi contoh ini menunjukkan bagaimana Anda dapat mendeteksi beberapa jenis kesalahan bahkan tanpa mengeksekusi kode.
Jadi, satu hal lagi yang mungkin Anda pikirkan adalah melakukan hal yang sama dengan analisis statis. Ini adalah fuzzing perangkat lunak. Idenya adalah bahwa Anda mengambil semua fungsi dalam kode program Anda dan kemudian memasukkan nilai acak ke dalamnya. Dengan demikian, semua opsi untuk nilai dan format kode Anda tumpang tindih. Artinya, Fuzzing adalah alat untuk mencari kerentanan secara otomatis dengan mengirimkan data yang tidak valid atau data dalam format yang salah ke input program. Misalnya, Anda memasukkan nilai 2, 4, 8, dan 15 dalam pengujian unit dan Anda mendapatkan pesan bahwa angka 15 mungkin salah, karena semua angka genap, tetapi ganjil.
Bahkan, Anda perlu melihat berapa banyak cabang program secara keseluruhan yang memengaruhi kode pengujian Anda, karena ini biasanya merupakan tempat di mana "bug" disembunyikan. Pemrogram tidak berpikir tentang "celah dan celah" seperti itu dan sebagai hasilnya mereka lulus beberapa tes unit, Anda dapat mengatakan sebagian besar dari tes ini. Namun, mereka tidak memeriksa semua "celah dan celah" dari program ini, dan di sinilah analisis statis dapat membantu. Sekali lagi, menggunakan hal-hal seperti konsep pembatasan. Misalnya, di bagian program kami, ini adalah kondisi untuk bercabang fungsi yang mendefinisikan offset lebih dari delapan. Dengan demikian, kita dapat mengetahui bahwa perpindahan ini statis. Dan jika kita menggunakan generasi input data Fuzzing otomatis berdasarkan pembatasan ini, maka kita dapat memastikan bahwa salah satu nilai input untuk offset akan kurang dari 8, satu akan menjadi 8 dan satu akan lebih dari 8. Apakah itu jelas?

Jadi ini adalah ide utama di balik konsep menciptakan alat untuk membantu programmer menemukan kesalahan. Bahkan analisis kode parsial dapat sangat berguna ketika bekerja dengan bahasa C. Banyak alat yang akan kita lihat berfungsi untuk mencegah buffer overflows atau memeriksa inisialisasi variabel yang tidak dapat mendeteksi semua masalah kode program. Tetapi mereka dapat bermanfaat praktis dalam meningkatkan keamanan program-program ini. Kerugian dari alat-alat ini adalah bahwa mereka tidak lengkap. Kemajuan prospektif bukanlah kemajuan yang lengkap. Oleh karena itu, Anda perlu secara aktif mengeksplorasi masalah perlindungan terhadap eksploitasi baik dalam program yang ditulis dalam C dan dalam program lain. Kami memeriksa 2 pendekatan untuk memecahkan masalah perlindungan buffer overflow, tetapi ada beberapa pendekatan lain.
Jadi, pendekatan ketiga adalah penggunaan bahasa memori-aman, atau bahasa yang menjamin keamanan memori. Bahasa-bahasa ini termasuk Python, Java, c #. Saya tidak ingin menempatkan Perl setara dengan mereka, karena digunakan oleh "orang jahat." Dengan cara ini Anda dapat menggunakan bahasa memori yang aman, dan sepertinya ini adalah hal yang paling jelas yang dapat Anda lakukan. Saya baru saja menjelaskan kepada Anda bahwa pada dasarnya C adalah encoder perakitan tingkat tinggi, tetapi memberikan pointer mentah dan hal-hal lain yang tidak diinginkan, jadi mengapa tidak hanya menggunakan salah satu dari bahasa pemrograman tingkat tinggi ini?
Ada beberapa alasan untuk ini. Pertama, dalam bahasa ini ada banyak elemen kode yang diwarisi dari C. Semuanya baik-baik saja jika Anda memulai proyek baru dan menggunakan salah satu bahasa tingkat tinggi yang menjamin keamanan memori untuknya. Tetapi bagaimana jika Anda diberi file biner besar atau distribusi sumber besar yang ditulis dalam C dan dipelihara selama 10-15 tahun, ini adalah proyek generasi, maksud saya bahkan anak-anak kita akan terus mengerjakannya ? Dalam hal ini, Anda tidak akan bisa mengatakan: "Saya hanya menulis ulang semuanya dalam C # dan mengubah dunia!".
Dan masalahnya bukan hanya dalam bahasa C, ada sistem yang Anda harus takut bahkan lebih, karena mereka menggunakan kode Fortran dan COBOL, hal-hal dari Perang Saudara. Mengapa ini terjadi? Karena, sebagai insinyur, kami ingin berpikir bahwa kami dapat membangun semuanya sendiri, dan itu akan luar biasa, akan seperti yang saya inginkan, dan saya akan memanggil variabel saya seperti yang saya inginkan.
Tetapi di dunia nyata ini tidak terjadi. Anda muncul di tempat kerja, dan Anda memiliki sistem ini yang sudah ada, dan Anda melihat dasar kode dan berpikir mengapa tidak melakukan apa yang dibutuhkan? Dan kemudian mereka berkata kepada Anda, "Dengar, kami akan melakukan semua yang Anda inginkan, tetapi hanya dalam versi kedua dari program ini, dan sekarang Anda harus membuat apa yang harus kami kerjakan, karena kalau tidak pelanggan akan mengambil uang mereka kembali."
Jadi, bagaimana kita menangani masalah besar penggunaan paksa kode warisan? Seperti yang Anda ketahui, salah satu kelebihan sistem dengan definisi batas yang salah adalah bahwa mereka bekerja dengan sempurna dengan kode yang ketinggalan zaman ini. Ini adalah salah satu alasan mengapa Anda tidak bisa menghilangkan masalah buffer overflow dengan hanya beralih ke bahasa yang menyediakan penggunaan memori yang aman.

Bagaimana jika kita membutuhkan akses tingkat rendah ke perangkat keras? Misalnya, memperbarui driver dan hal-hal lain.
Jadi, masalah lain muncul jika Anda memerlukan akses tingkat rendah ke peralatan, yang terjadi saat menulis driver untuk beberapa perangkat. Dalam hal ini, Anda benar-benar membutuhkan kelebihan yang diberikan C, misalnya, kemampuan untuk melihat register dan elemen fungsi serupa.
Lebih lanjut, kebutuhan untuk menggunakan C muncul ketika Anda khawatir tentang kinerja sistem. , , , . , , . , , memory-safe . , JIT. , Java, Java Script. , , «». , . , «» x86.
, , -. , , JVM, - Java. , - . , - JVM , . , , . . , , .
, , 86. JIT- , . JIT- , .
, JavaScript , , «» 32- , . JIT-, «» . , , JIT-, , , .

«» , asm.js – JavaScript, , , . , , , JavaScript , . JavaScript, JavaScript, C ++.
, -, IO. . , , , , . «» , .
. , - . , C C++, , . Python, , , . . .
, , , .
, . , . , . , «» , . , C C++, .
, ? , , ? ?
. , – . , - , . , . , -, , . IP , , . , - . , , . , , «» .
, , , , , , . , - , . , , , . , , , , , , .

, . stack canaries, « », , . « » , , , . , , .
Mari kita menggambar diagram tumpukan kita. Kita perlu memastikan bahwa penyerang pertama "masuk ke kenari" sebelum dia sampai ke alamat pengirim. Dan jika kita dapat mendeteksi ini sebelum kembali dari fungsi, maka kita dapat mendeteksi "kejahatan."28:30 menitLanjutan:Kursus MIT "Keamanan Sistem Komputer". Kuliah 2: "Kontrol serangan hacker", bagian 2Versi lengkap dari kursus ini tersedia di sini .Terima kasih telah tinggal bersama kami. Apakah Anda suka artikel kami? Ingin melihat materi yang lebih menarik? Dukung kami dengan melakukan pemesanan atau merekomendasikannya kepada teman-teman Anda,
diskon 30% untuk pengguna Habr pada analog unik dari server entry-level yang kami temukan untuk Anda: Seluruh kebenaran tentang VPS (KVM) E5-2650 v4 (6 Cores) 10GB DDR4 240GB SSD 1Gbps dari $ 20 atau bagaimana membagi server? (opsi tersedia dengan RAID1 dan RAID10, hingga 24 core dan hingga 40GB DDR4).
Dell R730xd 2 kali lebih murah? Hanya kami yang memiliki
2 x Intel Dodeca-Core Xeon E5-2650v4 128GB DDR4 6x480GB SSD 1Gbps 100 TV dari $ 249 di Belanda dan Amerika Serikat! Baca tentang
Cara Membangun Infrastruktur Bldg. kelas menggunakan server Dell R730xd E5-2650 v4 seharga 9.000 euro untuk satu sen?