Windows驱动开发(中间层)

时间:2024-03-10 19:37:28

Windows驱动开发

一、前言

依据《Windows内核安全与驱动开发》及MSDN等网络质料进行学习开发。

 

二、初步环境

1、下载安装WDK7.1.0(WinDDK\7600.16385.1)

地址:https://msdn.microsoft.com/en-us/windows/hardware/hh852365

 

2、下载InstDrv软件(用于安装、启动、停止、卸载驱动)

界面如下:

 

 

注:srvinstw.exe 也可以安装、卸载sys文件,但需要手动开启、关闭,即在cmd命令窗口下执行net start 驱动名、net stop 驱动名来启动、停止服务。

 

3、下载DbgWiew.exe 软件(查看内核模块的输出信息)

地址:https://technet.microsoft.com/en-us/sysinternals/bb896647.aspx

 

注:详见DebugView的使用文档。

 

4、下载64Signer-V1.2(Win7 64位 私有测试数字签名软件)

地址:http://www.yiiyee.cn/Blog/64signer-download/

 

5、其他软件:虚拟光驱DAEMON Tools Lite、EasyBCD(系统引导文件修复工具)、PartitionManager(将C盘设置为活动分区)等

 

三、初步示例

1、编写一个简单的驱动样例(加载、卸载及信息输出等)

 

注:详见TestDriver.c

 

内核模块的入口是DriverEnter函数,在加载时被系统进程System调用。若想能卸载驱动,必须设置驱动对象的DriverUnload函数指针。

 

Windows内核编程不是应用程序编程,它有自己的一套东西,所有的Win32函数都不能使用,部分C Runtime函数也不能使用,需要调用内核API函数,因此在使用函数前,现在帮助里查询该函数。

帮助文档在“开始”-->“所有程序”-->“Windows Driver Kits”下。如图:

 

 

2、使用WDK的build工具,编译时需要makefile.def和SOURCES文件。

1)makfile.def文件固定,拷贝一份即可,不需要任何改动。

2)SOURCES文件内容关联编译那些文件及编译出的.sys文件的名字等。

 

WDK的build工具在“开始”-->“所有程序”-->“Windows Driver Kits”-->“WDK 7600.16385.1”下,选择要根据本地主机的版本,如:本机是64位的Win7旗舰版,则build工具要选x64 Checked build Environment(对应debug)或x64 Free build Environment(对应Release)。

 

3、InstDrv软件直接安装生成的.sys文件,安装、卸载成功,启动失败,并提示“Windows需要已数字签名的驱动程序”,DebugView软件中无自定义信息输出。图如下:

 

 

 

解决方法:

1、从http://www.yiiyee.cn/Blog/64signer-download/下载Win7 64位私有测试数字签名软件(64Signer-V1.2),以管理员身份运行,浏览驱动文件,进行测试数字签名。图如下:

 

2、重新安装签名后的.sys驱动文件,并启动,依然报并提示“Windows需要已数字签名的驱动程序”。 图如下:

 

3、以管理员身份运行cmd.exe,,并执行bcdedit /set testsigning true 命令,若提示“操作成功完成”,则表示命令执行成功;否则,若失败,请将bcdedit命令所在boot文件夹的所在盘(一般C盘)设置为活动状态;若依然失败,请修复boot文件夹的所在盘下的Boot(系统引导文件所在),或干脆恢复|重装系统。

 

4、将Windows 7 设置为测试版本成功后,须重启计算机,重启后,以管理员身份运行InstDrv.exe软件,对已数字签名的.sys驱动文件进行安装、启动、关闭、卸载均成功。图如下:

 

 

注:若驱动中包含中断或错误,则计算机蓝屏,须重启恢复。恢复后的计算机系统内新安装的软件会遭到卸载或损坏,须重新安装。

 

 

 

 

 

以上是一个简单的驱动程序开发步骤。若要进行较复杂的开发,一般需要配置VS开发环境、安装虚拟机、WinDgb.exe等软件进行调试等。

 

 

 

 

 

四、完善环境

 

1、下载并安装VS2010(64位)

地址:https://www.visualstudio.com/downloads/download-visual-studio-vs

 

2、下载WinDbg.exe软件

地址:http://www.microsoft.com/whdc/devtools/debugging/default.mspx

 

3、虚拟机(WMware10.0供双机调试)

地址:http://www.microsoft.com/zh-CN/download/confirmation.aspx?id=8002

 

 

五、配置VS2010

 

1、依据本机版本配置管理器。win7旗舰64位系统配置如下:

点击VS内工具栏中的解决方案配置的下按钮(即debug所处的下拉框按钮),点击配置管理器,点击活动方案配置,点击新建,输入DriverDebug64、默认空,在项目上下文中的平台项新建X64并在活动解决方案平台:选X64。

 

注:32位系统选Win32

 

2、在VS中VisualC++下新建“空项目”,将TestDrive.c文件添加到工程中(或直接新建.c文件),.c文件添加后,在属性中就显示出C/C++配置项。

 

3、点击VS中“视图”菜单->“其他窗口”->“属性管理器”,右击属性管理器中的DriverDebug64,选择属性,在弹出的窗体中进行必要的设置。(或直接点击项目的属性)

 

注: 1、WDK7.1.0默认安装在C:\WinDDK\7600.16385.1文件夹下

2、此处的设置都是必须的, 对复杂些的驱动开发也许还要额外另加设置

具体如下:

1)常规

目标文件扩展名:.sys

 

2)VC++目录

可执行文件目录(编译器路径):

C:\WinDDK\7600.16385.1\bin\amd64

包含目录:

C:\WinDDK\7600.16385.1\inc\api

C:\WinDDK\7600.16385.1\inc\crt

C:\WinDDK\7600.16385.1\inc\ddk

库目录:

C:\WinDDK\7600.16385.1\lib\win7\amd64

3)C/C++

预处理器->预处理器定义:

_AMD64_=1,AMD64=1,STD_CALL=1,WINVER=0x0501,_DEBUG =1

高级->调用约定:

__stdcall (/Gz)

代码生成-->运行库:

选择多线程调试/MTd 或者 多线程调试DLL/MDd

(主要解决_DEBUG未预定义的问题)

 

常规-->调试信息格式:用于“编辑并继续”的程序数据库 (/ZI)

优化:已禁用 (/Od)

 

4)连接器

输入->附加依赖项:                                                                                        ntoskrnl.lib;Hal.lib;wdm.lib;wdmsec.lib;wmilib.lib;ndis.lib;MSVCRT.LIB;LIBCMT.LIB;%(AdditionalDependencies)

输入->忽略所有默认库:是 (/NODEFAULTLIB)

清单文件->启用用户账户控制(UAC):否 (/MANIFESTUAC:NO)

系统->子系统:控制台 (/SUBSYSTEM:CONSOLE)

系统->驱动程序:驱动程序 (/Driver)

系统->堆栈保留大小:4194304(可修改)

系统->堆栈提交大小:4096  (可修改)

高级->入口点:DriverEntry

高级->基址:0x10000

高级->随机基址:(清空)

高级->数据执行保护(dep):(清空)

        调试->生成调试信息:是 (/DEBUG)

注:参考C:\WinDDK\7600.16385.1文件夹的ia64、X86等路径,进行修改可以配置为ia64、32位系统。编译若通过,则配置成功,并生产.sys等文件。

 

 

 

六、配置WinDbg

 

1、设置系统字符表路径:WinDbg->File->Symbol File Path界面中输入

SRV*C:\WINDOWS\Symbols*http://msdl.microsoft.com/download/symbols; 并选择Reload,WinDbg会自动下载字符表,关键是勾选reload。

 

2)设置自己生成的.sys对应的字符(Symbol)路径:

C:\Users\shenc\Desktop\TestDriver\objchk_win7_amd64\amd64

3)设置自己生成.sys的原代码路径:

C:\Users\shenc\Desktop\TestDriver

 

注:

1)在调试源码时,若发生窜码(调试驱动与对应的源码不一致),须将WinDbg软件File菜单下存储的旧源码路径删除。

2)调试时WinDbg默认不输出Dbgprintf信息,须执行ednt!Kd_DEFAULT_Mask 0x8 命令。

3)驱动运行时,按下Ctrol+Break组合键,进入中断状态,点击工具按钮(快捷键:F8、F10等)可进行详细调试。

4)中断调试时,可查看局部变量等信息

 

七、虚拟机

 

1、安装虚拟机

 

若主板默认没有开启虚拟化技术,则一般方法是开机或重启时按F12键进入BIOS菜单,将虚拟化(Virtualization)设置为Enable。

 

2、配置虚拟机

1)开始-->WMware Work Stations-->双击“我的电脑”下的一个虚拟机(Windows 7 x64)-->编辑虚拟机设置-->移除打印机-->添加窜行端口

2)选中刚添加的窜行端口,在右侧的对话框中,设置如下:

勾选 启动时连接

选择 使用命名的管道(N)

其下的两个下拉框分别选择:该端是服务器、另一端是应用程序

 

八、安装并配置虚拟机上的Win7 64位系统

 

1、将Win7设置为可调试状态

1)以管理员身份打开cmd命令窗口:Win + R 打开运行输入框,输入cmd;或鼠标 点击系统开始图标,在输入框中输入cmd,右击上方搜索显示出的cmd.exe,以管理员身份运 行。

2)依次输入:

bcdedit /?

Bcdedit /enum OSLOADER

Bcdedit /copy {current} /d “Windows 7 Copy”

Bcdedit /debug on

Bcdedit /bootdebug on

Bcdedit /dbgsettings

Bcdedit /timeout 7

 

2、将Win7设置为测试版

管理员身份运行cmd命令,bcdedit /set testsigning true

 

3、测试证书数字签名

以管理员身份运行64Signer V1.2.exe,点击浏览找到并双机.sys文件,点击签名。

 

 4、安装.sys文件

以管理员身份运行运行InstDrv.exe,选择.sys文件,进行安装、启动等操作

 

5、查看内核输出信息

以管理员身份运行Dbgview.exe,点击Capture菜单,勾选上Captrue kernel项。

 

注:测试含有断点的内核,否则卡机,无法进行任何操作,因此不在本地主机测试,而采用双机调试(即新建个虚拟机,本地机与虚拟机通过管道进行通信)。

 

 

九、双机调试

 

1、源代码中添加中断语句

#if _DEBUG   //DBG

                 __debugbreak(); //64位

#endif

 

注: 1)汇编_asm int 3 中断,在64位Win7上报错。

2)如果未能进入断点进行调试,请检查sys文件是否在客户机(虚拟Win7系统)安装成功,系统字符集(Symbols)、本人字符Symbols是否下载或设置正确,管道端口是否正确等。

 

2、虚拟机共享主机文件夹

方法:

1)启动VMware,选择虚拟机,点击编辑虚拟机设置;

2) 点击选项,点击共享文件夹,右侧点击总是启用,然后点击添加按钮;

3) 输入win7主机下需要共享的文件夹路径,制定共享名,点击下一步;

4) 点击确定,点击虚拟机内的计算机,在网络下就可以访问共享的文件夹喽。

 

/*

3、Windows 7系统中的驱动签名强制要求,关闭强制驱动签名的命令为:

bcdedit /set loadoptions DISABLE_INTEGRITY_CHECKS

使用管理员的身份打开CMD命令行,然后输入上面的命令,完成之后重新启动计算机, 就可以在64位win7系统上使用未有数字签名的驱动程序。(测试时不签名启动失败!)

 */

 

 

十、NDIS中间层驱动开发

 

1、前言

TDI(传输层驱动接口)是传输层的过滤技术,在Windows Vista之前,常用来开发网络数据过滤,Windows Vista之后的操作系统中不再支持,取而代之的是WFP(Widows过滤平台)技术,一套系统API和服务,其简单稳定,但微软没有介绍XP等Windows Vista之前的系统如何支持WFP。NDIS Filter(网络驱动接口规范)过滤框架,即支持XP等Windows Vista之前的系统,又支持Win7等Windows Vista之前的系统;并且WDK7.1.0安装环境下的passthru工程,就是NDIS Filter开发的示例,在其基础上可以方便开发。passthru工程路径如下图:

 

 

2、Windows 网络架构

1)最上层是网络应用层。Socket、WinInet编写的程序。

2)第二层是网络API层,也是系统最顶层,为应用程序提供编程接口,且接口协议无关性。接口的定义必须在用户层,内部逻辑实现常在内核层。如:Windows套接字、WinInet库 API等

3)第三层是网络API的内核实现(即第二层的内核层)。内核实现层总是以内核模式设备驱动程序的形式体现,并且他有一个统一的责任:将上层网络请求格式化为TDI格式,并将这个格式化后的IRP发送到下层NDIS协议驱动。

4)再下一层是NDIS协议驱动,又叫TDI传输器。TCP/IP等都是NDIS协议驱动。

5)最下层是NDIS小端口驱动程序,直接驱动物理网卡。

图如下:

 

网络驱动模型的每一层都定义了对外的公开的公共接口,与其相连的上下层驱动模块,不需要关心其内部实现就可以很好的支持扩展性、完成工作。

 

3、NDIS中间层

