您的位置:首页 > 其它

最简单的分层驱动程序模型(汇编语言描述)(转)

2009-08-11 11:02 357 查看
首先声明一下不是什么新东西,这里只是对最近的学习做个总结而已。

一、什么是分层驱动

这里的分层驱动,是指过滤器驱动程序,搞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小节
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: