- 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
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 DutySaya 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
Berapa kali pun file seperti
.DS_Store,.fseventsd,._xxxxdihapus, Apple terus menebarkannya di mana-manaMeski 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 tidakShortcut to Documents (OneDrive - Personal)Sekarang sampah-sampah itu bahkan disinkronkan antarperangkat, sungguh menyenangkan
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
My Documents, dan menaruh file yang benar-benar saya pedulikan di lokasi lain dengan path yang lebih pendekSatu-dua program yang meng-hardcode lokasi data mungkin saya alihkan dengan directory junction
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 semacamnyaAgak 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...
Yang paling baru adalah firmware security enclave
https://techcommunity.microsoft.com/blog/windows-itpro-blog/...
win32k.syssudah di-port ke RustKalau mencari
win32*.sys, Anda bisa melihat bagaimana pemisahannyaMenarik. 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 manualMaaf kalau blak-blakan, tetapi kalau begini saya tidak tahu apa gunanya memakai Rust
UNICODE_STRINGyang dikembalikan daristring_to_ustringjelas merupakan use-after-freeKalau ingin menulis kode kernel Windows, jangan mulai dari sini
Kalau drivernya benar-benar melakukan hal menarik, Anda bisa menaruh antarmuka
unsafehanya di bagian batas, sementara bagian dalamnya ditulis sebagai kode Rust yang lebih tipikalApakah 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
Sepertinya WordPress.com
Tampaknya banyak bergantung pada caching dan CDN
Jadi menyesal tidak terus mempertahankan blog WordPress saya 10 tahun lalu