为了方便对网络操作进行过滤,依据网络驱动模型内NDIS协议驱动、NDIS小端口驱动的公共接口进行扩展出来的一层。对上面的NDIS协议驱动,扮演NDIS小端口特征的角色;对下面的NDIS小端口驱动,扮演NDIS协议特征的角色。

图如下:

 

 

注:NDIS中间层的数量理论上不限数量

 

协议驱动绑定了所有小端口驱动,于是能截获所有接受到的包;而中间层驱动不仅绑定了所有小端口驱动,而且还被“所有驱动协议”绑定,因此理论上能截获所有发送和接受到的包。

 

4、NDIS中间层驱动开发示例Passthru工程

1)NDIS驱动的入口函数DriverEntry:做了初始化包装句柄、注册NDIS小端口特征集、注册NDIS协议特征集、关联NDIS两个接口等必做工作,也可以在其中创建设备对象、初始化分发函数表。

初始化包装句柄:NdisMInitializeWrapper函数,获得NDIS包装句柄;

注册小端口特征:先填写小端口特征,再使用NdisIMRegisterLayeredMiniport函数进行注册,输入参数是小端口特征、NDIS包装句柄等,获得关联小端口的NDIS_HANDLE类型句柄(DriverHandle)。

注册小端口卸载关联程序:NdisMRegisterUnloadHandler函数,参数是NDIS包装句柄、卸载出来程序函数句柄。

注销小端口:NdisIMDeregisterLayeredMiniport函数,参数是注册小端口特征时获得的DriverHandle句柄。

注册协议特征:NdisRegisterProtocol函数,输入参数是协议特征、NDIS包装句柄等,获得关联协议的NDIS_HANDLE类型句柄(ProtHandle)。

关联两个接口:NdisIMAssociateMiniport函数,输入参数是DriverHandle、ProtHandle两句柄,这样小端口层和协议层,在一个不透明体中进行了关联。

 

注:中间层驱动本身就是集小端口驱动、协议驱动于一体的一个混合体驱动。

 

2)动态绑定NIC设备

绑定过程是由PNP管理器发起,当PNP管理器发现系统中有可用的NIC设备时,它最终会找到所有注册过的中间层驱动。依次调用它们的AddDevice函数。

 

注:驱动的AddDevice函数都是被NDIS库中函数托管的。

 

绑定的过程中会调用PtBindAdapter函数,在其函数内实现了协议驱动对小端口的绑定。PtBindAdapter函数调用NdisAllocatepacketPoolEx函数分配用于发送和接受数据包的缓冲池;调用NDISOpenAdapter函数绑定下层的NIC,本质是在NDIS的内核对象中,建立起中间层驱动和下层被绑定驱动之间的注册函数的调用关系;调用NdisIMInitializeDeviceInstanceEx函数,在这个函数内部,调用中间层驱动程序的MpInitialize函数来初始化驱动的虚拟NIC。

 

3)小端口的初始化(MpInitialize)

 

通过传入的DriverHandle句柄参数,驱动可以很方便地找到两个特征结构中的函数接口。

调用NdisMSetAttributesEx函数设置适配器上下文,其第三个参数必须设置属性值:

NDIS_ATTRIBUTE_IGNORE_PACKET_TIMEOUT :不对未决包进行超时处理。

            NDIS_ATTRIBUTE_IGNORE_REQUEST_TIMEOUT:不对驱动程序维持的队         列中的查询和设置命令进行超时处理

NDIS_ATTRIBUTE_INTERMEDIATE_DRIVER:告诉NDIS这是一个中间层驱动

调用PtRegisterDevice函数,生成一个控制设备对象,并设置其派遣函数。

 

4)中间层发送数据包

中间层驱动要发送网络数据包,最终都必须调用NDISSend/NDISSendPacket/NDISCoSendPackets这个系列的函数。以NDISSend为例,NdisSend在内部通过协议驱动的绑定句柄,找到所绑定的中间层驱动,并找到中间层驱动的MpSend/MpSendPackets函数调用。

 

因此可以在MpSend/MpSendPackets中对发送的数据包直接进行处理。

 

(包描述符进行重利用或重申请)

MpSend返回值是NDIS_STATUS_PENDING,则表示发送数据包的异步完成。那么久不能再对包描述符做任何操作了,因为已经对它失去了控制权,响应的操作应该保留到完成函数PtSendComplete中进行。

 

5)中间层驱动接受数据包

底层面向无连接的小端口驱动可通过下面两种方式指示数据包接收。

