2 poin oleh GN⁺ 2025-02-10 | 1 komentar | Bagikan ke WhatsApp
  • Untuk menunjukkan bahwa driver kernel Windows juga dapat ditulis dengan Rust, contoh ini mengimplementasikan driver Booster WDM yang mengubah prioritas thread arbitrer
  • Lingkungan build harus memadukan WDK atau EWDK, LLVM/Clang, crate WDK berbasis windows-drivers-rs, serta konfigurasi build.rs dan Cargo.toml
  • Di ruang kernel, pustaka standar tidak dapat digunakan, sehingga perlu menggabungkan #![no_std], WDK allocator, panic handler, dan pemanggilan FFI unsafe
  • Driver membuat \Device\Booster dan \??\Booster, lalu mengubah prioritas thread dalam rentang 1–31 menggunakan ThreadData yang diterima di IRP_MJ_WRITE
  • Menulis driver kernel Windows dengan Rust memang memungkinkan, tetapi karena WDK crates masih di tahap 0.3, dibutuhkan wrapper yang lebih aman dan lebih sedikit kode unsafe

Driver Booster WDM yang Diimplementasikan dengan Rust

  • Contoh ini memindahkan driver “Booster” yang digunakan dalam pemrograman kernel Windows ke Rust
    • Tujuannya adalah mengubah prioritas thread arbitrer ke nilai yang diinginkan
    • Model drivernya adalah WDM
  • Ekosistem Rust memiliki keamanan memori pada waktu kompilasi, keamanan konkurensi, sistem build cargo, dan ekosistem crates
  • Jika menangani tipe C secara langsung di Rust, kode bisa menjadi panjang dan bertele-tele
    • Dengan wrapper dan makro yang tepat, beban ini dapat dikurangi

Persiapan Build dan Konfigurasi Cargo

  • Untuk menyiapkan build driver, rujuk Windows Drivers-rs, lalu instal WDK atau EWDK
  • Instalasi LLVM juga diperlukan, dan digunakan untuk mengakses compiler Clang
  • Buat proyek pustaka Rust baru; secara teknis, driver kernel adalah DLL yang dimuat ke ruang kernel
cargo new --lib booster
  • build.rs mengatur agar cargo melakukan link statis ke CRT dan mengonfigurasi build biner WDK
fn main() -> Result<(), wdk_build::ConfigError> {
    std::env::set_var("CARGO_CFG_TARGET_FEATURE", "crt-static");
    wdk_build::configure_wdk_binary_build()
}
  • Cargo.toml mencantumkan model driver WDM, tipe crate cdylib, dan dependensi terkait WDK
    • Crate utamanya adalah wdk, wdk-macros, wdk-alloc, wdk-panic, dan wdk-sys
    • Pada profil dev/release, tetapkan panic = "abort" dan lto = true
    • wdk dan wdk-sys memiliki feature nightly yang bersifat opsional

Menulis Kode Kernel Tanpa Pustaka Standar

  • Karena tidak ada pustaka standar Rust di kernel, gunakan #![no_std]
  • wdk_sys menangani interoperabilitas dengan fungsi kernel tingkat rendah, sementara wdk menyediakan wrapper tingkat lebih tinggi
  • Vec dan String tampak seperti bagian dari pustaka standar, tetapi sebenarnya tipe dari modul alloc dapat digunakan
    • Untuk menggunakannya, diperlukan allocator global
    • Tetapkan WdkAllocator yang disediakan WDK crates sebagai #[global_allocator]
  • WdkAllocator mengelola alokasi menggunakan ExAllocatePool2 dan ExFreePool
  • Karena tidak ada pustaka standar, tambahkan crate eksternal wdk_panic dan alloc untuk dukungan allocator dan panic handler

