Beberapa dari Anda menyukainya dan beberapa dari Anda membencinya, tetapi pada titik ini seharusnya tidak mengherankan bahwa teknik .NET akan tetap digunakan, sedikit lebih lama dari yang diperkirakan. Kerangka kerja .NET adalah bagian integral dari sistem operasi Microsoft dengan rilis terbaru .NET menjadi .NET core. Core adalah penerus lintas platform untuk kerangka kerja yang membawa .NET ke Linux dan juga macOS. Ini menjadikan .NET lebih populer dari sebelumnya untuk teknik pasca eksploitasi di antara penyerang dan tim keamanan. Blog ini akan menyelami Beacon Object File (BOF) baru yang memungkinkan operator untuk mengeksekusi rakitan .NET dalam proses melalui Cobalt Strike versus modul execute-assembly built-in tradisional, yang menggunakan teknik fork and run.
Cobalt Strike, perangkat lunak simulasi penyerang yang populer, mengenali tren tim keamanan menjauh dari tooling PowerShell dan lebih bercondong ke C# karena peningkatan kemampuan deteksi untuk PowerShell, dan pada tahun 2018 dengan Cobalt Strike versi 3.11 memperkenalkan modul execute-assembly. Ini memungkinkan operator untuk memanfaatkan kekuatan rakitan .NET pasca-eksploitasi dengan mengeksekusinya dalam memori tanpa memiliki risiko tambahan menjatuhkan alat-alat tersebut ke disk. Sementara kemampuan memuat rakitan .NET dalam memori melalui kode yang tidak dikelola bukanlah hal baru atau tidak diketahui pada saat rilis, saya akan mengatakan Cobalt Strike membawa kemampuan ke arus utama dan membantu terus mendorong popularitas .NET untuk teknik pasca-eksploitasi.
Modul execute-assembly Cobalt Strike menggunakan teknik fork and run, yaitu menjalankan proses pengorbanan baru, menyuntikkan kode berbahaya pasca-eksploitasi Anda ke dalam proses baru itu, menjalankan kode berbahaya Anda dan setelah selesai, mematikan proses baru. Ini memiliki manfaat maupun kekurangan. Manfaat metode fork&run adalah eksekusi terjadi di luar proses implan Beacon. Ini berarti bahwa jika sesuatu dalam tindakan pasca-eksploitasi kita salah atau tertangkap, ada peluang yang jauh lebih besar untuk implan kita tetap bertahan. Singkatnya, ini sangat membantu stabilitas implan secara keseluruhan. Namun, karena vendor keamanan menangkap perilaku fork and run ini, Cobalt Strike mengakui bahwa pola tersebut menambah biaya OPSEC.
Pada versi 4.1 yang dirilis pada Juni 2020, Cobalt Strike memperkenalkan fitur baru untuk mencoba dan membantu alamat masalah ini dengan pengenalan Beacon Object Files (BOF). BOF memungkinkan operator untuk menghindari pola eksekusi yang terkenal seperti dijelaskan di atas atau kegagalan OPSEC lainnya seperti menggunakan cmd.exe/powershell.exe dengan mengeksekusi file objek dalam memori dalam proses yang sama dengan implan beacon kami. Meskipun saya tidak akan membahas cara kerja BoF, berikut adalah beberapa postingan blog yang menurut saya berwawasan luas:
Jika Anda membaca blog di atas, sekarang kita harus melihat bahwa BOF bukanlah anugerah penyelamatan yang kami harapkan dan jika Anda bermimpi menulis ulang semua alat-alat .NET yang luar biasa itu dan mengubahnya menjadi BOF, impian itu sekarang telah hancur. Maaf. Namun harapan tidak hilang karena, menurut saya, ada beberapa hal hebat yang dapat ditawarkan oleh BoF, dan baru-baru ini saya menikmati proses (dan beberapa frustrasi juga) dalam mendorong batas-batas apa yang dapat dilakukan menggunakannya. Pertama, adalah dengan membuat CredBandit yang melakukan dump memori lengkap dari proses seperti LSASS dan mengirimkannya kembali melalui saluran komunikasi Beacon Anda yang ada. Hari ini saya merilis InlineExecute-Assembly, yang dapat digunakan untuk mengeksekusi rakitan .NET di dalam proses beacon Anda tanpa modifikasi pada tooling .NET favorit Anda. Mari selami mengapa saya menulis BOF, beberapa fitur utamanya, batasan, dan bagaimana hal itu bisa berguna saat melakukan simulasi serangan/tim keamanan.
Buletin industri
Tetap terinformasi tentang tren industri yang paling penting—dan menarik—tentang AI, otomatisasi, data, dan di luarnya dengan buletin Think. Lihat Pernyataan Privasi IBM®.
Langganan Anda akan disediakan dalam bahasa Inggris. Anda akan menemukan tautan berhenti berlangganan di setiap buletin. Anda dapat mengelola langganan atau berhenti berlangganan di sini. Lihat Pernyataan Privasi IBM® kami untuk informasi lebih lanjut.
Alasan di balik perlunya InlineExecute-Assembly cukup sederhana. Saya menginginkan cara bagi tim simulasi serangan kami untuk mengeksekusi rakitan .NET dalam proses untuk menghindari beberapa jebakan OPSEC yang dibicarakan di atas ketika menggunakan Cobalt Strike untuk beroperasi di lingkungan yang matang. Saya juga membutuhkan alat ini untuk tidak membebani tim kami dengan waktu pengembangan ekstra dengan harus membuat modifikasi pada sebagian besar alat .NET kami saat ini. Itu juga harus stabil. Sestabil mungkin yang bisa dicapai BOF yang kompleks. Karena hal terakhir yang kita inginkan adalah hilangnya salah satu dari beberapa Beacon kita ke lingkungan. Pada dasarnya, ini harus berjalan selancar mungkin bagi operator, setara seperti modul execute-assembly Cobalt Strike.
Saya tahu, itu terlihat jelas. Kita tidak akan bisa melangkah terlalu jauh tanpanya. Seluk-beluk cara kerja CLR dan apa yang terjadi secara mendalam bisa menjadi postingan blog itu sendiri, jadi kami akan meninjau apa yang digunakan BOF pada tingkat yang sangat tinggi saat memuat CLR melalui kode yang tidak dikelola.
Memuat CLR
Seperti yang ditunjukkan pada tangkapan layar yang disederhanakan di atas, langkah-langkah utama yang akan diambil BOF untuk memuat CLR adalah sebagai berikut:
Jadi sekarang CLR diinisialisasi tetapi masih ada sedikit lagi yang perlu terjadi sebelum kita benar-benar menjalankan rakitan .NET favorit kita. Kita perlu membuat instance AppDomain kita yang dijelaskan oleh Microsoft sebagai “lingkungan terisolasi tempat aplikasi dijalankan”. Dengan kata lain, ini akan digunakan untuk memuat dan menjalankan rakitan .NET pasca eksploitasi kita.
AppDomain sedang dibuat dan perakitan sedang dimuat/dieksekusi
Seperti yang ditunjukkan pada tangkapan layar yang disederhanakan di atas, langkah-langkah utama yang akan diambil BOF untuk memuat dan memanggil perakitan.NET kami adalah sebagai berikut:
Semoga Anda sekarang memiliki pemahaman tingkat tinggi tentang eksekusi .NET melalui kode yang tidak dikelola, tetapi ini masih belum dekat untuk memiliki alat yang sehat secara operasional, jadi kami akan melihat beberapa fitur yang diterapkan di BOF untuk membuatnya benar-benar layak digunakan.
Anda mungkin bertanya-tanya mengapa ini penting. Nah, jika Anda seperti saya dan menghargai waktu, Anda tidak ingin menghabiskannya untuk memodifikasi hampir setiap rakitan .NET di luar sana sehingga titik masuknya mengembalikan string kembali dengan semua data Anda yang biasanya hanya disalurkan ke output standar konsol, bukan? Sudah kuduga. Untuk menghindarinya, yang perlu kita lakukan adalah mengarahkan output standar kita ke pipa bernama atau mail slot, membaca output setelah ditulis, dan kemudian mengembalikannya kembali ke keadaan semula. Dengan cara ini kita dapat menjalankan rakitan yang tidak dimodifikasi seperti yang kita lakukan dari cmd.exe atau powershell.exe. Nah, sebelum kita membahas kode apa pun, saya perlu mengucapkan terima kasih kepada @N4k3dTurtl3 dan postinganblog mereka tentang rakitan eksekusi dalam proses dan mail slot. Ini awalnya yang membuat saya menerapkan teknik ini ke implan C pribadi saya sendiri ketika pertama kali keluar dan berbulan-bulan kemudian saya mem-porting fungsi yang sama ke BOF. Oke, ucapan terima kasih telah diberikan, sekarang mari kita lihat contoh yang disederhanakan tentang bagaimana ini akan dicapai dengan mengarahkan stdout ke pipa bernama di bawah ini:
Mengalihkan output standar konsol ke pipa bernama dan mengembalikan
Ingat ketika memuat CLR melalui ICLRMetaHost->GetRuntime kita harus menentukan versi kerangka kerja apa yang kita butuhkan? Ingat bagaimana itu tergantung pada versi apa rakitan .NET kita dikompilasi? Tidak akan menyenangkan untuk secara manual menentukan versi mana yang dibutuhkan setiap kali, bukan? Untungnya, @b4rtik telah mengimplementasikan fungsi keren untuk menangani hal ini dalam modul execute-assembly mereka untuk frameworkMetasploit yang dapat dengan mudah diimplementasikan ke dalam alat kita sendiri seperti yang ditunjukkan di bawah ini:
Fungsi yang membaca rakitan .NET kami dan membantu menentukan versi .NET apa yang kami butuhkan saat memuat CLR
Pada dasarnya apa yang dilakukan fungsi ini adalah ketika melewati byte perakitan kami, itu akan membaca byte tersebut dan mencari nilai hex 76 34 2E 30 2E 33 33 31 39, yang ketika dikonversi ke ASCII adalah v4.0.30319. Mudah-mudahan itu terlihat familiar. Jika nilai itu ditemukan saat membaca rakitan, fungsi mengembalikan 1 atau true, dan jika tidak ditemukan mengembalikan 0 atau salah. Kita dapat menggunakan ini untuk dengan mudah menentukan versi apa yang akan dimuat dengan apakah 1/true atau 0/false kembali seperti yang ditunjukkan pada contoh kode di bawah ini:
Pernyataan if/else untuk mengatur variabel versi .NET
Kita tentu saja tidak dapat berbicara tentang teknik ofensif .NET dan tidak membicarakan AMSI. Meskipun kami tidak akan membahas secara mendalam tentang apa itu AMSI dan semua cara untuk melewatinya karena ini telah dibahas berkali-kali, kami akan berbicara sedikit tentang mengapa melakukan patch pada AMSI mungkin diperlukan tergantung pada apa yang Anda putuskan untuk dieksekusi melalui BOF. Sebagai contoh, jika Anda memutuskan untuk menjalankan Seatbelt tanpa ada gangguan, Anda akan segera menyadari bahwa tidak ada output apa pun dan beacon Anda mati. Ya, benar-benar mati. Ini karena AMSI menangkap rakitan Anda, memutuskan itu berbahaya, dan menghentikannya, seperti pesta rumah yang membuat terlalu banyak kebisingan. Tidak ideal, kan? Sekarang kita memiliki dua opsi bagus di sini terkait AMSI, kita dapat mengaburkan alat .NET kita melalui sesuatu seperti ConfuSerX atau Invisibility Cloak atau kita dapat menonaktifkan AMSI menggunakan berbagai teknik. Dalam kasus kami, kami akan menggunakan teknik dari RastaMouse, yaitu untuk melakukan patch pada amsi.dll dalam memori sehingga mengembalikan E_INVALIDARG dan membuat hasil pemindaian 0. Yang seperti yang ditunjukkan dalam postingan blog mereka, biasanya ditafsirkan sebagai AMSI_RESULT_CLEAN. Mari kita lihat versi kode yang disederhanakan untuk proses x64 di bawah ini:
Penambalan memori pada AmsiScanBuffer
Seperti yang Anda lihat di tangkapan layar di atas, kami cukup melakukan hal berikut:
Dengan menerapkan ini ke dalam alat kita, sekarang kita harus dapat menjalankan versi default Seatbelt.exe menggunakan flag -amsi untuk melewati deteksi AMSI seperti yang ditunjukkan di bawah ini:
Contoh bypass InlineExecute-Assemby AMSI
Untungnya bagi para pihak pertahanan, ada lebih dari sekadar AMSI untuk membantu dalam menangkap teknik .NET berbahaya dengan menggunakan ETW. Sayangnya, seperti AMSI, ini juga cukup mudah untuk dilewati oleh musuh dan @xpn melakukan riset mendalam tentang bagaimana hal ini bisa dilakukan. Mari kita lihat contoh sederhana tentang bagaimana Anda dapat melakukan patch pada ETW untuk menonaktifkannya sepenuhnya di bawah ini:
Patching dalam memori EtwEventWrite
Seperti yang Anda lihat dari tangkapan layar di atas, langkah-langkahnya cukup identik dengan cara kami menambal AMSI, jadi saya tidak akan membahas langkah-langkah untuk yang satu ini. Anda dapat melihat tangkapan layar sebelum dan sesudah menjalankan flag –etw di bawah ini:
Menggunakan Process Hacker untuk melihat properti PowerShell.exe sebelum menjalankan inlineExecute-assembly dengan flag –etw
Menjalankan inline-Execute-Assembly menggunakan flag –etw
Menggunakan Process Hacker untuk melihat properti PowerShell.exe yang sama setelah menjalankan inlineExecute-Assembly
Secara default, AppDomain, Named Pipe atau Slot Mail yang dibuat menggunakan nilai default “totesLegit”. Nilai-nilai ini dapat diubah agar lebih berbaur dalam lingkungan yang Anda uji dengan mengubahnya dalam skrip agresor yang disediakan atau melalui flag baris perintah dengan cepat. Contoh mengubahnya melalui baris perintah dapat ditunjukkan di bawah ini:
Contoh InlineExecute-Assembly menggunakan AppDomain Name yang unik dan nama pipa bernama unik
Contoh nama AppDomain unik ChangedMe
Contoh named pipe unik LookAtMe
Contoh AppDomain dihapus setelah eksekusi berhasil selesai
Contoh pipa bernama dihapus setelah eksekusi berhasil selesai
Bagian ini akan menjadi pengulangan dari apa yang saya sebutkan di repositori GitHub, tetapi saya merasa penting untuk mengulangi beberapa hal yang perlu Anda ingat saat menggunakan alat ini:
Di bawah ini adalah beberapa pertimbangan defensif: