首先声明一下不是什么新东西,这里只是对最近的学习做个总结而已。
一、什么是分层驱动
这里的分层驱动,是指过滤器驱动程序,搞windows驱动的应该非常清楚windows的驱动架构是
分层的,一个实际的物理设备,可以有多个不同的驱动设备对象,当然,一个驱动设备对象
对应一个驱动程序,而这些驱动设备对象的排列结构是分层的,即一个IRP包过来了,这个IRP
先交给最上层的设备对象对应的驱动程序处理,第一层处理完了,再调用下一层设备的驱动程序
来处理这个IRP,直到最底下的那层(WDM称为PDO,Physicl Device Object)处理完这个IRP,然后呢,内核再检查一下某一个驱动是否注册了OnCompletion Routine,如果是的话,再从下往上依次调用他们注册的OnCompletion Routine,然后,这个IRP完成了自己的使命,可以升天了。所以,分层,就必然可以过滤,这一可以过滤,有人就要干坏事喽....
二、最简单的分层驱动模型
嗯,WDM和KMD两种驱动程序模型下,分层驱动程序的写法是不太一样的,
WDM可参看<<Programming the Microsoft Windows Driver Mode>>第9章第一节
KMD的话,就来忍受我的唠叨吧
下面主要从3个方面展开说明
DriverEntry的编写
IRP分派函数的编写
DriverUnload的编写
1.DriverEntry
a.调用IoCreateDevice创建未命名的设备对象,类型指定为你想过滤的设备类型,如键盘,TCPIP
invoke IoCreateDevice,pDriverObject,sizeof DEVICE_EXTENSION,0,/
FILE_DEVICE_KEYBOARD,0,TRUE,addr @lpNewDevObj
要注意的地方
第2个参数指定为一个结构体的大小,内核在创建此设备对象的时候会帮我们分配好指定大小的内存,
并把这块内存的地址写入返回的设备对象的DeviceExtension字段
至于这个结构体的定义,完全是由我们自己定义的,但必须要留一个字段用来存放我们创建的设备
的底层设备对象的地址,因为这个地址相当重要,放在局部变量或放在全局变量中都不方便后面我们
编写分派函数的需要
然后是设备类型,这里以键盘驱动为例,即为FILE_DEVICE_KEYBOARD,Windows定义的所有可用值如下:
FILE_DEVICE_BEEP equ 01
FILE_DEVICE_CD_ROM equ 02
FILE_DEVICE_CD_ROM_FILE_SYSTEM equ 03
FILE_DEVICE_CONTROLLER equ 04
FILE_DEVICE_DATALINK equ 05
FILE_DEVICE_DFS equ 06
FILE_DEVICE_DISK equ 07
FILE_DEVICE_DISK_FILE_SYSTEM equ 08
FILE_DEVICE_FILE_SYSTEM equ 09
FILE_DEVICE_INPORT_PORT equ 0ah
FILE_DEVICE_KEYBOARD equ 0bh
FILE_DEVICE_MAILSLOT equ 0ch
FILE_DEVICE_MIDI_IN equ 0dh
FILE_DEVICE_MIDI_OUT equ 0eh
FILE_DEVICE_MOUSE equ 0fh
FILE_DEVICE_MULTI_UNC_PROVIDER equ 10h
FILE_DEVICE_NAMED_PIPE equ 11h
FILE_DEVICE_NETWORK equ 12h
FILE_DEVICE_NETWORK_BROWSER equ 13h
FILE_DEVICE_NETWORK_FILE_SYSTEM equ 14h
FILE_DEVICE_NULL equ 15h
FILE_DEVICE_PARALLEL_PORT equ 16h
FILE_DEVICE_PHYSICAL_NETCARD equ 17h
FILE_DEVICE_PRINTER equ 18h
FILE_DEVICE_SCANNER equ 19h
FILE_DEVICE_SERIAL_MOUSE_PORT equ 1ah
FILE_DEVICE_SERIAL_PORT equ 1bh
FILE_DEVICE_SCREEN equ 1ch
FILE_DEVICE_SOUND equ 1dh
FILE_DEVICE_STREAMS equ 1eh
FILE_DEVICE_TAPE equ 1fh
FILE_DEVICE_TAPE_FILE_SYSTEM equ 20h
FILE_DEVICE_TRANSPORT equ 21h
FILE_DEVICE_UNKNOWN equ 22h
FILE_DEVICE_VIDEO equ 23h
FILE_DEVICE_VIRTUAL_DISK equ 24h
FILE_DEVICE_WAVE_IN equ 25h
FILE_DEVICE_WAVE_OUT equ 26h
FILE_DEVICE_8042_PORT equ 27h
FILE_DEVICE_NETWORK_REDIRECTOR equ 28h
FILE_DEVICE_BATTERY equ 29h
FILE_DEVICE_BUS_EXTENDER equ 2ah
FILE_DEVICE_MODEM equ 2bh
FILE_DEVICE_VDM equ 2ch
FILE_DEVICE_MASS_STORAGE equ 2dh
FILE_DEVICE_SMB equ 2eh
FILE_DEVICE_KS equ 2fh
FILE_DEVICE_CHANGER equ 30h
FILE_DEVICE_SMARTCARD equ 31h
FILE_DEVICE_ACPI equ 32h
FILE_DEVICE_DVD equ 33h
FILE_DEVICE_FULLSCREEN_VIDEO equ 34h
FILE_DEVICE_DFS_FILE_SYSTEM equ 35h
FILE_DEVICE_DFS_VOLUME equ 36h
FILE_DEVICE_SERENUM equ 37h
FILE_DEVICE_TERMSRV equ 38h
FILE_DEVICE_KSEC equ 39h
总共有58个,但是要除去FILE_DEVICE_UNKNOWN,因为我们写过滤器驱动程序总得有个过滤目标吧
所以我们总共有57种过滤对象
b.必须为每种类型的IRP都设置一个派遣函数
mov esi,pDriverObject
assume esi:ptr DRIVER_OBJECT
mov ecx,IRP_MJ_MAXIMUM_FUNCTION
lea edi,[esi].MajorFunction
mov eax,offset _DispatchAny
cld
rep stosd
c.设置驱动的Unload函数
mov [esi].DriverUnload,offset _DriverUnload
d.为需要额外处理的IRP设置派遣函数
比如要处理IRP_MJ_READ,就这样编写
mov [esi].MajorFunction[IRP_MJ_READ*4],offset _DispatchRead
这里只是给定出一个简单的模型,就没有去特别的处理某种类型的IRP
我会在后面的一个实例中详细说明这一块
e.设置设备属性和原有设备的属性一致,原有设备属性可通过DeviceTree查看
mov edi,@lpNewDevObj
assume edi:ptr DEVICE_OBJECT
mov eax,[edi].Flags
or eax,DO_BUFFERED_IO
or eax,DO_POWER_PAGABLE
mov ecx,DO_DEVICE_INITIALIZING
not ecx
and eax,ecx
mov [edi].Flags,eax
f.清空系统为DEVICE_EXTENSION结构分配的内存空间
xor eax,eax
mov edi,[edi].DeviceExtension
mov ecx,sizeof DEVICE_EXTENSION
cld
repz stosb
当然,你使用RtlZeroMemory也可以
g.把当前设备加入到这种设备类型所在的设备链中
mov edi,@lpNewDevObj
mov esi,[edi].DeviceExtension
invoke IoAttachDevice,@lpNewDevObj,addr szDevKeyboard,esi
第2个参数的定义是:
stUnicode "//Device//KeyboardClass0",szDevKeyboard,4
stUnicode是我在那个俄国人的基础上改写的一个宏,用来定义一个UNICODE_STRING类型的结构体
第3个参数要注意,必须是我们的DEVICE_EXTENSION结构的某个字段的地址
这样DriverEntry就完工了
2.IRP分派函数的编写
下面是我的任意IRP分派函数
_DispatchAny proc _lpDevObj,_lpIrp
IoSkipCurrentIrpStackLocation _lpIrp
mov esi,_lpDevObj
assume esi:ptr DEVICE_OBJECT
mov ecx,[esi].DeviceExtension
mov ecx,dword ptr [ecx]
fastcall IofCallDriver,ecx,_lpIrp
assume esi:nothing
ret
_DispatchAny endp
比较短,就直接贴出来了
a.调用IoSkipCurrentIrpStackLocation
因为当IRP传递到我这一层驱动时,这个IRP我们并不关心,所以不需要额外地处理它,交给下一层驱动
处理就行了,但是我们也不能直接就把这个IRP交给下一层驱动处理,必须要调用
IoSkipCurrentIrpStackLocation宏
当驱动被分层以后,他们被注册到一个chain中,IRP会在这个chain中传递,从最上面,到最下面,再回到最上面
为适应这种体制,IRP的结构的大小是不固定的,大体结构如下:
--------------------
| IRP header |
--------------------
|IO_STACK_LOCATION |<-----lowest driver stack location #index1
--------------------
|IO_STACK_LOCATION |<-----next higher stack location #index2
--------------------
|IO_STACK_LOCATION |<-----topmost driver stack location #index3
--------------------
也就是说,chain的最顶层的驱动,对应的IO_STACK_LOCATION是在最下面
IRP头中存放着当前驱动对应的IO_STACK_LOCATION的索引,是从1开始的,不是从0开始
同时,也保存着当前驱动对应的IO_STACK_LOCATION的地址
最上面的驱动处理完这个IRP后,调用IoCallDriver,这会使索引加1,地址也修正为下一个IO_STACK_LOCATION的地址,
然后IRP被交给下一个Driver处理,
而IoSkipCurrentIrpStackLocation宏的作用就是使IO_STACK_LOCATION指针少前进一步
而IoCallDriver函数会使IO_STACK_LOCATION指针向前一步,中和的结果就是IO_STACK_LOCATION2006-11-20指针不变
当下一个驱动程序的派遣例程调用IoGetCurrentIrpStackLocation时,它将收到与我们正使
用的完全相同的IO_STACK_LOCATION指针,因此,它所处理的将是同一个请求(相同的主副功
能代码)以及相同的参数。
b.调用IofCallDriver,把IRP交给下一层驱动处理
注意这里用了fastcall,其实你可以不用fastcall,但为了性能,还是用fastcall吧
fastcall把第一,二个参数分别放入ecx,edx,剩下的参数才使用堆栈传递,所以性能较cdecl和stdcall
有所提高.当然,这个fastcall关键字是那个俄国人定义的宏
3.Unload函数的编写
也比较短,就直接贴出来了
_DriverUnload proc pDriverObject:PDRIVER_OBJECT
push esi
mov esi,pDriverObject
assume esi:ptr DRIVER_OBJECT
mov eax,[esi].DeviceObject
mov eax,(DEVICE_OBJECT PTR [eax]).DeviceExtension
mov eax,dword ptr [eax]
invoke IoDetachDevice,eax
invoke IoDeleteDevice,[esi].DeviceObject
assume esi:nothing
pop esi
ret
_DriverUnload endp
a.调用IoDetachDevice
要把当前设备从设备链中删除,所以它就一个参数,下层设备对象的地址
b.调用IoDeleteDevice
三、用汇编写分层驱动要注意的地方
在DriverEntry函数中,一定要事先保存寄存器,返回前恢复,要不然,会死得很惨
在其它分派函数中,也要注意寄存器的保存,这是过滤器驱动程序,可能会在任意上下文环境中
被调用
对函数的形参不要使用(DEVICE_OBJECT ptr [形参])这种类型的引用,要不然,你也会死得奶惨!
相关参考资料:
<<Programming the Microsoft Windows Driver Mode>>第9章第一节
<<Rootkits>>第6章第1节第1小节