DriverEntry dan Inisialisasi Perangkat

  • Entry point driver kernel Windows adalah DriverEntry
    • Nama fungsi Rust mengikuti konvensi sebagai driver_entry, dan #[export_name = "DriverEntry"] menetapkan nama yang dicari linker
  • Makro println! diimplementasikan ulang sebagai pemanggilan DbgPrint, sehingga dapat digunakan untuk output debug kernel seperti menggunakan DbgPrint di C/C++
  • UNICODE_STRING tidak didukung langsung oleh println!, sehingga dikonversi menjadi String Rust dengan fungsi unicode_to_string
  • Objek perangkat dibuat di \Device\Booster dengan IoCreateDevice
    • Jika gagal, status error dicetak dan NTSTATUS terkait dikembalikan
    • Untuk menentukan keberhasilan, digunakan nt_success, yang mirip dengan makro NT_SUCCESS dari WDK
  • Agar perangkat dapat dibuka dengan pemanggilan standar CreateFile, tautan simbolik \??\Booster dibuat dengan IoCreateSymbolicLink
    • Jika pembuatan tautan simbolik gagal, objek perangkat dihapus dan status kegagalan dikembalikan
  • Objek perangkat diatur untuk menggunakan Buffered I/O
    • DriverUnload diatur ke boost_unload
    • IRP_MJ_CREATE dan IRP_MJ_CLOSE ditangani oleh boost_create_close
    • IRP_MJ_WRITE ditangani oleh boost_write
  • Ada atau tidaknya callback direpresentasikan dengan tipe Option<> milik Rust

Pemrosesan Permintaan dan Perubahan Prioritas Thread

  • Rutin unload membersihkan tautan simbolik dan objek perangkat dengan memanggil IoDeleteSymbolicLink dan IoDeleteDevice
  • Penanganan IRP_MJ_CREATE dan IRP_MJ_CLOSE sederhana
    • IoStatus.Status milik IRP diatur ke STATUS_SUCCESS
    • IoStatus.Information diatur ke 0
    • Permintaan diselesaikan dengan IofCompleteRequest
  • IoStatus adalah IO_STATUS_BLOCK, dan saat mengakses Status harus melewati anggota union yang dibuat otomatis, sehingga kodenya kurang rapi
    • Definisi tersebut masih menjadi bagian yang perlu diperiksa lebih lanjut
  • Perubahan prioritas sebenarnya dilakukan di handler IRP_MJ_WRITE
  • Struktur yang diteruskan klien ke driver menggunakan #[repr(C)] agar tata letak memorinya sama seperti C/C++
#[repr(C)]
struct ThreadData {
    pub thread_id: u32,
    pub priority: i32,
}
  • boost_write menafsirkan SystemBuffer yang diteruskan melalui Buffered I/O sebagai pointer ke ThreadData
  • Pemeriksaan error mencakup kondisi berikut
    • Jika pointer data null, hasilnya STATUS_INVALID_PARAMETER
    • Jika prioritas lebih kecil dari 1 atau lebih besar dari 31, hasilnya STATUS_INVALID_PARAMETER
  • Objek thread dicari dengan PsLookupThreadByThreadId
    • Jika gagal, kemungkinan ID thread tersebut tidak ada, lalu keluar dari loop pemrosesan
  • Jika thread ditemukan, prioritas diatur dengan KeSetPriorityThread dan referensi dilepas dengan ObfDereferenceObject
  • Saat menyelesaikan permintaan, field status dan informasi IRP diatur, lalu IofCompleteRequest dipanggil

Penandatanganan, Instalasi, dan Pengujian

  • Jika ada file INF atau INX, tampaknya crates mendukung penandatanganan driver, tetapi contoh ini tidak menggunakan INF sehingga perlu penandatanganan manual
  • Dari root proyek, artefak build dapat ditandatangani dengan perintah berikut
signtool sign /n wdk /fd sha256 target\debug\booster.dll
  • /n wdk menggunakan sertifikat uji WDK yang biasanya dibuat otomatis oleh Visual Studio saat membangun driver
  • Ekstensi artefak build adalah DLL
    • Saat ini tidak ada cara untuk mengubah ekstensi secara otomatis dalam proses cargo build
    • Jika menggunakan INF/INX, ekstensi akan diubah menjadi SYS
    • Ekstensi dapat diubah secara manual, atau dibiarkan tetap DLL
  • Pada mesin dengan test signing aktif, driver dapat diinstal seperti driver perangkat lunak menggunakan sc.exe dari Command Prompt dengan hak administrator
sc.exe sc create booster type= kernel binPath= c:\path_to_driver_file
sc.exe start booster
  • Klien pengujian menggunakan aplikasi C++ yang sudah ada
    • Perangkat dibuka dengan CreateFile(L"\\\\.\\Booster", GENERIC_WRITE, ...)
    • ID thread dan prioritas dimasukkan ke ThreadData, lalu diteruskan dengan WriteFile
    • Contohnya adalah pengujian yang mengubah prioritas thread ber-ID 9408 menjadi 26

Tugas yang Tersisa dan Referensi

  • Menulis driver kernel dengan Rust memang memungkinkan
  • WDK crates masih berada pada versi 0.3, sehingga masih ada ruang untuk perbaikan
  • Untuk benar-benar mendapatkan keunggulan Rust, diperlukan lebih banyak wrapper yang aman
    • Kode harus tidak terlalu panjang
    • Blok unsafe perlu dikurangi
    • Manfaat keamanan yang disediakan Rust harus dimanfaatkan dengan lebih baik
  • Contoh driver Rust KMDF tersedia di Windows-rust-driver-samples
  • Kode contoh dapat dilihat di repositori Booster
  • Materi belajar Rust tersedia di https://trainsec.net

