Pengantar untuk mengeksploitasi dan membalikkan menggunakan IDA GRATIS dan alat gratis lainnya. Bab 2

Pada bagian pertama, kami memasang beberapa alat yang akan berguna bagi kami untuk mengikuti kursus ini. Fitur mereka adalah mereka semua gratis. Kami tidak akan menggunakan alat berbayar apa pun, dan dari mereka yang memiliki versi berbayar, seperti IDA atau PYCHARM, kami akan menggunakan versi GRATIS atau KOMUNITAS.

Mari kita lihat beberapa konsep sebelum kita mulai dengan latihan.

Apa itu TAS?

BAG adalah hasil dari kegagalan atau kekurangan dalam proses menciptakan program komputer (perangkat lunak) atau komputer. Kegagalan yang ditentukan dapat terjadi pada setiap tahap siklus hidup perangkat lunak, meskipun kegagalan yang paling jelas terjadi pada tahap pengembangan dan pemrograman.

Seperti yang selalu saya katakan, seorang programmer dapat membuat kesalahan, dan kesalahan ini dapat menyebabkan crash atau bug program. Sejauh ini, saya belum mengatakan hal baru.

Pertanyaannya adalah untuk mengetahui perbedaan antara BAG dan VULNERABILITY, jadi mari kita lihat apa VULNERABILITY itu.

Apa itu VULNERABILITY?

KERENTANAN adalah jenis bug tertentu dalam suatu program yang memungkinkan menggunakannya untuk melanggar keamanan sistem komputer.

Dengan demikian, kerentanan memungkinkan Anda untuk melakukan tindakan yang programnya tidak dimaksudkan, dan menyalahgunakannya.

Dengan kata lain, kerentanan adalah jenis bug tertentu, bagian dari mereka.



Tentu saja, ada banyak jenis kerentanan. Kami akan fokus pada studi dan eksploitasi kerentanan di WINDOWS.

Apa itu EXPLOIT?


EXPLOIT adalah program komputer yang mencoba memanfaatkan beberapa kerentanan dari program lain. Tujuan akhir dari suatu eksploitasi dapat berbahaya, misalnya, sebagai menghancurkan atau mematikan sistem yang diserang, meskipun biasanya merupakan pelanggaran terhadap langkah-langkah keamanan untuk mendapatkan akses ke informasi dengan cara yang tidak sah dan menggunakannya untuk kepentingan mereka sendiri atau sebagai sumber serangan lain terhadap pihak ketiga.

Penyalahgunaan kerentanan dapat menyebabkan kegagalan aplikasi atau sistem itu sendiri, pelaksanaan kode asli pada mesin lokal atau jarak jauh. Operasi dan kerumitannya bergantung pada kerentanan itu sendiri, lingkungan dan ukuran yang dimiliki tujuan selama operasi.

Jenis kerentanan pertama yang akan kita periksa adalah buffer overflows. Kami akan mulai dengan contoh-contoh paling sederhana, dan kemudian kami akan secara bertahap meningkatkan kompleksitas.

Pada awalnya, fitur keamanan sistem tidak akan diaktifkan, tetapi secara bertahap kami akan mengaktifkannya untuk mengetahui bagaimana kami dapat menghadapinya dan dalam situasi apa.

Apa itu BUFFER?


BUFFER adalah ruang memori dengan ukuran tertentu yang disediakan untuk penyimpanan dan manajemen data.

Contoh dasar adalah stoples 20 liter, yang saya miliki untuk menyimpan konten. Ini bisa kurang dari atau sama dengan 20 liter, yang merupakan ukuran maksimum. Jika Anda ingin menyimpan lebih banyak dalam satu tangki, Anda harus menemukan cara untuk menambah ukuran buffer, jika tidak ketika Anda mencoba menyimpan, misalnya, 40 liter dalam kaleng 20 liter, itu akan meluap.

Apa itu BUFFER OVERFILL?


BUFFER OVERFLOW terjadi ketika sebuah program komputer melebihi jumlah memori yang disediakan untuk itu dengan menulis data ke blok memori yang berdekatan.

https://www.welivesecurity.com/la-es/tag/buffer-overflow-la-es

