in Linux

Exploiting LDD for Arbitrary Code Execution

Hati-hati dengan program LDD, jangan sembarangan menjalankan LDD pada program executable yang tidak dikenal dan tidak dipercaya, sebab salah-salah malah anda menjalankan program tersebut dan bisa membuat komputer anda compromised. Apa itu LDD dan bagaimana cara mengexploitnya silakan disimak artikel berikut ini.

Shared Library and Linking

Seperti yang telah saya jelaskan dalam artikel sebelumnya tentang belajar assembly, program adalah kumpulan instruksi prosesor dalam bahasa mesin (binary). Namun yang perlu diketahui adalah tidak semua instruksi yang dibutuhkan dimasukkan (baca:dilink) dalam file executablenya.

Subroutine/prosedur/fungsi yang umum tidak perlu dimasukkan ke dalam file executable, cukup disimpan dalam suatu file library yang boleh dipakai semua program yang membutuhkan. Library ini disebut dengan shared library atau shared object (so). Shared library dalam lingkungan windows dikenal dengan DLL.

Proses linking adalah proses menggabungkan library external yang dibutuhkan untuk menjalankan program. Linking bisa dilakukan satu kali saja, yaitu setelah proses compiling, atau dilakukan setiap kali menjalankan program atau at-runtime.

compile and link

Pada gambar di atas terlihat proses linking yang dilakukan setelah tahap compiling. Hasil dari proses compile adalah object file yang masih memerlukan code dalam library external. Dalam tahap linking, semua code yang diperlukan dari library external di-include ke dalam file executable.

Berdasarkan waktu linkingnya, file executable bisa dibedakan menjadi 2:

  • Statically linked: Linking once at compile time

Bila semua code dari library external telah diinclude ke dalam file executable, maka file tersebut bertipe statically linked executable. Executable jenis ini tidak mempunyai ketergantungan (dependency) terhadap library apapun, sehingga bersifat portable.

Kelemahan executable jenis ini adalah boros space disk dan memori. Karena semua code dari external library diinclude ke dalam executable, maka ukuran file executable menjadi besar. Begitu pula pada saat run-time, memori yang diperlukan lebih besar karena code yang harus diload ke memori sebelum dieksekusi lebih besar.

Dalam artikel sebelumnya tentang pemrograman assembly, file executable yang dihasilkan dari source assembly adalah bertipe statically linked. Hal ini bisa dilihat dengan utility bernama “file” seperti contoh di bawah ini.

$ file ./hello
ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, stripped

Compiler gcc secara default membuat executable bertipe dynamic, sehingga untuk membuat executable statically linked perlu memakai parameter: –static seperti contoh di bawah ini:

$ gcc myapp.c -o myapp --static
$ file ./myapp
./myapp: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.9, statically linked, for GNU/Linux 2.6.9, not stripped
  • Dynamically linked: Linking at run-time

Disebut dynamically linked karena proses linking tidak terjadi pada saat pembuatan file executable (compile time). Proses linking dilakukan secara dinamis setiap kali program akan dijalankan (at runtime).

dynamic linking

Dengan tidak meng-include library ke dalam file executable, maka ukuran file menjadi kecil. Selain itu keuntungannya adalah lebih hemat memori. Memori yang diperlukan untuk code dari library cukup satu area saja kemudian banyak proses boleh memakainya bersama-sama. Dengan cara ini code yang sama tidak perlu diduplikasi di banyak tempat. Pada gambar di atas terlihat bahwa code untuk fungsi printf(), strlen() dan malloc() cukup berada di area memori untuk library, tidak perlu diduplikasi di masing-masing proses.

Pada executable bertipe statically linked, setiap proses memiliki area memori sendiri khusus untuk code yang tidak bisa di-share (dipakai) oleh proses lain sehingga banyak terjadi duplikasi code yang mengakibatkan pemborosan memori.

$ gcc myapp.c -o myapp_static --static
$ ls -l myapp_static
-rwxrwxr-x 1 masadmin masadmin 524172 Nov 12 15:23 myapp_static
$ gcc myapp.c -o myapp
$ ls -l myapp
-rwxrwxr-x 1 masadmin masadmin 4733 Nov 12 15:23 myapp

