深度剖析WinPcap之(三)——内核驱动NPF涉及的基础知识(1)

WinPcap的内核驱动程序NPF是一个协议驱动程序,其涉及到一些编写Windows驱动程序的基础知识与NDIS协议驱动程序编写的基础知识。本章主要对这些基础知识进行简要的描述以便于后续的理解。

 

 

1.1        Windows驱动的基础知识

本节主要描述在WinPcapNPF中经常使用一些编写Windows驱动程序所需掌握的部分基础知识,以便于后面的理解。

1.1.1        驱动对象DRIVER_OBJECT

    每个驱动程序都有唯一的驱动对象与之对应,该驱动对象在驱动程序被加载时由内核的对象管理程序所创建。

驱动对象用DRIVER_OBJECT数据结构表示,它作为驱动程序的一个实例被内核加载,对一个驱动程序内核I/O管理器只加载一个实例。

驱动对象数据结构在wdm.h文件中的定义如下。

typedef struct _DRIVER_OBJECT {

    CSHORT Type;

    CSHORT Size;

 

/*

*DeviceObject为每个驱动程序所创建的一个或多个设备对象链表,

*Flags提供一个扩展的标识定位驱动对象

*/

    PDEVICE_OBJECT DeviceObject;

    ULONG Flags;

  

/*下列各成员字段描述驱动程序从哪儿被加载*/

PVOID DriverStart;

    ULONG DriverSize;

    PVOID DriverSection;

    PDRIVER_EXTENSION DriverExtension;

 

    /*

*DriverName成员被错误日志线程用来

*确定一个I/O请求越界的驱动名称

*/

    UNICODE_STRING DriverName;

   

    /*指向注册表中硬件信息的路径*/

    PUNICODE_STRING HardwareDatabase;

 

   

/*

*如果驱动支持“fast I/O”,

*就指向一个“fast I/O”的派遣函数数组

*/

    PFAST_IO_DISPATCH FastIoDispatch;

 

  

/*

*描述该特定驱动的入口点。

*主函数(major function)派遣函数表必须是对象最后的成员,

*因此它仍然是可扩展的

*/

    PDRIVER_INITIALIZE DriverInit;

    PDRIVER_STARTIO DriverStartIo;

    PDRIVER_UNLOAD DriverUnload;

    PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];

 

} DRIVER_OBJECT;

typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;

下面分别描述驱动对象中驱动程序可访问的成员。

PDEVICE_OBJECT DeviceObject

每个驱动对象会有一个或多个设备对象。每个设备对象都有一个指针(NextDevice)指向下一个驱动对象,最后一个设备对象指向空。此处的DeviceObject指向驱动对象的第一个设备对象。该成员在成功调用IoCreateDevice后自动更新。一个驱动程序使用该程成员与设备对象(DEVICE_OBJECT)的NextDevice可遍历给驱动对象的所有设备对象。在驱动被卸载的时候,需要遍历每个设备对象,并将其删除。

PUNICODE_STRING HardwareDatabase

指向注册表中硬件配置信息的路径,用UNICODE字符串表示。该字符串一般为/REGISTRY/MACHINE/HARDWARE/DESCRIPTION/SYSTEM。

PFAST_IO_DISPATCH FastIoDispatch

指向一个定义驱动快速 I/O结构体的入口点,该成员只用于文件系统驱动与网络传输驱动。

PDRIVER_INITIALIZE DriverInit

DriverEntry例程的入口点,由I/O管理器设置。

PDRIVER_STARTIO DriverStartIo

Startl0例程的的入口点,如果需要,由DriverEntry例程设置,否则为NULL。

PDRIVER_UNLOAD DriverUnload

指向驱动卸载时所用回调函数的入口点

PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION+1]

一个函数指针数组数组MajorFunction中的每个成员保存着一个指针每一个指针指向一个处理对应IRP(IRP_MJ_XXX)的派遣函数(DispatchXxx)

每个派遣函数DispatchXxx)声明如下:

    NTSTATUS
(*PDRIVER_DISPATCH) (
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
      );

 

