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 Jadi, kami memiliki penyangga tempat kami meletakkan "kenari". Di atas itu adalah penunjuk breakpoint yang
disimpan menyimpan nilai
EBP dan alamat pengirim ditempatkan di atasnya. Jika Anda ingat, luapan meluap dari bawah ke atas, jadi sebelum Anda sampai ke alamat pengirim, itu akan menghancurkan "kenari" terlebih dahulu.
Hadirin: mengapa hal itu memengaruhi "kenari"?
Profesor: karena diasumsikan bahwa penyerang tidak tahu bagaimana sewenang-wenang "melompat" dalam memori. Serangan memory overflow tradisional dimulai dengan seorang hacker memeriksa batas ukuran buffer, setelah itu overflow dimulai dari garis bawah. Tapi Anda benar - jika penyerang bisa langsung masuk ke bilah alamat pengirim, tidak ada "kenari" yang akan membantu kami. Namun, dengan serangan buffer overflow tradisional, semuanya harus terjadi persis seperti itu - dari bawah ke atas.
Dengan demikian, ide utama menggunakan "kenari" adalah bahwa kami memungkinkan eksploit jahat meluap buffer memori. Kami memiliki kode run-time yang, ketika kembali dari suatu fungsi, memeriksa "kenari" untuk memastikan bahwa ia memiliki nilai yang benar.
Hadirin: Dapatkah penyerang menulis ulang alamat pengirim dan mengubah "kenari"? Bagaimana dia dapat memverifikasi bahwa itu telah dimodifikasi, tetapi terus memenuhi fungsinya?
Profesor: ya, mungkin. Dengan demikian, Anda harus memiliki beberapa kode yang benar-benar akan memeriksa ini sebelum fungsinya kembali. Artinya, dalam hal ini, perlu mendapat dukungan dari kompiler, yang sebenarnya akan memperluas
konvensi pemanggilan konvensi pemanggilan . Sehingga bagian dari urutan pengembalian terjadi sebelum kita mempertimbangkan validitas dari nilai ini untuk memastikan bahwa "kenari" belum dihancurkan. Hanya setelah itu kita bisa memikirkan hal lain.
Hadirin: Tidak bisakah penyerang tahu atau menebak apa arti "kenari"?
Profesor: inilah tepatnya yang akan saya bicarakan! Apa masalah dengan sirkuit ini? Bagaimana jika, misalnya, kita menempatkan nilai A di setiap program? Atau seluruh cabang 4 nilai A? Tentu saja, setiap hacker dapat mengetahui ukuran buffer, kapasitasnya, dan dengan demikian menentukan posisi "kenari" dalam sistem apa pun. Karena itu, kita dapat menggunakan berbagai jenis kuantitas yang kita masukkan ke dalam "kenari" untuk mencegah hal ini.
Ada satu hal yang dapat Anda lakukan dengan "kenari" kami. Ini akan menjadi tipe "kenari" yang sangat lucu, yang menggunakan fungsi program C dan memproses karakter khusus, yang disebut tipe deterministik "kenari".

