Fungsi-fungsi bahasa C yang rawan terhadap buffer over flow exploite:
strcpy (char *dest, const char *src)
strcat (char *dest, const char *src)
gets (char *s)
scanf (const char *format, ...)
printf (conts char *format, ...)
Buffer Overflow merupakan salah satu penyebab yang paling banyak menimbulkan masalah pada keamanan komputer baik yang bersifat lokal maupun jaringan. Menurut laporan CERT/CC, buffer overflow merupkan penyebab dari 50% semua bug keamanan yang dilaporkan dan dijadikan advisori oleh CERT/CC. Riset yang dilakukan oleh Crispin Cowan dan teamnya menganggap buffer overflow sebagai Vulnerbility of Decade.
Dengan tersedianya program-program eksploitasi di Internet, baik dalam bentuk source code maupun binary seringkali menjadi menjadi alat untuk membuka celah keamanan sehingga membuat orang tertarik untuk melakukan eksperimen yang sporadis tanpa memperdulikan akibat yang dapat ditimbulkannya. Banyak orang yang mengklaim dirinya sebagai seorang cracker jika sudah berhasil mengekspoitasi komputer orang lain, tanpa memahami cara kerja eksploitasi tersebut sesungguhnya.
Buffer overflow memiliki arti suatu keadaan di mana data yang diisikan ke suatu buffer mempunyai ukuran yang lebih besar dibandingkan ukuran buffer itu sendiri. Untuk lebih memahami buffer overflow, dapat dianalogikan dengan kehidupan sehari-hari, yaitu saat kita mengisi bak mandi melebihi daya tampungnya, maka air yang kita isikan akan meluap (overflow).
Berikut ini contoh sebuah program dalam bahasa C yang mengandung buffer overflow.
# Coba2.c
#include
void fungsi(char* txt)
{
char buffer[4];
strcpy(buffer, txt);
}
int main()
{
char buffer[17];
int i;
for (I=0; i<16;I++)
buffer[i]=0x19;
fungsi(buffer);
return 0;
}
Setelah sukses dikompilasi maka ketika program diatas dieksekusi akan ada pesan segmentation violation. Mengapa demikian ? Karena di fungsi fungsi(), variable array buffer didefinisikan hanya berukuran 4 byte, sedangkan data yang disalinkan kepadanya berukuran sebesar 17 byte. Sebagai catatan, fungsi stcpy() akan menyalinkan data yang direferensi oleh pointer txt ke buffer sampai karakter null ditemukan di txt.
Berikut ini adalah sintaks fungsi strcpy :
Char *strcpy(char *dest, const char *src);
Kondisi blok memori stack saat ter-overflow setelah pemanggilan fungsi diatas dapat dilihat pada gambar 3.
Seperti terlihat pada gambar, data yang mempunyai nilai karakter 0x19 sebesar 17 byte disalinkan ke memori stack mulai dari alamat buffer[0] ke arah stack bawah sampai memori stack yang mempunyai pointer *txt. Akibat yang fatal adalah termodifikasinya memori stack yang menyimpan alamat fungsi kembali RET. Dalam hal ini nilai RET berubah menjadi 0x19191919 yang merupakan alamat memori yang instruksinya akan dipanggil setelah fungsi fungsi() selesai dikerjakan. Tentu hal ini akan menyebabkan kesalahan karena instruksi yang terdapat pada alamat memori tersebut bukanlah instruksi yang valid.
Kondisi diatas menjadi prinsip apa yang disebut dengan eksploitasi buffer overflow, yaitu membuat buffer ter-overflow sehingga nilai dari RET termodifikasi untuk mengubah alur dari instruksi program sesuai dengan keinginan kita.
Mekanisme Eksploitasi Buffer Overflows
Secara prinsip ada dua hal penting yang harus dilakukan dalam proses eksploitasi buffer overflow. Pertama harus membuat instruksi yang di kehendaki agar dijalankan setelah buffer ter-overflow. Instruksi yang biasanya berupa kode assembly ini harus dikonversikan ke data heksadesimal. Kedua, harus menghitung alamat posisi RET dalam stack dan alamat kode instruksi. Kemudian alamat kode instruksi ini harus dimasukkan ke dalam nilai RET, sehingga jika buffer ter-overflow instruksi tersebut dijalankan.
Menghasilkan instruksi dan kemudian mengkonversinya ke format bilangan heksadesimal bukanlah hal yang mudah, perlu pemahaman cara menghitung alamat posisi RET di stack dan bagaimana cara memodifikasinya.
Memodifikasi Nilai RET
Untuk lebih mudah dimengerti, dibawah ini contoh program kecil yang tujuannya memodifikasi nilai RET sehingga instruksi yang seharusnya dikerjakan setelah pemanggilan suatu fungsi akan dilompati.
# Coba3.c
#include
void fungsi(int satu, int dua)
{
int buffer[2];
int* tmp;
// Jarak ke RET 3*4 byte
tmp = buffer+3;
//Modifikasi isi dari RET
*tmp = *tmp + 10;
}
int main()
{
int a;
a=1;
fungsi(1,2);
// Instruksi a=2 akan dilompati
a=2;
printf(“%d\n”,a);
return 0;
}
Pada pemanggilan fungsi fungsi() nilai RET dimodifikasi di dalam fungsi tersebut, sehingga instruksi a=2 yang seharusnya dikerjakan setelah kembali dari fungsi akan dilompati.
Program diatas dikompilasi dengan option –ggdb agar nanti juga dapat dimanfaatkan oleh tool gdb.
$ gcc -ggdb -o coba3 coba3.c
Seperti yang diharapkan, setelah program coba3 dieksekusi, maka dilayar monitor akan keluar tampilan 1 dan bukan 2, karena instruksi a=2 tidak dikerjakan. Di bawah ini dipaparkan secara detail bagaimana hal ini bias terjadi.
Nilai memori stack yang menyimpan nilai RET dapat dihitung dari jarak antara alamat buffer[ ] dengan alamat RET yaitu sebesar 12 byte. Nilai 3 di atas didapatkan karena operasi aritmatika terhadap sebuah pointer tergantung dari tipe data, yang dalam kasus ini adalah int yang mempunyai ukuran 4 byte. Hal berikut yang harus dilakukan adalah menghitung jarak RET ke alamat yang ingin di tuju yaitu baris yang memanggil fungsi printf(). Jarak ini akan ditambahkan ke nilai RET untuk melompati baris instruksi a=2.
Nilai 10 diatas bukanlah hasil kira-kira atau tebak-tebakan, melainkan dihitung dengan bantuan program gdb.
$ gdb coba3
………
(gdb) disassemble main
Dump of assembler code for function main :
Ox8048430
Ox8048431
Ox8048433
Ox8048436
Ox804843d
Ox8048440
Ox8048442
Ox8048444
Ox8048449
Ox804844c
Ox8048453
Ox8048456
Ox8048459
Ox804845a
Ox804845f
Ox8048300
………
Setelah fungsi fungsi() selesai dikerjakan maka seharusnya instruksi pada alamat 0x8048449
Perubahan nilai RET dengan cara seperti diatas hanyalah sebuah contoh yang mungkin tidak ada dalam dunia pemrograman. Pada hampir semua kasus, pengubahan nilai RET biasanya dilakukan dengan memanfaatkan buffer overflow seperti pada program coba2.c Pada program tersebut pointer *txt akan mengacu ke data buffer yang berisi instruksi yang akan dikerjakan setelah stack ter-overflow. Untuk itu nilai RET juga harus dimodifikasi sehingga mengacu ke instruksi tersebut.
Mencegah Buffer Overflow
Ada sebuah ungkapan yang bagus sekali “mencegah lebih baik daripada mengobati”. Hal yang sama berlaku juga pada buffer overflow. Mencegah buffer overflow jauh lebih baik dari pada memperbaikinya. Namun adalah lebih mudah mengatakan daripada melakukannya, terlebih lagi bila menggunakan bahasa pemrograman C. Hal ini disebabkan karena :
- C tidak memeriksa batasan array dan referensi pointer secara otomatis
- Banyak fungsi-fungsi yang disediakan oleh library standar C yang tidak aman
Namun demikian, keadaan tersebut tidak mengurangi riset-riset yang dilakukan untuk mencegah terjadinya buffer overflow. Berikut ini beberapa metoda yang digunakan untuk mencegah buffer overflow]:
Keterangan | Nama Fungsi | Alternatif Solusi |
Membaca sebuah baris dari stdin ke buffer | Gets (char *s) | Menggunakan fgets (char *s, int size, FILE *stream) |
Menyalinkan string sember ke sebuah buffer | Strcpy (char *dest, const char *src) | Gunakan strncpy (char *dest, const char *scr, size_t n) |
Menambahkan sebuah string ke string lain | Strcat (char *dest, const char *src) | Gunakan strncat (char *dest, const char *src, size_t n) |
Mencetak output berdasarkan suatu format string | Sprintf (char *str, const char *format……) | Gunakan snprintf (char *str, size_t size, const char *format,…..) |
Membaca input dari stream pointer sesuai dengan format tertentu | Fscanf (FILE *stream, const char *format, ……..) | Gunakan precision specifer |
Mengembalikan nama path absolut | Realpath (const char *path, char *resolved_path) | Alokasikan buffer sebesar MAXPATHLEN, serta periksa juga argumen untuk memastikan bahwa argumen input tidak lebih panjang daripada MAXPATHLEN |
Memparsing option perintah baris | Getopt_log (int argc, char * const argv [ ], const char *optstring, const struct option *longopts, int *longindex) | Potong semua input string agar berukuran cukup sebelum memberikannya ke fungsi ini |
Melakukan pemrograman yang baik adalah sebuah tugas yang sulit, terlebih lagi bila program tersebut di tulis dengan bahasa C. Berikut adalah beberapa hal yang disarankan dalam melakukan pemrograman dengan bahasa C.
Penggunaan fungsi standar C yang lebih aman. Beberapa fungsi standar C seharusnya dihindari, karena mereka tidak melakukan pengecekan terhadap panjang string yang dimasukkan. Berikut ini adalah beberapa buah fungsi standar C umum yang tidak aman beserta alternatif solusinya:
Salah satu program yang menggunakan metoda ini adalah libsafe. Libsafe akan mencegah semua panggilan ke fungsi-fungsi yang diketahui tidak aman, kemudian ia akan menggantikannya dengan fungsi-fungsi serupa dengan yang awal namun memiliki fasilitas untuk memastikan bahwa semua buffer overflow berada di dalam stack frame yang aktif.
Referensi:
1. http://budi.insan.co.id/
2. www.cert.org
No comments:
Post a Comment