20 November, 2011

bahasa C yang rawan terhadap buffer over flow exploite

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

: push %ebp

Ox8048431 : move %esp,%ebp

Ox8048433 : sub $0x18,%esp

Ox8048436 : movl $0x1,0xfffffffc(%ebp)

Ox804843d : add 0xfffffff8,%esp

Ox8048440 : push $0x2

Ox8048442 : push $0x1

Ox8048444 : call 0x8048410

Ox8048449 : add $0x10,%esp

Ox804844c : movl $0x2,0xfffffffc(%ebp)

Ox8048453 : add $0xfffffff8,%esp

Ox8048456 : mov 0xfffffffc(%ebp),%eax

Ox8048459 : push %eax

Ox804845a : push $0x80484e4

Ox804845f : call

Ox8048300

………

Setelah fungsi fungsi() selesai dikerjakan maka seharusnya instruksi pada alamat 0x8048449 dikerjakan, karena alamat 0x8048449 inilah yang disimpan oleh RET. Sedang instruksi a=2 yang ingin dilompati dapat dilihat pada alamat 0x804844c . Jarak yang diinginkan dapat dihitung dengan mengurangi alamat instruksi berikutnya dengan alamat RET, yaitu 0x8048453-0x8048449=0xA atau 10.

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 :

  1. C tidak memeriksa batasan array dan referensi pointer secara otomatis
  2. 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