Perhatikan contoh di atas, ukuran statically linked executable (myapp_static) adalah 516KB sedangkan dynamically linked (myapp) hanya 8KB. Statically linked jauh lebih besar ukurannya dibandingkan dinamically linked karena semua code dari library ikut di-include ke dalam file executable.

Dynamic Linker

Executable dynamically linked tidak bisa langsung dieksekusi karena membutuhkan code yang ada pada external library. Agar bisa dieksekusi diperlukan proses linking yang dilakukan sesaat sebelum eksekusi (at run-time). Linking at runtime ini dilakukan oleh program yang disebut dengan dynamic linker.

Setiap ELF executable yang dilink secara dinamis, ketika akan dieksekusi, OS akan terlebih dahulu mengeksekusi dynamic linker sebelum mengeksekusi program. Dynamic linker bertanggung jawab menyiapkan semua code library yang dibutuhkan agar program siap dieksekusi, proses ini juga dikenal dengan dependency resolving.

Setiap executable boleh menggunakan dynamic linker yang berbeda-beda. Full path lokasi dynamic linker yang dipakai suatu file disimpan dalam section “.interp” (interpreter) dalam executable. Default dynamic linker untuk Linux adalah ld-linux.so atau ld-linux.so.2. Dengan objdump, kita bisa melihat dynamic linker yang dipakai suatu file:

$ objdump -j .interp -s ./myapp

./myapp:     file format elf32-i386

Contents of section .interp:
 8048114 2f6c6962 2f6c642d 6c696e75 782e736f  /lib/ld-linux.so
 8048124 2e3200                               .2.

Dalam contoh di atas, myapp menggunakan dynamic linker “/lib/ld-linux.so.2” yaitu dynamic linker default Linux.

Ingat bahwa dynamic linker yang digunakan tidak harus dynamic linker default dari OS, setiap executable boleh memilih memakai dynamic linker apapun.

What is LDD

LDD dipakai untuk melihat shared library apa saja yang dipakai oleh suatu file executable yang dilink secara dinamik. Dengan kata lain untuk menampilkan library dependency suatu executable. Untuk lebih jelasnya, kita langsung saja praktek memakai LDD.

$ ldd /bin/ls
        linux-gate.so.1 =>  (0x00452000)
        librt.so.1 => /lib/librt.so.1 (0x005d9000)
        libacl.so.1 => /lib/libacl.so.1 (0x005c4000)
        libselinux.so.1 => /lib/libselinux.so.1 (0x00562000)
        libc.so.6 => /lib/libc.so.6 (0x00110000)
        libpthread.so.0 => /lib/libpthread.so.0 (0x00520000)
        /lib/ld-linux.so.2 (0x003b2000)
        libattr.so.1 => /lib/libattr.so.1 (0x00646000)
        libdl.so.2 => /lib/libdl.so.2 (0x0051a000)
        libsepol.so.1 => /lib/libsepol.so.1 (0x0057c000)
$ ldd ./myapp
        linux-gate.so.1 =>  (0x0059f000)
        libc.so.6 => /lib/libc.so.6 (0x003d5000)
        /lib/ld-linux.so.2 (0x003b2000)

Dalam contoh di atas terlihat library apa saja yang diperlukan oleh /bin/ls dan ./myapp. Semua library dalam daftar di atas harus tersedia sebab program akan memanggil fungsi-fungsi yang ada pada library tersebut, bila ada satu saja library yang dibutuhkan tidak tersedia, maka program tidak bisa dieksekusi.

Creating Dummy Linker

Sekarang kita akan coba membuat executable yang tidak memakai default dynamic linker (/lib/ld-linux.so.2). Linker sebenarnya adalah program yang sangat kompleks, namun dalam contoh ini saya akan membuat dummy linker. Saya memakai program hello yang dibuat dalam assembly di artikel ini sebagai dummy linker. Perhatikan source hello_ld.asm di bawah ini:

section .text
global _start

