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

 

 

搞定

總結

這裡從頭到尾寫了一個簡單但是完整的驅動,還寫了一個客戶端交互。挺不錯了。加油!