Bayangkan Anda menggunakan karakter 0 untuk "kenari". Nilai biner nol adalah byte nol, karakter nol di ASCII. Nilai -1 berarti kembali ke posisi sebelumnya dan seterusnya. Banyak fungsi menghentikan atau mengubah operasi ketika mereka menemukan karakter atau nilai-nilai seperti 0, CR, LF, -1. Bayangkan bahwa Anda, sebagai peretas, menggunakan beberapa fungsi manajemen string untuk naik buffer, menemukan karakter 0 di "kenari" dan proses berhenti! Jika Anda menggunakan fungsi “carriage return” -1, yang sering digunakan sebagai terminator garis, prosesnya juga akan berhenti. Jadi -1 adalah tanda ajaib lain.
Ada satu hal lagi yang dapat digunakan dalam "kenari" - ini adalah nilai acak yang sulit ditebak untuk penyerang. Kekuatan nilai acak didasarkan pada seberapa sulit bagi seorang penyerang untuk menebaknya. Misalnya, jika penyerang menyadari bahwa hanya ada 3 bit entropi di sistem Anda, maka ia akan dapat menggunakan serangan brute-force. Oleh karena itu, kemungkinan menggunakan angka acak untuk melindungi dari serangan sangat terbatas.
Audiens: Biasanya saya membaca dari buffer lain dan menulis apa yang saya baca di buffer tumpukan ini. Dalam situasi ini, tampaknya nilai acak "kenari" tidak berguna, karena saya membaca data dari buffer lain dan saya tahu di mana "kenari" itu. Saya memiliki buffer lain yang saya kontrol dan yang tidak pernah saya periksa. Dan di buffer ini saya bisa memasukkan apa yang ingin saya masukkan. Saya tidak memerlukan “kenari” acak, karena saya dapat menulis ulang dengan aman. Jadi saya tidak melihat cara kerjanya - dalam skenario yang Anda usulkan, ketika fungsi berhenti saat membaca data dari buffer.
Profesor: Saya mengerti pertanyaan Anda - maksud Anda bahwa kami menggunakan "kenari" deterministik, tetapi jangan gunakan salah satu fungsi perpustakaan standar yang dapat "ditipu" oleh karakter kami 0, CR, LF, -1. Maka ya, dalam situasi yang Anda jelaskan, "kenari" tidak diperlukan.
Idenya adalah Anda dapat mengisi buffer ini dengan byte dari mana saja, tetapi apa pun yang memungkinkan Anda untuk menebak nilai-nilai ini atau mendapatkannya secara acak akan menyebabkan kegagalan.
Hadirin: Apakah mungkin menggunakan sesuatu seperti jumlah detik atau milidetik sebagai angka acak dan menggunakannya dalam "kenari"?
Profesor: Panggilan data tidak mengandung kecelakaan sebanyak yang Anda pikirkan. Karena program memiliki log atau fungsi yang dapat Anda hubungi untuk mengetahui kapan program itu diunduh, dan hal-hal serupa lainnya. Tetapi secara umum, Anda benar - dalam praktiknya, jika Anda dapat menggunakan perangkat perangkat keras, biasanya dari level rendah, dengan pengaturan waktu sistem yang lebih baik, pendekatan semacam ini mungkin berhasil.
Hadirin: bahkan jika kami berhasil melihat log tentang awal buffer overflow, masih penting kapan kami menolak permintaan tersebut. Dan jika kita tidak bisa mendapatkan kontrol atas berapa lama permintaan komputer ke server, diragukan bahwa waktu yang tepat dapat ditebak secara deterministik.
Profesor: benar sekali, saya sudah mengatakan bahwa kejahatan terletak pada perincian, ini hanya kasus seperti itu. Dengan kata lain, jika Anda memiliki beberapa cara untuk, misalnya, menentukan jenis saluran waktu, Anda mungkin menemukan bahwa jumlah entropi, atau jumlah keacakan, tidak mengisi seluruh timestamp, tetapi lebih sedikit. Oleh karena itu, seorang penyerang dapat menentukan jam dan menit kapan Anda melakukan ini, tetapi tidak satu detik.
Hadirin: sebagai catatan, mencoba mengurangi keacakan Anda sendiri adalah ide yang buruk?
Profesor: benar sekali!
Hadirin: yaitu, biasanya kita hanya perlu menggunakan segala sesuatu yang didukung sistem kita, bukan?
Profesor: ya, itu benar. Ini seperti penemuan cryptosystem kita sendiri, yang merupakan hal populer yang kadang-kadang ingin dilakukan oleh lulusan kita. Tapi kami bukan NSA, kami bukan ahli matematika, jadi ini biasanya gagal. Jadi Anda benar tentang itu.
Tetapi bahkan jika Anda menggunakan keacakan sistem, Anda masih bisa mendapatkan lebih sedikit entropi daripada yang Anda harapkan. Biarkan saya memberi Anda contoh pengacakan fase alamat. Pada prinsip inilah pendekatan
stack canaries bekerja . Karena kita terlibat dalam keamanan komputer, Anda mungkin bertanya-tanya dalam kasus apa "burung kenari" tidak dapat mengatasi tugas mereka dan apakah ada cara untuk gagal "kenari".
Salah satu caranya adalah serangan dengan menulis ulang pointer fungsi. Karena jika pukulan mengenai pointer fungsi, "kenari" tidak dapat melakukan apa pun.
Misalkan Anda memiliki kode bentuk
int * ptr ... .. , pointer awal, tidak peduli bagaimana, maka Anda memiliki buffer
buf char [128] , fungsi
mendapat (buf) , dan di paling bawah pointer yang diberi nilai tertentu :
* ptr = 5 .
Saya perhatikan bahwa kami tidak mencoba menyerang alamat pengirim dari fungsi yang berisi kode ini. Seperti yang Anda lihat, ketika buffer meluap, alamat pointer yang terletak di atasnya akan rusak. Jika seorang penyerang dapat merusak pointer ini, maka ia kemudian dapat menetapkan 5 ke salah satu alamat yang ia kontrol. Bisakah semua orang melihat bahwa "kenari" tidak akan membantu di sini? Karena kita tidak menyerang jalur di mana fungsi kembali.
Pemirsa: dapatkah penunjuk berada di bawah buffer?
Profesor: memang bisa, tetapi urutan variabel tertentu bergantung pada banyak hal berbeda, pada cara kompiler mengatur konten, pada ukuran kolom perangkat keras, dan sebagainya. Tapi Anda benar, jika buffer overflow naik, dan pointer terletak di bawah buffer, maka overflow tidak dapat merusaknya.
Hadirin: mengapa Anda tidak dapat mengaitkan "kenari" dengan fungsi "kenari", seperti yang Anda lakukan dengan alamat pengirim?
Profesor: ini adalah momen yang menarik! Anda dapat melakukan hal-hal seperti itu. Bahkan, Anda dapat mencoba membayangkan kompiler yang setiap kali memiliki pointer, selalu mencoba untuk menambahkan add-on untuk beberapa hal. Namun, memeriksa semua hal ini akan terlalu mahal. Karena setiap kali Anda ingin menggunakan pointer atau memanggil fungsi apa pun, Anda harus memiliki kode yang akan memeriksa apakah "kenari" ini benar. Pada dasarnya, Anda bisa melakukan hal serupa, tetapi apakah itu masuk akal? Kami melihat bahwa "kenari" tidak membantu dalam situasi ini.
Dan satu hal lagi yang kita bahas sebelumnya adalah bahwa jika penyerang dapat menebak keacakan, maka, pada prinsipnya, "kenari" acak tidak akan berfungsi. Membuat sumber daya keamanan berdasarkan keacakan adalah topik terpisah, sangat kompleks, jadi kami tidak akan membahasnya.
Hadirin: Jadi kenari mengandung bit lebih sedikit dari alamat pengirim? Karena kalau tidak, Anda tidak bisa hanya mengingat alamat ini dan memeriksa apakah alamatnya sudah berubah?
Profesor: mari kita lihat. Anda berbicara tentang skema ini ketika "kenari" terletak di atas buffer, dan Anda berarti bahwa sistem tidak dapat aman jika tidak mungkin untuk melihat alamat pengirim dan memeriksa apakah sudah diubah.

Ya dan tidak Harap perhatikan bahwa jika serangan buffer overflow terjadi, apa pun di atasnya akan ditimpa, jadi ini masih dapat menyebabkan masalah. Tetapi pada dasarnya, jika hal-hal ini tidak dapat diubah dengan cara apa pun, maka Anda dapat melakukan sesuatu seperti ini. Tetapi masalahnya adalah bahwa dalam banyak kasus, memanipulasi alamat pengirim adalah hal yang agak rumit. Karena Anda dapat membayangkan bahwa fungsi khusus dapat dipanggil dari tempat yang berbeda, dan seterusnya. Dalam hal ini, kami berlari sedikit di depan, dan jika ada waktu di akhir kuliah, kami akan kembali ke ini.
Ini adalah situasi di mana "kenari" mungkin gagal. Ada tempat-tempat lain di mana kegagalan dimungkinkan, misalnya, ketika menyerang
malloc dan fungsi-fungsi
gratis . Fungsi malloc mengalokasikan blok memori dengan ukuran tertentu dalam byte dan mengembalikan pointer ke awal blok. Isi blok memori yang dialokasikan tidak diinisialisasi, tetap dengan nilai yang tidak ditentukan. Dan fungsi
bebas membebaskan memori yang sebelumnya dialokasikan secara dinamis.
Ini adalah serangan unik dalam gaya C. Mari kita lihat apa yang terjadi di sini. Bayangkan Anda memiliki dua pointer di sini, p dan q, yang kami gunakan
malloc untuk mengalokasikan 1,024 byte memori untuk masing-masing pointer ini. Misalkan kita membuat fungsi
strcpy untuk p dari beberapa jenis kesalahan buffer yang dikendalikan oleh penyerang. Di sinilah terjadi overflow. Dan kemudian kita jalankan perintah
free q dan
free p . Ini kode yang cukup sederhana, bukan?

