Kursus MIT "Keamanan Sistem Komputer". Kuliah 3: Buffer Overflows: Eksploitasi dan Perlindungan, Bagian 2

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 3
Kuliah 2: "Kontrol serangan hacker" Bagian 1 / Bagian 2 / Bagian 3
Kuliah 3: “Buffer Overflows: Exploits and Protection” Bagian 1 / Bagian 2 / Bagian 3

Menariknya, seorang penyerang tidak dapat melompat ke alamat tertentu, meskipun pada kenyataannya kami terutama menggunakan alamat yang dikodekan dengan keras. Apa yang dia lakukan disebut "heap attack", dan jika Anda adalah orang jahat, maka itu akan sangat menyenangkan bagi Anda. Dengan serangan seperti itu, seorang hacker mulai secara dinamis mengalokasikan banyak kode shell dan cukup memasukkannya secara acak ke dalam memori. Ini sangat efektif jika Anda menggunakan bahasa tingkat tinggi yang dinamis seperti JavaScript. Dengan demikian, pembaca tag berada dalam satu lingkaran sempit dan hanya menghasilkan sejumlah besar baris kode shell dan kemudian mengisinya.

Penyerang tidak dapat menentukan lokasi garis yang tepat, ia hanya memilih 10 MB baris kode shell dan membuat lompatan sewenang-wenang. Dan jika dia entah bagaimana bisa mengendalikan salah satu pointer ret , ada kemungkinan dia akan "mendarat" dalam kode shell.



Anda dapat menggunakan satu trik yang disebut NOP slide , NOP sled atau NOP ramp , di mana NOP adalah instruksi tanpa operasi , atau perintah kosong yang tidak digunakan. Ini berarti bahwa aliran eksekusi perintah prosesor "tergelincir" ke tujuan akhir yang diinginkan setiap kali program pergi ke alamat memori di mana saja pada slide.

Bayangkan jika Anda memiliki satu baris kode shell dan Anda pergi ke tempat acak di baris itu, ini mungkin tidak berfungsi, karena itu tidak memungkinkan Anda untuk menyebarkan serangan dengan cara yang benar.

Tapi mungkin barang-barang yang Anda masukkan pada heap pada dasarnya hanya satu ton NOP , dan pada akhirnya, Anda memiliki kode shell. Ini sebenarnya cukup pintar, karena itu berarti sekarang Anda benar-benar bisa sampai ke tempat yang tepat di mana Anda melompat. Karena jika Anda melompat ke salah satu NOP ini, itu hanya terjadi "boom, boom, boom, boom, boom, boom, boom, boom", dan kemudian Anda masuk ke kode shell.

Sepertinya orang-orang datang dengan ini, yang mungkin Anda lihat di tim kami. Mereka menciptakan sesuatu seperti itu, dan itulah masalahnya. Jadi ini adalah cara lain untuk menyiasati beberapa hal acak hanya dengan membuat pengacakan kode Anda kuat, jika itu masuk akal.

Jadi, kami telah membahas beberapa jenis keacakan yang dapat Anda gunakan. Ada beberapa ide bodoh yang juga muncul pada orang. Jadi sekarang Anda tahu bahwa ketika Anda ingin membuat panggilan sistem, misalnya, menggunakan fungsi syscall libc , Anda pada dasarnya memasukkan nomor unik yang mewakili panggilan sistem yang ingin Anda buat. Jadi mungkin fungsi garpu 7, tidur 8, atau sesuatu seperti itu.

Ini berarti bahwa jika seorang penyerang dapat mengetahui alamat dari instruksi syscall ini dan mendapatkannya dengan cara apa pun, ia sebenarnya hanya dapat mengganti nomor system call yang ingin mereka gunakan secara langsung. Anda dapat membayangkan bahwa setiap kali program berjalan, Anda benar-benar membuat penugasan dinamis nomor syscall untuk syscall yang valid, untuk mempersulit penangkapan penyerang.



Bahkan ada beberapa saran untuk mengubah perangkat keras sehingga peralatan tersebut berisi kunci enkripsi xor , yang digunakan untuk fungsi dinamis xor . Bayangkan bahwa setiap kali Anda mengkompilasi suatu program, semua kode instruksi mendapatkan kunci xor tertentu. Kunci ini disimpan dalam register peralatan ketika Anda pertama kali mengunduh program, dan setelah itu, setiap kali Anda menjalankan instruksi, peralatan secara otomatis melakukan operasi untuknya sebelum melanjutkan dengan instruksi ini. Hal yang baik tentang pendekatan ini adalah bahwa sekarang, bahkan jika penyerang dapat menghasilkan kode shell, ia tidak akan mengenali kunci ini. Jadi akan sangat sulit baginya untuk mencari tahu apa yang sebenarnya perlu dimasukkan ke dalam memori.

