Windows内核开发-4-内核编程基础

Windows内核开发-4-内核编程基础

这里会构建一个简单但是完整的驱动程序和一个客户端,部署内核执行一些平时user下无法执行的操作。

将通过以下内容进行讲解:

1 介绍

2 驱动初始化

3 Create和Close操作调度实例

4 DeviceIoControl操作调度实例

5 安装和测试驱动程序

整个完整源代码最后面

1 介绍

该驱动将解决Windows API设置线程优先级的不灵活性。

在User模式下,线程的优先级由其进程优先级类和基于每个线程的偏移量组合来确定,偏移量具有有限的级别数。更改进程的优先级类别可以采用SetPriorityClass函数来实现。

每个优先级类对应着一个优先级,这个对应的优先级也是在进程中创建线程时默认的优先级。可以使用SetThreadPriority函数来修改特定线程的优先级。

基于进程优先级类和线程的优先级偏移量的可用线程优先级图 :

Priority Class -Sat -2 -1 0(default) +1 +2 +Sat Comments
Idle(Low) 1 2 3 4 5 6 15  
Below Normal 1 4 5 6 7 8 15  
Normal 1 6 7 8 9 10 15  
Above Normal 1 8 9 10 11 12 15  
High 1 11 12 13 14 15 15 只有6个级别可以选,不是7个。
Real-time 16 22 23 24 25 26 31 16-31所有级别都可以选

SetThreadPriority函数可以接受指定偏移量的值,五个普通级别分别对应的偏移量是从-2到2:THREAD_PRIORITY_LOWEST (-2), THREAD_PRIORITY_BELOW_NORMAL (-1), THREAD_PRIORITY_NORMAL (0), THREAD_PRIORITY_ABOVE_NORMAL (+1), THREAD_PRIORITY_HIGHEST (+2)。另外两个级别被称为饱和级别,将优先级设置为支持的两个极端:THREAD_PRIORITY_IDLE (-Sat) 和 THREAD_PRIORITY_TIME_CRITICAL (+Sat)。

//修改优先级的例子
SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS);
//将进程优先级类修改为ABOVE NORAML
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
//将线程优先级修改为Above_normal

上面的基于进程优先级类和线程的优先级偏移量的可用线程优先级图表示了我们要解决的问题,这里只有一小部分的线程优先级可以设置,我们这次准备写的驱动就是为了来绕过这些限制,允许将线程的优先级设置为任意数字并且不用考虑进程优先级类。

小结:设计驱动的目的就是在User态下设计的线程优先级不太行,东西比较少,而且麻烦,采用内核来处理后直接将线程的优先级设置为任何数字而且不用考虑进行的优先级类。

 

2 Driver Initialization初始化驱动

创建WDM项目,删除inf文件,再创建C++源文件,然后添加WDK头文件创建一个空的DriverEntry()函数。

#include<ntddk.h>

extern"C"
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{




return STATUS_SUCCESS;
}

大部分一般的驱动需要实现以下内容:

1 设置Unload卸载函数。

2 设置驱动程序支持的调度实例。

3 创建设备对象。

4 创建对设备对象的符号链接。

实现完以上的内容后,一个驱动程序就可以进行交互了。

2.1 Unload

第一步:创建一个Unload实例函数,并且将DriverEntry中的驱动对象指针指向Unload:

#include<ntddk.h>

void PriorityBoosterUnload(_In_ PDRIVER_OBJECT DriverObject);


extern"C"
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = PriorityBoosterUnload;
return STATUS_SUCCESS;
}

对于Unload函数,我们需要根据DriverEntry函数来具体实现里面的逻辑结构,因为该函数的主要目的还是释放资源。

2.2 Dispatch routines调度实例

其实也可以理解为交互。其实所有的驱动都应该支持IRP_MJ_CREATE和IRP_MJ_CLOSE操作,不然是无法打开和关闭驱动对象的。

在DriverEntry中添加以下代码:

#include<ntddk.h>

void PriorityBoosterUnload(_In_ PDRIVER_OBJECT DriverObject);
NTSTATUS PriorityBoosterCreateClose(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp);//新增


