当前位置: 首页 > news >正文

2507rust,rust写驱动

原文

Rust语言生态系统每天都在增长,它的受欢迎程度越来越高,这是有充分理由的.它是唯一在编译时提供内存和并发安全性主流语言,有强大而丰富构建系统(cargo)和越来越多的包().

操作者的日常驱动仍是C++,因为大部分工作都是窗口CCOMAPI很容易使用的低级系统和内核编程的.然而,Rust是一个系统语言,即可,或至少可在与C/C++相同场景中应用.

主要问题是按Rust期望的冗长转换C类型时.该"冗长"可通过适当的包装器和宏来缓解.
我决定试编写简单有用的WDM驱动.它是我在书中说明的"Booster"驱动的Rust版本(窗口内核编程),它允许按任何值更改任何线程的优先级.

开始

要准备生成驱动,请查阅这里,但基本上应该安装WDK(正常或EWDK).此外,文档需要安装LLVM,才能访问Clang编译器,这里,这里.

如果想尝试以下操作,我假设你已安装了这些.

可从创建一个新的Rust库项目开始(因为驱动在技术上是一个,在内核空间中加载的DLL):

cargo new -lib booster

可在VSCode中打开booster目录,然后开始编码.首先,为了成功编译和链接实际代码,需要做一些准备工作.
需要一个告诉货物在CRT静态链接的build.rs文件.在根助推器目录添加build.rs文件,代码如下:

fn main() -> Result<(), wdk_build::ConfigError> {std::env::set_var("CARGO_CFG_TARGET_FEATURE", "crtstatic");wdk_build::configure_wdk_binary_build()
}

接着,需要编辑cargo.toml添加各种依赖.以下是最低依赖:

[package]
name = "booster"
version = "0.1.0"
edition = "2021"[package.metadata.wdk.drivermodel]
drivertype = "WDM"[lib]
cratetype = ["cdylib"]
test = false[builddependencies]
wdkbuild = "0.3.0"[dependencies]
wdk = "0.3.0"
wdkmacros = "0.3.0"
wdkalloc = "0.3.0"
wdkpanic = "0.3.0"
wdksys = "0.3.0"[features]
default = []
nightly = ["wdk/nightly", "wdksys/nightly"][profile.dev]
panic = "abort"
lto = true[profile.release]
panic = "abort"
lto = true

重要的部分是WDK依赖.该在lib.rs中取实际代码了.

代码

首先删除标准库,因为内核中没有它:

#![no_std]

接着,添加一些use语句以减少代码的冗长性:

use core::ffi::c_void;
use core::ptr::null_mut;
use alloc::vec::Vec;
use alloc::{slice, string::String};
use wdk::*;
use wdk_alloc::WdkAllocator;
use wdk_sys::ntddk::*;
use wdk_sys::*;

wdk_sys包提供低级互操作内核函数.WDK包提供更高级的包装器.alloc::vec::Vec是有趣的.因为不能使用标准库,你或会认为std::vec::Vec<>不可用,这是正确的.

但是,Vec实际上是在,可在标准库之外使用的叫alloc::vec较低级模块中定义的.这工作,因为Vec的唯一要求是有一个分配和释放内存的方法.

Rust通过任何人都可提供的全局分配器对象公开了这一方面.因为没有标准库,因此没有全局分配器,因此必须提供一个.

然后,Vec(和)就可正常工作了:

#[global_allocator]
static GLOBAL_ALLOCATOR: WdkAllocator = WdkAllocator;

这是与手动一样使用ExAllocatePool2ExFreePool来管理分配的WDK包提供的全局分配器.
这里
这里

接着,添加两个extern包以支持分配器和一个恐慌处理器的支持,因为不包括标准库,这是必须提供的另一件事.Cargo.toml有一个,如果任何代码出现恐慌,则中止驱动(使系统崩溃)的设置:

extern 包 wdk_panic;
extern 包 alloc;

现在该编写实际的代码了.从DriverEntry开始,它是任何窗口内核驱动的入口:

#[export_name = "DriverEntry"]
pub unsafe extern "system" fn driver_entry(driver: &mut DRIVER_OBJECT,registry_path: PUNICODE_STRING,
) -> NTSTATUS {

熟悉内核驱动的人会识别函数签名.函数名,driver_entry符合函数的snake_case(蛇形)Rust命名约定,但因为链接器会查找DriverEntry,因此使用export_name(导出名)属性装饰函数.

如果愿意,可用DriverEntry并忽略或禁止编译器的警告.

与使用C/C++一样,可用熟悉的调用DbgPrint,重新实现println!宏.注意,你仍可调用DbgPrint,但println!更简单:

println!("DriverEntry from Rust! {:p}", &driver);
let registry_path = unicode_to_string(registry_path);
println!("Registry Path: {}", registry_path);

可惜,它似乎是println!还不支持UNICODE_STRING,所以可编写叫unicode_to_string的函数来按普通的Rust串转换UNICODE_STRING:

fn unicode_to_string(str: PCUNICODE_STRING) -> String {String::from_utf16_lossy(unsafe {slice::from_raw_parts((*str).Buffer, (*str).Length as usize / 2)})
}

回到DriverEntry,下个业务的顺序是创建叫"\Device\Booster"设备对象:

let mut dev = null_mut();
let mut dev_name = UNICODE_STRING::default();
string_to_ustring("\\Device\\Booster", &mut dev_name);
let status = IoCreateDevice(driver,0,&mut dev_name,FILE_DEVICE_UNKNOWN,0,0u8,&mut dev,
);

string_to_ustring函数按UNICODE_STRING转换Rust串:

fn string_to_ustring(s: &str, uc: &mut UNICODE_STRING) -> Vec<u16> {let mut wstring: Vec<_> = s.encode_utf16().collect();uc.Length = wstring.len() as u16 * 2;uc.MaximumLength = wstring.len() as u16 * 2;uc.Buffer = wstring.as_mut_ptr();wstring
}

比想要的更复杂,但可按编写一次然后可随处使用的函数对待它.
如果创建设备失败,将返回失败状态:

if !nt_success(status) {println!("Error creating device 0x{:X}", status);return status;
}

nt_success类似WDK头文件提供的NT_SUCCESS宏.
接着,将创建一个符号链接,这样标准CreateFile调用可打开设备句柄:

let mut sym_name = UNICODE_STRING::default();
let _ = string_to_ustring("\\??\\Booster", &mut sym_name);
let status = IoCreateSymbolicLink(&mut sym_name, &mut dev_name);
if !nt_success(status) {println!("Error creating symbolic link 0x{:X}", status);IoDeleteDevice(dev);return status;
}

剩下就是初化设备对象,以支持缓冲I/O(为了简单,将使用IRP_MJ_WRITE),设置驱动卸载例程及要支持的主函数:

    (*dev).Flags |= DO_BUFFERED_IO;driver.DriverUnload = Some(boost_unload);driver.MajorFunction[IRP_MJ_CREATE as usize] = Some(boost_create_close);driver.MajorFunction[IRP_MJ_CLOSE as usize] = Some(boost_create_close);driver.MajorFunction[IRP_MJ_WRITE as usize] = Some(boost_write);STATUS_SUCCESS
}

注意使用RustOption<>类型来指示有回调.
卸载例程如下:

unsafe extern "C" fn boost_unload(driver: *mut DRIVER_OBJECT) {let mut sym_name = UNICODE_STRING::default();string_to_ustring("\\??\\Booster", &mut sym_name);let _ = IoDeleteSymbolicLink(&mut sym_name);IoDeleteDevice((*driver).DeviceObject);
}

与普通的内核驱动一样,只调用IoDeleteSymbolicLinkIoDeleteDevice了.

处理请求

有三个请求类型需要处理:IRP_MJ_CREATE,IRP_MJ_CLOSEIRP_MJ_WRITE.创建和关闭很简单的,只需成功完成IRP:

unsafe extern "C" fn boost_create_close(_device: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS {(*irp).IoStatus.__bindgen_anon_1.Status = STATUS_SUCCESS;(*irp).IoStatus.Information = 0;IofCompleteRequest(irp, 0);STATUS_SUCCESS
}

IoStatus是一个但用包含状态Pointer的联定义的IO_STATUS_BLOCK.这似乎是错误的,因为Information应该在带Pointer(而不是状态)的联中.

代码通过"自动生成"的联访问状态成员,这很难看.绝对是需要进一步研究的东西.但它管用.

真正有趣的函数IRP_MJ_WRITE处理器,它会更改实际的线程优先级.首先,将声明一个表示驱动请求的结构:

#[repr(C)]
struct ThreadData {pub thread_id: u32,pub priority: i32,
}

使用repr(C)很重要,以确保字段按C/C++一样在内存中布局.这允许非Rust客户与驱动通信.事实上,我将使用我有的C++版本驱动的C++客户测试驱动.

驱动接受要更改的线程ID和要使用的优先级.现在可从boost_write开始:

unsafe extern "C" fn boost_write(_device: *mut DEVICE_OBJECT, irp: *mut IRP) -> NTSTATUS {let data = (*irp).AssociatedIrp.SystemBuffer as *const ThreadData;

首先,因为请求缓冲I/O支持,从IRP中的SystemBuffer中取数据指针.这是客户缓冲内核副本.接着,检查错误:

let status;
loop {if data == null_mut() {status = STATUS_INVALID_PARAMETER;break;}if (*data).priority < 1 || (*data).priority > 31 {status = STATUS_INVALID_PARAMETER;break;}

循环语句创建一个可通过中断退出的无限块.一旦验证了优先级在范围内,就可找线程对象了:

let mut thread = null_mut();
status = PsLookupThreadByThreadId(((*data).thread_id) as *mut c_void, &mut thread);
if !nt_success(status) {break;
}

使用的是PsLookupThreadByThreadId.如果失败,则表明可能没有线程ID,会退出.剩下就是设置优先级并以拥有的任何状态完成请求:
这里

        KeSetPriorityThread(thread, (*data).priority);ObfDereferenceObject(thread as *mut c_void);break;}(*irp).IoStatus.__bindgen_anon_1.Status = status;(*irp).IoStatus.Information = 0;IofCompleteRequest(irp, 0);status
}

就这样!

只剩下签名驱动.如果有INFINX文件,则似乎支持签名驱动,但此驱动未使用INF.
所以需要在部署前手动签名.可从项目的根目录中使用以下内容:

signtool sign /n wdk /fd sha256 target\debug\booster.dll

/n wdk使用一般由VS在生成驱动时自动创建WDK测试证书.我只是抓住存储中第一个以"wdk"开头的并使用它.

文件扩展名,是一个DLL,当前无法在货物构建过程中自动更改它.如果使用INF/INX,则会按SYS更改文件扩展名.

文件扩展名并不重要,可手动重命名它,或直接改成DLL.

安装驱动

软件驱动可按"正常"方式安装生成的文件,如在有测试登录的计算机上使用sc.exe工具(从提升的命令窗口).然后可用sc start在系统中加载驱动:

sc.exe sc create booster type= kernel binPath= c:\path_to_driver_file
sc.exe start booster

测试驱动

我使用了一个现有的与驱动通信,并期望传递正确的结构的C++应用.它像这样:

#include <Windows.h>
#include <stdio.h>
struct ThreadData {int ThreadId;int Priority;
};
int main(int argc, const char* argv[]) {if (argc < 3) {printf("Usage: boost <tid> <priority>\n");return 0;}int tid = atoi(argv[1]);int priority = atoi(argv[2]);HANDLE hDevice = CreateFile(L"\\\\.\\Booster",GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0,nullptr);if (hDevice == INVALID_HANDLE_VALUE) {printf("Failed in CreateFile: %u\n", GetLastError());return 1;}ThreadData data;data.ThreadId = tid;data.Priority = priority;DWORD ret;if (WriteFile(hDevice, &data, sizeof(data),&ret, nullptr))printf("Success!!\n");elseprintf("Error (%u)\n", GetLastError());CloseHandle(hDevice);return 0;
}

结论

可以用Rust编写内核驱动.WDK仓库的版本为0.3,还有很长的路要走.

为了在该空间中充分利用Rust,应该创建安全包装器,这样代码不那么冗长,没有不安全的块,并享受Rust可提供的好处.

注意,我可能在该简单的实现中遗漏了一些包装器.
可在这里,找到更多KMDFRust驱动示例.
文章的代码.

http://www.dtcms.com/a/300830.html

相关文章:

  • rust- 定义模块以控制作用域和隐私
  • 无刷电机三项霍尔连接线序组合详细分析与波形实例
  • ETF历史每笔成交分钟级高频数据深度解析
  • 墨者:通过手工解决SQL手工注入漏洞测试(MongoDB数据库)
  • Rust与Java DynamoDB、MySQL CRM、tokio-pg、SVM、Custors实战指南
  • 零基础 “入坑” Java--- 十四、字符串String
  • mybatis-plus实体类主键生成策略
  • 使用uni-app开发一个点餐收银台系统前端静态项目练习
  • 车辆网络安全规定之R155与ISO/SAE 21434
  • 09_opencv_遍历操作图像像素
  • uniapp input 聚焦时键盘弹起滚动到对应的部分
  • 基础配置介绍,VLAN配置,DHCP配置
  • 迷宫生成与路径搜索(A算法可视化)
  • SparkSQL — get_json_object函数详解(解析 json)
  • 离散组合数学 : 母函数
  • QT6 源,七章对话框与多窗体(16)多文档 MDI 窗体 QMdiArea 篇二:源代码带注释
  • 栈----4.每日温度
  • AIC 2025 热点解读:如何构建 AI 时代的“视频神经中枢”?
  • 主要分布于内侧内嗅皮层的层Ⅲ的边界向量细胞(BVCs)对NLP中的深层语义分析的积极影响和启示
  • Tkinter美化 - 告别土味Python GUI
  • VScode输出中文乱码问题解决
  • C++ 构造函数中阻止资源泄漏的实践探索
  • Java中get()与set()方法深度解析:从封装原理到实战应用
  • 2025年项目数据看板工具选型指南,精选12款
  • Spring Cloud Alibaba:微服务架构的最佳选择?
  • 系统思考:快就是慢
  • 编写SQL语句时,#{} 和 ${}的区别
  • 一文读懂 JWT(JSON Web Token)
  • 使用橙武低代码平台做数据统计:定时任务汇总数据并生成日报表
  • 零基础学习性能测试:JVM性能分析与调优-JVM垃圾回收机制,GC对性能的影响