Hadirin: tetapi jika dia bisa mendapatkan kode, maka dia juga bisa menggunakan xor untuk mengubah kode kembali menjadi instruksi.

Profesor: ya, itu adalah masalah kanonik, benar. Ini agak mirip dengan apa yang terjadi selama serangan BROP , ketika kita tampaknya mengacak lokasi kode, tetapi penyerang dapat "merasakan" itu dan mencari tahu apa yang terjadi. Orang dapat membayangkan bahwa, misalnya, jika seorang penyerang mengetahui beberapa sub-urutan kode yang ia harapkan akan ditemukan dalam file biner, ia akan mencoba menggunakan operasi xor untuk file ini untuk mengekstraksi kunci.

Pada dasarnya, kami membahas semua jenis serangan pengacakan yang ingin saya sampaikan kepada Anda hari ini. Sebelum kita beralih ke pemrograman, ada baiknya membahas metode perlindungan mana yang digunakan dalam praktiknya. Ternyata GCC dan Visual Studio menyertakan pendekatan stack canaries secara default . Ini adalah komunitas yang sangat populer dan sangat terkenal. Jika Anda melihat Linux dan Windows, mereka juga memanfaatkan hal-hal seperti memori yang tidak dapat dieksekusi dan pengacakan ruang alamat. Benar, sistem batas longgar tidak begitu populer dengan mereka, mungkin karena biaya memori, prosesor, alarm palsu, dan sebagainya, yang telah kita bicarakan. Jadi pada dasarnya kami telah memeriksa bagaimana hal-hal akan mencegah masalah buffer overflow.

Sekarang kita akan berbicara tentang ROP , pemrograman berorientasi terbalik. Hari ini saya sudah memberi tahu Anda apa yang diwakilinya dalam hal pengacakan ruang alamat dan mencegah data dieksekusi - itu membaca, menulis, dan mengeksekusi. Ini sebenarnya adalah hal yang sangat kuat. Karena pengacakan mencegah kemungkinan penyerang memahami di mana alamat hard-coded kami. Dan kemampuan untuk mencegah eksekusi data memastikan bahwa bahkan jika Anda meletakkan kode shell pada stack, seorang penyerang tidak bisa langsung melompat ke sana dan menjalankannya.

Semua ini terlihat cukup progresif, tetapi peretas terus mengembangkan metode serangan terhadap solusi pertahanan progresif tersebut.

Jadi apa inti dari pemrograman berorientasi terbalik?

Bagaimana jika, alih-alih hanya membuat kode baru selama serangan, seorang penyerang dapat menggabungkan potongan-potongan kode yang ada dan kemudian menggabungkannya secara abnormal? Bagaimanapun, kita tahu bahwa program itu berisi banyak kode seperti itu.



Jadi, untungnya, atau sayangnya, semuanya tergantung di sisi mana Anda berada. Jika Anda dapat menemukan beberapa potongan kode yang menarik dan menggabungkannya, Anda bisa mendapatkan sesuatu seperti bahasa Turing , di mana penyerang pada dasarnya dapat melakukan apapun yang dia inginkan.

Mari kita lihat contoh yang sangat sederhana yang akan terasa akrab bagi Anda pada awalnya, tetapi kemudian dengan cepat berubah menjadi sesuatu yang gila.

Katakanlah kita memiliki program berikut. Jadi, mari kita memiliki beberapa fungsi dan, yang nyaman bagi penyerang, inilah fungsi shell run yang bagus. Jadi ini hanya panggilan ke sistem, itu akan menjalankan perintah bin / bash dan ini akan berakhir. Selanjutnya, kami memiliki proses buffer overflow kanonik atau, maaf, fungsi yang akan mengumumkan pembuatan buffer dan kemudian menggunakan salah satu fungsi tidak aman ini untuk mengisi buffer dengan byte.



Jadi, kita tahu bahwa buffer overflow terjadi tanpa masalah. Tetapi hal yang menarik adalah bahwa kita memiliki fungsi run shell ini , tetapi sulit untuk mendapatkannya dengan cara berdasarkan buffer overflows. Bagaimana seorang penyerang dapat menjalankan perintah shell run ini?