Sebenarnya, buffer overflow terjadi dalam aplikasi ketika tidak memiliki pemeriksaan keamanan yang diperlukan dalam kode programnya, seperti mengukur jumlah data yang akan disalin ke buffer dan yang tidak melebihi ukuran buffer.

Jenis buffer overflows yang paling umum adalah stack buffer overflows dan heap buffer overflows.

Di sini kita melihat definisi buffer overflow, dan dalam contoh kita sebelumnya, jika saya mencoba menuangkan 40 liter ke dalam tangki 20 liter, itu akan meluap, seperti yang kita pahami. Ini adalah overflow yang menyebabkan buffer overflow, mis. meluap tangki saya ketika kapasitas maksimumnya terlampaui.

Sekarang jelaskan perbedaan antara tumpukan dan tumpukan.

Apa itu STACK?


STACK digunakan untuk menyimpan variabel fungsi lokal yang diperlukan hanya selama fungsi dijalankan. Dalam sebagian besar bahasa pemrograman, penting bahwa kita tahu pada waktu kompilasi seberapa besar variabel jika kita ingin menyimpannya di stack.

Apa itu BANYAK?


Heap digunakan untuk menyimpan memori dinamis, masa manfaat yang tidak diketahui sebelumnya, tetapi diharapkan akan bertahan beberapa saat. Jika kita tidak tahu ukurannya atau ditentukan saat runtime, ukuran harus dihitung dan dicadangkan di heap.

Heap juga digunakan untuk objek yang ukurannya bervariasi karena kita tidak tahu pada waktu kompilasi berapa lama mereka akan digunakan.

Saya telah bekerja di perusahaan kami selama lebih dari 13 tahun sebagai penulis eksploitasi, dan hal pertama yang kami lakukan dengan semua orang yang disewa bahkan kepada saya ketika saya bergabung adalah mencoba mengurai tumpukan dan tumpukan GERARDO RICHART yang terkenal. Dia adalah salah satu pendiri CORE SECURITY dan seorang guru analisis eksploit.

Kami akan mulai perlahan dengan tumpukan paling sederhana. Tentu saja, seperti yang saya katakan, mereka dikompilasi pada saat ini dengan perlindungan minimal dan 32-bit untuk memfasilitasi operasi.

Mari kita lihat kode sumber untuk tugas STACK1.

https://drive.google.com/open?id=16btJAetpa1V5yHDZE2bnnFWTQNpsUR4H

Kami melihat folder dengan latihan, dan di dalamnya ada kode sumber STACK1 yang disebut STACK1_VS_2017.CPP.

#define _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_DEPRECATE

#include <stdlib.h>
#include  <stdio.h> 
#include "Windows.h"


int main(int argc, char **argv) 
{


	MessageBoxA((HWND)-0, (LPCSTR) "Imprimir You win..\n", (LPCSTR)"Vamosss", (UINT)0);

	int cookie;
	char buf[80];

	printf("buf: %08x cookie: %08x\n", &buf, &cookie);
	gets(buf);

	if (cookie == 0x41424344) printf("you win!\n");

}


Kami akan mencoba memahami kode ini dan melihat di mana buffer overflow dapat terjadi, dan apakah itu akan menjadi buffer overflow pada tumpukan atau pada heap.

Panggilan fungsi MessageBoxA ditambahkan ke kode sumber STACK1 untuk menunjukkan kepada kami sebuah pesan kecil yang mendorong kami untuk menyelesaikannya. Ini hanyalah tambahan yang tidak mempengaruhi apa pun. Ini adalah panggilan standar ke fungsi WINDOWS yang ditentukan, yang tidak akan kami analisis di sini.

Siapa yang butuh informasi tentang fungsi ini, Anda bisa mendapatkannya di sini.

Kita tahu bahwa di dalam fungsi, jika ada variabel lokal, Anda harus memesan tempat untuk mereka.

Jadi kita dibiarkan dengan kode sumber ini dibuat oleh GERARDO RICHART.

int main(int argc, char **argv) 
{

	int cookie;
	char buf[80];

	printf("buf: %08x cookie: %08x\n", &buf, &cookie);
	gets(buf);

	if (cookie == 0x41424344) printf("you win!\n");

}

