Hampir mendekati nol: Mengeksploitasi layanan streaming kernel Microsoft.

Foto seorang pria berjenggot yang sedang bekerja lembur di kantor untuk menyelesaikan tenggat waktunya, dua rekan kerja duduk di belakangnya

Bulan lalu Microsoft menambal kerentanan di Microsoft Kernel Streaming Server, komponen kernel Windows yang digunakan dalam virtualisasi dan berbagi perangkat kamera. Kerentanan, CVE-2023-36802, memungkinkan penyerang lokal untuk meningkatkan hak istimewa menjadi SYSTEM.

Postingan blog ini merinci proses saya menjelajahi permukaan serangan baru di kernel Windows, menemukan kerentanan 0-day, menjelajahi kelas bug yang menarik, dan membangun eksploitasi yang stabil. Postingan ini tidak memerlukan pengetahuan khusus tentang kernel Windows untuk mengikutinya, tetapi pemahaman dasar tentang korupsi memori dan konsep sistem operasi akan sangat membantu. Saya juga akan membahas dasar-dasar melakukan analisis awal pada driver kernel yang tidak dikenal dan menyederhanakan proses melihat target baru.

Permukaan serangan

Microsoft Kernel Streaming Server (mskssrv.sys) adalah komponen dari layanan Windows Multimedia Framework, Frame Server. Layanan ini memvirtualisasi perangkat kamera dan memungkinkan perangkat untuk dibagikan di antara beberapa aplikasi.

Saya mulai menjelajahi permukaan serangan ini setelah mencatat CVE-2023-29360, yang awalnya terdaftar sebagai kerentanan driver. Bug sebenarnya ada di Microsoft Kernel Streaming Server. Meskipun pada saat itu saya tidak terbiasa dengan MS KS Server, nama driver ini sudah cukup untuk menarik minat saya. Meskipun tidak tahu apa-apa tentang tujuan atau fungsinya, saya pikir server streaming di kernel bisa menjadi tempat yang bermanfaat untuk mencari kerentanan. Masuk secara buta, saya berusaha menjawab pertanyaan-pertanyaan berikut:

  • Dalam kapasitas apa aplikasi yang tidak memiliki hak istimewa dapat berinteraksi dengan modul kernel ini?
  • Jenis data apa dari aplikasi yang langsung diproses modul?

Untuk menjawab pertanyaan pertama, saya mulai dengan menganalisis biner dengan disassembler. Saya dengan cepat mengidentifikasi kerentanan yang disebutkan di atas, bug logika yang sederhana dan elegan. Masalahnya tampak mudah dipicu dan dieksploitasi, jadi saya berusaha mengembangkan bukti konsep cepat untuk lebih memahami cara kerja driver mskssrv.sys.

Berita teknologi terbaru, didukung oleh insight dari pakar

Tetap terinformasi tentang tren industri yang paling penting—dan menarik—tentang AI, otomatisasi, data, dan di luarnya dengan buletin Think. Lihat Pernyataan Privasi IBM®.

Terima kasih! Anda telah berlangganan.

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.

Analisis awal

Memicu eksekusi di dalam MS KS Server

Pertama, kita harus dapat menjangkau driver dari aplikasi ruang pengguna. Fungsi rentan dapat diakses dari rutin DispatchDeviceControl driver, yang berarti fungsi dapat dipicu dengan mengirimkan IOCTL ke driver. Untuk melakukan itu, handle untuk perangkat driver perlu diperoleh melalui panggilan ke CreateFile menggunakan jalur perangkat. Biasanya, menemukan nama/jalur perangkat sangat mudah diidentifikasi: temukan panggilan ke IoCreateDevice dalam driver dan periksa parameter ketiga yang berisi nama perangkat.

Fungsi di dalam mskssrv.sys yang memanggil IoCreateDevice dengan pointer NULL untuk nama perangkat

Fungsi di dalam mskssrv.sys yang memanggil IoCreateDevice dengan pointer NULL untuk nama perangkat