Pertama-tama, penyerang dapat membongkar program, memulai GDB , dan mencari tahu alamat benda ini di file yang dapat dieksekusi. Anda mungkin akrab dengan metode ini dari pekerjaan laboratorium. Kemudian, selama buffer overflow, penyerang dapat mengambil alamat ini, memasukkannya ke dalam buffer overflow yang dihasilkan, dan memverifikasi bahwa fungsinya kembali ke run shell .

Untuk membuatnya jelas, saya akan menggambarnya. Jadi, Anda memiliki tumpukan yang terlihat seperti ini: di bagian bawah ada buffer yang meluap, di atasnya adalah indikator gap yang disimpan, di atasnya adalah alamat pengirim untuk prosess_msg . Kiri bawah kita memiliki stack pointer baru yang memulai fungsi, di atasnya break pointer baru, kemudian stack pointer yang akan digunakan, dan lebih tinggi lagi adalah break pointer dari frame sebelumnya. Semuanya terlihat cukup akrab.



Seperti yang saya katakan, selama serangan, GDB digunakan untuk mencari tahu apa alamat run shell . Jadi, ketika buffer overflows, kita cukup meletakkan alamat run shell di sini di sebelah kanan. Ini sebenarnya adalah perpanjangan yang cukup sederhana dari apa yang sudah kita ketahui bagaimana melakukannya. Pada dasarnya, ini berarti bahwa jika kita memiliki perintah yang meluncurkan shell, dan jika kita dapat membongkar file biner untuk mencari tahu di mana alamat ini berada, kita dapat dengan mudah meletakkannya di limpahan limpahan ini yang terletak di bagian bawah tumpukan. Ini sangat sederhana.

Jadi, ini adalah contoh yang sangat sembrono, karena programmer, untuk beberapa alasan gila, meletakkan fungsi ini di sini, dengan demikian menghadirkan penyerang hadiah nyata.
Sekarang anggaplah bahwa alih-alih memanggil hal ini run_shell , kita akan menyebutnya run_boring , dan kemudian menjalankan perintah / bin / ls . Namun, kami tidak kehilangan apa-apa, karena kami akan memiliki string char * bash_path di atas , yang akan memberi tahu kami jalan ke bin / bash ini .



Jadi hal yang paling menarik tentang ini adalah bahwa seorang penyerang yang ingin menjalankan ls dapat "mengurai" program dan menemukan lokasi run_boring , dan ini sama sekali tidak menyenangkan. Tetapi pada kenyataannya, kita memiliki garis dalam memori yang menunjuk ke jalur shell, di samping itu, kita tahu sesuatu yang menarik. Ini adalah bahwa meskipun program tidak memanggil sistem dengan argumen / bin / ls , ia masih membuat semacam panggilan.

Jadi, kita tahu bahwa sistem harus entah bagaimana terhubung dengan program ini - sistem ("/ bin / ls") . Oleh karena itu, kita dapat menggunakan dua operasi void ini untuk benar-benar mengasosiasikan sistem dengan argumen char * bash_path ini . Hal pertama yang kita lakukan adalah masuk ke GDB dan mencari tahu di mana sistem ini ("/ bin / ls") berada di gambar proses biner. Jadi, Anda hanya pergi ke GDB , cukup ketik print_system dan dapatkan informasi tentang offset-nya. Ini cukup sederhana, dan Anda dapat melakukan hal yang sama untuk bash_path . Artinya, Anda cukup menggunakan GDB untuk mencari tahu di mana benda ini hidup.

Setelah selesai, Anda perlu melakukan sesuatu yang lain. Karena sekarang kita benar-benar perlu mencari cara bagaimana memanggil sistem menggunakan argumen yang kita pilih. Dan cara kita melakukan ini pada dasarnya terdiri dari memalsukan bingkai panggilan untuk sistem. Jika Anda ingat, frame adalah apa yang digunakan oleh kompiler dan perangkat keras untuk mengimplementasikan panggilan stack.

Kami ingin mengatur tumpukan seperti apa yang saya gambarkan dalam gambar ini. Sebenarnya, kita akan memalsukan sistem yang seharusnya ada di stack, tetapi tepat sebelum itu benar-benar mengeksekusi kodenya.