Kita melihat warna merah bagian pertama dari program, di mana disediakan ruang untuk variabel lokal. Dalam hal ini, ada dua variabel lokal, COOKIE dan BUF.

Anda bisa melihat tipe data di tabel . Jenis variabel lain juga terletak di sana.

Kode akan dikompilasi menjadi 32 bit.

Kita melihat bahwa variabel COOKIE akan bertipe INT, jadi 4 byte memori akan disediakan untuk variabel ini.



Dalam kasus variabel BUF, kita melihat bahwa itu adalah array atau serangkaian karakter (ukuran karakter = 1 byte).



Itu itu akan menjadi array 80 karakter, mis. panjangnya akan menjadi 80x1 = 80 byte.

Siapa pun yang tidak tahu array apa yang bisa membacanya di sini:

https://www.programiz.com/c-programming/c-arrays

Dengan demikian, sebuah array dapat menyimpan banyak nilai dari tipe data yang sama. Anda hanya perlu memberi tahu dia jenis data apa yang akan dan berapa banyak akan ada.



Dalam contoh pertama, ini adalah array bilangan bulat, mis. itu akan menjadi 100 byte, dan karena setiap integer mengambil 4 byte, panjang array akan menjadi 100 x 4 = 400 byte.

Pada contoh kedua, FLOAT membutuhkan 4 byte, jadi arraynya adalah 5 FLOAT, jadi panjangnya 5 x 4 = 20 byte.

Ketika kita menganalisis array pada level rendah, kita akan melihat bahwa itu adalah ruang memori yang disediakan atau buffer. Ini bukan satu-satunya cara untuk menghemat ruang memori. Ada tipe data variabel lain yang juga membutuhkan ruang cadangan dalam memori yang akan menjadi buffer untuk menyimpan kontennya.

Kembali ke latihan kami:

char buf[80];

Ini adalah array karakter yang panjangnya 80 x 1 = 80 byte, mis. sepertinya toples 20 liter kami. Jika kami mencoba menyimpan lebih dari 80 byte, bank akan meluap.

Sekarang mari kita lihat di mana buffer BUF digunakan.



Kami melihat bahwa buffer digunakan di dua tempat yang ditandai dengan panah merah.

Instruksi pertama memiliki fungsi PRINTF, yang digunakan untuk menampilkan pesan di konsol, yang akan menjadi string yang dikutip.

"buf: %08x cookie: %08x\n"

Tetapi fungsi PRINTF tidak hanya mencetak string dalam tanda kutip, tetapi juga mencetak string dalam format yang ditentukan. Persentase di dalam memberi tahu kami bahwa garis keluaran akan dibuat. Kita melihat bahwa string hanyalah argumen pertama ke fungsi. Format output dan argumen lain mungkin ada beberapa (akan ada satu untuk setiap% argumen dalam format). Dalam kasus kami, ada dua dari mereka.



Dalam hal ini, kami memiliki dua format X%, oleh karena itu, jika saya melihat tabel format PRINTF:



Kami melihat bahwa fungsi tersebut akan mengambil bilangan bulat (INT) ini dan memasukkannya ke dalam garis keluaran dengan basis sistem bilangan 16, yaitu. dalam format heksadesimal. 08 menunjukkan bahwa jika angka mengandung kurang dari 8 digit, fungsi akan mengisinya dengan spasi.

Output untuk "buf:% 31x", & buf akan seperti ini

buf:             19FED4 

Kita melihat bahwa dalam contoh ini diisi dengan spasi sebelum nomor. Ada beberapa pengubah untuk menampilkan output.

Semua kasus yang mungkin tercantum di sini:

http://www.cplusplus.com/reference/cstdio/printf/

Kasus kami adalah ini:





Kami melihat bahwa hasilnya tidak terpotong, diisi dengan spasi hanya jika panjang argumen yang dimasukkan kurang dari nilai. sebelum X.

Oleh karena itu, kita tahu bahwa fungsi mencetak dua angka heksadesimal, yang diperoleh dari dua argumen.

printf("buf: %08x cookie: %08x\n", &buf, &cookie);