extern"C"
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
DriverObject->DriverUnload = PriorityBoosterUnload;
DriverObject->MajorFunction[IRP_MJ_CREATE] = PriorityBoosterCreateClose;//新增
DriverObject->MajorFunction[IRP_MJ_CLOSE] = PriorityBoosterCreateClose;//新增
return STATUS_SUCCESS;
}

这里我们注意到Create和CLOSE都指向的是同一个实例函数,这是因为它们两个执行的代码逻辑差不多,如果有比较复杂的情况,可以将其分开写。其实所有的驱动对象的majorfunction函数指针数组都有一个相同的原型(因为他们都是函数指针数组的一部分),所以这里的新增的函数申明就是major function对于的函数原型:

NTSTATUS PriorityBoosterCreateClose(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp);
//当然函数的名字是可以改的。

该函数必须返回一个NTSTATUS类型变量,然后接受一个设备对象的指针,和一个指向I/O Request Packet(IRP)的指针,对于所有类型的请求,IRP是存储请求信息的主要对象。

2.3 将信息传给驱动程序

光有Create和Close肯定不够的,因为我们的需求里面我们还需要告诉驱动给那一个线程设置成为什么优先级。用User Client的角度来看有三个基本API可以用WriteFile,ReadFile,DeviceIoControl,Read是一个读,不能写数据进去,所以从驱动程序来看就可以不用这个函数了,因为我们要把信息传给驱动。对于Write和DeviceIoControl的选择这个就全看大家喜欢了。一般来说如果真的是一个写的操作就用Write,但是对于其它任何东西DeviceIoControl肯定是首选,因为它是将数据传入和传出驱动程序的通用机制。

更改线程的优先级并不是纯粹的Write,所以这里我们采用DeviceIoControl:

BOOL WINAPI DeviceIoControl(
_In_ HANDLE hDevice,
_In_ DWORD dwIoControlCode,
_In_reads_bytes_opt_(nInBufferSize) LPVOID lpInBuffer,
_In_ DWORD nInBufferSize,
_Out_writes_bytes_to_opt_(nOutBufferSize,*lpBytesReturned) LPVOID lpOutBuffer,
_In_ DWORD nOutBufferSize,
_Out_opt_ LPDWORD lpBytesReturned,
_Inout_opt_ LPOVERLAPPED lpOverlapped);

对DeviceIoControl来说有三个东西非常重要:

1:可控制的代码

2:一个输入缓冲区

3:一个输出缓冲区

DeviceIoControl:比较灵活,可以支持多种控制代码。

在驱动端,DeviceIoControl对应IRP_MJ_DEVICE_CONTROL的MajorFunction函数指针数组的内容。所以添加已下代码到DriverEntry中:

    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = PriorityBoosterDeviceControl;

2.4 客户端和驱动的通信协议

为了让Client和kernel可以交换数据,我们得实现刚刚申明的PriorityBoosterDeviceControl函数,我们需要控制的代码逻辑以及输出输入的缓冲区,缓冲区应该包含我们需要的线程ID和要设置的优先级,这些信息由客户端提供驱动对其采取行动。要两个交互就意味着Client/kernel需要有一个单独的文件来传输信息。

所以这里我们新建一个PriorityBoosterCommon.h 头文件来作为信息传输的介质,该文件也会被Client使用。

该文件我们需要两个数据一个是需要的结构体,另一个是更改线程优先级的控制代码。

先看看结构体:

struct ThreadData {
ULONG ThreadId;
int Priority;
};

需要线程的唯一ID和目标优先级,TID(Thread ID)是一个32位无符号整数,用ULONG不用DWORD是因为ntddk里面没有DWORK只有ULONG,而ULONG比较通用。

优先级应该是1-31之间的数字,所以采用一个简单的int就好了。

接下来还需要一个控制代码,该控制代码必须采用CTL_CODE宏来定义,该宏接受构成最终控制代码的四个参数,CTL_CODE宏的定义:

#define CTL_CODE( DeviceType, Function, Method, Access ) ( \
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))

对这段宏定义的讲解

