2 puan yazan GN⁺ 2025-02-10 | 1 yorum | WhatsApp'ta paylaş
  • Windows çekirdek sürücülerinin de Rust ile yazılabileceğini göstermek için, herhangi bir iş parçacığının önceliğini değiştiren Booster WDM sürücüsü uygulanıyor
  • Derleme ortamında WDK veya EWDK, LLVM/Clang, windows-drivers-rs tabanlı WDK crate’leri, build.rs ve Cargo.toml ayarlarının birlikte doğru yapılandırılması gerekiyor
  • Çekirdek alanında standart kütüphane kullanılamadığı için #![no_std], WDK allocator, panic handler ve unsafe FFI çağrılarının birlikte kullanılması gerekiyor
  • Sürücü \Device\Booster ve \??\Booster oluşturuyor; IRP_MJ_WRITE içinde alınan ThreadData ile iş parçacığı önceliğini 1–31 aralığında değiştiriyor
  • Rust ile Windows çekirdek sürücüsü yazmak mümkün, ancak WDK crate’leri 0.3 aşamasında olduğu için daha güvenli sarmalayıcılara ve daha az unsafe koda ihtiyaç var

Rust ile uygulanan Booster WDM sürücüsü

  • Örnek, Windows çekirdek programlamasında kullanılan “Booster” sürücüsünün Rust’a taşınmış hâli
    • Amaç, herhangi bir iş parçacığının önceliğini istenen değere değiştirmek
    • Sürücü modeli WDM
  • Rust ekosistemi derleme zamanında bellek güvenliği, eşzamanlılık güvenliği, cargo derleme sistemi ve crate ekosistemi sunuyor
  • C tiplerini Rust’ta doğrudan ele almak kodu gereksiz derecede uzun hâle getirebilir
    • Uygun sarmalayıcılar ve makrolar kullanıldığında bu yük azaltılabilir

Derleme hazırlığı ve Cargo ayarları

  • Sürücü derlemesine hazırlanmak için Windows Drivers-rs incelenmeli ve WDK veya EWDK kurulmalı
  • LLVM kurulumu da gerekir; Clang derleyicisine erişmek için kullanılır
  • Yeni bir Rust kütüphane projesi oluşturulur; çekirdek sürücüsü teknik olarak çekirdek alanına yüklenen bir DLL’dir
cargo new --lib booster
  • build.rs, cargo’nun CRT’ye statik link vermesini ayarlar ve WDK ikili derlemesini yapılandırır
fn main() -> Result<(), wdk_build::ConfigError> {
    std::env::set_var("CARGO_CFG_TARGET_FEATURE", "crt-static");
    wdk_build::configure_wdk_binary_build()
}
  • Cargo.toml içine WDM sürücü modeli, cdylib crate tipi ve WDK ile ilgili bağımlılıklar eklenir
    • Başlıca crate’ler wdk, wdk-macros, wdk-alloc, wdk-panic, wdk-sys
    • dev/release profillerinde panic = "abort" ve lto = true ayarlanır
    • wdk ve wdk-sys için isteğe bağlı nightly feature’ı bulunur

Standart kütüphane olmadan çekirdek kodu yazma

  • Çekirdekte Rust standart kütüphanesi bulunmadığından #![no_std] kullanılır
  • wdk_sys düşük seviyeli çekirdek fonksiyonlarıyla birlikte çalışmayı üstlenir; wdk ise daha yüksek seviyeli sarmalayıcılar sağlar
  • Vec ve String standart kütüphanenin parçası gibi görünse de aslında alloc modülündeki tipler kullanılabilir
    • Bunları kullanmak için küresel bir ayırıcı gerekir
    • WDK crate’lerinin sağladığı WdkAllocator, #[global_allocator] olarak belirtilir
  • WdkAllocator, ayırmaları yönetmek için ExAllocatePool2 ve ExFreePool kullanır
  • Standart kütüphane olmadığı için allocator desteği ve panic handler için wdk_panic ile alloc harici crate’leri eklenir

DriverEntry ve aygıt başlatma

  • Windows çekirdek sürücüsünün giriş noktası DriverEntry’dir
    • Rust fonksiyon adı gelenek gereği driver_entry olarak bırakılır; linker’ın aradığı ad #[export_name = "DriverEntry"] ile belirtilir
  • println! makrosu DbgPrint çağrısı olarak yeniden uygulanmıştır; C/C++’ta DbgPrint kullanır gibi çekirdek debug çıktısı için kullanılabilir
  • UNICODE_STRING, println! içinde doğrudan desteklenmediğinden unicode_to_string fonksiyonuyla Rust String’ine dönüştürülür
  • Aygıt nesnesi IoCreateDevice ile \Device\Booster üzerinde oluşturulur
    • Başarısız olursa hata durumu yazdırılır ve ilgili NTSTATUS döndürülür
    • Başarı durumunu değerlendirmek için WDK’nin NT_SUCCESS makrosuna benzer nt_success kullanılır
  • Aygıtın standart CreateFile çağrısıyla açılabilmesi için IoCreateSymbolicLink ile \??\Booster sembolik bağlantısı oluşturulur
    • Sembolik bağlantı oluşturulamazsa aygıt nesnesi silinir ve hata durumu döndürülür
  • Aygıt nesnesi Buffered I/O kullanacak şekilde ayarlanır
    • DriverUnload, boost_unload olarak ayarlanır
    • IRP_MJ_CREATE ve IRP_MJ_CLOSE, boost_create_close ile işlenir
    • IRP_MJ_WRITE, boost_write ile işlenir
  • Geri çağrıların mevcut olup olmadığı Rust’ın Option<> tipiyle ifade edilir

İstek işleme ve iş parçacığı önceliğini değiştirme

  • Unload rutini, sembolik bağlantıyı ve aygıt nesnesini temizlemek için IoDeleteSymbolicLink ve IoDeleteDevice çağırır
  • IRP_MJ_CREATE ve IRP_MJ_CLOSE işlemleri basittir
    • IRP’nin IoStatus.Status alanı STATUS_SUCCESS olarak ayarlanır
    • IoStatus.Information 0 olarak ayarlanır
    • İstek IofCompleteRequest ile tamamlanır
  • IoStatus, IO_STATUS_BLOCK’tur ve Status erişimi sırasında otomatik oluşturulan union üyesinden geçmek gerektiği için kod pek hoş görünmez
    • Bu tanım daha fazla incelenecek bir nokta olarak kalır
  • Asıl öncelik değiştirme işlemi IRP_MJ_WRITE handler’ında yapılır
  • İstemcinin sürücüye geçirdiği yapı, C/C++ ile aynı bellek yerleşimi için #[repr(C)] kullanır
#[repr(C)]
struct ThreadData {
    pub thread_id: u32,
    pub priority: i32,
}
  • boost_write, Buffered I/O ile iletilen SystemBuffer’ı ThreadData işaretçisi olarak yorumlar
  • Hata kontrolleri şu koşulları içerir
    • Veri işaretçisi null ise STATUS_INVALID_PARAMETER
    • Öncelik 1’den küçük veya 31’den büyükse STATUS_INVALID_PARAMETER
  • PsLookupThreadByThreadId ile iş parçacığı nesnesi bulunur
    • Başarısız olursa ilgili iş parçacığı ID’sinin mevcut olmama olasılığı vardır ve işleme döngüsünden çıkılır
  • İş parçacığı bulunursa KeSetPriorityThread ile öncelik ayarlanır ve ObfDereferenceObject ile referans serbest bırakılır
  • İstek tamamlanırken IRP durum ve bilgi alanları ayarlandıktan sonra IofCompleteRequest çağrılır

İmzalama, kurulum, test

  • INF veya INX dosyası varsa crate’ler sürücü imzalamayı destekliyor gibi görünür; ancak bu örnek INF kullanmadığından elle imzalama gerekir
  • Proje kökünde aşağıdaki komutla derleme çıktısı imzalanabilir
signtool sign /n wdk /fd sha256 target\debug\booster.dll
  • /n wdk, Visual Studio’nun sürücü derlerken genellikle otomatik oluşturduğu WDK test sertifikasını kullanır
  • Derleme çıktısının uzantısı DLL’dir
    • Şu anda cargo build sürecinde uzantıyı otomatik değiştirecek bir yöntem yok
    • INF/INX kullanılırsa uzantı SYS olarak değişir
    • Uzantı elle değiştirilebilir veya DLL olarak bırakılabilir
  • Test imzalamanın açık olduğu bir makinede, yönetici yetkili komut isteminden sc.exe kullanılarak bir yazılım sürücüsü gibi kurulabilir
sc.exe sc create booster type= kernel binPath= c:\path_to_driver_file
sc.exe start booster
  • Test istemcisi mevcut C++ uygulamasını kullanır
    • CreateFile(L"\\\\.\\Booster", GENERIC_WRITE, ...) ile aygıtı açar
    • ThreadData içine iş parçacığı ID’si ve önceliği koyup WriteFile ile iletir
    • Örnek, ID’si 9408 olan iş parçacığının önceliğini 26’ya değiştiren bir testtir

Kalan işler ve referanslar

  • Rust ile çekirdek sürücüsü yazmak mümkündür
  • WDK crate’leri 0.3 sürüm aşamasında olduğundan hâlâ geliştirmeye açıktır
  • Rust’ın avantajlarından tam olarak yararlanmak için daha güvenli sarmalayıcılara ihtiyaç var
    • Kod daha az uzun olmalı
    • unsafe blokları azalmalı
    • Rust’ın sağladığı güvenlik avantajları daha iyi kullanılmalı
  • KMDF Rust sürücü örnekleri Windows-rust-driver-samples içinde bulunur
  • Örnek kod Booster deposunda görülebilir
  • Rust öğrenme materyalleri https://trainsec.net adresinde bulunur