Kami memiliki 2 pointer yang mengalokasikan memori, kami menggunakan salah satunya untuk fungsi tertentu, terjadi buffer overflow, dan kami membebaskan memori kedua pointer.
Misalkan garis memori p dan q keduanya terletak bersebelahan dalam ruang memori. Dalam hal ini, hal-hal buruk dapat terjadi, bukan? Karena fungsi
strcpy digunakan untuk menyalin isi
str2 ke
str1 .
Str2 harus berupa pointer ke string yang diakhiri dengan nol, dan
strcpy mengembalikan pointer ke
str1 . Jika garis
str1 dan
str2 tumpang tindih, maka perilaku fungsi
strcpy tidak ditentukan.
Oleh karena itu, fungsi
strycpy yang memproses memori
p dapat secara bersamaan memengaruhi memori yang dialokasikan untuk
q . Dan itu bisa menyebabkan masalah.
Ada kemungkinan bahwa Anda melakukan sesuatu seperti itu dalam kode Anda sendiri secara tidak sengaja ketika Anda menggunakan beberapa jenis pointer aneh. Dan semuanya tampaknya berfungsi, tetapi ketika Anda perlu memanggil fungsi
bebas , gangguan seperti itu terjadi. Dan seorang penyerang dapat memanfaatkannya, saya akan menjelaskan mengapa ini terjadi.
Bayangkan di dalam implementasi fungsi
bebas dan
malloc , blok yang disorot akan terlihat seperti ini.
Mari kita asumsikan bahwa di bagian atas blok ada data aplikasi yang terlihat, dan di bawah ini kita memiliki ukuran variabel. Ukuran ini bukan apa yang dilihat langsung oleh aplikasi, tetapi semacam "penghitungan" yang dilakukan oleh
gratis atau
malloc , sehingga Anda tahu ukuran buffer memori yang dialokasikan. Blok gratis terletak di sebelah blok yang disorot. Misalkan blok bebas memiliki beberapa metadata yang terlihat seperti ini: kita memiliki ukuran blok di atas, ada ruang kosong di bawahnya, penunjuk belakang dan penunjuk maju di bawahnya. Dan di bagian paling bawah blok, ukuran ditampilkan lagi.

Mengapa kita memiliki 2 petunjuk di sini? Karena sistem alokasi memori dalam kasus ini menggunakan daftar tertaut ganda untuk melacak bagaimana blok bebas saling terkait. Karena itu, ketika Anda memilih blok gratis, Anda mengecualikannya dari daftar yang ditautkan dua kali lipat ini. Dan kemudian, ketika Anda membebaskannya, Anda akan melakukan beberapa aritmatika untuk pointer dan mengatur semuanya. Setelah itu, Anda menambahkannya ke daftar tertaut ini, bukan?
Setiap kali Anda mendengar tentang pointer aritmatika, Anda harus berpikir bahwa ini adalah "kenari" Anda. Karena akan ada banyak masalah. Biarkan saya mengingatkan Anda bahwa kami memiliki buffer overflow
p . Jika kita berasumsi bahwa
p dan
q bersebelahan, atau sangat dekat dalam ruang memori, maka pada akhirnya mungkin terjadi bahwa buffer overflow dapat menimpa beberapa data ukuran untuk pointer yang dialokasikan
q - ini adalah bagian bawah dari blok yang dialokasikan untuk kita. Jika Anda terus mengikuti pemikiran saya dari awal, maka imajinasi Anda akan memberi tahu Anda di mana semuanya mulai salah. Memang, pada dasarnya, apa yang akhirnya terjadi dengan operasi ini adalah
q bebas dan
bebas p - mereka melihat metadata ini di blok yang dipilih untuk melakukan semua manipulasi yang diperlukan dengan pointer.