参数 作用
DeviceType 标识一种设备,可以是WDK中定义的FILE_DEVICE_XXX里面常量之一,但是这个主要用于硬件,和我们这个软件的驱动来说这个值不重要,但是微软指定第三方的驱动程序的这个值应该以0x8000开头
Function 一个升序数字用来表示特定操作,一般情况下,这个数字在同一驱动的不同控制代码下必须不同,同样,任何数字都可以,但是官方文档规定 第三方驱动程序该值应该以0x800开头
Method 最重要的部分,表示客户端提供的输入和输出缓冲区如何传递给驱动程序,对于我们的驱动程序这里采用最简单的值METHOD_NEITHER
Access 指示此操作是针对驱动程序 (FILE_WRITE_ACCESS)、来自驱动程序 (FILE_READ_ACCESS) 还是双向 (FILE_ANY_ACCESS)。

这里我们采用下面这种宏定义:

#define PRIORITY_BOOSTER_DEVICE 0x8000
#define IOCTL_PRIORITY_BOOSTER_SET_PRIORITY CTL_CODE(PRIORITY_BOOSTER_DEVICE, \
0x800, METHOD_NEITHER, FILE_ANY_ACCESS)

2.3 创建设备对象

在DriverEntry中还需要设备对象,以便我们可以打开句柄来到达驱动程序。

典型的软件驱动程序只需要一个设备对象,并带有指向它的符号链接(可以理解为文件的快捷方式)来方便User Client获取它的句柄。

创建一个设备对象需要使用IoCreateDevice API:

NTSTATUS IoCreateDevice(
_In_ PDRIVER_OBJECT DriverObject,
_In_ ULONG DeviceExtensionSize,
_In_opt_ PUNICODE_STRING DeviceName,
_In_ DEVICE_TYPE DeviceType,
_In_ ULONG DeviceCharacteristics,
_In_ BOOLEAN Exclusive,
_Outptr_ PDEVICE_OBJECT *DeviceObject);

IoCreateDevice API参数解析

参数 说明
DriverObject 设备对象所属的驱动程序对象,一般只用来传递给DriverEntry函数的驱动程序对象
DeviceExtensionSize 除了sizeof(DEVICE-OBJECT)还有额外字节,用于将某些数据结构与设备相关联。 对于仅创建单个设备对象的软件驱动程序而言,它不太有用,因为设备所需的状态可以简单地由全局变量管理。
DeviceName 内部设备名称,通常在设备对象管理器目录下创建
DeviceType 与某种类型的基于硬件的驱动程序相关。对于软件驱动采用FILE_DEVICE_UNKNOWN值
DeviceCharacteristics 一组标志和某些特定驱动程序相关(软件驱动程序很少用它),如果软件驱动程序支持真正的命名空间则该值指定0或者FILE_DEVICE_SECURE_OPEN
Exclusive 是否允许多个文件对象打开同一设备。FALSE同意,TRUE不同意
DeviceObject 返回的设备对象指针,如果成功函数会从Non paged Pool非分页内存池分配结构并将结果指针存储在引用参数中

在创建设备对象前,先要创建一个UNICODE_STRING字符串来保存该内部设备名字:

UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\PriorityBooster");
// 或者RtlInitUnicodeString(&devName, L"\\Device\\ThreadBoost");

设备对象的名字可以是任意的,但是必须在Device目录下。有两种使用常量字符串来初始化UNICODE_STRING的办法,第一种是使用RtlInitUnicodeString 这个很好用,但是RtlInitUnicodeString必须计算字符串中的字符数才能很好的初始化。

还有一种更快的办法是采用RTL_CONSTANT_STRING宏,它在编译时静态计算字符串的长度,这意味着它只能与常量字符串一起工作。

然后在DriverEntry中写入我们的代码:

PDEVICE_OBJECT DeviceObject;
NTSTATUS status = IoCreateDevice(
DriverObject // our driver object,
0 // no need for extra bytes,
&devName // the device name,
FILE_DEVICE_UNKNOWN // device type,
0 // characteristics flags,
FALSE // not exclusive,
&DeviceObject // the resulting pointer
);
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to create device object (0x%08X)\n", status));
return status;
}

