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 3Kuliah 3: “Buffer Overflows: Exploits and Protection”
Bagian 1 /
Bagian 2 /
Bagian 3 Selamat datang di ceramah tentang eksploitasi untuk buffer overflows. Hari ini kita akan mengakhiri diskusi tentang
batas -
batas Baggy dan kemudian beralih ke metode perlindungan buffer overflow lainnya.

Selanjutnya, kita akan berbicara tentang materi cetak kuliah hari ini, yang dikhususkan untuk
Pemrograman Berorientasi Buta (BROP) - pemrograman berorientasi-balik buta. Ini adalah teknik eksploitasi yang dapat dilakukan bahkan jika penyerang tidak memiliki biner target. Eksploitasi ini bertujuan menghancurkan "kenari" di tumpukan sistem 64-bit. Jadi jika Anda seperti saya ketika saya pertama kali membaca materi ini, Anda seharusnya merasa seperti menonton film Christopher Nolan. Itu hanya ledakan otak!
Kami akan mempertimbangkan bagaimana gadget ini berfungsi dengan baik. Oleh karena itu, saya berharap bahwa pada akhir kuliah Anda akan dapat memahami semua teknologi tinggi yang dijelaskan dalam materi kuliah ini. Tapi pertama-tama, seperti yang saya katakan, kita akan mengakhiri diskusi tentang
batas-batas Baggy . Pertimbangkan contoh yang sangat sederhana.
Misalkan kita akan menetapkan pointer
p dan mengalokasikan ukuran 44 byte untuknya. Asumsikan juga bahwa ukuran slot adalah 16 byte.
Apa yang terjadi ketika kami menetapkan fungsi
malloc ? Anda sudah tahu bahwa dalam hal ini sistem
batas Baggy akan mencoba melengkapi distribusi ini dengan logaritma
2n . Jadi untuk pointer kita 44 byte, 64 byte memori akan dialokasikan. Tetapi ukuran slot adalah 16 byte, jadi kami akan membuat 64/16 = 4 tabel batas masing-masing 16 byte. Setiap entri ini akan ditempatkan di log distribusi ukuran.
Selanjutnya, tetapkan pointer pointer lain
* q = p + 60 . Kami melihat bahwa nilai ini di luar batas karena ukuran
p adalah 44 byte, dan di sini 60 byte. Tapi
batas Baggy bekerja sehingga dalam hal ini tidak ada hal
buruk yang akan terjadi, meskipun programmer seharusnya tidak melakukannya.
Sekarang mari kita asumsikan bahwa hal berikutnya yang kita lakukan adalah menetapkan pointer lain, yang akan sama dengan
char * r = q + 16 . Sekarang ini sebenarnya akan menyebabkan kesalahan, karena ukuran offset akan menjadi 60 + 16 = 76, yang merupakan 12 byte lebih besar dari 4 slot (4x16 = 64 byte) yang
dialokasikan sistem
batas Baggy . Dan kelebihan ini benar-benar lebih dari setengah slot.

Jika Anda ingat, dalam hal ini sistem
Batas Baggy akan segera merespons kesalahan sinkronisasi kritis, yang akan menyebabkan program macet dan benar-benar menghentikannya.
Jadi mari kita bayangkan bahwa kita hanya memiliki dua baris:
char * p = malloc (44)
char * q = p + 60Dan tidak ada baris ketiga dengan kode tersebut. Sebaliknya, kami akan melakukan ini:
char * s = q + 8Dalam hal ini, pointer akan memiliki nilai 60 + 8 = 68 bit, yang akan menjadi 4 byte lebih dari batas yang ditetapkan oleh
batas Baggy . Bahkan, ini tidak akan menyebabkan kesalahan kritis, meskipun nilainya melampaui batas. Apa yang kami lakukan di sini adalah mengatur bit orde tinggi untuk pointer. Jadi, jika seseorang kemudian mencoba mereduksinya, ini akan menyebabkan kesalahan kritis pada saat ini.

Dan hal terakhir yang akan kita lakukan adalah menetapkan pointer lain:
char * t = s - 32Faktanya, kami melakukan ini - kami mengembalikan pointer ke perbatasan. Jadi jika awalnya
s melampaui, maka sekarang kita telah mengembalikannya ke volume yang dialokasikan sebelumnya yang kita buat untuk pointer. Oleh karena itu, sekarang
t tidak akan memiliki bit orde tinggi dalam komposisinya, dan dapat dengan mudah dideferensikan.
Audiens: bagaimana program mengetahui bahwa
r memiliki kelebihan lebih dari setengah tumpukan?
Profesor Mickens: perhatikan bahwa ketika kami membuat
r , kami mendapat kode alat yang akan bekerja di semua operasi ini dengan pointer. Jadi kita bisa tahu di mana
q akan ditempatkan, dan kita tahu bahwa itu berada dalam
batas-batas Baggy . Karena itu, ketika kami melakukan operasi ini
q + 16 , alat
Baggy bounds tahu dari mana nilai awal ini berasal. Dan kemudian, jika offset dari ukuran asli ini
terjadi ,
batas Baggy akan dengan mudah menentukan bahwa offset lebih besar dari ½ dari ukuran slot.
Pada prinsipnya, ketika Anda melakukan operasi dengan pointer, Anda harus melihat apakah mereka melebihi ukuran yang dialokasikan atau tidak. Pada titik tertentu, Anda memiliki pointer yang terletak di dalam batas batas
Baggy , dan kemudian terjadi sesuatu yang membuatnya melampaui batas. Jadi, tepat ketika ini terjadi, kita akan menemukan bahwa semacam “merajut” kode kita.
Semoga ini bisa dimengerti. Itu adalah ikhtisar pekerjaan rumah yang sangat singkat, tetapi saya harap Anda dapat dengan mudah memahaminya.
Jadi, kami memiliki pointer yang terlihat seperti ini:
char * p = malloc (256) , lalu kita tambahkan pointer
char * q = p + 256 , setelah itu kita akan mencoba untuk meringkas pointer ini.
Jadi apa yang akan terjadi? Perhatikan bahwa 256 adalah urutan
2n , jadi itu akan berada dalam
batas Baggy . Oleh karena itu, ketika kita menambahkan 256 bit, ini berarti bahwa kita membuat lintasan lain ke ujung
batas batas Baggy . Seperti pada contoh sebelumnya, baris ini cukup baik, tetapi mengarah pada fakta bahwa bit pesanan yang lebih tinggi akan ditetapkan untuk
q . Karena itu, ketika kami mencoba untuk menundanya, semuanya akan meledak dan harus menghubungi agen asuransi kami. Apakah itu jelas?

Dari 2 contoh ini, Anda dapat memahami cara
kerja sistem
Batas Baggy . Seperti yang saya sebutkan di kuliah terakhir, Anda tidak perlu benar-benar instrumen setiap operasi pointer jika Anda dapat menggunakan analisis kode statis untuk mengetahui bahwa serangkaian operasi pointer tertentu aman. Saya akan menunda diskusi lebih lanjut dari beberapa analisis statis, tetapi cukup untuk mengatakan bahwa Anda tidak selalu perlu melakukan tindakan matematika ini, kami telah memeriksa ini sebelumnya.
Pertanyaan lain yang disebutkan di Piazza: bagaimana memastikan kompatibilitas
batas -
batas Baggy dengan perpustakaan non-alat sebelumnya. Idenya adalah bahwa ketika
Baggy terikat menginisialisasi tabel perbatasan, mereka menetapkan bahwa semua catatan harus dalam 31 bit. Oleh karena itu, ketika kita membaca tabel batas, setiap rekaman di dalamnya mewakili nilai dari formulir
2n + 31 . Jadi, menginisialisasi batas-batas awal ukuran 31 bit, kami mengasumsikan bahwa setiap pointer akan memiliki ukuran maksimum yang mungkin sebesar
2n + 31 . Biarkan saya memberi Anda contoh yang sangat sederhana yang akan membuat ini jelas.
Misalkan kita memiliki ruang memori yang kita gunakan untuk tumpukan. Ruang memori ini terdiri dari dua komponen. Di bagian atas, kami memiliki tumpukan yang telah dialokasikan menggunakan kode non-alat, dan di bawah ini adalah tumpukan yang telah dialokasikan dengan kode alat. Jadi apa yang akan dilakukan
batasan Baggy ? Seperti yang Anda ingat, sistem ini memiliki konsep slot, yang ukurannya 16 bit. Oleh karena itu, tabel batas akan terdiri dari 2 bagian, dimulai dari 31 bit.
Namun, ketika menjalankan kode alat, itu sebenarnya akan menggunakan algoritma
batas Baggy untuk menetapkan nilai yang sesuai untuk baris tabel ini.