_start:
; write()
mov edx,len
mov ecx,msg
mov ebx,1
mov eax,4
int 0x80
; exit(0)
mov ebx,0
mov eax,1
int 0x80

section .data
msg db "I'm Hello Linker!",0xa
len equ $ - msg

Source assembly di atas adalah source program yang akan kita pakai sebagai dummy linker. Compile program di atas dengan:

$ nasm -f elf hello_ld.asm

Setelah itu akan terbentuk object file hello_ld.o. Object file itu perlu di-link dengan ld agar menjadi statically linked executable.:

Dynamic linker haruslah statically linked executable, sebab bila tidak, berarti dynamic linker juga perlu dynamic linker alias mbulet!

$ ld -s -o hello_ld hello_ld.o

Setelah menjadi executable, kita bisa coba jalankan:

$ ./hello_ld
I'm Hello Linker!

Oke, sekarang kita sudah punya dummy dynamic linker. Sekarang kita buat program sederhana yang menggunakan hello_ld sebagai dynamic linker-nya. Program ini dibuat dengan bahasa C yang sangat sederhana untuk contoh saja.

#include 
int main() {
  printf("This is My App!\n");
}

Sekarang kita compile source di atas 2x untuk menunjukkan bedanya menggunakan dynamic linker default dengan dynamic linker non-default. Pertama kita compile untuk memakai dynamic linker default Linux. Tidak ada opsi yang ditambahkan pada gcc karena kita memakai default linker.

$ gcc myapp.c -o myapp_defaultld
$ objdump -j .interp -s myapp_default
myapp_defaultld:     file format elf32-i386

Contents of section .interp:
 8048114 2f6c6962 2f6c642d 6c696e75 782e736f  /lib/ld-linux.so
 8048124 2e3200                               .2.

$ ./myapp_defaultld
This is My App!

Sekarang kita compile source yang sama namun dengan menyebutkan dynamic linker yang akan dipakai. Opsi yang ditambahkan pada gcc adalah -Wl,–dynamic-linker seperti pada contoh di bawah ini:

$ gcc -Wl,--dynamic-linker,./hello_ld myapp.c -o myapp_hellold
$ objdump -j .interp -s myapp_hellold
myapp_hellold:     file format elf32-i386

Contents of section .interp:
 8048114 2e2f6865 6c6c6f5f 6c6400             ./hello_ld.
$ ./myapp_hellold
I'm Hello Linker!

Ketika myapp_hellold dijalankan yang muncul adalah “I’m Hello Linker!”, padahal dari source myapp.c seharusnya yang muncul adalah “This is My App!”. Hal ini bisa terjadi karena myapp_hellold memakai linker hello_ld (lihat isi .interp di atas). Ingat yang pertama kali dieksekusi bukan code programnya, tapi dynamic linker, jadi program hello_ld dieksekusi dulu, baru kemudian myapp_hellold akan dieksekusi.

Seharusnya myapp_hellold juga dieksekusi, namun karena pada hello_ld ada instruksi exit(0) yang artinya keluar dari program (berhenti), jadi program berhenti sebelum mengeksekusi program myapp_hellold.

Force Execution using Default Linker

Sebelumnya sudah saya jelaskan dua contoh program: myapp_defaultld (memakai default linker), dan myapp_hellold (memakai hello_ld sebagai linker). Bila kita mengeksekusi dengan cara menyebutkan nama programnya di command prompt, maka program itu akan dieksekusi dengan linker yang ada di section “.interp”.

Namun kita bisa membuat program mengabaikan linker yang ada di .interp dan memakai default linker. Perhatikan contoh di bawah ini:

$ ./myapp_hellold
I'm Hello Linker!
$ /lib/ld-linux.so.2 ./myapp_hellold
This is My App!

Pada baris ke-1 kita mengeksekusi dengan cara langsung menyebutkan nama program di command prompt. Dengan cara tersebut, OS akan mengeksekusi linker yang ada di section .interp dalam file executable, baru kemudian mengeksekusi code program sehingga muncul “I’m Hello Linker!”, myapp_hellold tidak dieksekusi karena dummy linker hello_ld mengandung instruksi exit().

