Compilation, installation, debugging of Windows NT drivers


Introduction to Windows NT Drivers

Windows drivers are divided into two categories, one is the driver model left over from Windows NT called the traditional Windows NT driver model, and the other is the KMDF (WDM) driver after Windows has added power management. This article first takes the simplest Windows NT driver model as an example to introduce the simple writing, compiling, installation and debugging of Windows drivers.

NT driver code analysis

If you have studied Linux drivers, you will basically understand that the writing of drivers is actually required to declare and define the API specified by the kernel, and you can realize your own defined driver functions by writing according to a certain driver model or example. (Refer to Linux driver: Linux driver development - (Linux kernel GPIO operation library function) gpio(1) ).

In the Linux driver, the kernel stipulates that to add a driver, you need to implement the driver's entry function and exit function, and then declare it with the following macro definition:

module_init(led_init);	//驱动入口函数声明
module_exit(led_exit);	//驱动退出函数声明
MODULE_LICENSE("GPL");	//驱动遵循的开源规则声明

And there are similar module entry functions and module exit functions in the Windows driver. The difference is that the function entry in the Windows driver is unified by a fixed function name DriverEntry. You only need to implement this fixed function entry in your own driver code (the Windows kernel will actively call this entry function to initialize the driver after loading the driver). The specific declaration of this function is as follows:

NTSTATUS DriverEntry(
  _In_ PDRIVER_OBJECT  DriverObject,
  _In_ PUNICODE_STRING RegistryPath
);
  • parameter

    • DriverObject [in]: Pointer to a DRIVER_OBJECT structure that represents the driver's WDM driver object.
    • RegistryPath [in]: Pointer to a UNICODE_STRING structure that specifies the path to the driver's Parameters key in the registry.
  • Return Value
    If the routine succeeds, it must return STATUS_SUCCESS. Otherwise, it must return one of the error status values ​​defined in ntstatus.h.

Both the Windows NT model driver and the WDM model driver DriverEntrystart to load the driver and complete the initial initialization of the driver through the entry function (this is also the driver entry function that the Windows kernel will always call). So when we write Windows driver, we actually start from the initial DriverEntryentry function.

Here we first write the simplest Windows NT driver as a model routine for explanation. The entire driver source file consists of two files: the driver source file (helloNt.cpp) and the driver header file (helloNt.h).

Here, it is best to refer to the content of the article " Building Windows Driver Development Environment " to build a Windows driver development environment, and use VS2019 to create a KMDF driver solution project, as shown below:

insert image description here
insert image description here

After the solution is created using VS2019, the following files will be automatically created. Here we want to create the simplest Windows NT driver, so just delete unnecessary files (Device.c, Device.h, Queue.c, Queue.h, helloNt.inf).

insert image description here
After the deletion is complete, only Driver.c and Driver.h remain

insert image description here

It should be noted here that some settings are made on the project properties (platform parameter x64, compilation treats warnings as errors, sets no, links treats warnings as errors, cancels WPP trace operation)

insert image description here
insert image description here
insert image description here

The specific code content of the driver module is as follows:

Driver.c

#include "driver.h"


#ifdef ALLOC_PRAGMA
#pragma alloc_text (INIT, CreateDevice)
#pragma alloc_text (PAGE, HelloDDKDispatchRoutine)
#pragma alloc_text (PAGE, HelloDDKUnload)
#endif

NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
/*++

Routine Description:
    DriverEntry initializes the driver and is the first routine called by the
    system after the driver is loaded. DriverEntry specifies the other entry
    points in the function driver, such as EvtDevice and DriverUnload.

Parameters Description:

    DriverObject - represents the instance of the function driver that is loaded
    into memory. DriverEntry must initialize members of DriverObject before it
    returns to the caller. DriverObject is allocated by the system before the
    driver is loaded, and it is released by the system after the system unloads
    the function driver from memory.

    RegistryPath - represents the driver specific path in the Registry.
    The function driver can use the path to store driver related data between
    reboots. The path does not store hardware instance specific data.

Return Value:

    STATUS_SUCCESS if successful,
    STATUS_UNSUCCESSFUL otherwise.

--*/
{
    
    
    NTSTATUS status;
    KdPrint(("Enter DriverEntry\n"));

    //注册其他驱动调用函数入口
    DriverObject->DriverUnload = HelloDDKUnload;
    DriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;
    DriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;
    DriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;

    //创建驱动设备对象
    status = CreateDevice(DriverObject);

    KdPrint(("DriverEntry end\n"));
    return status;
}




/************************************************************************
* 函数名称:CreateDevice
* 功能描述:初始化设备对象
* 参数列表:
      pDriverObject:从I/O管理器中传进来的驱动对象
* 返回 值:返回初始化状态
*************************************************************************/
#pragma INITCODE
NTSTATUS CreateDevice(
    IN PDRIVER_OBJECT	pDriverObject)
{
    
    
    NTSTATUS status;
    PDEVICE_OBJECT pDevObj;
    PDEVICE_EXTENSION pDevExt;

    //创建设备名称
    UNICODE_STRING devName;
    RtlInitUnicodeString(&devName, L"\\Device\\MyDDKDevice");

    //创建设备
    status = IoCreateDevice(pDriverObject,
        sizeof(DEVICE_EXTENSION),
        &devName,
        FILE_DEVICE_UNKNOWN,
        0, TRUE,
        &pDevObj);
    if (!NT_SUCCESS(status))
        return status;

    pDevObj->Flags |= DO_BUFFERED_IO;
    pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
    pDevExt->pDevice = pDevObj;
    pDevExt->ustrDeviceName = devName;
    //创建符号链接
    UNICODE_STRING symLinkName;
    RtlInitUnicodeString(&symLinkName, L"\\??\\HelloDDK");
    pDevExt->ustrSymLinkName = symLinkName;
    status = IoCreateSymbolicLink(&symLinkName, &devName);
    if (!NT_SUCCESS(status))
    {
    
    
        IoDeleteDevice(pDevObj);
        return status;
    }
    return STATUS_SUCCESS;
}

/************************************************************************
* 函数名称:HelloDDKUnload
* 功能描述:负责驱动程序的卸载操作
* 参数列表:
      pDriverObject:驱动对象
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
VOID HelloDDKUnload(IN PDRIVER_OBJECT pDriverObject)
{
    
    
    PDEVICE_OBJECT	pNextObj;
    KdPrint(("Enter DriverUnload\n"));
    pNextObj = pDriverObject->DeviceObject;
    while (pNextObj != NULL)
    {
    
    
        PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
            pNextObj->DeviceExtension;

        //删除符号链接
        UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
        IoDeleteSymbolicLink(&pLinkName);
        pNextObj = pNextObj->NextDevice;
        IoDeleteDevice(pDevExt->pDevice);
    }
}

/************************************************************************
* 函数名称:HelloDDKDispatchRoutine
* 功能描述:对读IRP进行处理
* 参数列表:
      pDevObj:功能设备对象
      pIrp:从IO请求包
* 返回 值:返回状态
*************************************************************************/
#pragma PAGEDCODE
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
    IN PIRP pIrp)
{
    
    
    KdPrint(("Enter HelloDDKDispatchRoutine\n"));
    NTSTATUS status = STATUS_SUCCESS;
    // 完成IRP
    pIrp->IoStatus.Status = status;
    pIrp->IoStatus.Information = 0;	// bytes xfered
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    KdPrint(("Leave HelloDDKDispatchRoutine\n"));
    return status;
}

Driver.h

#pragma once

#ifdef __cplusplus
extern "C"
{
    
    
#endif

#include <ntddk.h>
#include <NTDDK.h>

#ifdef __cplusplus
}
#endif 

#define arraysize(p) (sizeof(p)/sizeof((p)[0]))