Ketika sebuah pointer datang dari atas ruang memori, selalu diatur ke batas maksimum
2n + 31 . Ini berarti bahwa
batas Baggy tidak akan pernah mempertimbangkan bahwa operasi penunjuk yang telah "datang" dari pustaka non-alat dapat melampaui batas.
Idenya adalah bahwa dalam kode alat, kita akan selalu melakukan perbandingan ini untuk pointer, tetapi jika kita menetapkan batas penulisan pointer untuk kode non-alat dari formulir
2n + 31 , maka kita tidak akan pernah memiliki kesalahan dereferensi. Artinya, kami memiliki interaksi yang baik antara
entri kode
batas Baggy dan
catatan non-instrumental dari perpustakaan sebelumnya.
Ini berarti bahwa kami memiliki sistem ini, yang bagus, karena tidak merusak program saat menggunakan pustaka non-alat, tetapi ia memiliki masalah. Masalahnya adalah bahwa kita tidak pernah dapat menentukan batas pointer yang dihasilkan oleh kode non-alat. Karena kita tidak akan pernah mengatur bit orde tinggi ketika, misalnya, pointer ini mendapat terlalu banyak atau terlalu sedikit ruang. Jadi, kami sebenarnya tidak dapat memastikan keamanan memori untuk operasi yang terjadi saat menggunakan kode non-instrumental. Anda juga tidak dapat menentukan kapan kami meneruskan pointer yang melampaui batas ukuran dari kode instrumental ke kode non-instrumental. Dalam hal ini, sesuatu yang tak terbayangkan dapat terjadi. Jika Anda memiliki pointer yang ditarik dari kode alat, maka bit set orde tinggi diatur ke 1. Jadi sepertinya ia memiliki dimensi raksasa.
Kami tahu bahwa jika kami hanya menempatkan kode ini dalam kode alat, kami dapat menghapus tanda ini di beberapa titik saat kembali ke perbatasan. Tetapi jika kita hanya mengirimkan alamat yang sangat besar ini ke kode non-instrumental, maka itu dapat melakukan sesuatu yang tidak terbayangkan. Bahkan mungkin mengembalikan pointer ini kembali ke batas, tetapi kita tidak akan pernah memiliki kesempatan untuk menghapus bit orde tinggi ini. Jadi kita masih dapat memiliki masalah bahkan ketika menggunakan rangkaian yang ditunjukkan di sini.
Audiens: jika kita memiliki kode alat untuk mengalokasikan memori, apakah ia menggunakan fungsi
malloc yang sama dengan yang digunakan kode atribut?
Profesor: Ini adalah pertanyaan yang sulit. Jika kita mempertimbangkan kasusnya di sini, maka ini diamati dengan seksama, karena kita memiliki dua bidang memori, yang masing-masing mematuhi aturan yang ditetapkan untuk itu. Tetapi pada prinsipnya, itu akan tergantung pada kode yang menggunakan bahasa pemrograman yang dipilih. Bayangkan dalam C ++, misalnya, Anda dapat menetapkan kualifikasi Anda sendiri. Jadi itu tergantung pada detail kode tertentu.
Pemirsa: bagaimana kualifikasi dapat memeriksa apakah batasnya diatur ke 31 bit atau tidak?
Profesor: di tingkat bawah, algoritma distribusi berfungsi sehingga saat Anda memanggil sistem yang tidak dikenal, penunjuknya bergerak ke atas. Jadi, jika Anda memiliki beberapa pengalokasi, maka mereka semua mencoba mengalokasikan memori, masing-masing memiliki sepotong ingatan sendiri, yang mereka simpan sendiri, pada dasarnya, dengan benar. Jadi dalam kehidupan nyata itu bisa lebih terfragmentasi daripada di level tinggi.
Jadi, semua yang kami periksa di atas terkait dengan pengoperasian
batas Baggy dalam sistem 32-bit. Pertimbangkan apa yang terjadi ketika menggunakan sistem 64-bit. Dalam sistem seperti itu, Anda sebenarnya dapat menyingkirkan tabel batas, karena kami dapat menyimpan beberapa informasi tentang batas-batas dalam pointer itu sendiri.
Pertimbangkan seperti apa penunjuk biasa di batas Baggy. Ini terdiri dari 3 bagian. 21 bit dialokasikan untuk bagian pertama, nol, 5 bit lainnya dialokasikan untuk ukuran, ini adalah ukuran utama dari log, dan 38 bit lainnya adalah bit dari alamat biasa.