Dalam hal ini, parameter untuk nama perangkat adalah NULL. Nama fungsi pemanggilan menunjukkan bahwa mskssrv adalah driver PnP, dan panggilan ke IoAttachDeviceToDeviceStack mengindikasikan objek perangkat yang dibuat adalah bagian dari tumpukan perangkat. Akibatnya ini berarti bahwa beberapa driver dipanggil ketika permintaan I/O dikirim ke perangkat. Untuk perangkat PnP, jalur antarmuka perangkat diperlukan untuk mengakses perangkat.

Menggunakan debugger kernel WinDbg kita dapat melihat perangkat apa yang termasuk dalam driver mskssrv dan tumpukan perangkat:

Output dari perintah !drvobj dan !devobj menunjukkan perangkat atas dan bawah

Output dari perintah !drvobj dan !devobj menunjukkan perangkat atas dan bawah

Di atas kita melihat perangkat mskssrv terpasang ke objek perangkat yang lebih rendah milik driver swenum.sys dan memiliki perangkat atas yang terpasang milik ksthunk.sys.

Dari Device Manager kita dapat menemukan ID instans perangkat target:

Device Manager menampilkan ID instans perangkat dan GUID antarmuka

Device Manager menampilkan ID instans perangkat dan GUID antarmuka

Kita sekarang memiliki informasi yang cukup untuk mendapatkan jalur antarmuka perangkat menggunakan fungsi manajer konfigurasi atau SetupApi. Dengan menggunakan jalur antarmuka perangkat yang diambil, kita dapat membuka handle untuk perangkat tersebut.

Akhirnya, kita sekarang dapat memicu eksekusi kode di dalam mskssrv.sys. Ketika perangkat dibuat, fungsi dispatch create PnP driver akan dipanggil. Untuk memicu eksekusi kode tambahan, kita dapat mengirim IOCTL untuk berbicara dengan perangkat yang akan dieksekusi dalam fungsi kontrol perangkat pengiriman driver.

Debug pada ghost driver

Saat melakukan analisis biner, praktik terbaik adalah menggunakan kombinasi alat statis (disassembler, decompiler) dan dinamis (debugger). WinDbg dapat digunakan untuk men-debug kernel driver target. Dengan menetapkan beberapa breakpoint di tempat eksekusi kode diharapkan terjadi (dispatch create, dispatch device control).

Saat memulai, saya mengalami beberapa kesulitan, tidak ada satu pun breakpoint yang saya tetapkan di dalam driver yang berhasil terpicu. Saya memiliki keraguan apakah saya membuka perangkat yang tepat, atau melakukan sesuatu yang salah. Kemudian saya menyadari bahwa breakpoint saya dihapus karena driver sedang di-unload. Saya mencari jawaban di internet, namun tidak banyak hasil saat mencari mskssrv, meskipun dimuat dan dapat diakses secara default di Windows. Di antara beberapa hasil yang saya temukan, terdapat sebuah thread di OSR, di mana orang lain mengalami masalah yang sama.

Berkomentar di forum

Ternyata, driver filter PnP dapat di-unload jika tidak digunakan untuk sementara waktu, dan dimuat kembali sesuai permintaan saat diperlukan.

Saya memperbaiki masalah ini dengan menetapkan breakpoint setelah handle ke perangkat dibuka, tetapi sebelum memanggil DeviceIoControl, untuk memastikan driver baru saja dimuat.

Survei cepat tentang fungsionalitas driver

Driver mskssrv hanyalah biner yang berukuran 72 KB dan mendukung kode kontrol Device IO yang memanggil fungsi-fungsi berikut:

  • FSRendezvousServer::InitializeContext
  • FSRendezvousServer::InitializeStream
  • FSRendezvousServer::RegisterContext
  • FSRendezvousServer::RegisterStream
  • FSRendezvousServer::DrainTx
  • FSRendezvousServer::NotifyContext
  • FSRendezvousServer::PublishTx
  • FSRendezvousServer::PublishRx
  • FSRendezvousServer::ConsumeTx
  • FSRendezvousServer::ConsumeRx

Dari melihat nama-nama simbol ini kita dapat menyimpulkan beberapa fungsi driver, sesuatu yang berhubungan dengan transmisi dan penerimaan aliran. Pada titik ini saya menggali lebih banyak fungsi yang dimaksudkan driver. Saya menemukan presentasi ini oleh Michael Maltsev tentang kerangka kerja Multimedia Windows di mana saya mendapatkan bahwa driver adalah bagian dari mekanisme antar-proses untuk berbagi aliran kamera.

