Iczelion 的 Win32Asm VxD 汇编教程 (四) (转)[@more@]
We 我们在上一节学会了如何编写一个什么事也不做的VxD程序。在这一节里,我们要给它增加处理控制消息的功能。
当VMM加载你的静态VxD程序时,你的VxD程序会按以下顺序接收到三个 系统控制消息:
当要结束静态VxD的时候,VMM发送如下的控制消息:
你也许会感到奇怪:为什么这两个消息后面都跟着个“ 2" ”。这是因为:在VMM加载VxD程序的时候,它是按照初始化顺序值小的VxD先加载的顺序加载的,这样VxD程序就可以使用那些在它们之前加载的VxD程序提供的服务。例如,VxD2要用到VxD1中的服务,它就必须把它的初始化顺序值定义的比VxD小。加载的顺序是:
还有两种退出消息:
上一节我们提到的例子是一个静态的VxD,你可以把它转换成一个动态的VxD,只要在.def文件中 VxD标记的后面加上关键字 DYNAMIC。
一个动态的VxD可以按以下的方法被加载:
如果你用CreateFile来加载一个动态VxD,那么这个动态VxD 必须处理 w32_DeviceIoControl 消息。当你的动态VxD第一次被CreateFile 函数加载的时候, VWIN32 向你的VxD发出这个消息。你的VxD响应这个消息,返回时eax中的值必须为零。当应用程序调用 DeviceIoControl API来与一个动态VxD通讯时, w32_DeviceIoControl消息也被发送。我们会在下一章讲到 DeviceIoControl接口。
一个动态VxD在初始化时收到一个消息:
你要用 BeginProc和 EndProc 宏来定义你的函数:
因为 BeginProc-EndProc 宏比 proc-endp 指令的功能要强,所以你应该用 BeginProc-EndProc宏来代替 proc-endp指令
VxD 程序设计入门
VxD的初始化和结束
VxD程序分为两种:静态的和动态的。每种的加载方法都不同,接受到的初始化和结束的控制消息也不同。静态VxD:
下列情况下,VMM加载一个静态VxD: HKEY_LOCAL_MACHINESystemCurrentControlSetServicesVxDkeyStaticVxD=VxD带路径文件名- 此VxD在system.ini中的[386enh]行下有定义:[386enh] section:
当VMM加载你的静态VxD程序时,你的VxD程序会按以下顺序接收到三个 系统控制消息:
- Sys_Critical_Init VMM在转入到保护模式后,开放中断前发出这个控制消息。大多数VxD程序到不要用到这个消息,除非:
- 你的VxD程序要接管一些其他VxD程序或者保护模式程序要用到的中断。既然你处理这个消息的时候这个中断还没有打开,你就可以确定在你接管这个中断的时候此中断不会被调用。
- 你的VxD程序为其他的VxD程序提供了一些VxD服务。例如,一些在你的VxD程序后加载的VxD程序在处理Device_Init控制消息时需要调用一些你的VxD服务,既然Sys_Critical_Init 控制消息在Device_Init消息之前被发送,所以你应该在Sys_Critical_Init 消息发送时初始化你的程序。
- Device_Init VMM在开放中断后发送此信息。大多数VxD程序都在得到这个消息时初始化。因为中断都开放了,所以耗时的操作也可以在这里执行而不必怕会导致硬中断的丢失。你可以在这时进行初始化(如果你需要的话)。
- Init_Complete 在所有的VxD程序处理完Device_Init 消息之后,VMM释放初始化段(ICODE和RCODE段类)之前,VMM发出这个控制消息。只有少数几个VxD要处理这个消息。
当要结束静态VxD的时候,VMM发送如下的控制消息:
- System_Exit2 当你的VxD程序收到这个消息,Windows95正在关闭系统,除了系统虚拟机所有其他虚拟机都已经退出了。尽管如此,cpu仍然处于保护模式下,在系统虚拟机上执行实模式编码也是安全的。在这时Kernel32.dll也已经被卸载了。
- Sys_Critical_Exit2 当所有的VxD完成对System_Exit2的响应处理并且中断都被关闭后,你的VxD收到到这个消息。
你也许会感到奇怪:为什么这两个消息后面都跟着个“ 2" ”。这是因为:在VMM加载VxD程序的时候,它是按照初始化顺序值小的VxD先加载的顺序加载的,这样VxD程序就可以使用那些在它们之前加载的VxD程序提供的服务。例如,VxD2要用到VxD1中的服务,它就必须把它的初始化顺序值定义的比VxD小。加载的顺序是:
..... VxD1 ===> VxD2 ===> VxD3 .....那么卸载的时候,理所当然的是初始化顺序值大的VxD程序先被卸载,这样他们仍然可以使用比它们后加载的那些VxD程序提供的服务。如上面的例子,次序是:
.... VxD3 ===> VxD2 ===> VxD1.....在上边的例子中,如果VxD2在初始化时调用了VxD1中的某些服务,那么卸载时它可能也要再次用到一些VxD1中的服务。 System_Exit2和 Sys_Critical_Exit2是 反初始化顺序发送的。这表示,当VxD2接受到这些消息时,VxD1还没有被卸载,它仍可以调用VxD1的服务,而 System_Exit和 Sys_Critical_Exit消息不是按照反初始化顺序发送的。这意味着,你不能肯定你是否仍能调用在你之前加载的VxD提供的VxD服务。新一代的VxD程序不应该使用这些消息。
还有两种退出消息:
- Device_Reboot_Notify2 告诉VxD程序VMM正在准备重新启动系统。这时候中断还是开放的。
- Crit_Reboot_Notify2 告诉VxD程序VMM正在准备重新启动系统。这时候中断已经被关闭了。
动态VxD:
动态VxD在Windows9x里可以动态的被加载和卸载。这个特点在Window3.x下是没有的。动态VxD程序的主要作用是用来支持某些动态的 硬件设备的重装,比如:即插即用设备。尽管如此,你可以从你的 win32程序中加载/卸载它,也可以把它看作是你的程序的一个到ring-0的扩展。上一节我们提到的例子是一个静态的VxD,你可以把它转换成一个动态的VxD,只要在.def文件中 VxD标记的后面加上关键字 DYNAMIC。
VxD FIRSTVxD DYNAMIC这就是你把一个静态VxD转换成一个动态的VxD所要做的一切。
一个动态的VxD可以按以下的方法被加载:
- 把它放到你的Windows目录下的SYSTEMIOSUBSYS目录中。在这个目录里的VxD会被输入输出监视器(ios)加载。这些VxD必须支持层设备驱动。所以用这种方法加载你的动态VxD并不是一个好办法。
- 用VxD加载服务。 VxDLDR是一个可以加载动态VxD的静态VxD。你可以在其他VxD里面或者在16位代码里面调用它的服务。
- 用Win32应用程序里的 CreateFile api。你在调用CreateFile时,你的动态VxD要以下面的格式填写:
例如,如果你要加载一个在当前目录下名为FirstVxD的动态VxD,你需要做如下的工作:
.data
VxDName db ".FirstVxD.VxD",0
......
.data?
hDevice dd ?
.....
.code
.....
invoke CreateFile, addr VxDName,0,0,0,0, FILE_FLAG_DELETE_ON_CLOSE,0
mov hDevice,eax
......
invoke CloseHandle,hDevice
......
如果你用CreateFile来加载一个动态VxD,那么这个动态VxD 必须处理 w32_DeviceIoControl 消息。当你的动态VxD第一次被CreateFile 函数加载的时候, VWIN32 向你的VxD发出这个消息。你的VxD响应这个消息,返回时eax中的值必须为零。当应用程序调用 DeviceIoControl API来与一个动态VxD通讯时, w32_DeviceIoControl消息也被发送。我们会在下一章讲到 DeviceIoControl接口。
一个动态VxD在初始化时收到一个消息:
- Sys_Dynamic_Device_Init
- Sys_Dynamic_Device_Exit
其它系统控制消息
当VxD在内存里的时候,除了接收和初始化及结束相关的消息外,它还要收到许多别的控制消息。有些消息是关于虚拟机管理器的,有的是关于各种事件的。例如,关于虚拟机的消息如下:- Create_VM
- VM_Critical_Init
- VM_Suspend
- VM_Resume
- Close_VM_Notify
- Destroy_VM
在VxD内创建函数
你要在一个段里面定义你的函数。你应该首先定义一个段,然后把你的函数放进去。例如,如果你要把你的函数放到一个可调页段中。你应该先定义一个可调页段,像这样:VxD_PAGEABLE_CODE_SEG你可以在一个段里面插入多个的函数。作为一个VxD编写者,你必须决定每一个函数应该放到哪个段里面去。如果你的函数必须时刻存在于内存中,如某些硬件中断处理程序,就把它们放到锁定页面段里面,否则,你应该把它们放到可调页段。(你的函数写在这里)
VxD_PAGEABLE_CODE_ENDS
你要用 BeginProc和 EndProc 宏来定义你的函数:
BeginProc 函数名使用 BeginProc 宏还可以加上一些参数,想了解这些细节,你可以看看Win95 DDK的文档。大多数时候,你只用填写函数的名字就够了。EndProc 函数名
因为 BeginProc-EndProc 宏比 proc-endp 指令的功能要强,所以你应该用 BeginProc-EndProc宏来代替 proc-endp指令
VxD编程约定
寄存器的使用
你的VxD程序可以使用所有的寄存器,FS和GS。但是在改动段寄存器的时候一定要小心。尤其是,一定不要改动CS和SS的内容,除非你对将发生的事情有绝对的把握。你可以使用DS和ES,但一定要记住在返回时恢复它们初值。有两个特征位尤其重要:方向和中断特征位。不要长时间的屏蔽中断。还有如果你要改动方向特征位,不要忘了在返回之前恢复它的初值。参数传递约定
VxD服务函数有两种调用约定:寄存器法和堆栈法。调用寄存器法服务函数时,你通过各种寄存器来传递服务函数的参数。并且,在调用完成后检查寄存器的值来看操作是否成功。不要总是以为在调用服务函数后主要寄存器的值还和以前一样。当调用堆栈法服务函数时,你把要传递的参数压栈,在eax得到返回值。堆栈调用法的服务函数保存ebx,esi,edi和ebp的值。许多寄存器调用法服务函数都源于Windows3.x的时代。在大多数时候,你可以通过名字来区分这两种服务函数,如果一个函数的名字一下划线开头,如 _HeapAllocate,它就是一个堆栈法的服务函数(除了少数从 VWIN32.VxD导出的函数)。如果函数名不是一下划线开头,它就是一个寄存器法的服务函数。调用VxD服务函数
你可以通过 VMMCall和 VxDCall 宏来调用VMM和VxD服务。这两个宏的语法是一样的。当你要调用VMM导出的VxD服务函数时,用 VMMCall。当你要用其它VxD程序导出的VxD服务函数时,用 VxDCall。VMMCall service ; 调用寄存器法服务函数e正如我在前面所讲的, VMMCall和 VxDCall分解出一个跟着一个双字的20h中断,这样用起来很方便。当你调用堆栈法服务时,你必须用角括号把你的参数列括起来。
VMMCall _service, ; 调用堆栈法服务函数
VMMCall _HeapAllocate, <, HeapLockedIfDP>_HeapAllocate是一个堆栈法服务函数。它有两个参数,我们必须用角括号把它们括起来。由于第一个参数是一个这个宏不能正确解释的 表达式,所以我们又要用一个角括号把它括起来。
Flat地址
在老的编译工具里,当你使用 offset 操作符时, 编译器和联接器会生成错误地址,所以VxD编写者用 offset flat:来代替 offset。imm.inc包括了一个使这更简单的宏: OFFSET32 来代替 offset flat:。所以如果你要用地址操作时,用 OFFSET32 来代替offset操作符。注意: 当我写这篇教程的时候,我试了一下用offset 操作符。它可以生成正确的地址。所以我想MASM6.14修正了这个bug。但是为了安全起见,你还是应该用OFFSET32宏来代替offset。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10752019/viewspace-980586/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/10752019/viewspace-980586/