Pada baris ke-3, kita menjalankan program dengan menyebutkan dynamic linker dan memberikan argumen berupa nama program yang akan diekekusi di command prompt. Dengan cara ini maka dynamic linker tersebutlah yang dipakai, bukan linker yang disebutkan dalam section .interp. Karena ld-linux.so.2 adalah linker beneran bukan dummy linker, maka program myapp_hellold berhasil dieksekusi dengan sempurna sehingga yang muncul adalah teks “This is My App!”.

How LDD Works

Sekarang kita masuk pada pembahasan mengenai LDD. LDD diapakai untuk menampilkan daftar library dependency atau library yang dibutuhkan oleh suatu executable yang dilink secara dinamis. Bagaimana cara LDD menampilkan daftar dependency library? Perhatikan contoh di bawah ini untuk memahaminya.

$ ldd /bin/pwd
        linux-gate.so.1 =>  (0x00e88000)
        libc.so.6 => /lib/libc.so.6 (0x003d5000)
        /lib/ld-linux.so.2 (0x003b2000)
$ LD_TRACE_LOADED_OBJECTS=1 /bin/pwd
        linux-gate.so.1 =>  (0x00cc3000)
        libc.so.6 => /lib/libc.so.6 (0x003d5000)
        /lib/ld-linux.so.2 (0x003b2000)
$ LD_TRACE_LOADED_OBJECTS=1 /lib/ld-linux.so.2 /bin/pwd
        linux-gate.so.1 =>  (0x00a54000)
        libc.so.6 => /lib/libc.so.6 (0x003d5000)
        /lib/ld-linux.so.2 (0x003b2000)

Pada baris ke-1 kita menggunakan program LDD untuk menampilkan library dependency. Pada baris ke-5 kita juga bisa menampilkan library dependency tanpa menggunakan program LDD. Caranya adalah dengan mengeset variabel environment LD_TRACE_LOADED_OBJECTS=1 ketika mengeksekusi /bin/pwd. Di command prompt kita bisa mengeset variabel environment sekaligus mengeksekusi program dalam satu baris seperti pada contoh di baris ke-5. Ketika environment variable tersebut diset, maka program tidak dieksekusi, dan library dependency program tersebut ditampilkan.

Pada baris ke-9 kita mengeset environment variable sekaligus mengeksekusi /bin/pwd dengan menggunakan linker /lib/ld-linux.so.2. Dengan cara ini linker yang ada di section .interp file /bin/pwd tidak dipakai (kebetulan /bin/pwd juga memakai linker /lib/ld-linux.so.2).

Dari contoh di atas terlihat bahwa LDD bekerja dengan cara mengeset variabel environment LD_TRACE_LOADED_OBJECTS=1, kemudian mengeksekusi program tersebut. Jadi sebenarnya tanpa program LDD kita juga bisa melihat library dependency suatu executable dengan cara seperti pada baris ke-5 dan baris ke-9 di atas.

Kalau diperhatikan LDD sebanarnya adalah bash script. Dalam script tersebut akan terlihat bahwa sebenarnya yang dilakukan ldd adalah mengeset environment variable LD_TRACE_LOADED_OBJECTS sebelum mengeksekusi program (jangan kaget, memang ldd mengeksekusi program itu!). Hanya itu yang dilakukan ldd, selanjutnya semua tergantung dari dynamic linker yang ada pada executable tersebut.

Exploiting LDD for Arbitrary Code Execution

Sampai di sini sepertinya tidak ada yang salah dengan LDD. Namun sebenarnya ada masalah serius dalam cara kerja LDD. Sekarang kita ganti contohnya executablenya bukan /bin/passwd tetapi myapp_defaultld dan myapp_hellold dan silakan perhatikan perbedaannya.

$ ldd ./myapp_defaultld
        linux-gate.so.1 =>  (0x007f8000)
        libc.so.6 => /lib/libc.so.6 (0x003d5000)
        /lib/ld-linux.so.2 (0x003b2000)
$ ldd ./myapp_hellold
I'm Hello Linker!

