驱动入口函数原型

驱动程序入口函数原型

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)

第一个参数为 PDRIVER_OBJECT 类型,指向一个驱动对象,一个驱动文件 (sys) 运行后,操作系统在内存中为该驱动分配一个类型为 DRIVER_OBJECT 的数据结构,用于记录详细信息。

DRIVER_OBJECT

先来看看 DRIVER_OBJECT 的定义

DRIVER_OBJECT

字段说明

Type

指定驱动程序对象类型标识符,I/O 管理器在分配驱动程序对象时设置此字段,驱动程序不应使用或修改此字段。以下为类型标识符可能的值

#define IO_TYPE_ADAPTER                 0x00000001
#define IO_TYPE_CONTROLLER              0x00000002
#define IO_TYPE_DEVICE                  0x00000003
#define IO_TYPE_DRIVER                  0x00000004
#define IO_TYPE_FILE                    0x00000005
#define IO_TYPE_IRP                     0x00000006
#define IO_TYPE_MASTER_ADAPTER          0x00000007
#define IO_TYPE_OPEN_PACKET             0x00000008
#define IO_TYPE_TIMER                   0x00000009
#define IO_TYPE_VPB                     0x0000000a
#define IO_TYPE_ERROR_LOG               0x0000000b
#define IO_TYPE_ERROR_MESSAGE           0x0000000c
#define IO_TYPE_DEVICE_OBJECT_EXTENSION 0x0000000d
#define IO_TYPE_IORING                  0x0000000e

Size

指定驱动程序对象结构的大小 (以字节为单位),I/O 管理器在分配驱动程序对象时设置此字段,驱动程序不应使用或修改此字段。

DeviceObject

DEVICE_OBJECT 类型,指向驱动程序创建的设备对象链表中第一个设备对象的指针,此字段将单个驱动程序创建的所有设备链接到一个列表中。当驱动程序成功调用 IoCreateDevice 时,I/O 管理器会自动更新此成员。驱动程序可以使用此成员和 DEVICE_OBJECTNextDevice 成员来遍历驱动程序创建的所有设备对象。这在驱动程序卸载期间尤其有用,可以确保所有设备对象都被正确清理。

Flags

包含系统定义的标志,用于描述驱动程序的各种属性和状态,此字段为驱动程序对象提供了一个可扩展的标志位置。这些标志由 I/O 管理器和其他系统组件设置和维护,驱动程序不应直接修改此字段。可能的值以 DRVO_ 开头的宏表示

#define DRVO_REINIT_REGISTERED          0x00000008
#define DRVO_INITIALIZED                0x00000010
#define DRVO_BOOTREINIT_REGISTERED      0x00000020
#define DRVO_LEGACY_RESOURCES           0x00000040

WDK 只暴露了部分,WDK 里的 DRVO_ 不等于内核里的全部 DRVO_,另外在上述宏定义的上方有 DO_ 开头的宏定义,此为 DEVICE_OBJECT 结构中 Flags 字段可能的值,两者不一样,注意区分

#define DO_BUFFERED_IO                      0x00000004      
#define DO_EXCLUSIVE                        0x00000008      
#define DO_DIRECT_IO                        0x00000010      
#define DO_MAP_IO_BUFFER                    0x00000020      
#define DO_DEVICE_HAS_NAME                  0x00000040      
#define DO_DEVICE_INITIALIZING              0x00000080      
#define DO_SYSTEM_BOOT_PARTITION            0x00000100      
#define DO_LONG_TERM_REQUESTS               0x00000200      
#define DO_NEVER_LAST_DEVICE                0x00000400      
#define DO_SHUTDOWN_REGISTERED              0x00000800      
#define DO_BUS_ENUMERATED_DEVICE            0x00001000      
#define DO_POWER_PAGABLE                    0x00002000      
#define DO_POWER_INRUSH                     0x00004000      
#define DO_LOW_PRIORITY_FILESYSTEM          0x00010000      
#define DO_SUPPORTS_TRANSACTIONS            0x00040000      
#define DO_FORCE_NEITHER_IO                 0x00080000      
#define DO_VOLUME_DEVICE_OBJECT             0x00100000      
#define DO_SYSTEM_SYSTEM_PARTITION          0x00200000      
#define DO_SYSTEM_CRITICAL_PARTITION        0x00400000      
#define DO_DISALLOW_EXECUTE                 0x00800000
#define DO_DEVICE_TO_BE_RESET               0x04000000      
#define DO_DEVICE_IRP_REQUIRES_EXTENSION    0x08000000      
#define DO_DAX_VOLUME                       0x10000000      
#define DO_BOOT_CRITICAL                    0x20000000      

DriverStart

指向驱动程序映像加载到系统内存中的虚拟基地址。该地址表示内核地址空间中驱动程序代码段的起始位置,I/O 管理器在加载驱动程序时设置此值。