1 komentar

 
GN⁺ 2025-02-10
Opini Hacker News
  • Saya pernah terpikir membuat driver filter filesystem yang bisa menetapkan aturan pemetaan ulang path per aplikasi
    Misalnya seperti %userprofile%\.vscode -> %appdata%\vscode, %CSIDL_MYDOCUMENTS%\Call of Duty -> %userprofile%\Saved Games\Call of Duty
    Saya begitu kesal karena folder Documents dan direktori home sudah penuh dengan sampah yang lokasinya ditentukan, sampai membuat kerangka proyek driver filter dengan Rust dan membaca dokumentasi minifilter, tetapi menyerah setelah melihat besarnya pekerjaan
    Akhirnya saya menerima kenyataan bahwa sistem Windows memang tak terhindarkan akan penuh sampah

    • Sistem operasi yang memakai filesystem pengguna seperti tempat sampah bukan cuma Windows
      Berapa kali pun file seperti .DS_Store, .fseventsd, ._xxxx dihapus, Apple terus menebarkannya di mana-mana
      Meski begitu, macOS umumnya punya satu lokasi untuk instalasi aplikasi dan satu lokasi untuk dokumen pengguna, dan kebanyakan aplikasi cukup mematuhinya
      Sebagai gantinya ada tempat sampah yang sudah ditentukan seperti ~/Library, dan di sana penuh berbagai macam barang yang entah saya butuhkan atau tidak
    • Folder Documents saya anehnya kosong, lalu ketika saya lihat ternyata itu Shortcut to Documents (OneDrive - Personal)
      Sekarang sampah-sampah itu bahkan disinkronkan antarperangkat, sungguh menyenangkan
    • Ini juga bisa dilakukan di user mode dengan berbagai cara
      Library Detours memungkinkan menempelkan DLL pengguna saat proses dijalankan untuk meng-hook panggilan Win32 ke API filesystem
      “Compatibility shim” bawaan juga hampir tidak terdokumentasi, tetapi bekerja dengan cara serupa; Anda bisa mengaktifkan flag kompatibilitas dan mengalihkan path file serta registry
      Praktis juga karena dirancang agar aktif secara heuristik hanya untuk EXE tertentu
      Untuk mendukung teknologi seperti App-V atau kemudian container Docker, Windows memiliki permukaan API untuk memvirtualisasi filesystem, registry, dan namespace kernel NT lainnya
      Selain itu ada User Mode Filesystem, dan mungkin masih ada pendekatan lain yang tidak saya ketahui atau sudah saya lupakan
    • Saya sepenuhnya mengabaikan tempat limbah beracun bernama My Documents, dan menaruh file yang benar-benar saya pedulikan di lokasi lain dengan path yang lebih pendek
      Satu-dua program yang meng-hardcode lokasi data mungkin saya alihkan dengan directory junction
    • Microsoft/Windows sendiri juga menaruh banyak sampah di folder home, dan lebih parahnya lagi namanya terlalu panjang
      Contohnya ntuser.ini, ntuser.dat.LOG1, ntuser.dat.LOG2, NTUSER.DAT, NTUSER.DAT{b2352f18-cdbf-1122-8680-002248483d79}.TM.blf, NTUSER.DAT{b2352f18-cdbf-1122-8680-002248483d79}.TMContainer00000000000000000001.regtrans-ms, NTUSER.DAT{b2352f18-cdbf-1122-8680-002248483d79}.TMContainer00000000000000000002.regtrans-ms, dan semacamnya
  • Agak terkait, saya penasaran apakah ada informasi terbaru tentang status penggunaan Rust di kernel Windows
    Hampir 2 tahun lalu disebut ada “36.000 baris kode termasuk system call” [1], dan saya ingin tahu bagaimana perkembangan proyek itu
    [1] https://www.thurrott.com/windows/282471/microsoft-is-rewriti...

  • Menarik. Ini terlihat cukup berbeda dari driver embedded yang banyak saya kerjakan dengan Rust
    Di bidang itu, sebagian besar berkutat pada membaca/menulis register, bit shift, DMA, dan merujuk datasheet

  • Kode di sini praktis terlihat seperti C dengan sintaks berbeda. Semua fungsi ditandai unsafe, dan semua resource dikelola manual
    Maaf kalau blak-blakan, tetapi kalau begini saya tidak tahu apa gunanya memakai Rust

    • Dan entah bagaimana terlewat, tetapi upaya menggunakan UNICODE_STRING yang dikembalikan dari string_to_ustring jelas merupakan use-after-free
      Kalau ingin menulis kode kernel Windows, jangan mulai dari sini
    • Sayangnya contoh sederhana pada dasarnya hampir seluruhnya berupa kode plumbing, sehingga bagian seperti itu semuanya menyentuh antarmuka native yang tidak aman
      Kalau drivernya benar-benar melakukan hal menarik, Anda bisa menaruh antarmuka unsafe hanya di bagian batas, sementara bagian dalamnya ditulis sebagai kode Rust yang lebih tipikal
    • Di sini juga ada sangat banyak nama tipe dengan huruf kapital semua
      Apakah benar-benar mau membuang semua konvensi penamaan standar Rust dan menerima kebiasaan lama Windows yang “menyatakan tipe lewat nama”?
  • Sekitar 25 tahun lalu saya harus menulis driver tertentu untuk Windows
    Saat itu saya sudah sepenuhnya beralih ke Linux, jadi saya tidak mau memakai Windows untuk menulis dan membangunnya; saya cukup berusaha keras agar bisa dibangun dengan MSYS
    Akhirnya berhasil, dan drivernya juga berjalan dengan baik
    Sepertinya agar benar-benar bisa dimuat, saya harus memakai patcher pada file PE hasil build (.sys)
    Masa-masa yang menarik

  • Artikelnya bagus, tetapi desain blognya lebih mengesankan
    Rapi, intuitif, nyaman di mata, dan langsung termuat

    • Saya juga merasakan hal yang sama. Saya sampai harus mengecek lagi blog itu memakai teknologi apa
      Sepertinya WordPress.com
      Tampaknya banyak bergantung pada caching dan CDN
      Jadi menyesal tidak terus mempertahankan blog WordPress saya 10 tahun lalu