Jadi, di sini kita memiliki argumen sistem, ini adalah baris yang ingin kita jalankan. Di bagian bawah kita memiliki garis di mana sistem harus kembali ketika garis dengan argumen selesai. Sistem mengharapkan tumpukan terlihat seperti itu sebelum eksekusi dimulai.



Kami dulu beranggapan bahwa tidak ada argumen ketika Anda melewati fungsi, tetapi sekarang terlihat sedikit berbeda. Kita hanya perlu memastikan bahwa argumen ada dalam kode overflow yang kita buat. Kita hanya perlu memastikan bahwa frame panggilan palsu ini ada dalam array ini. Dengan demikian, pekerjaan kami adalah sebagai berikut. Ingat bahwa stack overflow berpindah dari bawah ke atas.



Pertama, kita akan meletakkan alamat sistem di sini. Dan di atas kita akan menempatkan beberapa alamat pengirim sampah . Ini adalah tempat di mana sistem akan kembali setelah selesai. Alamat ini akan menjadi set byte acak. Di atasnya kita akan meletakkan alamat bash_path . Apa yang terjadi ketika buffer overflow sekarang?

Setelah prosess_msg mencapai garis finish, dia akan berkata: "OK, ini adalah tempat di mana saya harus kembali"! Kode sistem terus berjalan, bergerak lebih tinggi dan melihat bingkai panggilan palsu yang kami buat. Untuk sistem, tidak ada yang menakjubkan yang akan terjadi, ia akan mengatakan: "Ya, ini dia, argumen yang ingin saya jalankan adalah bin / bash ", dijalankan, dan selesai - penyerang telah menangkap shell!

Apa yang sudah kita lakukan sekarang? Kami memanfaatkan pengetahuan tentang konvensi pemanggilan , konvensi pemanggilan , sebagai platform untuk membuat bingkai tumpukan palsu, atau nama bingkai palsu, menurut saya. Dengan menggunakan bingkai panggilan palsu ini, kita dapat melakukan fungsi apa pun yang dirujuk dan yang sudah ditentukan oleh aplikasi.

Pertanyaan selanjutnya yang harus kita tanyakan adalah: bagaimana jika program tidak memiliki baris char * bash_path ini sama sekali ? Saya perhatikan bahwa baris ini hampir selalu ada dalam program. Namun, anggaplah kita hidup di dunia yang terbalik, dan itu masih belum ada. Jadi apa yang bisa kita lakukan untuk meletakkan baris ini dalam sebuah program?

Hal pertama yang dapat Anda lakukan untuk ini adalah menentukan alamat yang benar untuk bash_path , menempatkannya lebih tinggi, di sini di kompartemen tumpukan kami, memasukkan di sana tiga elemen, yang masing-masing berukuran 4 byte:

/ 0
/ pat
/ bin



Tetapi bagaimanapun juga, pointer kita datang ke sini dan - booming! - Masalahnya sudah selesai. Dengan begitu, Anda sekarang dapat memanggil argumen hanya dengan menempatkannya di kode shell Anda. Mengerikan, bukan? Dan semua ini dibangun sebelum serangan BROP penuh. Tetapi sebelum Anda menunjukkan serangan BROP penuh, Anda perlu memahami bagaimana Anda hanya rantai bersama hal-hal yang sudah ada di dalam kode. Ketika saya memiliki alamat pengirim yang dibuang di sini, kami hanya ingin mengakses shell. Tetapi jika Anda seorang penyerang, maka Anda bisa mengarahkan alamat pengirim ini, atau alamat pengirim, ke sesuatu yang benar-benar dapat digunakan. Dan jika Anda melakukan ini, maka Anda bisa merangkai beberapa fungsi berturut-turut menjadi satu baris, beberapa tanda fungsi dalam satu baris. Ini memang pilihan yang sangat kuat.

Karena jika kita hanya mengatur alamat pengirim untuk lompatan, maka setelah itu program biasanya macet, yang mungkin kita tidak inginkan. Oleh karena itu, ada baiknya menghubungkan beberapa hal ini bersama-sama untuk melakukan hal-hal yang lebih menarik dengan program ini.

Misalkan tujuan kita adalah bahwa kita ingin memanggil sistem beberapa kali secara sewenang-wenang. Kami tidak hanya ingin melakukan ini sekali saja, kami akan melakukannya beberapa kali secara sewenang-wenang. Jadi bagaimana ini bisa dilakukan?