Karena driver tidak terlalu besar dan tidak banyak IOCTL, saya bisa melihat setiap fungsi untuk mendapatkan gambaran tentang internal driver. Setiap fungsi IOCTL beroperasi baik pada objek registrasi konteks atau objek pendaftaran aliran, yang dialokasikan dan diinisialisasi melalui IOCTL “Inisialisasi” yang sesuai. Pointer ke objek disimpan di Irp->CurrentStackLocation->FileObject->FsContext2. FileObject menunjuk ke objek file perangkat yang dibuat per file terbuka, dan FsContext2 adalah bidang yang dimaksudkan untuk menyimpan metadata objek per file.

Kerentanan

Saya melihat bug ini saat mencoba memahami cara berkomunikasi dengan driver secara langsung, pertama-tama mengabaikan analisis komponen usermode, fsclient.dll dan frameserver.dll. Saya hampir melewatkan bug, karena berasumsi pengembang membuat contoh pemeriksaan sederhana yang diabaikan. Mari kita lihat fungsi IOCTL PublishRx:

Potongan dekompilasi FSRendezvousServer::PublishRx

Potongan dekompilasi FSRendezvousServer::PublishRx

Setelah objek stream diambil dariFsContext2, fungsi FSRendezvousServer::FindObject dipanggil, untuk memverifikasi penunjuk cocok dengan objek yang ditemukan dalam dua daftar yang disimpan oleh FSRendezvousServer global. Pada awalnya, saya berasumsi fungsi ini akan memiliki beberapa cara untuk memverifikasi jenis objek yang diminta. Namun, fungsi mengembalikan TRUE jika pointer ditemukan dalam daftar objek konteks maupun daftar objek aliran. Perhatikan bahwa tidak ada informasi tentang jenis objek apa yang seharusnya diteruskan ke FindObject. Itu berarti objek konteks dapat diteruskan sebagai objek aliran. Ini adalah kerentanan kebingungan jenis objek! Ini terjadi di setiap fungsi IOCTL yang beroperasi pada objek aliran. Untuk menambal kerentanan, Microsoft menggantiFSRendezvousServer::FindObject dengan FSRendezvousServer::FindStreamObject, yang pertama-tama memverifikasi objek adalah objek aliran dengan memeriksa bidang jenis.

Eksploitasi

Primitif

Karena objek registrasi konteks lebih kecil dari (0x78 byte) objek pendaftaran aliran (0x1D8 byte), operasi objek aliran dapat dilakukan pada memori di luar batas

Ilustrasi kerentanan kebingungan jenis objek

Ilustrasi kerentanan kebingungan jenis objek

Pool spray

Untuk memanfaatkan kerentanan primitif, kita membutuhkan kemampuan untuk mengontrol memori di luar batas yang diakses. Ini dapat dilakukan dengan memicu alokasi banyak objek di area memori yang sama dari objek rentan. Teknik ini disebut heap atau pool spraying. Objek yang rentan dialokasikan dalam heap pool fragmentasi rendah Non-Paged. Kita dapat menggunakan teknik klasik oleh Alex Ionescu untuk melakukan spraying pada buffer yang memberikan kontrol total terhadap isi memori di bawah header DATA_QUEUE_ENTRY 0x30 byte. Dengan melakukan spraying menggunakan teknik ini, kita bisa mendapatkan tata letak memori yang ditunjukkan pada diagram:

Ilustrasi Pool Spray Non-Paged

Menggunakan metode pool spraying yang dipilih, bidang dalam offset objek dalam rentang 0xC0-0x10F dan 0x150-0x19F dapat dikontrol. Saya sekali lagi meninjau kembali fungsi IOCTL untuk objek aliran untuk mencari primitif mengeksploitasi. Saya mencari tempat di mana bidang objek yang dapat dikontrol diakses dan dimanipulasi.

Penulisan konstan

Saya menemukan primitif write-where yang menulis nilai konstan di IOCTL PublishRx. Primitif ini dapat digunakan untuk menulis nilai konstan pada alamat memori arbitrer. Mari kita lihat cuplikan fungsi FSStreamReg::PublishRx:

Cuplikan dekompilasi FSStreamReg::PublishRx

Cuplikan dekompilasi FSStreamReg::PublishRx

Objek aliran berisi kepala daftar di offset 0x188 yang menjelaskan daftar objek FSFrameMdl. Dalam cuplikan dekompilasi di atas, daftar ini diiterasi dan jika nilai tag dalam objek FSFrameMDL cocok dengan tag di buffer sistem yang diteruskan dari aplikasi, fungsi FSFrameMDL::UnmapPages dipanggil.

Menggunakan primitif eksploitasi yang disebutkan di atas, FSFrameMdlList dan dengan demikian objek FSFrameMdl yang ditunjuk oleh pFrameMdl dapat dikontrol sepenuhnya. Sekarang mari kita lihat UnmapPages:

FSFrameMdl:Dekompilasi UnmapPages

FSFrameMdl:Dekompilasi UnmapPages

Pada baris terakhir dari fungsi yang didekompilasi di atas, nilai konstanta 2 dituliskan ke nilai offset ini (objek FSFrameMdl) yang dapat dikontrol. Penulisan konstan ini dapat digunakan bersama dengan teknik I/O Ring untuk mendapatkan penulisan baca kernel arbitrer dan eskalasi hak istimewa. Anda dapat membaca lebih lanjut tentang cara kerja teknik ini di sini dan di sini.

Meskipun saya memilih untuk menggunakan primitif tulis konstan, primitif eksploitasi lain yang berguna juga muncul dalam fungsi ini. Kedua argumen BaseAddress dan MemoryDescriptorList untuk panggilan ke MmUnmapLockedPages dapat dikontrol. Ini dapat digunakan untuk menghapus pemetaan pada alamat virtual arbriter dan membangun use-after-free seperti primitif.

Masalah penetapan beban

Pada titik ini, beberapa primitif eksploitasi yang sesuai yang memberikan baca-tulis kernel arbitrer telah diidentifikasi. Anda mungkin telah menyadari bahwa ada beberapa pemeriksaan pada konten objek stream yang harus dilewati untuk memicu jalur kode yang diinginkan. Untuk sebagian besar, keadaan objek yang tepat dapat dicapai melalui pool spraying. Namun, saya mengalami masalah yang menyebabkan beberapa kesulitan. Di bawah ini menunjukkan cuplikan kode FSStreamReg::PublishRx setelah selesai looping melalui FSFrameMdlList:

Cuplikan dekompilasi FSStreamReg::PublishRx

Cuplikan dekompilasi FSStreamReg::PublishRx

Pada dekompilasi di atas, bPagesUnmapped adalah variabel boolean yang akan diatur jika FSFrameMdl::UnmapPages dipanggil. Jika demikian, maka offset 0x1a8 dari objek stream diambil dan jika tidak null, KESetEvent dipanggil di atasnya.

Offset ini sesuai dengan memori di luar batas dan titik dalam POOL_HEADER, struktur data yang memisahkan alokasi buffer di pool. Secara khusus ini menunjuk ke bidang ProcessBilled, yang digunakan untuk menyimpan pointer ke objek _EPROCESS untuk proses yang “dibebankan” pada alokasi. Ini digunakan untuk memperhitungkan berapa banyak alokasi pool yang dapat dimiliki proses tertentu. Tidak semua alokasi pool “dibebankan” terhadap suatu proses, dan yang tidak memiliki bidang ProcessBilled diatur ke NULL di POOL_HEADER. Selain itu, pointer EPROCESS yang disimpan di ProcessBilled sebenarnya di-XOR dengan cookie acak, sehingga ProcessBilled tidak berisi pointer yang valid.

Hal ini menimbulkan kesulitan, karena buffer NpFr dibebankan ke proses panggilan, dan dengan demikian ProcessBilled ditetapkan. Ketika memicu primitif eksploitasi yang dibutuhkan, bPagesUnmapped akan ditetapkan ke TRUE. Jika pointer tidak valid diteruskan ke KeSetEvent, sistem akan crash. Oleh karena itu, perlu untuk memastikan bahwa POOL_HEADER adalah untuk alokasi yang tidak dibebankan. Pada titik ini, saya menyadari bahwa objek pendaftaran konteks (Creg) itu sendiri tidak dibebankan. Namun, objek ini tidak mengizinkan kontrol atas isi memori pada offset FSFrameMdl MDL. Jadi, baik objek NpFr maupun Creg , keduanya harus di-spray, dan juga harus diurutkan dengan benar.

