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; }
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
sources
TARGETNAME=first TARGETTYPE=DRIVER SOURCES=first.c TARGETPATH=obj