1.1.2        设备对象(DEVICE_OBJECT

设备对象保存设备特征和状态的信息。一个设备对象表示一个逻辑的、虚拟的或物理的设备,由一个驱动对象操控设备对象的I/O请求。每一个内核模式的驱动必须创建设备对象,通过调用IoCreateDevice一次或多次。

每个驱动程序会创建一个或多个设备对象,用DEVICE_OBJECT数据结构表示。每个设备对象有一个指针(NextDevice)指向下一个设备对象,从而形成一个设备链表。设备链表第一个设备是由驱动对象结构体中DeviceObject指明的。

设备对象数据结构在wdm.h文件中的定义如下。

typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT

{

    CSHORT Type;

    USHORT Size;

    LONG ReferenceCount;

    struct _DRIVER_OBJECT *DriverObject;

    struct _DEVICE_OBJECT *NextDevice;

    struct _DEVICE_OBJECT *AttachedDevice;

    struct _IRP *CurrentIrp;

    PIO_TIMER Timer;

ULONG Flags;

ULONG Characteristics;

    __volatile PVPB Vpb;

    PVOID DeviceExtension;

    DEVICE_TYPE DeviceType;

    CCHAR StackSize;

    union {

        LIST_ENTRY ListEntry;

        WAIT_CONTEXT_BLOCK Wcb;

    } Queue;

    ULONG AlignmentRequirement;

    KDEVICE_QUEUE DeviceQueue;

    KDPC Dpc;

 

   /*

*下列成员是为支持文件系统的互斥操作,

*为了对文件系统处理线程使用设备的计数保持跟踪

*/

    ULONG ActiveThreadCount;

    PSECURITY_DESCRIPTOR SecurityDescriptor;

    KEVENT DeviceLock;

 

    USHORT SectorSize;

    USHORT Spare1;

 

    struct _DEVOBJ_EXTENSION  *DeviceObjectExtension;

    PVOID  Reserved;

 

} DEVICE_OBJECT;

 

typedef struct _DEVICE_OBJECT *PDEVICE_OBJECT;

 

下面分别描述设备对象中驱动程序可访问的成员。

PDRIVER_OBJECT DriverObject

指向驱动程序中的驱动对象。同属于一个驱动程序的驱动对象指向的是同一个驱动对象。

PDEVICE_OBJECT NextDevice

指向下一个设备对象。这里的下一个设备对象是同一个驱动程序创建的若干设备对象。每个设备对象根据NextDevice域形成链表,从而可以遍历每个设备对象。在每次成功调用IoCreateDevice 后I/O管理器更新该链表。在驱动被卸载的时候,需要遍历该链表,删除每个设备对象。

PIRP CurrentIrp

如果驱动使用Startl0例程时此成员指向当前IRP结构。否则为NULL。  

ULONG Flags

此成员是一个32位昀无符号整型,每个位有不同的含义。通过位或操作为新创建的设备对象设置不同的特性。

ULONG Characteristics

当驱动程序调用IoCreateDevice时,设置下列一个合适的值:

FILE_REMOVABLE_MEDIA

FILE_READ_ONLY_DEVICE

FILE_FLOPPY_DISKETTE

FILE_WRITE_ONCE_MEDIA

FILE_DEVICE_SECURE_OPEN

PVOID DeviceExtension

指向设备扩展对象。设备扩展对象是由程序员在驱动中自行定义的结构体,结构体的大小在调用IoCreateDevice时设置。每个设备都会指定一个设备扩展对象,设备扩展对象记录的是特别定义的结构体。在驱动程序中应该尽量避免全局变量的使用,因为全局变量涉及不容易同步的问题,解决的办法可将全局变量存储在设备扩展中。

DEVICE_TYPE DeviceType

指明设备的类型IoCreateDevice设置。根据设备需要填写相应的设备类型。.

CCHAR StackSize

在多层驱动的情况下,驱动与驱动之间形成类似堆栈的结构。IRP会依次从最高层传递到最底层。StackSize就是指定发送到该驱动的IRP在堆栈位置的最小层数IoCreateDevice在一个新创建的设备对象中设置该成员。

ULONG AlignmentRequirement

设备在大容量传输的时候,为了保证传输速度需要内存对齐。每个设备对象在它新创建的设备对象中设置该成员。

1.1.3        设备扩展(_DEVICE_EXTENSION

 设备对象记录设备的“通用”信息,而另外一些“特殊”信息记录在设备扩展中。设备扩展由程序员自行定义,指定内容与大小,由I/O管理器创建,保存在非分页内存中。

在驱动程序中,尽量避免使用全局函数,因为全局函数往往导致函数的不可重入性。一个解决办法就是将全局变量以设备扩展的形式存储,并加以适当的同步保护措施。

       WinPcapNPF的设备扩展结构体,主要用于存储每个被NPF绑定的适配器的一些信息,结构体定义如下:

typedef struct _DEVICE_EXTENSION {

//适配器名称

NDIS_STRING    AdapterName;

//设备导出的名称,也就是通过WinPcap应用程序使用该名称来打开该适配器

    PWSTR          ExportString;

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

 

1.1.4        IRP与派遣函数

驱动程序的主要功能是负责处理l/O请求,大部分1/0请求是在派遣函数中处理的。IRP的处理机制类似Windows应用程序中的“消息处理”机制。用户空间对驱动程序的所有1/0请求,全部由操作系统转化为一个IRP数据结构,不同的IRP数据会被“派遣”到不同的派遣函数中,在派遣函数中处理IRP 

IRPWindows内核中输入输出请求包(I/O Request PackageIRP)的缩写。IRP具有两个基本属性:MajorFunctionMinorFunction,分别记录IRP的主功能和子功能。操作系统根据MajorFunctionIRP“派遣”到不同的派遣函数中,在派遣函数中还可以根据MinorFunction继续判断该IRP属于哪种子功能。

一般来说驱动程序都是在DriverEntry函数中注册派遣函数的。在DriverEntry的驱动对象pDriverObject中,有个函数指针数组MajorFunction,每个数组元素都记录着一个派遣函数的地址。通过设置该数组,可以将不同类型的IRP和对应的派遣函数关联起来。

       大部分的IRP都源于文件I/O处理的API,如CreateFileReadFileWriteFileCloseHandle等函数会使操作系统产生IRP_MJ_CREATEIRP_MJ_READIRP_ MJ_WRITEIRP_MJ_CLOSE等不同类型的IRP,这些IRP会被传送到驱动程序中,调用对应的派遣函数。此外,内核中的文件I/O处理函数,如ZwCreateFileZwReadFileZwWriteFileZwClose也同样会创建IRP_MJ_CREATEIRP_MJ_READlRP_MJ_WRITEIRP_MJ_CLOSEIRP,并将lRP传送到驱动程序中,调用对应的派遣函数。                      

处理IRP最简单的方法就是在相应的派遣函数中,将IRP的状态设置为成功,然后结束IRP的请求,并让派遣函数返回成功。结束IRP的请求使用函数IoCompleteRequest。下面为NPFNPF_Close的代码,为处理IRP_MJ_CLOSE类型IRP的派遣函数。

NTSTATUS NPF_Close(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)

{

     Irp->IoStatus.Status = STATUS_SUCCESS;

     Irp->IoStatus.Information = 0;

     IoCompleteRequest(Irp, IO_NO_INCREMENT);

     return STATUS_SUCCESS;

}

函数NPF_Close设置IRP的完成状态为STATUS_SUCCESS。这样发起请求的 API(如CloseHandle)将会返回TRUE。相反,如果将IRP的完成状态设置为不成功,发起I/O请求的 API(如CloseHandle)将会返回FALSE。出现该情况时,可以使用GetLastError API获得错误代码。所得的错误代码会和IRP设置的状态一致。

除了设置IRP的完成状态,函数还要设置这个IRP请求操作了多少字节。在本代码中,将操作字节数设置成了0。如果是ReadFile产生的IRP,这个字节数代表从设备读了多少字节。如果是WriteFile产生的IRP,这个字节数代表对设备写了多少字节。最后函数通过IoCompleteR0quest函数将IRP请求结束。

IoCompleteRequest的声明如下:

VOID IoCompleteRequest(

IN PIRP  Irp,

IN CCHAR  PriorityBoost

);

参数Irp代表需要被结束的IRP。参数PriorityBoost代表线程恢复时的优先级别,指的是被阻塞的线程以何种优先级恢复运行。一般情况下,优先级设置为IO_NO_INCREMENT

你可能感兴趣的:(网络嗅探)