Kita tahu bahwa suatu variabel memiliki alamat memori dan nilai yang dapat disimpan. Sepertinya tabung 20 liter kami. Itu memiliki konten atau artinya, yaitu liter disimpan di dalam, tetapi juga jika saya memiliki garasi penuh kaleng serupa saya perlu beberapa cara untuk menentukan di mana kaleng yang saya inginkan adalah di antara semua yang saya miliki.

Simbol & menunjukkan ini. Ia mengembalikan alamat atau lokasi toples, bukan isinya atau nilainya.

Definisi AMPERSAND


AMPERSAND digunakan untuk menunjukkan alamat memori dari variabel di mana data akan disimpan.

Oleh karena itu, jika saya menjalankan executable di konsol, saya akan melihat, misalnya, bahwa ketika mengeksekusi fungsi PRINTF, ia akan mencetak:



Alamat-alamat dapat berubah pada PC Anda, tetapi karena alamat bawah kedua alamat cocok dengan alamat BUF, kita melihat bahwa mereka terletak dengan cara ini:



Alamat BUF kurang dari alamat COOKIE, sehingga akan meningkat.

Dan apa yang dikatakan alamat variabel ini kepada kami? (Dalam kasus saya, & BUF = 0x19FED4 dan & COOKIE = 0x19FF24)



Keduanya dalam format heksadesimal. Ingat ini format% X? Jadi saya menempatkan 0x di depan untuk membedakan angka desimal yang akan kami wakili tanpa penambahan apa pun.

Jika saya melakukan pengurangan di konsol PYTHON atau di PYCHARM:



Kami mendapatkan hasil 80 byte, karena variabel COOKIE seharusnya dimulai tepat di mana buffer BUF berakhir, jadi perbedaannya memberi kita ukuran buffer.

Seringkali, ketika kita membuat tipe variabel ini berdasarkan kode sumber, mungkin saja kompiler memberi kita ukuran yang lebih besar daripada yang dicadangkan dalam kode sumber. Kompiler menjamin bahwa ia akan memesan setidaknya 80 byte, yaitu dia bisa memesan lebih banyak, tidak sedikit.

Faktanya adalah bahwa kita sudah tahu sesuatu tentang kode, ukuran variabel dan lokasinya karena fakta bahwa ia memiliki fungsi PRINTF.

Sekarang mari kita lihat tempat lain di mana buffer BUF digunakan, karena sekarang program hanya mencetak alamatnya, tetapi tidak menggunakannya untuk menyimpan data di dalamnya.

int main(int argc, char **argv) 
{

	int cookie;
	char buf[80];

	printf("buf: %08x cookie: %08x\n", &buf, &cookie);
	gets(buf);

	if (cookie == 0x41424344) printf("you win!\n");

}

Di sini, pada garis merah, GET adalah fungsi untuk memasukkan data dari keyboard. Data akan dimasukkan sampai saya menekan Enter.

Program tidak dapat membatasi jumlah data yang dimasukkan oleh pengguna, dan juga tidak ada cara untuk memverifikasi data ini. Segala sesuatu yang dimasukkan sebelum menekan tombol ENTER disalin ke buffer BUF.

Ini masalahnya. Kami mengatakan bahwa BUF hanya dapat menyimpan maksimum 80 byte, jadi jika kami memasukkan lebih banyak, kami akan membuat buffer overflow, dan di sini semua syarat untuk ini, karena jika pengguna menulis lebih dari 80 byte, maka tangki kami akan meluap dan cairan akan menetes ke bawah .



Faktanya adalah bahwa di bawah BUF adalah variabel COOKIE, sehingga overflow akan menimpa dan mengisinya dengan nilai yang dapat Anda kontrol.

Misalnya, jika seseorang yang mencetak menulis 80 * A dan 4 * B, 80 * A akan mengisi BUF, dan 4 * B akan mengisi COOKIE, dan seperti yang kita tahu, ketika seseorang mencetak karakter di konsol, nilainya akan tetap rendah. ASCII.



Karena cookie akan diisi dengan empat huruf B, yang setara dengan nilai 0x42, kami dapat menjamin bahwa nilai cookie akan menjadi 0x42424242, yaitu di komputer saya, alamat cookie 0x19FF24 akan memiliki 0x42424242 sebagai konten.

0x19FF24 => 42424242