Kebocoran pool - Jangan hanya melakukan spray dan berharap yang terbaik!

Tidak seperti alokasi pool yang besar, Anda tidak bisa membocorkan alamat alokasi pool LFH melalui NtQuerySystemInformation. Selain itu, urutan alokasi bersifat acak. Oleh karena itu, tidak ada cara untuk mengetahui apakah buffer yang berdekatan dengan objek rentan berada dalam urutan yang benar untuk memicu primitif eksploitasi dan menghindari kerusakan sistem. Untungnya, kerentanan ini dapat digunakan untuk memicu kebocoran pool dari buffer yang berdekatan. Mari kita lihat fungsi IOCTL untuk ConsumeTx:

FSRendezvousServer::Cuplikan dekompilasi ConsumeTx

FSRendezvousServer::Cuplikan dekompilasi ConsumeTx

Di atas, fungsi FSStreamReg::GetStats dipanggil:

Dekompilasi FSStreamReg::GetStats

Dekompilasi FSStreamReg::GetStats

Di sini, konten memori di luar batas dari objek aliran rentan disalin ke SystemBuffer yang dikembalikan ke aplikasi ruang pengguna yang melakukan panggilan. Primitif kebocoran informasi kumpulan ini dapat digunakan untuk melakukan pemeriksaan tanda tangan pada buffer yang berdekatan dengan objek yang rentan. Pemindaian banyak objek rentan dapat dilakukan sampai objek dalam tata letak memori yang diinginkan ditemukan. Setelah objek yang dicari ditemukan, tata letak memori adalah sebagai berikut:

CVE-2023-36802 Tata Letak Heap Pool Groom Fragmentasi Rendah

CVE-2023-36802 Tata Letak Heap Pool Groom Fragmentasi Rendah

Sekarang, setelah menemukan objek rentan target di posisi yang benar dalam memori, primitif eksploitasi yang disebutkan di atas pada objek target dapat dipicu tanpa menyebabkan crash pada sistem.

Eksploitasi di dunia nyata

Setelah melaporkan masalah ini ke MSRC, eksploitasi terhadap kerentanan tersebut ditemukan di dunia nyata.

Metode eksploitasi yang disajikan dalam postingan blog ini adalah beberapa dari banyak pendekatan yang dapat diambil. Saat ini, tidak ada informasi publik tentang bagaimana penyerang di dunia nyata mengeksploitasi kerentanan ini. Anda dapat menemukan kode eksploitasi di sini.

Kesimpulan

Analisis tambalan retroaktif mengungkapkan bahwa sebagian besar kode baru ditambahkan ke mskssrv.sys di build 1809 Windows 10. Pemantauan penambahan kode baru seringkali bermanfaat untuk menemukan kerentanan.

Pelajaran lain yang membosankan, tetapi klasik, yang dapat dipelajari dari analisis ini: jangan membuat asumsi tentang pemeriksaan yang dilakukan. Seorang teman dan kolega menyarankan bahwa kebingungan jenis menggunakan FsContext2bisa menjadi “kelas bug yang umum tetapi kurang diteliti”. Saya percaya bahwa diperlukan lebih banyak analisis varian untuk kelas bug ini, terutama pada driver yang berhubungan dengan komunikasi antar proses.

Penemuan kerentanan ini muncul saat hanya mencoba berinteraksi dengan permukaan serangan yang tidak dikenal. Memiliki pengetahuan ”hampir mendekati nol” dari suatu sistem juga dapat berarti memiliki pola pikir segar untuk merusaknya.

Mixture of Experts | 12 Desember, episode 85

Decoding AI: Rangkuman Berita Mingguan

Bergabunglah dengan panel insinyur, peneliti, pemimpin produk, dan sosok kelas dunia lainnya selagi mereka mengupas tuntas tentang AI untuk menghadirkan berita dan insight terbaru seputar AI.