Untuk melakukan ini, kami menggunakan dua informasi yang sudah kami ketahui cara mendapatkannya. Kami tahu cara mendapatkan alamat sistem - Anda hanya perlu melihat ke GDB dan menemukannya di sana. Kami juga tahu cara menemukan alamat baris ini, bin / bash . Sekarang, untuk memulai serangan ini menggunakan beberapa panggilan ke sistem, kita perlu menggunakan gadget. Ini membawa kita lebih dekat dengan apa yang terjadi di BROP .

Jadi yang kita butuhkan sekarang adalah menemukan alamat dari dua operasi kode ini: pop% eax and ret . Yang pertama menghapus bagian atas tumpukan dan meletakkannya di register eax , dan yang kedua meletakkannya di pointer instruksi eip . Inilah yang kami sebut gadget. Sepertinya seperangkat instruksi perakitan yang dapat digunakan penyerang untuk membangun serangan yang lebih ambisius.



Gadget ini adalah alat standar yang digunakan peretas untuk menemukan hal-hal seperti file biner. Juga mudah untuk menemukan salah satu gadget ini, dengan asumsi Anda memiliki salinan biner, dan kami tidak repot-repot dengan pengacakan. Hal-hal ini sangat mudah ditemukan, juga sangat mudah untuk menemukan alamat sistem dan sebagainya.

Jadi, jika kita memiliki salah satu gadget ini, mengapa kita dapat menggunakannya? Tentu saja, melakukan kejahatan! Untuk melakukan ini, Anda dapat melakukan hal berikut.

Misalkan kita mengubah tumpukan kita sehingga terlihat seperti ini, exploit, seperti sebelumnya, diarahkan dari bawah ke atas. Hal pertama yang kita lakukan adalah menempatkan alamat sistem di sini, dan di atasnya kita masukkan alamat gadget pop / ret . Bahkan lebih tinggi, kami memasukkan alamat bash_path , dan kemudian mengulangi semuanya: dari atas kami lagi menempatkan alamat sistem, alamat pop / ret gadget dan alamat bash_path .



Apa yang akan terjadi di sini sekarang? Ini akan sedikit rumit, jadi catatan kuliah ini tersedia di Internet, dan untuk sekarang Anda bisa mendengarkan apa yang terjadi di sini, tetapi ketika saya pertama kali memahami ini, rasanya seperti memahami bahwa Santa Claus tidak ada!

Kami akan mulai dari tempat entri entri berada, kembali ke sistem tempat pernyataan ret akan menghapus item dari tumpukan menggunakan perintah pop , jadi sekarang bagian atas penunjuk tumpukan ada di sini. Jadi, kami menghapus elemen menggunakan pop , lalu mengembalikan prosedur ret , yang mentransfer kontrol ke alamat kembali yang dipilih dari tumpukan, dan alamat kembali ini ditempatkan di sana dengan perintah panggilan . Jadi, kami kembali melakukan panggilan ke sistem, dan proses ini dapat diulangi lagi dan lagi.



Jelas bahwa kita dapat menghubungkan urutan ini untuk melakukan sejumlah hal yang sewenang-wenang. Pada dasarnya, kernel mendapatkan apa yang disebut pemrograman berorientasi terbalik. Harap perhatikan bahwa kami tidak melakukan apa pun pada tumpukan ini. Kami melakukan apa yang memungkinkan kami untuk mencegah eksekusi data tanpa merusak apa pun. Kami baru saja melakukan lompatan tak terduga untuk melakukan apa yang kami inginkan. Sebenarnya itu sangat, sangat, sangat, pintar.

Dan yang menarik adalah bahwa pada level tinggi kami telah mengidentifikasi model baru ini untuk komputasi. , , , . , , . , - . , , . , . . , . , , stack canaries.

, «» , . , , «» ret address saved %ebp , - , «». , ret , , «», , - . stack canaries .

, «». , . , «»?

, , , .
, , , «» , «» «».

, , , «» , , .
, - , «» , . , ? ?
, fork . , fork . , , , fork , , , , «» . , stack canaries .

«»? . , , , «». «» . .



, , – , «». , , 0. , «», . , :

«, «»! , 0. «»! 1 – «», 2 – . , 2- . , , «».



, , , .

«», , , . , , «».

57:10

:

Kursus MIT "Keamanan Sistem Komputer". Kuliah 3: Buffer Overflows: Eksploitasi dan Perlindungan, Bagian 3


Versi 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?

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


All Articles