Faktanya adalah bahwa kita telah melihat cara meluap dan mengontrol nilai COOKIE.

int main(int argc, char **argv) 
{

	int cookie;
	char buf[80];

	printf("buf: %08x cookie: %08x\n", &buf, &cookie);
	gets(buf);

	if (cookie == 0x41424344) printf("you win!\n");

}

Anda harus mencetak "Anda menang" untuk menyelesaikan latihan. Untuk ini, COOKIE harus sama dengan nilai 0x41424344, dan jika tidak ada luapan, ini tidak mungkin, karena nilai COOKIE tidak pernah berubah sejak awal program. Kami tidak akan dapat mencetak "Anda menang", dan untuk ini kami menggunakan buffer overflow, yang mengatakan bahwa ini dapat menyebabkan program melakukan beberapa tindakan selain dari apa yang diprogram.

Dalam hal ini, Anda tidak pernah bisa mengetik "Anda menang", hanya overflow yang akan memungkinkan Anda melakukan ini.

AAAAAAAA

Dengan kata lain, alih-alih lewat, misalnya, 80 * A dan 4 * B, untuk mencetak "Anda menang", Anda harus melewati 80 * A dan kemudian huruf DCBA, karena ini akan menyebabkan nilai disimpan dalam COOKIE ASCII.

44434241

https://www.arumeinformatica.es/blog/los-formatos-big-endian-y-little-endian/

Format penyimpanan data adalah LITTLE ENDIAN. Dengan kata lain, data dalam memori disimpan dalam urutan terbalik, untuk membuatnya lebih sederhana.



Dan jika urutan 0x41424344 disimpan, sistem akan menyimpannya dalam memori sebagai:

44 43 42 41

Karena alasan ini, ketika menyalin ke memori, menyalin akan terjadi saat kita mengetik, jadi kita harus menulis nilai dalam urutan terbalik sehingga ketika membaca dari memori itu berada dalam kondisi yang tepat.

Kita dapat menjalankan executable di konsol.



Dan kursor akan berkedip, ketika fungsi GET meminta saya untuk memasukkan input. Ketik 80 karakter A dengan hati-hati dan kemudian DCBA.

Di konsol PYTHON atau PYCHARM, saya dapat mencetak baris, menyalinnya tanpa tanda kutip dan menempelkannya ke konsol agar tidak mencetaknya seperti orang gila, dan kemudian tekan tombol ENTER untuk memasukkannya.





Kami melihat bahwa kami mendapat "Anda menang".

Kita bisa melihat ini di debugger. Untuk ini kita akan menggunakan X64DBG.



Saya memilih versi 32 bit.





Jika kami berhenti di perpustakaan NTDLL.DLL, kami menekan RUN lagi dengan F9.

Kita melihat bahwa debugger berhenti pada instruksi pertama dari modul STACK1, yang disebut POIN MASUK atau instruksi pertama yang dijalankan oleh modul.



Jelas ini tidak seperti kode sumber kami. Anda harus memahami bahwa kompiler menambahkan banyak kode untuk membuat pekerjaan yang dapat dieksekusi dan berjalan dengan benar. Kami akan mencoba menemukan fungsi utama kami. Kita dapat mengarahkan diri kita sendiri dengan melihat garis-garis program.



Kami memilih hanya pencarian di wilayah saat ini. Kita tahu bahwa garis akan berada di bagian yang sama.



Di sini kita melihat bahwa ada baris-baris program dan lainnya yang ditambahkan oleh kompiler. Kami mengklik dua kali pada salah satu baris kami.



Sekarang Anda bisa melihat lebih banyak. Kami melihat panggilan ke fungsi MessageBoxA, PRINTF, GETS dan perbandingan dengan nilai 0x41424344.

Selain itu, kami menambahkan plugin untuk mendekompilasi SNOWMAN. Kita dapat mencoba melihat bagaimana ia mendekompilasi kode, mis. bagaimana dia mencoba untuk mendapatkan kode sumber atau sesuatu yang semirip mungkin dari file yang dikompilasi.





Kita melihat bahwa ini tidak sempurna, tetapi lebih baik daripada apa itu.

Saya akan meletakkan BP di awal fungsi dan tekan F9 sampai debugger berhenti.