typedef struct _DEVICE_EXTENSION {
    
    
	PDEVICE_OBJECT pDevice;
	UNICODE_STRING ustrDeviceName;
	UNICODE_STRING ustrSymLinkName;
} DEVICE_EXTENSION, * PDEVICE_EXTENSION;

NTSTATUS CreateDevice(IN PDRIVER_OBJECT pDriverObject);
VOID HelloDDKUnload(IN PDRIVER_OBJECT pDriverObject);
NTSTATUS HelloDDKDispatchRoutine(IN PDEVICE_OBJECT pDevObj,
	IN PIRP pIrp);


compile

Here, before compiling, the basic compiling environment has been built according to the article " Building Windows Driver Development Environment ". So we can build the solution directly in VS 2019.

We have set the properties of the project before, here we only need to select the platform debug-X64 we set, and then click "Generate" - "Generate Solution"/"Regenerate Solution" to generate the driver file we need.

insert image description here

insert image description here

Install

For the driver program we wrote ourselves, in order to prevent our operating system from crashing due to the loading and use of the driver program (the Windows driver program will run in the RING-0 kernel space after loading, the program crash in this space will directly cause the Windows operating system to crash, the most common phenomenon is the blue screen). So we need to build a Windows virtual machine environment, and then install and debug the new driver in the virtual machine.

Since the Windows NT driver is actually a Windows service driver, what we write here is not really related to the device, so you can understand that it is a service program running in the Windows kernel space. For Windows NT driver installation, here are two ways:

  • DriverMonitorOpen and load the installation through the tool
  • Modify the registry, add driver services, restart the computer and use net start helloNtthe command to load and start

Modify the registry to install

Use the shortcut key Win+R to open the dialog box and enter regeditto open the registry of the system after running. In the registry we find the registration location where the service driver loads.

计算机\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\helloNt

Add a new service program item in it, and then add the content to describe it, and note that the ImagePath description content is the loaded driver file (helloNt.sys).

insert image description here
It involves the path of the driver file, and the input content is as follows:
insert image description here
\??\C:\Tools\helloNt\helloNt\helloNt.sys

Driver file storage location:C:\Tools\helloNt\helloNt
insert image description here

After the registry content is added and configured, we restart the computer (restart will make the registry effective).
After the restart is complete, use the shortcut key Win+X, select Windows powershell (administrator) to open the terminal dialog box, and use the command net start helloNtto start and load the driver.

insert image description here
The driver is now loaded and started. We can open the "System Information" tool to view the status of this service.

insert image description here
You can see that the driver has been started here.

Use the tool DriverMonitorto open and load the installation

Originally, the DriverMonitor tool was part of the DriverStudio tool set, but the DriverStudio tool set has also been eliminated, so it is actually not easy to find at present. I found a warehouse with this part of the tool on github, so I can git clonedownload it from here. ( https://github.com/HotIce0/windows_kernel_driver_learn_note.git )

insert image description here

After downloading and decompressing this tool, open it to see a simple interface:

insert image description here
Select "File", "Open Driver" to open the driver file.

insert image description here
insert image description here

Then select "File" - "Start Driver" to complete the loading and running of the driver.

insert image description here

debugging

The routine here is actually obtained from the book "Detailed Explanation of Windows Driver Development Technology" (Zhang Fan). When the driver is loaded, it is normal, but if we use manual uninstallation to start, there will be problems. Use the command to uninstall the driver net:

net stop helloNt

It turned out that the system blue screened (Windows crashed)

insert image description here
At this time, a simple debugging of the driver is required to find the problem. Although we have added log printing ( KdPrint) to the driver, when the system crashes, we cannot see the log printout in time (it has already crashed and looked at a hammer, only to see a blue screen). In this case, we need to use windbg dual-machine debugging to carry out. (For the dual-machine debugging environment, please refer to the article " windbg dual-machine debugging environment construction (virtual machine) ")

After starting windbg on the host machine, establish a connection with the virtual machine. Execute windbg traditional debugging steps:

  1. Load symbol tables (especially for self-compiled drivers)
  2. Set a breakpoint at the entry of the driver module, or directly operate to crash the system (the crash will automatically stop at the breakpoint)
  3. View the current call stack and refer to the source code to infer the cause of the crash
  4. Modify the source code, recompile, load and verify

insert image description here

insert image description here
insert image description here
Here first break down, and then load the symbol table through the command, or use the GUI operation to load the symbol table.

.srcpath+ "C:\Tools\helloNt\*.pdb"
insert image description here

insert image description here
Since the crash is caused by the uninstall operation, we add a breakpoint here in the uninstall function:

insert image description here
After executing net stop helloNtthe command, windbg will automatically jump to the breakpoint (the symbol table is recorded) and display the corresponding code.

insert image description here

Single-step execution, and then find the call stack of the crash point:
insert image description here
insert image description here
here is a problem when calling DeleteSymbolicLink in Unload(). Review our code here:

VOID HelloDDKUnload(IN PDRIVER_OBJECT pDriverObject)
{
    
    
    PDEVICE_OBJECT	pNextObj;
    KdPrint(("Enter DriverUnload\n"));
    pNextObj = pDriverObject->DeviceObject;
    while (pNextObj != NULL)
    {
    
    
        PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)
            pNextObj->DeviceExtension;
        UNICODE_STRING pLinkName = pDevExt->ustrSymLinkName;
        IoDeleteSymbolicLink(&pLinkName);
        pNextObj = pNextObj->NextDevice;
        IoDeleteDevice(pDevExt->pDevice);
    }
}

It is found that this is called IoDeleteSymbolicLink(&pLinkName);, and then we print the address of pLinkName here to see if this address space is normally accessible.

You can also use !analyze -v for analysis (this analysis result is for reference)
insert image description here

The results were found to be basically consistent with our predicted positions. The problem arises in this link variable here. Why does it crash when we unload link variables?

insert image description here
Through the data in the Unload local variable, we can see that the memory here pDevExt->ustrSymLinkName;is not accessible, that is to say, there has been a problem here. Go back to the source code and check the initialization part to find that this string variable is put in when CreateDevice:

	//创建设备名称
	UNICODE_STRING devName;
	RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice");
	
	//创建设备
	status = IoCreateDevice( pDriverObject,
						sizeof(DEVICE_EXTENSION),
						&(UNICODE_STRING)devName,
						FILE_DEVICE_UNKNOWN,
						0, TRUE,
						&pDevObj );
	if (!NT_SUCCESS(status))
		return status;

	pDevObj->Flags |= DO_BUFFERED_IO;
	pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
	pDevExt->pDevice = pDevObj;
	pDevExt->ustrDeviceName = devName;

A string variable devName is created here, and then this variable is placed in pDevExt. But here it is found that the devName is a local variable of this function, and only the string pointer is put in here, and there will definitely be problems if the pointer that has been released in the subsequent function call is taken out and used.

After finding the problem, let's modify it and change the local variable here to a global variable or a static variable to try:


UNICODE_STRING gdevName;
UNICODE_STRING gsymLinkName;

/************************************************************************
* 函数名称:CreateDevice
* 功能描述:初始化设备对象
* 参数列表:
      pDriverObject:从I/O管理器中传进来的驱动对象
* 返回 值:返回初始化状态
*************************************************************************/
#pragma INITCODE
NTSTATUS CreateDevice(
    IN PDRIVER_OBJECT	pDriverObject)
{
    
    
    NTSTATUS status;
    PDEVICE_OBJECT pDevObj;
    PDEVICE_EXTENSION pDevExt;

    //创建设备名称
    //UNICODE_STRING devName;
    RtlInitUnicodeString(&gdevName, L"\\Device\\MyDDKDevice");

    //创建设备
    status = IoCreateDevice(pDriverObject,
        sizeof(DEVICE_EXTENSION),
        &gdevName,
        FILE_DEVICE_UNKNOWN,
        0, TRUE,
        &pDevObj);
    if (!NT_SUCCESS(status))
        return status;

    pDevObj->Flags |= DO_BUFFERED_IO;
    pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
    pDevExt->pDevice = pDevObj;
    pDevExt->ustrDeviceName = gdevName;
    //创建符号链接
    //UNICODE_STRING symLinkName;
    RtlInitUnicodeString(&gsymLinkName, L"\\??\\HelloDDK");
    pDevExt->ustrSymLinkName = gsymLinkName;
    status = IoCreateSymbolicLink(&gsymLinkName, &gdevName);
    if (!NT_SUCCESS(status))
    {
    
    
        IoDeleteDevice(pDevObj);
        return status;
    }
    return STATUS_SUCCESS;
}

It was found that after changing it to a global variable, the result is still the same, and the memory here still cannot be read normally.

insert image description here

insert image description here

The comparison found that this is due to the read error of this part of the buffer space, modify the code, and move the initialization of this part of the string from the #pragma INITCODEdeclared CreateDevicefunction to DriverEntryin:


NTSTATUS
DriverEntry(
    _In_ PDRIVER_OBJECT  DriverObject,
    _In_ PUNICODE_STRING RegistryPath
    )
{
    
    
    NTSTATUS status;
    KdPrint(("Enter DriverEntry\n"));

    UNICODE_STRING devName;
    UNICODE_STRING symLinkName;
    RtlInitUnicodeString(&devName, L"\\Device\\MyDDKDevice");
    RtlInitUnicodeString(&symLinkName, L"\\??\\HelloDDK");

    //注册其他驱动调用函数入口
    DriverObject->DriverUnload = HelloDDKUnload;
    DriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutine;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutine;
    DriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutine;
    DriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutine;

    //创建驱动设备对象
    status = CreateDevice(DriverObject, devName, symLinkName);

    KdPrint(("DriverEntry end\n"));
    return status;
}

/************************************************************************
* 函数名称:CreateDevice
* 功能描述:初始化设备对象
* 参数列表:
      pDriverObject:从I/O管理器中传进来的驱动对象
* 返回 值:返回初始化状态
*************************************************************************/
#pragma INITCODE
NTSTATUS CreateDevice(
    IN PDRIVER_OBJECT	pDriverObject,
    IN UNICODE_STRING  devName,
    IN UNICODE_STRING  symLinkName)
{
    
    
    NTSTATUS status;
    PDEVICE_OBJECT pDevObj;
    PDEVICE_EXTENSION pDevExt;

    //创建设备名称
    //UNICODE_STRING devName;
    //RtlInitUnicodeString(&devName, L"\\Device\\MyDDKDevice");

    //创建设备
    status = IoCreateDevice(pDriverObject,
        sizeof(DEVICE_EXTENSION),
        &devName,
        FILE_DEVICE_UNKNOWN,
        0, TRUE,
        &pDevObj);
    if (!NT_SUCCESS(status))
        return status;

    pDevObj->Flags |= DO_BUFFERED_IO;
    pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
    pDevExt->pDevice = pDevObj;
    pDevExt->ustrDeviceName = devName;
    //创建符号链接
    //UNICODE_STRING symLinkName;
    //RtlInitUnicodeString(&symLinkName, L"\\??\\HelloDDK");
    pDevExt->ustrSymLinkName = symLinkName;
    status = IoCreateSymbolicLink(&symLinkName, &devName);
    if (!NT_SUCCESS(status))
    {
    
    
        IoDeleteDevice(pDevObj);
        return status;
    }
    return STATUS_SUCCESS;
}

insert image description here
And it can be seen that the loading and unloading of the driver operation has been normal. It shows that our judgment is correct.

insert image description here

Supongo que te gusta

Origin blog.csdn.net/qq_37596943/article/details/131758959
Recomendado
Clasificación