2 पॉइंट द्वारा GN⁺ 2025-02-10 | 1 टिप्पणियां | WhatsApp पर शेयर करें
  • यह दिखाने के लिए कि Windows kernel driver भी Rust में लिखे जा सकते हैं, किसी भी thread की priority बदलने वाला Booster WDM driver implement किया गया
  • build environment में WDK या EWDK, LLVM/Clang, windows-drivers-rs आधारित WDK crates, और build.rsCargo.toml configuration का आपस में सही मेल होना चाहिए
  • kernel space में standard library इस्तेमाल नहीं की जा सकती, इसलिए #![no_std], WDK allocator, panic handler, और unsafe FFI calls को मिलाकर इस्तेमाल करना पड़ता है
  • driver \Device\Booster और \??\Booster बनाता है, और IRP_MJ_WRITE में मिले ThreadData से thread priority को 1~31 की range में बदलता है
  • Rust से Windows kernel driver लिखना संभव है, लेकिन WDK crates अभी 0.3 stage में हैं, इसलिए ज्यादा सुरक्षित wrappers और कम unsafe code की जरूरत है

Rust में implement किया गया Booster WDM driver

  • यह example Windows kernel programming में इस्तेमाल होने वाले “Booster” driver को Rust में port किया गया है
    • मकसद किसी भी thread की priority को मनचाही value में बदलना है
    • driver model WDM है
  • Rust ecosystem में compile-time memory safety, concurrency safety, cargo build system, और crates ecosystem मौजूद हैं
  • C types को Rust में सीधे handle करने पर code लंबा और verbose हो सकता है
    • सही wrappers और macros इस्तेमाल करने से यह बोझ कम किया जा सकता है

build तैयारी और Cargo configuration

  • driver build की तैयारी के लिए Windows Drivers-rs देखें, और WDK या EWDK install करना होगा
  • LLVM install करना भी जरूरी है, जिसका इस्तेमाल Clang compiler access करने के लिए होता है
  • एक नया Rust library project बनाएं, क्योंकि kernel driver तकनीकी रूप से kernel space में load होने वाला DLL है
cargo new --lib booster
  • build.rs cargo को CRT से static link करने के लिए configure करता है और WDK binary build setup करता है
fn main() -> Result<(), wdk_build::ConfigError> {
    std::env::set_var("CARGO_CFG_TARGET_FEATURE", "crt-static");
    wdk_build::configure_wdk_binary_build()
}
  • Cargo.toml में WDM driver model, cdylib crate type, और WDK-संबंधित dependencies डालें
    • मुख्य crates wdk, wdk-macros, wdk-alloc, wdk-panic, wdk-sys हैं
    • dev/release profiles में panic = "abort" और lto = true set करें
    • wdk और wdk-sys में optionally nightly feature होता है

standard library के बिना kernel code लिखना

  • kernel में Rust standard library नहीं होती, इसलिए #![no_std] इस्तेमाल करें
  • wdk_sys low-level kernel functions के साथ interoperability संभालता है, और wdk ज्यादा high-level wrappers देता है
  • Vec और String standard library का हिस्सा लगते हैं, लेकिन असल में alloc module के types इस्तेमाल किए जा सकते हैं
    • इन्हें इस्तेमाल करने के लिए global allocator चाहिए
    • WDK crates द्वारा दिए गए WdkAllocator को #[global_allocator] के रूप में specify करें
  • WdkAllocator allocation manage करने के लिए ExAllocatePool2 और ExFreePool का इस्तेमाल करता है
  • standard library न होने की वजह से allocator support और panic handler के लिए wdk_panic और alloc external crate जोड़ें

DriverEntry और device initialization

  • Windows kernel driver का entry point DriverEntry होता है
    • Rust function name convention के हिसाब से driver_entry रखें, और linker द्वारा खोजे जाने वाले नाम के लिए #[export_name = "DriverEntry"] specify करें
  • println! macro को DbgPrint call के रूप में reimplement किया गया है, इसलिए C/C++ में DbgPrint इस्तेमाल करने की तरह इसे kernel debug output के लिए इस्तेमाल कर सकते हैं
  • UNICODE_STRING को println! में सीधे support नहीं मिलता, इसलिए unicode_to_string function से Rust String में convert करें
  • device object IoCreateDevice से \Device\Booster पर बनाया जाता है
    • fail होने पर error status print करके वही NTSTATUS return करता है
    • success check के लिए WDK के NT_SUCCESS macro जैसा nt_success इस्तेमाल होता है
  • standard CreateFile call से device खोला जा सके, इसके लिए IoCreateSymbolicLink से \??\Booster symbolic link बनाया जाता है
    • symbolic link creation fail होने पर device object delete करके failure status return करता है
  • device object को Buffered I/O इस्तेमाल करने के लिए configure किया जाता है
    • DriverUnload को boost_unload पर set किया जाता है
    • IRP_MJ_CREATE और IRP_MJ_CLOSE को boost_create_close handle करता है
    • IRP_MJ_WRITE को boost_write handle करता है
  • callback मौजूद है या नहीं, इसे Rust के Option<> type से represent किया जाता है

request handling और thread priority बदलना

  • unload routine IoDeleteSymbolicLink और IoDeleteDevice call करके symbolic link और device object को clean up करता है
  • IRP_MJ_CREATE और IRP_MJ_CLOSE handling simple है
    • IRP के IoStatus.Status को STATUS_SUCCESS पर set करता है
    • IoStatus.Information को 0 पर set करता है
    • IofCompleteRequest से request complete करता है
  • IoStatus एक IO_STATUS_BLOCK है, और Status access करते समय auto-generated union member से होकर जाना पड़ता है, जिससे code अच्छा नहीं दिखता
    • इस definition को और verify करने की जरूरत बाकी है
  • वास्तविक priority change IRP_MJ_WRITE handler में किया जाता है
  • client जो structure driver को देता है, उसमें C/C++ जैसा memory layout रखने के लिए #[repr(C)] इस्तेमाल होता है
#[repr(C)]
struct ThreadData {
    pub thread_id: u32,
    pub priority: i32,
}
  • boost_write Buffered I/O से मिले SystemBuffer को ThreadData pointer के रूप में interpret करता है
  • error checks में ये conditions शामिल हैं
    • data pointer null हो तो STATUS_INVALID_PARAMETER
    • priority 1 से कम या 31 से ज्यादा हो तो STATUS_INVALID_PARAMETER
  • PsLookupThreadByThreadId से thread object खोजता है
    • fail होने पर संभव है कि वह thread ID मौजूद न हो, और processing loop से बाहर निकल जाता है
  • thread मिल जाए तो KeSetPriorityThread से priority set करता है और ObfDereferenceObject से reference release करता है
  • request complete करते समय IRP status और information fields set करने के बाद IofCompleteRequest call करता है

signing, installation, testing

  • INF या INX file होने पर crates driver signing support करते दिखते हैं, लेकिन यह example INF इस्तेमाल नहीं करता, इसलिए manual signing चाहिए
  • project root से नीचे दिए command से build artifact sign किया जा सकता है
signtool sign /n wdk /fd sha256 target\debug\booster.dll
  • /n wdk उस WDK test certificate का इस्तेमाल करता है जिसे Visual Studio आमतौर पर driver build के समय automatically generate करता है
  • build artifact का extension DLL है
    • फिलहाल cargo build process में extension automatically बदलने का कोई तरीका नहीं है
    • INF/INX इस्तेमाल करने पर extension SYS में बदल जाता है
    • extension को खुद बदल सकते हैं या DLL वैसे ही रख सकते हैं
  • test signing enabled वाली machine पर administrator command prompt से sc.exe इस्तेमाल करके इसे software driver की तरह install किया जा सकता है
sc.exe sc create booster type= kernel binPath= c:\path_to_driver_file
sc.exe start booster
  • test client मौजूदा C++ application इस्तेमाल करता है
    • CreateFile(L"\\\\.\\Booster", GENERIC_WRITE, ...) से device खोलता है
    • ThreadData में thread ID और priority डालकर WriteFile से भेजता है
    • example ID 9408 वाले thread की priority 26 में बदलने का test है

बाकी काम और reference material

  • Rust से kernel driver लिखना संभव है
  • WDK crates version 0.3 stage में हैं, इसलिए अभी सुधार की गुंजाइश है
  • Rust के फायदे ठीक से पाने के लिए ज्यादा safe wrappers चाहिए
    • code कम verbose होना चाहिए
    • unsafe blocks कम होने चाहिए
    • Rust द्वारा दिए जाने वाले safety advantages का बेहतर इस्तेमाल होना चाहिए
  • KMDF Rust driver samples Windows-rust-driver-samples में हैं
  • example code Booster repository में देखा जा सकता है
  • Rust learning material https://trainsec.net पर है

1 टिप्पणियां

 
GN⁺ 2025-02-10
Hacker News की राय
  • मैंने कभी ऐसा filesystem filter driver बनाने के बारे में सोचा था जिसमें application के हिसाब से path remapping rules सेट किए जा सकें
    उदाहरण के लिए %userprofile%\.vscode -> %appdata%\vscode, %CSIDL_MYDOCUMENTS%\Call of Duty -> %userprofile%\Saved Games\Call of Duty जैसी चीज़ें
    Documents folder और home directory पहले से तय जगहों पर पड़े हर तरह के कचरे से भर जाते हैं, यह बात मुझे बहुत चिढ़ाती थी। इसलिए मैंने Rust में filter driver project का skeleton बनाया और minifilter docs पढ़े, लेकिन काम की मात्रा देखकर छोड़ दिया
    आखिरकार मैंने मान लिया कि Windows system तो अंत में कचरे से भर ही जाएगा

    • user filesystem को कूड़ेदान की तरह इस्तेमाल करने वाला OS सिर्फ Windows नहीं है
      .DS_Store, .fseventsd, ._xxxx जैसी फाइलें Apple बार-बार हर जगह बिखेर देता है, चाहे उन्हें कितनी ही बार मिटाओ
      फिर भी macOS में application install location और user documents location आम तौर पर एक-एक तय जगह पर होते हैं, और ज़्यादातर apps किसी हद तक इसका पालन करते हैं
      इसकी जगह ~/Library जैसा एक तयशुदा कूड़ाघर है, जिसमें न जाने किस काम की ढेर सारी बेकार चीज़ें भरी रहती हैं
    • मेरा Documents folder अजीब तरह से खाली था, तो देखा कि वह Shortcut to Documents (OneDrive - Personal) निकला
      अब वह कचरा devices के बीच sync भी हो रहा है, क्या ही शानदार बात है
    • यह user mode में भी कई तरीकों से किया जा सकता है
      Detours library process launch के समय user DLL attach करके filesystem API के लिए Win32 calls को hook करने देती है
      built-in “compatibility shim” भी, भले लगभग documented न हो, इसी तरह काम करती है और compatibility flags ऑन करके file और registry paths को redirect किया जा सकता है
      यह बात भी सुविधाजनक है कि इसे सिर्फ खास EXE के लिए heuristics के आधार पर trigger होने के लिए design किया गया है
      App-V या बाद में Docker containers जैसी technologies को support करने के लिए Windows में filesystem, registry और बाकी NT kernel namespace को virtualize करने वाली API surface मौजूद है
      इसके अलावा User Mode Filesystem भी है, और शायद कुछ ऐसे approaches भी होंगे जिनके बारे में मुझे पता नहीं या जो मैं भूल रहा हूँ
    • मैं My Documents नाम के toxic waste dump को पूरी तरह ignore करता हूँ, और जिन files की सच में परवाह है उन्हें छोटे path वाली किसी दूसरी location पर रखता हूँ
      एक-दो programs जो data location hardcode करते थे, उन्हें शायद directory junctions से redirect किया था
    • Microsoft/Windows खुद भी home folder में बहुत सारा कचरा डालता है, और उससे भी बुरा यह कि उनके नाम बेवजह बहुत लंबे होते हैं
      जैसे 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 जैसी चीज़ें
  • थोड़ा संबंधित सवाल है, Windows kernel में Rust के उपयोग की स्थिति पर कोई ताज़ा जानकारी है क्या?
    करीब 2 साल पहले “system calls सहित 36,000 lines of code” कहा गया था [1], तो जानना चाहता हूँ कि वह project कहाँ तक पहुँचा
    [1] https://www.thurrott.com/windows/282471/microsoft-is-rewriti...

    • kernel वाली तरफ़ की कोई खबर नहीं सुनी, लेकिन Microsoft दूसरी sensitive low-level areas में Rust को आगे बढ़ाता दिख रहा है
      सबसे हाल का उदाहरण security enclave firmware है
      https://techcommunity.microsoft.com/blog/windows-itpro-blog/...
    • 24H2 changes में इससे जुड़ी जानकारी है: https://learn.microsoft.com/en-us/windows/whats-new/whats-ne...
    • यह बस एक anecdote है, लेकिन win32k.sys का कुछ हिस्सा Rust में port किया गया है
      win32*.sys खोजोगे तो देख सकते हो कि इसे कैसे अलग किया गया है
  • दिलचस्प है। यह उस embedded driver काम से काफ़ी अलग लगता है जो मैंने Rust में ज़्यादा किया है
    वहाँ ज़्यादातर काम register read/write, bit shifts, DMA और datasheet देखने के इर्द-गिर्द होता है

  • यहाँ का code तो लगभग ऐसी C जैसा लगता है जिसमें बस syntax अलग है। हर function unsafe के रूप में mark है, और हर resource manually manage किया जा रहा है
    साफ़ कहूँ तो समझ नहीं आता कि ऐसे में Rust इस्तेमाल करने का मतलब क्या है

    • और पता नहीं यह बात कैसे छूट गई, लेकिन string_to_ustring से लौटाया गया UNICODE_STRING इस्तेमाल करने की कोशिश तो पक्का use-after-free है
      अगर Windows kernel code लिखना है, तो यहाँ से शुरुआत नहीं करनी चाहिए
    • अफ़सोस की बात है कि simple examples लगभग पूरी तरह plumbing code ही होते हैं, इसलिए ऐसे हिस्सों में unsafe native interfaces को छूना ही पड़ता है
      कोई driver जो सच में दिलचस्प काम करता हो, उसमें boundary पर ही unsafe interfaces रखे जा सकते हैं और अंदर का हिस्सा ज़्यादा पारंपरिक Rust code में लिखा जा सकता है
    • यहाँ पूरी तरह uppercase type names भी बहुत ज़्यादा हैं
      क्या सच में standard Rust naming conventions को पूरी तरह छोड़कर पुरानी Windows-शैली की “नाम से type बताने” वाली परंपरा अपना ली जाएगी?
  • करीब 25 साल पहले मुझे Windows के लिए एक खास driver लिखना पड़ा था
    तब तक मैं पूरी तरह Linux पर जा चुका था, इसलिए लिखने और build करने के लिए Windows इस्तेमाल नहीं करना चाहता था। इस वजह से मैंने इसे MSYS से build होने लायक बनाने में काफ़ी मेहनत की
    आख़िरकार यह सफल रहा और driver भी ठीक चला
    शायद सच में load कराने के लिए output PE file (.sys) पर एक patcher चलाना पड़ता था
    मज़ेदार दौर था

  • लेख अच्छा है, लेकिन blog design उससे भी ज़्यादा प्रभावशाली है
    साफ़, सहज, आँखों को सुकून देने वाला और तुरंत load हो जाता है

    • मेरे साथ भी यही हुआ। मुझे फिर से जाकर देखना पड़ा कि blog किस technology पर बना है
      देखकर लगा कि यह WordPress.com है
      लगता है caching और CDN पर काफ़ी निर्भर है
      अब अफ़सोस होता है कि 10 साल पहले अपना WordPress blog चलाए नहीं रखा