Bagi mereka yang tidak tahu apa fungsi argumen.



Dalam kasus kami, fungsi utama memiliki argumen, tetapi mereka tidak digunakan di dalam fungsi.

int main(int argc, char **argv) 
{

	int cookie;
	char buf[80];

	printf("buf: %08x cookie: %08x\n", &buf, &cookie);
	gets(buf);

	if (cookie == 0x41424344) printf("you win!\n");

}

Di sini kita melihat bahwa ada dua argumen, dan mereka melewati stack ketika dieksekusi dikompilasi menjadi 32 bit.

Tepat sebelum pemanggilan fungsi, argumen akan disimpan di stack.

Ketika kita berhenti di awal fungsi, nilai pertama pada stack adalah RETURN ALAMAT, yaitu di mana fungsi akan kembali setelah fungsi selesai, dan di bawah nilai ini akan menjadi argumen fungsi ini.

Jika saya klik kanan pada RETURN ALAMAT dan pilih FOLLOW DWORD IN DISASSEMBLER, saya akan melihat di mana debugger harus kembali setelah fungsi selesai.



Dia akan kembali ke sini. Ini berarti bahwa fungsi utama dipanggil dari panggilan yang lebih tinggi. Saya dapat menempatkan BP di sini, memulai kembali latihan dan memastikannya.



Saya akan menempatkan BP sedikit lebih awal dan me-reboot program.



Debugger akan berhenti di sini.



Ini akan menyimpan argumen ke fungsi utama menggunakan instruksi PUSH ini.

Di bawah ini adalah tautan untuk mereka yang ingin tahu lebih banyak tentang argumen fungsi:

https://publications.gbdirect.co.uk/c_book/chapter10/arguments_to_main.html

Ini tidak terlalu sulit. Argumen pertama ke ARGC adalah INT, yang menunjukkan jumlah parameter konsol yang digunakan untuk menjalankan program, termasuk path ke executable, dan ARGV adalah array pointer ke string.

Kita melihat bahwa jika saya mengubah baris perintah, saya akan memberikan lebih banyak argumen dan memuat ulang.





Di sini debugger berhenti ketika hendak menyimpan argumen menggunakan instruksi PUSH. Argumen pertama yang disimpan adalah yang terjauh, dan yang terakhir yang Anda simpan akan menjadi argumen pertama untuk fungsi tersebut.

Saya melacak dan setiap instruksi PUSH menyimpan argumen.



Di sini saya bisa melihat argumen fungsi. Di atas adalah argumen pertama ke ARGC. Ini adalah 3, karena menandai jumlah argumen yang diteruskan ke konsol.



Berikut 3 argumen.

Sekarang kita tekan F7 untuk melakukan STEP INTO dan masuk ke fungsi.

Di sini kita melihat bahwa ketika memasuki CALL, debugger menyimpan RETURN ALAMAT ke tumpukan.



Jadi, seperti yang sudah kita katakan ketika memasuki fungsi, hal pertama yang akan disimpan di stack adalah RETURN ADDRESS (dalam kompilasi 32-bit), dan di bawah ini adalah argumen untuk fungsi, pertama argumen pertama, dan kemudian sisanya secara berurutan.



Argumen kedua, seperti yang kita lihat, adalah array pointer. Di sini kita melihat dalam memori bahwa ada tiga pointer ke tiga baris yang dilewatkan sebagai argumen.



Di sini kami berhenti di awal fungsi, sedikit lebih rendah kami memiliki ALAMAT KEMBALI dan ARGUMEN.

Kami mengklarifikasi bahwa file tersebut dikompilasi dalam 32-bit, karena dalam versi 64-bit, argumen diteruskan dengan cara yang berbeda. Kami akan melihatnya nanti.

Kemudian fungsi mulai dijalankan. Hal pertama adalah yang disebut PROLOGUE, yang menyimpan nilai EBP dari fungsi yang disebut milik kita.



Ini akan menjaga nilai EBP tepat di atas alamat pengirim.



Jika saya mengikuti instruksi menggunakan F7.

Saya melihat bahwa nilai EBP GUARDADO ada di tumpukan di atas alamat pengirim.



Instruksi berikut dalam PROLOG:

MOV EBP, ESP