Oops, ldd tidak bekerja pada myapp_hellold. Justru sistem terancam compromised karena program dieksekusi padahal niatnya hanya ingin melihat dependencynya saja. Kita lanjutkan dengan contoh berikutnya di bawah ini.

$ LD_TRACE_LOADED_OBJECTS=1 ./myapp_defaultld
        linux-gate.so.1 =>  (0x00970000)
        libc.so.6 => /lib/libc.so.6 (0x003d5000)
        /lib/ld-linux.so.2 (0x003b2000)
$ LD_TRACE_LOADED_OBJECTS=1 ./myapp_hellold
I'm Hello Linker!

Sekarang kita tidak memakai LDD, namun mengeset environment variable LD_TRACE_LOADED_OBJECTS=1 sebelum mengeksekusi program. Sekali lagi kita melihat, dependency library hanya muncul untuk program ./myapp_defaultld. Dependency ./myapp_hellold tidak muncul, malah program tersebut dieksekusi. Apa yang sebenarnya terjadi? Sebelum menjawabnya, perhatikan lagi contoh di bawah ini.

$ LD_TRACE_LOADED_OBJECTS=1 /lib/ld-linux.so.2 ./myapp_hellold
        linux-gate.so.1 =>  (0x004a2000)
        libc.so.6 => /lib/libc.so.6 (0x00110000)
        ./hello_ld => /lib/ld-linux.so.2 (0x003b2000)
$ LD_TRACE_LOADED_OBJECTS=1 /lib/ld-linux.so.2 ./myapp_defaultld
        linux-gate.so.1 =>  (0x004fb000)
        libc.so.6 => /lib/libc.so.6 (0x00110000)
        /lib/ld-linux.so.2 (0x003b2000)

Aha, kali ini kedua program tersebut berhasil dilihat dependency-nya. Kenapa kali ini bisa? Pada contoh yang terakhir ini kedua program dieksekusi dengan menggunakan dynamic linker “/lib/ld-linux.so.2” bukan linker yang ada pada masing-masing executable.

Sebenarnya yang menampilkan dependency library suatu executable adalah dynamic linker, bukan ldd. Itu sebabnya mengapa myapp_hellold tidak bisa dilihat dependencynya karena memakai linker sendiri yaitu ./hello_ld (bukan default linker “/lib/ld-linux.so.2”)

Sebagai dynamic linker, ld-linux.so.2 bertanggung jawab melakukan dependency resolving dan menyiapkan program untuk dieksekusi. Setelah dynamic linker selesai dieksekusi, baru code pada program mulai dieksekusi. Namun ada satu kondisi di mana code program tidak dieksekusi, yaitu bila ada variable environment LD_TRACE_LOADED_OBJECTS. Bila ld-linux.so.2 menemukan variabel tersebut, maka program tidak diekseksui, dan ld-linux.so.2 menampilkan dependency library executable tersebut.

Kesalahan LDD adalah menganggap semua linker yang dipilih suatu executable (di section .interp file executable) bisa dipercaya. Dengan mengeset LD_TRACE_LOADED_OBJECTS sebelum mengeksekusi program, LDD berharap linker tidak mengeksekusi program tersebut.

LD_TRACE_LOADED_OBJECTS berfungsi sebagai penanda, atau rambu yang khusus dibuat agar linker tidak mengeksekusi program, namun hanya menampilkan dependency library. Namun masalahnya tidak semua linker menghormati aturan itu, attacker bisa membuat malicious linker yang tidak mempedulikan rambu LD_TRACE_LOADED_OBJECTS. Akibatnya seorang administrator/root yang berniat melihat dependency suatu executable, bisa terjebak mengeksekusi executable tersebut.

Administrator yang menggunakan ldd untuk melihat dependency list ./myapp_hellold akan terjebak karena bukannya mendapatkan dependency list, malah mengeksekusi program tersebut. Bila pada program tersebut terdapat malicious code, maka akibatnya bisa fatal sekali, terutama bila admin menggunakan ldd dengan account root.

Reference: ldd arbitrary code execution

Write a Comment

Comment