方式一:小端口驱动调用过滤无关的NdisMindicateReceivePacket函数,向上层驱动传递数据包描述符指针。当上层驱动处理完毕后,将向NIC驱动程序返回那些包描述符及其所指向的资源。此方式下,如果中间层驱动提供了PtReceivePacket处理函数,则PtReceivePacket函数被调用;否则PtReceive函数被调用。

方式二:小端口驱动调用过滤相关的NdisMXxxindicateReceivePacket函数,传递包头及数据缓冲区指针和缓冲区大小。此方式下,PtReceive函数被调用。

 

因此可以在PtReceive/PtReceivePacket中对接受的数据包直接进行处理。

 

上层驱动收到网络包接收通知后,会在合适的时候调用NdisTransferData函数来要求底层驱动将完整的包数据发送给它。我们会在MpTransferData函数中得到上层的这个请求;但因为我们没有完整的报数据,所以应该在这个函数中继续把请求往底层传递。底层驱动如果立刻返回包数据。那么我们在MpTransferData中即能立刻截获到;否则在MpTransferData的异步完成函数PtTransferDataComplete中才能截获到完整的包内容。

 

因此可以在MpTransferData、PtTransferDataComplete中对数据包进行处理。

 

6)中间层驱动程序查询和设置

查询和设置,是小端口特征回调中两个重要的接口:一个用来处理OID查询请求,一个用来处理OID设置请求。Passthru中分别对应的是MPQueryInformation和MPSetInformation.

 

 

综上,可以在MpSend/MpSendPackets,PtReceive/PtReceivePacket,MpTransferData、PtTransferDataComplete中对数据包进行处理。

 

 

5、NDIS包描述符

调用NdisQueryPacket函数,可以找到第一个Ndis_Buffer,然后通过NdisGetNextBuffer函数,来获得后续的NDIS_BUFFER。

调用NdisQueryBufferSafe函数,可以取得Ndis_Buffer中存储缓冲区的虚拟地址。

调用NdisMoveMemory函数,可以将各NDIS_BUFFER内容拷贝到指定的存储区。

 

 

十一、安装驱动

 

1、将netsf.inf、netsf_m.inf、Passthru.sys放在同一目录下。

2、安装

(1) 打开“网络和共享中心”。

(2) 右击“本地连接”或“无线网络”,选择“属性”。

(3) 在弹出的“本地连接 属性”对话框中选中“常规”属性页,点击“安装”按钮。

(4) 在弹出的“选择网络组件类型”对话框中选中“服务”,然后点击“添加”按钮。

(5) 在弹出的“选择网络服务”对话框中点击“从磁盘安装”按钮。

(6) 在弹出的“从磁盘安装”对话框中点击“浏览...”按钮。“netsf.inf”文件,点击“打开”按钮,确定。

(7) 在弹出的“选择网络服务”对话框中选中“Passthru”,点击“确定”按钮。

(8) 在安装过程中对弹出的数字签名对话框都要点击“确认”按钮。

(9) 安装完成后,“Passthru”就出现在了组件列表中。

 

十二、 其他

 first.c

///
/// @file first.c
/// @author crazy_chu
/// @date2008-11-1
/// 

#include <ntddk.h>
    

// 提供一个Unload函数只是为了
VOID DriverUnload(PDRIVER_OBJECT driver)
{
    // 但是实际上我们什么都不做,只打印一句话:
    DbgPrint("first: Our driver is unloading…\r\n");
}

// DriverEntry,入口函数。相当于main。
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
#if DBG
      // _asm int 3
#endif
    // 这是我们的内核模块的入口,可以在这里写入我们想写的东西。
    // 我在这里打印一句话。因为”Hello,world” 常常被高手耻笑,所以
    // 我们打印一点别的。
    DbgPrint("first: Hello, my salary!");

    // 设置一个卸载函数便于这个函数能退出。
    driver->DriverUnload = DriverUnload;
    return STATUS_SUCCESS;
}
View Code

makefile

!IF 0

Copyright (C) Microsoft Corporation, 1999 - 2002

Module Name:

    makefile.

Notes:

    DO NOT EDIT THIS FILE!!!  Edit .\sources. if you want to add a new source
    file to this component.  This file merely indirects to the real make file
    that is shared by all the components of Windows NT (DDK)

!ENDIF

!INCLUDE $(NTMAKEENV)\makefile.def
View Code

sources

TARGETNAME=first
TARGETTYPE=DRIVER
SOURCES=first.c
TARGETPATH=obj
View Code