DriverStart

DriverSize

指定驱动程序映像在内存中的大小 (以字节为单位)。此值表示已加载驱动程序的总内存占用量,包括代码、数据和其他部分,I/O 管理器在加载驱动程序时设置此值。

DriverSection

指向驱动程序节对象,该对象代表内存管理器中的驱动程序映像。这是一个不透明的系统结构,仅供内存管理器和加载程序内部使用,驱动程序不应访问或修改此成员。

DriverExtension

指向驱动程序扩展的指针,驱动程序扩展中唯一可访问的成员是 DriverExtension->AddDevice,驱动程序的 DriverEntryAddDevice 存储到其中。

DRIVER_EXTENSION

DriverName

包含驱动程序的 Unicode 字符串名称,错误日志线程使用此字段来确定 I/O 请求绑定到的驱动程序名称,格式通常为 \Driver\DriverName,其中 DriverName 对应于驱动程序在注册表中的服务名称。I/O 管理器会根据驱动程序的注册表配置设置此值。

DriverName

HardwareDatabase

指向注册表中硬件配置信息的 \Registry\Machine\Hardware 路径的指针。

HardwareDatabase

FastIoDispatch

指向定义驱动程序快速 I/O 入口点的 FAST_IO_DISPATCH 结构的指针,此可选指针指向一个数组,其中包含驱动程序用于快速 I/O 支持的备用入口点。快速 I/O 通过使用单独的参数直接调用驱动程序例程来执行,而不是使用标准的 IRP 调用机制。请注意,这些函数只能用于同步 I/O,并且仅当文件已缓存时才可执行。此成员仅供文件系统驱动程序 (FSD) 和网络传输驱动程序使用。

DriverInit

I/O 管理器在驱动程序加载时会将此字段设置为指向驱动程序的初始化函数。这是驱动程序加载到内存后调用的第一个函数,负责初始化驱动程序对象并设置调度例程。这个跟 DriverStart 不一样,再次强调,DriverStart 指向 DriverEntry 入口

DriverInit

DriverStartIo

驱动程序的 StartIo 例程的入口点,由驱动程序初始化时的 DriverEntry 例程设置。如果驱动程序没有 StartIo 例程,则此成员为 NULL 。

DriverUnload

驱动程序卸载例程的入口点,由驱动程序初始化时的 DriverEntry 例程设置。如果驱动程序没有卸载例程,则此成员为 NULL 。

DriverUnload

MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1]

DRIVER_DISPATCH 类型成员数组,其中列出了驱动程序 DispatchXxx 例程的入口点。该数组的索引值是 IRP_MJ_XXX 值,代表每个 IRP 主功能代码。宏定义

#define IRP_MJ_CREATE                   0x00
#define IRP_MJ_CREATE_NAMED_PIPE        0x01
#define IRP_MJ_CLOSE                    0x02
#define IRP_MJ_READ                     0x03
#define IRP_MJ_WRITE                    0x04
#define IRP_MJ_QUERY_INFORMATION        0x05
#define IRP_MJ_SET_INFORMATION          0x06
#define IRP_MJ_QUERY_EA                 0x07
#define IRP_MJ_SET_EA                   0x08
#define IRP_MJ_FLUSH_BUFFERS            0x09
#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
#define IRP_MJ_SET_VOLUME_INFORMATION   0x0b
#define IRP_MJ_DIRECTORY_CONTROL        0x0c
#define IRP_MJ_FILE_SYSTEM_CONTROL      0x0d
#define IRP_MJ_DEVICE_CONTROL           0x0e
#define IRP_MJ_INTERNAL_DEVICE_CONTROL  0x0f
#define IRP_MJ_SHUTDOWN                 0x10
#define IRP_MJ_LOCK_CONTROL             0x11
#define IRP_MJ_CLEANUP                  0x12
#define IRP_MJ_CREATE_MAILSLOT          0x13
#define IRP_MJ_QUERY_SECURITY           0x14
#define IRP_MJ_SET_SECURITY             0x15
#define IRP_MJ_POWER                    0x16
#define IRP_MJ_SYSTEM_CONTROL           0x17
#define IRP_MJ_DEVICE_CHANGE            0x18
#define IRP_MJ_QUERY_QUOTA              0x19
#define IRP_MJ_SET_QUOTA                0x1a
#define IRP_MJ_PNP                      0x1b
#define IRP_MJ_PNP_POWER                IRP_MJ_PNP      // Obsolete....
#define IRP_MJ_MAXIMUM_FUNCTION         0x1b

为了保持对象的可扩展性,此主功能调度表必须是对象的最后一个字段。每个驱动程序都必须在此数组中为其处理的 IRP_MJ_XXX 请求设置入口点。为了帮助验证分析工具,每个 DispatchXxx 例程都应使用 DRIVER_DISPATCH 类型声明