Alasan mengapa ini tidak secara besar-besaran membatasi ukuran alamat program yang Anda gunakan adalah bahwa sebagian besar bit orde tinggi dari sistem operasi dan / atau peralatan yang terletak di 2 bagian pertama dari pointer tidak memungkinkan aplikasi digunakan karena berbagai alasan. Jadi, ternyata, kami tidak banyak mengurangi jumlah aplikasi yang digunakan dalam sistem. Seperti inilah tampilan pointer biasa.
Apa yang terjadi ketika kita hanya memiliki satu dari petunjuk ini? Nah, pada sistem 32-bit, yang bisa kita lakukan hanyalah mengatur bit pesanan tinggi dan berharap benda ini tidak akan pernah mendapatkan lebih dari setengah ukuran slot. Tetapi sekarang karena kita memiliki semua ruang alamat tambahan ini, Anda dapat meletakkan offset di luar batas OOB (di luar batas) langsung di pointer ini. Jadi kita bisa melakukan sesuatu seperti yang ditunjukkan pada gambar, membagi pointer menjadi 4 bagian dan mendistribusikan kembali ukurannya.
Dengan demikian, kita bisa mendapatkan 13 bit untuk batas offset, yaitu, tulis seberapa jauh penunjuk OOB ini dari tempat seharusnya. Kemudian lagi, Anda dapat mengatur ukuran sebenarnya dari objek yang ditunjukkan di sini menjadi 5, dan sisanya dari bagian nol, yang sekarang akan menjadi 21-13 = 8 bit. Dan kemudian ikuti bagian alamat kami dari 38 bit. Dalam contoh ini, Anda melihat keuntungan menggunakan sistem 64-bit.

Perhatikan bahwa di sini kita memiliki ukuran biasa untuk pointer biasa, dalam kedua kasus ukuran ini adalah 64 bit, dan deskripsinya adalah dasar. Dan ini bagus, karena ketika menggunakan pointer "tebal", kita perlu banyak kata untuk menggambarkannya.
Saya juga mencatat bahwa kode non-alat dapat dengan mudah diterapkan di sini, karena ia berfungsi dan menggunakan ukuran yang sama dengan pointer biasa. Kita bisa meletakkan hal-hal ini di sebuah
struct , misalnya, dan ukuran
struct ini akan tetap tidak berubah. Jadi ini sangat bagus ketika kita memiliki kesempatan untuk bekerja di dunia 64-bit.
Hadirin: mengapa pada kasus kedua offset terletak di depan ukuran, dan tidak seperti pada kasus sebelumnya, dan apa yang akan terjadi jika ukuran offset besar?
Profesor: Saya pikir dalam beberapa kasus kita memiliki masalah terbatas yang harus kita selesaikan. Misalnya, masalah akan terjadi jika ada lebih banyak bit. Tetapi pada dasarnya, saya tidak berpikir ada alasan mengapa Anda tidak bisa membaca beberapa dari hal-hal ini. Kecuali kondisi ketat tertentu, yang tidak saya pikirkan sekarang, seharusnya menentukan ukuran bagian nol, jika tidak mungkin ada masalah dengan perangkat keras.
Jadi, Anda masih dapat memulai buffer overflow di sistem
Batas Baggy , karena menerapkan pendekatan di atas tidak menyelesaikan semua masalah, bukan? Masalah lain yang mungkin Anda temui jika Anda memiliki kode non-instrumental, karena kami tidak akan dapat mendeteksi masalah dalam kode non-instrumental. Anda juga dapat menemukan kerentanan memori yang timbul dari sistem alokasi memori dinamis. Jika Anda ingat, dalam kuliah sebelumnya kami melihat petunjuk aneh ini untuk
membebaskan malloc , dan
batasan Baggy tidak dapat mencegah terjadinya hal-hal seperti itu.
Kami juga membahas pada kuliah terakhir tentang fakta bahwa penunjuk kode tidak memiliki batasan yang akan dikaitkan dengan mereka. Misalkan kita memiliki struktur di mana buffer memori terletak di bagian bawah, dan pointer di bagian atas, dan buffer overflow. Kami berasumsi bahwa buffer overflow masih dalam
batas Baggy . Maka Anda harus mendefinisikan kembali pointer fungsi ini. Kalau tidak, jika kita mencoba menggunakannya, itu dapat dikirim ke kode berbahaya untuk menyerang bagian memori yang terkontrol. Dan dalam hal ini, batas tidak akan membantu kami, karena kami tidak memiliki batas yang terkait, terkait dengan pointer fungsi ini.
Jadi, berapa harga menggunakan
batas Baggy ? Bahkan, kami hanya memiliki 4 komponen dari harga ini.