Ini menetapkan nilai EBP untuk fungsi saat ini yang disimpan dan adalah fungsi induk yang memanggil kita (Dalam kasus ini, fungsi utama saya adalah fungsi berbasis EBP, dalam kasus lain mungkin berbeda, dan kami lihat nanti)

Dengan menempatkan nilai ESP saat ini di EBP, kami memastikan bahwa kami membuat bingkai untuk fungsi kami saat ini.



Sekarang, karena fungsi ini didasarkan pada EBP atau EBP BASED, nilai EBP akan disimpan di dalam fungsi dan akan diterima sebagai referensi, dan ESP akan berubah.



Nilai EBP ini diambil sebagai basis.

Dalam fungsi berbasis EBP, variabel dan argumen dapat dinamai berdasarkan jaraknya dari alamat ini, yang akan disimpan dalam nilai EBP hingga epilognya.

Kita dapat melihat beberapa variabel dalam daftar yang disebut sebagai EBP-4 atau EBP-54, merujuk pada nilai EBP yang saat ini diterima.

Kita dapat mengatakan bahwa segera setelah EBP mengambil nilainya setelah PROLOG, itu akan terlihat seperti sia-sia, sehingga argumen akan selalu pergi ke arah itu, oleh karena itu EBP + XXX mengacu pada argumen (EBP yang disimpan dan ALAMAT KEMBALI juga lebih rendah, tetapi tidak akan memiliki tautan dalam kode), sementara variabel, seperti yang akan kita lihat, akan berada di atas alamat ini, sehingga tautan ke EBP-XXX merujuk ke beberapa variabel lokal.

Dengan demikian, dalam fungsi berbasis

EBP : EBP + XXXX = argumen yang diteruskan ke fungsi
EBP - XXXX = variabel fungsi lokal

Setelah PROLOG, akan ada cara untuk menyediakan ruang untuk variabel. Dalam kasus kami, ini dilakukan dengan menggerakkan ESP ke atas sehingga ruang yang tersisa di bawah dicadangkan untuk jumlah semua panjang variabel dan, kadang-kadang, sedikit lebih berjaga-jaga, yang tergantung pada kompiler.

00401043 | 83EC 54 | SUB ESP, 54

Kita melihat bahwa ESP terletak di atas EBP, yang akan tetap menjadi tautan, dan bahwa 0x54 yang dikonversi menjadi desimal adalah 84, yang merupakan jumlah panjang BUF dan COOKIE. Ingatlah bahwa mereka masing-masing berusia 80 dan 4 tahun.







Pada eksekusi, spasi dibuat untuk variabel BUF dan COOKIE dengan ukuran 84 byte. Anda bisa mengklik kolom pertama dalam arah horisontal dan melihat nilai EBP dan menemukan nilai itu di tumpukan. Jelas, sekarang akan lebih rendah.



Saya klik dua kali di sini.



Dengan demikian, kita akan memiliki nilai mengenai EBP juga di stack.

Misalnya, EBP-4 cocok dengan daftar, -4 ditampilkan di kolom pertama tumpukan, dan dalam penjelasannya, juga ditampilkan sebagai EBP-4.



Jika saya melacak, kita melihat bahwa dari tempat ESP berada untuk memesan variabel, ia akan selalu naik, karena harus memperhitungkan ruang yang dialokasikan untuk variabel. Saat melakukan 4 PUSH untuk MessageBoxA, debugger menempatkan variabel di atas ruang yang disediakan dan meningkatkan ESP.



Jika saya melihat tumpukan, saya melihat 4 argumen hijau yang saya tambahkan di atas ruang yang disediakan ditandai dengan warna merah.



Saat Anda memasukkan fungsi MessageBoxA, ALAMAT KEMBALI dari fungsi ini disimpan di tumpukan.



Ini alamat pengirim MessageBoxA. Ketika saya sampai ke RET fungsi ini dengan menelusuri dengan F8, dan saya menjalankan MessageBoxA.



Kami melihat bahwa debugger akan kembali tepat di bawah panggilan MessageBoxA.