Yaitu, pada titik tertentu dalam eksekusi, fungsi bebas akan mendapatkan pointer tertentu berdasarkan nilai ukuran:
p = get.free.block (size) , dan ukurannya adalah apa yang
diserang oleh penyerang, karena ia melakukan buffer overflow, dengan benar ?
Dia melakukan banyak perhitungan aritmatika, melihat fungsi
belakang dan pointer dari blok ini dan sekarang akan melakukan sesuatu seperti memperbarui pointer "kembali" dan "maju" - ini adalah dua baris terbawah.

Tetapi pada kenyataannya ini seharusnya tidak mengganggu Anda. Ini hanyalah contoh dari kode yang terjadi dalam kasus ini. Tetapi faktanya adalah, karena ukuran yang ditulis ulang oleh hacker, ia sekarang mengontrol pointer ini, yang melewati fungsi
bebas . Dan karena ini, dua status di sini di baris paling bawah sebenarnya adalah pembaruan pointer. Dan karena penyerang mampu mengendalikan
p ini, sebenarnya mengontrol dua petunjuk ini. Di tempat inilah serangan dapat terjadi.
Karena itu, ketika berlari
bebas dan mencoba melakukan sesuatu seperti menggabungkan dua blok ini, Anda memiliki daftar yang ditautkan dua kali lipat. Karena jika Anda memiliki dua blok yang saling bertabrakan dan keduanya gratis, Anda ingin menggabungkannya menjadi satu blok besar.
Tetapi jika kita mengontrol ukurannya, itu berarti kita mengendalikan keseluruhan proses dari empat baris di atas. Ini berarti bahwa jika kita memahami cara kerja overflow, maka kita dapat menulis data ke memori dengan cara yang kita pilih. Seperti yang saya katakan, hal-hal seperti itu sering terjadi dengan kode Anda sendiri, jika Anda tidak pintar dengan pointer. Ketika Anda membuat beberapa kesalahan bebas ganda seperti
q gratis dan
p gratis atau sesuatu yang lain, fungsi Anda macet. Karena Anda mengacaukan metadata yang hidup di setiap blok yang dipilih ini, dan pada titik tertentu perhitungan ini akan menunjukkan beberapa jenis nilai "sampah", setelah itu Anda akan "mati". Tetapi jika Anda seorang penyerang, Anda dapat memilih nilai ini dan menggunakannya untuk keuntungan Anda.
Mari kita beralih ke pendekatan lain untuk mencegah serangan buffer overflow. Pendekatan ini untuk memeriksa batas. Tujuan pemeriksaan batas adalah untuk memastikan bahwa ketika Anda menggunakan pointer tertentu, itu hanya akan merujuk pada apa yang merupakan objek memori. Dan pointer ini berada dalam batas yang diizinkan dari objek memori ini. Ini adalah ide utama verifikasi. — . , C, . , : , , ?
, – . 1024 , :
char [1024] ,
char *y = & [108].
? ? Sulit dikatakan. , , . , , - .
- , , , . . , , , . , , . , , .
, ,
struct union . , . :
integer ,
struct ,
int .
,
union , . ,
integer ,
struct , .
, , - :
int p: & (u,s,k) , : u, s, k.

, , , , . , ,
union integer ,
struct . , , , . .
p' ,
p ,
p' , .

, , . , ,
union . , - -
union , , , . , , X. , , , , . , , . .
, . ,
p p' , . .
? Electric fencing – . , , , , .

, - , . , , . , . , , , .
- C C++, , , . - , , - . , . , «» — , , , . , , .
, guard page – ! , .
59:00
:
MIT « ». 2: « », 2Versi lengkap dari kursus ini tersedia di
sini .
, . ? ? ,
30% entry-level , : VPS (KVM) E5-2650 v4 (6 Cores) 10GB DDR4 240GB SSD 1Gbps $20 ? ( RAID1 RAID10, 24 40GB DDR4).
Dell R730xd 2 ? 2 Intel Dodeca-Core Xeon E5-2650v4 128GB DDR4 6x480GB SSD 1Gbps 100 $249 ! . c Dell R730xd 5-2650 v4 9000 ?