Yang pertama adalah ruang. Karena jika Anda menggunakan pointer "tebal", maka jelas bahwa Anda harus membuat pointer lebih besar. Jika Anda menggunakan sistem
batas Baggy yang baru saja kita bicarakan, Anda harus menyimpan tabel perbatasan. Dan tabel ini memiliki ukuran slot yang memungkinkan Anda mengontrol seberapa besar tabel ini hingga Anda kehabisan kemungkinan memori yang dialokasikan untuknya.
Selain itu, Anda juga menambah beban pada CPU, yang dipaksa untuk melakukan semua operasi instrumental ini dengan pointer. Karena untuk setiap atau hampir setiap penunjuk, perlu untuk memeriksa batas menggunakan mode operasi yang sama, yang akan memperlambat eksekusi program Anda.
Ada juga masalah dengan alarm palsu. Kita telah membahas apa yang mungkin terjadi bahwa sebuah program menghasilkan pointer yang melampaui batas, tetapi tidak pernah mencoba untuk meringkasnya. Sebenarnya, ini bukan masalah.
Batas longgar akan menandai bendera "di luar
batas " ini jika melampaui 1/2 dari ukuran slot, setidaknya dalam solusi 32-bit.
Apa yang akan Anda lihat di sebagian besar alat keamanan adalah bahwa alarm palsu mengurangi kemungkinan orang akan menggunakan alat ini. Karena dalam praktiknya, kita semua berharap bahwa kita peduli dengan keamanan, tetapi apa yang benar-benar menggairahkan orang? Mereka ingin dapat mengunggah foto bodoh mereka ke Facebook, mereka ingin mempercepat proses pengunggahan dan sebagainya. Jadi, jika Anda benar-benar ingin alat keamanan Anda diminati, mereka seharusnya tidak memiliki alarm palsu. Mencoba untuk menangkap semua kerentanan biasanya mengarah ke alarm palsu, yang akan mengganggu pengembang atau pengguna.
Ini juga mengarah pada pengeluaran tidak produktif yang Anda butuhkan dukungan kompiler. Karena Anda harus menambahkan semua alat ke sistem, melewati pemeriksaan pointer, dan seterusnya dan seterusnya.
Jadi, untuk menggunakan sistem
batas Baggy kita harus membayar harga, yang terdiri dari penggunaan ruang yang berlebihan, peningkatan beban CPU, alarm palsu dan kebutuhan untuk menggunakan kompiler.
Ini
menyimpulkan diskusi tentang
batas-batas Baggy .
Sekarang kita dapat memikirkan dua strategi mitigasi buffer overflow lainnya. Bahkan, mereka jauh lebih mudah untuk dijelaskan dan dipahami.
Salah satu pendekatan ini disebut
memori yang tidak dapat dieksekusi . Gagasan utamanya adalah bahwa perangkat keras swap akan menunjukkan 3 bit
R ,
W, dan
X - baca, tulis, dan jalankan - untuk setiap halaman yang Anda miliki di memori. , , . 2 , , , , .
, . , , , , - . , . «
W X » , , , , , . , , . . , , . ?
- , . , . , , .
, , , , . , , . .
, . –
just-in-time , .
-, JavaScript . JavaScript, , - - «» , - «» , x86 . , .
. , ,
just-in-time W ,
X . , , .
— . , , , .

, , . GDB, , . , . , . .
, . , , , , , .
: , , — . , , , , , , , - . , , .
, , , GDB , , , , . .
, , , , . - , - , . , . , , .
, ? , . , , . , , , , - , .
27:10
:
Kursus MIT "Keamanan Sistem Komputer". 3: « : », 2.
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?