Dan nilai PUSH yang Anda berikan untuk fungsi MessageBoxA dan RETURN ADDRESS untuk fungsi ini telah digunakan, dan ESP lagi tepat di atas area yang dipesan, seperti sebelum fungsi apa pun dipanggil. Hal yang sama akan terjadi dengan memanggil fungsi PRINTF.

Setelah Anda melewati fungsi PRINTF, alamat BUF dan COOKIE akan dicetak.



Alamat BUF pada mesin saya akan 0x19FED4, dan alamat COOKIE akan menjadi 0x19FF24.

Di sini, program membaca alamat BUF untuk meneruskannya ke fungsi GETS dan mengisi BUF. Kami dapat memeriksa apakah alamatnya cocok dengan yang ditampilkan oleh konsol 0x19FED4.





Di sini kita melihat bahwa itu adalah EBP-54. Jika saya klik dua kali pada tumpukan yang menunjukkan -54, itu akan menunjukkan bahwa alamatnya BUF = 0x19FED4 pada mesin saya.



Sekarang, ketika saya akan menyimpan data yang dimasukkan di alamat ini, saya bisa meletakkannya di dump untuk melihat bagaimana byte disimpan di sana.





Di sini mereka. Selain itu, tidak ada yang ditampilkan di bawah, karena tidak ada data.

Ketika saya memanggil fungsi GETS menggunakan F8, saya harus pergi ke konsol, ketik dan tekan ENTER untuk mengisi buffer BUF dan menulis ulang COOKIE.



Kita melihat bahwa variabel COOKIE berada di 19FF24 pada mesin saya.

Di sini, program membandingkan cookie dengan 0x41424344.



Kita melihat bahwa EBP-4 mengatakan itu adalah COOKIE, selain alamat, jika kita menetapkan HORIZON ke nilai EBP, seperti sebelumnya.



Saya klik dua kali di sini.

Kita melihat bahwa EBP-4 adalah COOKIE, karena variabelnya berada pada level -4 stack, menghilangkan HORIZON.



Kami melihat bahwa program tidak akan melompat dan akan menunjukkan kepada kami bahwa Anda menang!





Jadi, kami secara manual mencapai tujuan yang Anda menangkan! Kata

kami, kami secara dinamis menganalisis STACK1 menggunakan X64DBG, yang merupakan debugger dan tidak memungkinkan kami untuk menganalisis program tanpa memulainya. Untuk melakukan ini, kita harus menggunakan alat lain, seperti IDA PRO, GHIDRA atau RADARE.

Saya dapat membuat model skrip untuk mengoperasikan latihan dari PYTHON.

import sys
from subprocess import Popen, PIPE

payload = b"A" * 80 + b"\x44\x43\x42\x41"

p1 = Popen(r"C:\Users\ricardo\Desktop\abos y stack nuevos\STACK1_VS_2017.exe", stdin=PIPE)
print ("PID: %s" % hex(p1.pid))
print ("Enter para continuar")
p1.communicate(payload)
p1.wait()
input()

Dalam kasus PYTHON 3, saya harus meletakkan tanda kurung dalam fungsi PRINT dan berhati-hati ketika menambahkan baris yang seharusnya byte (letakkan b di depan baris di PYTHON 2).

Saya memeriksa apakah pathnya benar dan kapan saya menjalankan file.



Baik. Kami sudah memiliki model skrip untuk PYTHON 3 untuk mengoperasikan STACK1. Pada bagian selanjutnya, kami akan melanjutkan analisis statis di IDA, RADARE dan GHIDRA.


Harap dicatat bahwa selain tugas STACK1, ada juga versi 2, 3 dan 4. Anda dapat mencoba menyelesaikannya. Mereka sangat sederhana dan mirip dengan STACK1, jadi jangan bosan.

Pada bagian selanjutnya kita akan melihat IDA GRATIS, RADARE dan GHIDRA.

Sampai jumpa di bagian 3 berikutnya.

Ricardo Narvaha
25/10/2019

PS # 1
PDF yang indah dapat diunduh di halaman rumah saya - yasha.su

PS # 2
Segera saya akan menulis kelanjutan artikel https://habr.com/en/post/464117/ tentang bagaimana saya mengumpulkan bantuan untuk Pastor Chris Kaspersky dan bagaimana dengan ini terjadi.

Jangan bosan.

All Articles