2.4 创建符号链接

现在我们有一个指向我们的设备对象的指针,下一步需要提供符号链接来使User态的调用者可以访问该设备对象,以下几行代码创建一个符号链接并将其连接到我们的设备对象:

UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\PriorityBooster");
status = IoCreateSymbolicLink(&symLink, &devName);
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to create symbolic link (0x%08X)\n", status));
IoDeleteDevice(DeviceObject);
return status;
}

同样名字随你取,但是目录必须是 \??目录下

IoCreateSymbolicLink 通过接受符号链接和链接的目标来完成工作。但是务必注意,如果创建失败,需要调用IoDeleteDevice来销毁创建的内容。一般情况下,如果DriverEntry返回的是失败状态,则不会调用Unload函数,如果我们有很多初始化要做,那么记得如果失败记得销毁掉。

一旦我们前面的都成功了,那么一定不要忘记在Unload函数里面撤销在DriverEntry中所做的任何事情。

2.5 unload撤销

我们前面创建了设备对象,已经符号链接,是先有的设备对象后有的符号链接。所以我们销毁的时候得反着来,先销毁符号链接,再销毁设备对象。这里有点像C++的析构函数了。

void PriorityBoosterUnload(_In_ PDRIVER_OBJECT DriverObject) {
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\??\\PriorityBooster");
// delete symbolic link
IoDeleteSymbolicLink(&symLink);
// delete device object
IoDeleteDevice(DriverObject->DeviceObject);
}

2.6 初始化驱动小结:

对于驱动一般有几个板块,1 开始 2 中间交互 3 结束

中间交互呢一般是User和Kernel通过I/O设备对象来进行交互,常常是采取开辟设备对象,然后User采用符号链接来使用该对象设备对象可以理解为一个用来交互的东西。然后结束需要删除掉中间用了的东西。

3 Client Code 编写客户端代码

再在该解决方案下添加一个空项目,然后新建一个.cpp文件来编写客户端代码:

image-20210723144511398

添加以下头文件:

#include"../PriorityBooster/PriorityBoosterCommon.h"
#include<Windows.h>
#include<stdio.h>
#include<iostream>
//PriorityBoosterCommon.h是我们用来给Client和Driver进行交互的文件。

修改main函数来接受命令行参数,我们需要接受线程id,和优先级的value值。

int main(int argc, const char* argv[])
{
if (argc < 3)
{
std::cout << "Usage: Booster <threadid> <priority>" << endl;
return 0;
}
}

然后需要打开设备的句柄来获取设备传输的数据,CreateFile 的第一个参数“filename”应该是前缀为“\\.\”的符号链接:

HANDLE hDevice = CreateFile(L"\\\\.\\PriorityBooster", GENERIC_WRITE,
FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
if (hDevice == INVALID_HANDLE_VALUE)
return Error("Failed to open device");
//再定义了一个error函数来打印错误的文本。
int Error(const char* message) {
printf("%s (error=%d)\n", message, GetLastError());
return 1;
}

CreateFile会通过IRP_MJ_CREATE调度实例来写入驱动,如果驱动没有加载就会报错说没有符号链接。就会收到错误2(File not found)。

现在通过符号链接拿到了设备的句柄,现在开始可以调用DeviceIoControl和设备对象进行交互了。在交互前先定义结构体和给给结构体赋值。

    ThreadData TempData;
TempData.ThreadId = atoi(argv[1]);//命令行的第一个参数,atoi字符串int
TempData.Priority = atoi(argv[2]);//命令行的第一个参数

调用DeviceIoControl传递数据,然后关闭Device句柄:

    DWORD returned;
BOOL success = DeviceIoControl(
hDevice,//设备句柄
IOCTL_PRIORITY_BOOSTER_SET_PRIORITY,//控制代码
&TempData, sizeof(TempData),//输入buffer和length
nullptr, 0, //输出buffer和length
&returned, nullptr
);
CloseHandle(hDevice);

DeviceControl通过IRP_MJ_DEVICE_CONTROL majorfunction的实例函数来和driver交互。

这样客户端的代码就搞定了。

4 打开和关闭的调度函数实例

现在我们需要添加的就是驱动代码里面的调度函数,因为之前我们只是申明了而已,并没有实现这个函数。

4.1 Create/Close调度函数

Create和Close的调度函数是最好实现的,只需要返回成功就好。

_Use_decl_annotations_//函数的注释和参数的注释一样,可有可无
NTSTATUS PriorityBoosterCreateClose(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
UNREFERENCED_PARAMETER(DeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

每个调度实例函数都接受设备对象和一个I/O对象IRP,设备对象不用处理,因为我们这里只有一个设备对象只能是我们在DriverEntry中创建的那个,IRP非常重要,在下下章讲。

IRP是一个表示请求(交互)的半文档结构,通常来自执行中的管理器:I/O管理器、即插即用管理器或电源管理器。对于一个简单的软件驱动程序很可能就是一个I/O管理器,IRP如何创建不用考虑,driver的目的都是用来处理IRP,请求(交互)的细节需要完成它才行。

驱动的各种请求都是包含在IRP中,通过查看IRP的成员可以找到请求的类型和详细数据。

需要注意的是IRP不会单独运行,它伴随着一个或多个IO_STACK_LOCATION类型的结构。我们这个简单驱动就只有一个IO_STACK_LOCATION。

简单来说就是我们需要的一些信息在基础的IRP结构里,还有一些在我们设备堆栈的IO_STACK_LOCATION中。

在创建和关闭的情况下,我们不需要查看任何成员。 我们只需要在其 IoStatus 成员(类型为 IO_STATUS_BLOCK)中设置 IRP 的状态,该成员有两个成员:

 

   
Status 表明此请求将完成的状态
Information 一个多态成员,在不同的请求中意味着不同的东西。 在创建和关闭的情况下,零值就可以了。

为了真正完成IRP,还在最后调用了IoCompleteRequest函数,这个函数主要是将IRP传播回给它的调用者通知客户端操作已经完成。第二个参数是驱动程序可以提供给其客户端的临时优先级提升至,大多数情况下0值是比较好的IO_NO_INCREMENT被定义为0,因为这样请求就是同步的了,就大家优先级都一样。该函数还要做的最后一个操作是返回与放入 IRP 的操作相同的状态。

4.2 DeviceIoControl调度函数

这是最重要的地方了。首先需要检查的是控制代码。 典型的驱动程序可能支持很多控制码,所以如果控制码不被识别,我们立即返回请求失败 :

_Use_decl_annotations_
NTSTATUS PriorityBoosterDeviceControl(PDEVICE_OBJECT, PIRP Irp) {
// 获取IO_STACK_LOCATION
auto stack = IoGetCurrentIrpStackLocation(Irp); // IO_STACK_LOCATION*
auto status = STATUS_SUCCESS;
switch (stack->Parameters.DeviceIoControl.IoControlCode) {
//获取控制代码
case IOCTL_PRIORITY_BOOSTER_SET_PRIORITY:
// do the work
break;
default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}

任何获取IRP信息的关键是查看和当前设备管理的IO_STACK_LOCATION中的内容,调用IoGetCurrentIrpStackLocation会返回一个指向正确IO_STACK_LOCATION的指针。

IO_STACK_LOCATION中的主要成分是一个名为Parameters 的union成员,它包含一组结构体和每种IRP一一对应。

在IRP_MJ_DEVICE_CONTROL 情况下,我们要查看它的DeviceControl成员,在该结构体中我们可以找到传递给client的信息,如:控制代码、缓冲区和缓冲区长度等等。

不管前面怎么判断,最后必须有一段代码来确定执行,来返回status:

Irp->IoStatus.Status = status;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;

这段代码通常是放到最后,如果前面的都判断正确时才真正实现DeviceControl。

 

开头和结尾我们写好了,最后是最好玩和最重要的部分了,就是修改线程优先级。

首先我们要检测我们收到的缓冲区是否足够大可以包含一个ThreadData,为什么要检测?因为Kernel和User下的栈是不一样的,不属于一个东西,所以必须检查,特别是对于kernel的东西检查是非常重要的。

指向User提供的输入缓冲区指针在Type3InputBuff中,输入缓冲区长度在InputBufferLength中:

    if (stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(ThreadData)) {
status = STATUS_BUFFER_TOO_SMALL;
break;
}

然后假设缓冲区够大,那么我们就获取得到缓冲区指针:

auto data = (ThreadData*)stack->Parameters.DeviceIoControl.Type3InputBuffer;

如果指针为空需要终止:

    if (data == nullptr) {
status = STATUS_INVALID_PARAMETER;
break;
}

然后检测线程的优先级number是否在1-31之间:

    if (data->Priority < 1 || data->Priority > 31) {
status = STATUS_INVALID_PARAMETER;
break;
}

接下来调用设置线程优先级API :

KPRIORITY KeSetPriorityThread(
_Inout_ PKTHREAD Thread,
_In_ KPRIORITY Priority);

PKTHREAD是一个8位整数,线程本身是由一个指向KTHREAD对象的指针来标识的,KTHREAD是内核管理线程的方式之一,KTHREAD没有文档来记录只能由API来通过线程ID获取Kernel中指向真是线程对象的指针。该API叫做PsLookupThreadByThreadId,使用它需要添加头文件<ntifs.h>。

现在可以把线程ID变成一个指针了:

PETHREAD Thread;
status = PsLookupThreadByThreadId(ULongToHandle(data->ThreadId), &Thread);
if (!NT_SUCCESS(status))
break;

找到之后就可以调用优先级函数来改动优先级了:

KeSetPriorityThread((PKTHREAD)Thread, data->Priority);

但是在使用完之后还需要释放线程句柄防止资源滥用:

ObDereferenceObject(Thread);

最终函数代码:

_Use_decl_annotations_
NTSTATUS PriorityBoosterDeviceControl(PDEVICE_OBJECT, PIRP Irp) {
// get our IO_STACK_LOCATION
auto stack = IoGetCurrentIrpStackLocation(Irp);
auto status = STATUS_SUCCESS;

switch (stack->Parameters.DeviceIoControl.IoControlCode) {
case IOCTL_PRIORITY_BOOSTER_SET_PRIORITY:
{
// do the work
if (stack->Parameters.DeviceIoControl.InputBufferLength < sizeof(ThreadData)) {
status = STATUS_BUFFER_TOO_SMALL;
break;
}

auto data = (ThreadData*)stack->Parameters.DeviceIoControl.Type3InputBuffer;
if (data == nullptr) {
status = STATUS_INVALID_PARAMETER;
break;
}

if (data->Priority < 1 || data->Priority > 31) {
status = STATUS_INVALID_PARAMETER;
break;
}

PETHREAD Thread;
status = PsLookupThreadByThreadId(ULongToHandle(data->ThreadId), &Thread);
if (!NT_SUCCESS(status))
break;

KeSetPriorityThread((PKTHREAD)Thread, data->Priority);
ObDereferenceObject(Thread);
KdPrint(("Thread Priority change for %d to %d succeeded!\n",
data->ThreadId, data->Priority));
break;
}

default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}

Irp->IoStatus.Status = status;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}

最终的完全代码:

Windows内核驱动–实现修改线程优先级demo – Sna1lGo – 博客园 (cnblogs.com)

5 安装和测试

终于到了这里了,千万不要把东西直接安装到本机上,说不定就蓝屏了。最好是用虚拟机来操作。

在虚拟机中用sc.exe来加载,不清楚的可以看一看前面的博客:

Windows内核开发-2-开始内核开发-2-内核开发入门 – Sna1lGo – 博客园 (cnblogs.com)

 

 

 

添加并加载该驱动,采用WinObj来查看加载的数据:

符号链接没问题

 

 

 

然后使用一下:

这里我们通过process Explorer看到cmd进程由一个线程的级别是8,我们给它改一下

 

 

booster 768 25

 

 

搞定

总结

这里从头到尾写了一个简单但是完整的驱动,还写了一个客户端交互。挺不错了。加油!