1 yorum

 
GN⁺ 2025-02-10
Hacker News yorumları
  • Uygulama bazında yol yeniden eşleme kuralları ayarlanabilen bir dosya sistemi filtre sürücüsü yapmayı düşünmüştüm.
    Örneğin %userprofile%\.vscode -> %appdata%\vscode, %CSIDL_MYDOCUMENTS%\Call of Duty -> %userprofile%\Saved Games\Call of Duty gibi.
    Documents klasörünün ve ev dizininin zaten belirli yerleri olan ıvır zıvırla dolmasına o kadar sinir olmuştum ki Rust ile bir filtre sürücüsü proje iskeleti hazırlayıp minifilter belgelerini okumuştum; ama iş yükünü görünce vazgeçtim.
    Windows sistemlerinin eninde sonunda çöple dolmaya mahkûm olduğu gerçeğini kabullendim.

    • Kullanıcı dosya sistemini çöplük gibi kullanan işletim sistemi yalnızca Windows değil.
      .DS_Store, .fseventsd, ._xxxx gibi dosyaları kaç kez silsem de Apple bunları oraya buraya saçmaya devam ediyor.
      Yine de macOS’te uygulamaların kurulduğu yer ve kullanıcı belgelerinin bulunduğu yer genelde birer tane ve çoğu uygulama buna bir ölçüde uyuyor.
      Bunun yerine ~/Library gibi belirlenmiş bir çöplük var; içinde de ihtiyacım olup olmadığını bilmediğim bir sürü ıvır zıvır duruyor.
    • Documents klasörüm garip şekilde boştu; baktım ki Shortcut to Documents (OneDrive - Personal) imiş.
      Şimdi o çöplerin cihazlar arasında senkronize ediliyor olması da ayrı güzel.
    • Bu, kullanıcı modunda da birkaç farklı yolla yapılabilir.
      Detours kütüphanesi, süreç çalışırken kullanıcı DLL’i ekleyip dosya sistemi API’lerine yapılan Win32 çağrılarını hook’lamanıza olanak tanır.
      Yerleşik “uyumluluk shim”leri de neredeyse hiç belgelenmemiş olsalar da benzer çalışır; uyumluluk bayraklarını açıp dosya ve kayıt defteri yollarını yönlendirebilirsiniz.
      Yalnızca belirli bir EXE için sezgisel kurallarla devreye girecek şekilde tasarlanmış olması da kullanışlıdır.
      App-V veya daha sonra Docker container’ları gibi teknolojileri desteklemek için Windows’ta dosya sistemi, kayıt defteri ve diğer NT çekirdeği namespace’lerini sanallaştıran bir API yüzeyi var.
      Bunların dışında User Mode Filesystem de var; benim bilmediğim ya da unuttuğum başka yaklaşımlar da muhtemelen vardır.
    • My Documents adlı zehirli atık sahasını tamamen yok sayıyor, gerçekten önemsediğim dosyaları daha kısa yollu başka bir konuma koyuyorum.
      Verinin yerini hardcode etmiş bir iki programı da dizin junction’larıyla yönlendirmiş olabilirim.
    • Microsoft/Windows’ın kendisi de home klasörüne bir sürü ıvır zıvır koyuyor; daha kötüsü, adları da aşırı uzun.
      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 gibi şeyler.
  • Biraz bağlantılı olarak, Windows çekirdeğinde Rust kullanımı konusunda güncel bilgi var mı merak ediyorum.
    Yaklaşık 2 yıl önce “sistem çağrıları da dahil 36.000 satır kod” denmişti [1]; o projenin nasıl ilerlediğini bilmek isterim.
    [1] https://www.thurrott.com/windows/282471/microsoft-is-rewriti...

  • İlginç. Benim Rust ile epey yaptığım gömülü sürücülerden oldukça farklı görünüyor.
    O tarafta işin çoğu register okuma/yazma, bit shift, DMA ve datasheet’e bakma etrafında dönüyor.

  • Buradaki kod fiilen sadece sözdizimi farklı olan C gibi görünüyor. Tüm fonksiyonlar unsafe olarak işaretlenmiş ve tüm kaynaklar elle yönetiliyor.
    Açık konuştuğum için kusura bakmayın ama böyle olunca Rust kullanmanın anlamı nedir, anlayamıyorum.

    • Ayrıca nasıl gözden kaçtı bilmiyorum ama string_to_ustring tarafından döndürülen UNICODE_STRING’i kullanmaya çalışmak kesin bir use-after-free durumu.
      Windows çekirdek kodu yazmak istiyorsanız buradan başlamamalısınız.
    • Ne yazık ki basit örnekler fiilen tamamen tesisat kodundan oluşuyor; bu kısımların hepsi güvenli olmayan native arayüzlere dokunuyor.
      Gerçekten ilginç bir iş yapan bir sürücüde unsafe arayüzleri yalnızca sınırda tutup iç kısmı daha tipik Rust koduyla yazabilirsiniz.
    • Burada tamamı büyük harfli tip adları da inanılmaz fazla.
      Gerçekten standart Rust adlandırma kurallarını tamamen bırakıp eski Windows tarzı “tipi isimle ifade etme” geleneğini mi benimseyeceğiz?
  • Yaklaşık 25 yıl önce Windows için belirli bir sürücü yazmam gerekiyordu.
    O sırada zaten tamamen Linux’a geçmiş olduğumdan yazma ve derleme için Windows kullanmak istemiyordum; bu yüzden MSYS ile derlenebilir hale getirmek için epey uğraşmıştım.
    Sonunda başardım ve sürücü de gayet iyi çalıştı.
    Gerçekten yüklenebilmesi için çıkan PE dosyasına (.sys) bir patcher uygulamam gerekmişti sanırım.
    Eğlenceli zamanlardı.

  • Yazı güzel ama blog tasarımı daha da etkileyici.
    Temiz, sezgisel, göz yormuyor ve anında yükleniyor.

    • Ben de aynıydım. Blogun hangi teknolojiyle yapıldığını tekrar kontrol etmem gerekti.
      Görünüşe göre WordPress.com.
      Caching ve CDN’e epey dayanıyor gibi.
      10 yıl önce kendi WordPress blogumu sürdürmediğime üzüldüm.