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