DRIVER_DISPATCH DispatchXxx;

DRIVER_DISPATCHwdm.h 头文件中定义

//
// Define driver dispatch routine type.
// The default is that it can be called <= DISPATCH
// because it might be called from another driver.
// See also below.
//

_Function_class_(DRIVER_DISPATCH)
_IRQL_requires_max_(DISPATCH_LEVEL)
_IRQL_requires_same_
typedef
NTSTATUS
DRIVER_DISPATCH (
    _In_ struct _DEVICE_OBJECT *DeviceObject,
    _Inout_ struct _IRP *Irp
    );

typedef DRIVER_DISPATCH *PDRIVER_DISPATCH;

回调例程的实现方式应如下

_Use_decl_annotations_
NTSTATUS DispatchXxx(struct _DEVICE_OBJECT* DeviceObject, struct _IRP* Irp) {
    // ......
}

为了在运行代码分析工具时更准确地识别错误,请务必在函数定义中添加 _Use_decl_annotations 注解。_Use_decl_annotations_ 注解确保使用头文件中应用于 DRIVER_DISPATCH 函数类型的注解。在第一个驱动程序例子中并没有设置,所以显示无效的设备请求,每一项对应上述宏每项

MajorFunction

UNICODE_STRING

入口函数的第二个参数 RegistryPath 为 PUNICODE_STRING 类型,UNICODE_STRING 是个结构体,包含三个字段

typedef struct _UNICODE_STRING {
    USHORT Length;
    USHORT MaximumLength;
#ifdef MIDL_PASS
    [size_is(MaximumLength / 2), length_is((Length) / 2) ] USHORT * Buffer;
#else // MIDL_PASS
    _Field_size_bytes_part_opt_(MaximumLength, Length) PWCH   Buffer;
#endif // MIDL_PASS
} UNICODE_STRING;

Length 表示 Buffer 所指向缓冲区中字符串的长度,单位为字节,需要注意的是,Buffer 指向的字符串,并不要求以 \0 作为结束,在大多数情况下,Buffer 指向的字符串没有以 \0 结尾。MaximumLength 表示 Buffer 所指向缓冲区的总空间大小,一般等于 Buffer 被分配时的内存大小,单位为字节。

registry-path-value

例如上述字符串包含 65 个字符,因为为宽字符,所以长度为 130,十六进制为 0x82。

驱动文件 (sys) 要运行,即要加载到内核中,需要以服务的方式进行,首先要把这个驱动文件注册成一个服务,注册成功后,系统会把服务信息写入到注册表中,路径为

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
registry-path

DisplayName 为显示的服务名称,ErrorControl 驱动程序加载失败时系统应如何响应,ImagePath 表示驱动程序二进制文件的路径,Start 为启动类型,表示驱动程序何时加载

  • boot – 启动
  • system – 系统
  • automatic – 自动
  • demand – 按需
  • disabled – 禁用

Type 为服务类型,例如内核驱动程序、文件系统驱动程序等。以服务的名字作为一个注册表的键名,由于我这个是用第三方驱动加载工具,给出的默认服务名为 MyDriver1_Service,所以 RegistryPath 应为

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\MyDriver1_Service

但是这与上面图中路径不相符,这是因为内核态与用户态下对注册表的路径表示方式有差异

用户态内核态
HKEY_LOCAL_MACHINE\Registry\Machine
HKEY_USERS\Registry\User
HKEY_CURRENT_USER\Registry\User\<SID>

函数体内容

函数体中 DbgPrint 函数用于打印字符串,打印的字符串需在 dbgview 中查看,用法与 C 语言中的 printf 差不多,在判断驱动对象不为 NULL 的情况下为 DriverUnload 成员赋予了卸载函数,何时调用,因为驱动运行是以服务方式进行,当服务停止时,系统把该驱动模块对应在内核地址空间中的代码及数据移除,DriverUnload 成员指定的函数会被调用,开发者可以在这个函数中执行清理相关的工作。

驱动卸载函数非常重要,但也不是必须,它是可选的,如果不指定,驱动运行后就无法停止,该特性被很多安全软件利用,刻意不提供,避免驱动被恶意停止。

返回值

返回值为 NSTATUS 类型,它是一个 LONG 类型,STATUS_SUCCESS 表示初始化成功,如果返回的是该值的以外值,则表示驱动初始化失败,用户态则表现为服务启动失败

#define STATUS_SUCCESS                   ((NTSTATUS)0x00000000L)    // ntsubauth

系统发现驱动初始化失败会移除内核地址空间的驱动代码与数据,这个操作与驱动服务停止类似,但是初始化失败不会去调用驱动卸载函数。