[原创]Win32Asm 驱动学习笔记<5> 驱动程序的安装与卸载
Win32Asm 驱动学习笔记 第五章《 驱动程序的安装与卸载》
版权声明
本文所述内容很多来自互联网以及其他一些参考资料,并不是全部原创,属于学习笔记汇总,凡因此文引发的版权问题,作者本人不负任何责任。
自序
虽我未学,下笔无文,然驱动入门之路何其曲折,何妨我将其倾注于笔墨文字,因其内容多引用经典,作者心得体会穿插其间,故不敢擅称教程,借以笔记之说,攒此文字,以谓后来者。
另外,作者不才,内容错漏之处还请海涵指正,谢谢。
第五章 驱动程序的安装与卸载
本章目录:
5.1 Windows服务
5.2 服务控制管理器(SCM)
5.3 服务控制程序(SCP)
5.3.1 建立到SCM的连接
5.3.2 安装一个新的驱动
5.3.3 打开已存在的驱动
5.3.4 停止已存在的驱动
5.3.5 启动驱动程序
5.3.6 卸载驱动
5.4 通用模板
5.4.1 模板的建立
5.4.2 模板的导入
5.5 本章小结
正文开始:
前面我们学到了一个简单的驱动程序“beeper.sys”,今天我们来学习怎么自己编程来加载它,我们前面介绍过,NT式驱动的加载与卸载是基于服务的, 我们先来简单介绍下Windows服务吧,在这方面罗云彬翻译的KmdTut中文版里介绍的不错,我对原文进行了适当的补充完善后摘抄入此,内容如下:
5.1 Windows服务
Windows NT使用某种机制来启动进程,并让它们不和某个具体的交互式的用户界面相关联,这些进程就被称为服务(service),服务的一个很好的例子就是Web 服务器,这些Web服务都没有用户界面,服务是唯一以这种方式运行的应用程序(注:指没有用户界面,当然,严格地说病毒、木马以及所有不想见光的程序也是 这样的~~),服务可以在系统启动的时候自动启动,也可以被手工启动,从这一点来看,设备驱动程序和服务是类似的。
Windows NT还支持驱动程序服务,只要使用的时候遵循设备驱动程序协议就可以了,这和用户模式的服务类似,所以,"服务"一词既可以指用户模式的服务进程或者内核 模式的设备驱动程序,微软不知何故没有明确地区分两者的概念,所以下面的叙述可能看起来有点让人疑惑。可能有的地方我会说到"driver"一词,但在其 他的文章中可能说到"service"一词,但既然这篇教程讲的是如何编写内核设备驱动程序,那么我们就约定无论说到"service"还 是"driver",我们的意思都是指"驱动程序",当的确需要提及"服务"的时候,我会明确地指出来的。
另外,请读者时刻记得,文档中关于服务管理的函数其实是叙述得相当含糊的,因为这些函数既能用于驱动程序也能用于服务,在下面的文章中,我们只强调它们在驱动方面的用途和忽略服务方面的用途。
Windows NT中有主要有两个组件和服务管理相关:
◎ 服务控制管理器(Service Control Manager/SCM)--用于启动服务以及和它通讯
◎ 服务控制程序(Service Control Program/SCP)--用于和SCM进行通讯,告诉它何时启动或者停止服务
服务程序中包含可执行代码,这两个组件对服务和驱动程序的处理方式是相同的。我们先来看看这两个组件,在后面再讲述驱动程序。
5.2 服务控制管理器(SCM)
SCM的代码位于\%SystemRoot%\System32\Services.exe中,当系统启动的时候,SCM被WinLogon进程启动,然 后它扫描注册表中HKLM\SYSTEM\CurrentControlSet\Services键下的相关内容,根据这些内容创建一个服务数据库,数据 库中包括所有服务的相关参数,如果服务或者驱动被标为自动启动的,那么启动它们并检测启动中是否出错。
为了更深入一步,我们可以用注册表编辑器regedit.exe来打开并观察注册表中的 HKLM\SYSTEM\CurrentControlSet\Services\下面的内容。
想要查看系统中安装了哪些服务(注意不是驱动),可以在控制面板中选择"管理工具",再打开"服务"来查看。
要查看系统中安装了哪些驱动,可以在控制面板中选择"管理工具",再打开"计算机管理",在"系统信息"下的"软件环境"中,你可以看到所有驱动的列表,但是不幸的是,在Windows XP中,这个功能被取消了。
仔细对比一下上面三个地方的内容,我们可以发现这些内容是很一致的。
HKLM\SYSTEM\CurrentControlSet\Services\下面有很多子键,表示一个服务的内部名称,每个子键下包含了和这个服务相关的参数。
现在来考察一下安装一个服务所需的最低数量的参数,我们拿前面提到beeper.sys来举例。
图5.1 beeper.sys驱动的注册表键值
这些参数的含义如下:
◎ DisplayName--用户程序访问服务时使用的名称;
◎ ErrorControl--如果SCM启动服务的时候驱动报错,这个值决定了SCM如何对付这个错误,我们对两种取值有点兴趣:
· SERVICE_ERROR_IGNORE (0)--I/O管理器忽略这个错误,不作记录
· SERVICE_ERROR_NORMAL (1)--如果驱动被装入的时候报错,系统将给用户显示一个告警框,并将错误记录到系统日志中
你可以在控制面板中的"管理工具"中选择"事件查看器"来查看系统日志,例如,beeper.sys驱动在初始化的时候做完了所有该做的事(这个例子会让 喇叭发声音,但是发声功能是在初始化函数DriverEntry中做的,初始化函数执行完,后面就没什么事了),所以它就返回一个错误,系统就会将它从内 存中卸载。但是这里的ErrorControl参数等于SERVICE_ERROR_IGNORE,所以系统日志中并没有错误记录。
◎ ImagePath--指驱动文件的全路径文件名,如果该参数没有指定路径,那么系统会在\%SystemRoot%\Drivers目录下查找
◎ Start--指明何时装载驱动,这里我们关心的也是两个取值
· SERVICE_AUTO_START (2)--驱动在系统启动的时候装载
· SERVICE_DEMAND_START (3)--驱动由SCM根据用户要求装载
如果驱动的Start参数为SERVICE_AUTO_START (2),那么SCM会在系统启动的时候就装载它,这样的驱动被称为自动启动的服务,如果驱动的执行依赖于其他的驱动,SCM也会把其他的驱动也启动起来 (要控制设备驱动被装载的顺序,可以使用Group、Tag和DependOnGroup等参数值;要控制服务被装载的顺序,可以使用Group和 DependOnService参数)。Start参数还有其他的取值,如SERVICE_BOOT_START (0),但这个参数只能供设备驱动程序使用,I/O管理器将在用户模式的进程启动之前把装载这些驱动程序,这时SCM还没有启动呢!
◎ Type--用于指定服务的类型,既然我们这里讲的是KMD的编程,那么我们只对一个取值感兴趣,那就是SERVICE_KERNEL_DRIVER (1)
仔细观察图5.1后,你对beeper.sys有什么要说的吗?好的,我们看到beeper这个内核模式驱动程序位于”D:\RadASM\Masm\Projects\beeper"目录下,它的名称为"beeper",由用户控制启动,出错信息不被记录。
Path前面的"\??"前缀的含义你下面就会知道!
如果我们要启动SCM数据库中不存在的驱动程序,那么可以在任何时刻在SCP的帮助下动态装入(也许称为DCP/device control program更为贴切,但是微软的术语库中并没有这个词)。
5.3 服务控制程序(SCP)
从名称理解,服务控制程序(service control program/SCP)可以控制服务或者设备驱动程序,这些功能是在SCM的管理下,通过调用适当的函数来完成的,这些函数位 于\%SystemRoot%\System32\advapi.dll (Advanced API)中。
这里是一段关于使用SCP来控制beeper.sys驱动的代码例子
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; Service Control Program for beeper driver
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.386
.model flat, stdcall
option casemap:none
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; I N C L U D E F I L E S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
include windows.inc
include kernel32.inc
include user32.inc
include advapi32.inc
includelib kernel32.lib
includelib user32.lib
includelib advapi32.lib
include \RadASM\masm32\Macros\Strings.mac
includelib debug.lib
include debug.inc
; ----------------------debug-------------------------------
; PrintError
; PrintText<"debug message!">
; PrintString<driver>
; PrintStringByAddr<offset driver>
; PrintDword<hSCManager,driver>
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; C O D E
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.code
start proc
local hSCManager:HANDLE
local hService:HANDLE
local acDriverPath[MAX_PATH]:CHAR
LOCAL bRet:BOOL
LOCAL SvrSta
invoke OpenSCManager, NULL, NULL, SC_MANAGER_CREATE_SERVICE
mov hSCManager, eax
PrintDec hSCManager
.if hSCManager != NULL
invoke GetFullPathName, $CTA0("beeper.sys"),sizeof acDriverPath,addr acDriverPath,NULL
PrintString acDriverPath
invoke CreateService, hSCManager, $CTA0("beeper"), $CTA0("Beeper"), \
SERVICE_START + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, \
SERVICE_ERROR_IGNORE, addr acDriverPath, NULL, NULL, NULL, NULL, NULL
mov hService, eax
PrintDec hService
.if hService==NULL
PrintError
invoke GetLastError
; 服务创建失败,是由于服务已经创立过
.if eax==ERROR_IO_PENDING || eax==ERROR_SERVICE_EXISTS || eax==ERROR_SERVICE_MARKED_FOR_DELETE
invoke OpenService ,hSCManager,$CTA0("beeper"), SERVICE_ALL_ACCESS
mov hService, eax
push eax
;如果打开服务也失败,则意味错误
.if eax==NULL
pop eax
PrintError
jmp exit
.endif
pop eax
;服务创建失败,是由于服务已经标记删除
.if eax==ERROR_SERVICE_MARKED_FOR_DELETE
invoke ControlService,hService,SERVICE_CONTROL_STOP, addr SvrSta
invoke CloseServiceHandle,hService
;再次创建
invoke CreateService, hSCManager, $CTA0("beeper"), $CTA0("beeper"), \
SERVICE_START + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, \
SERVICE_ERROR_IGNORE, addr acDriverPath, NULL, NULL, NULL, NULL, NULL
;如果再次创建再失败,则意味着错误
.if eax==NULL
PrintError
mov bRet,FALSE
jmp exit
.endif
.endif
;由于其他原因创建服务失败
.else
PrintText "注册驱动时出错,出错信息如下:"
PrintError
mov bRet,FALSE
jmp exit
.endif
.endif
invoke StartService, hService, 0, NULL
invoke DeleteService, hService
.else
invoke MessageBox, NULL, $CTA0("打开服务管理器出错."), \
NULL, MB_ICONSTOP
PrintText "打开服务管理器出错"
PrintError
.endif
exit:
.if hService
invoke CloseServiceHandle ,hService ; 服务句柄
.endif
.if hSCManager
invoke CloseServiceHandle ,hSCManager; SCM句柄
.endif
invoke ExitProcess, 0
start endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
end start
5.3.1 建立到SCM的连接在上面的例子中,我们首先要做的事情是使用OpenSCManager函数来建立到SCM的连接,以便在指定的计算机上打开服务数据库。
OpenSCManager proto lpMachineName:LPSTR, lpDatabaseName:LPSTR, dwDesiredAccess:DWORD
函数使用的参数说明如下:
◎ lpMachineName--指向需要打开的计算机名字符串,字符串以NULL结尾,如果参数指定为NULL,表示连接到本机上的SCM
◎ lpDatabaseName--指向以NULL结尾的包含SCM数据库名称的字符串,字符串应该指定为"ServicesActive",如果参数指定为NULL,则默认打开"ServicesActive"
.const
szActiveDatabase db "ServicesActive", 0
SERVICES_ACTIVE_DATABASE equ offset szActiveDatabase
现在我们要打开的就是这个当前被激活的数据库,所以我们使用了NULL参数
◎ dwDesiredAccess--指定访问SCM的权限,这个参数告诉SCM我们需要进行什么样的操作,常用的取值有三个:
· SC_MANAGER_CONNECT--允许连接到SCM,这个取值是默认值,它的定义就是0
· SC_MANAGER_CREATE_SERVICE--允许创建服务
· SC_MANAGER_ALL_ACCESS--允许进行所有的操作
我们可以使用下面的代码连接到SCM:
invoke OpenSCManager, NULL, NULL, SC_MANAGER_CREATE_SERVICE .if eax != NULL mov hSCManager, eax
如果OpenSCManager函数执行成功,那么返回值就是被连接的SCM的句柄,我们在以后使用其他函数在对SCM数据库进行操作的时候会用到这个句柄。
另外,忘了提醒大家,安装内核模式驱动程序需要超级用户的权限,为了安全起见,普通权限的用户没有被授权的话是无法执行特权代码的。当然,本文的例子总是假设你是有超级用户权限的。
5.3.2 安装一个新的驱动
打开SCM后,我们可以用CreateService函数将驱动添加到服务数据库中,这里是该函数的原型,CreateService函数远不止三个参数,但不要害怕,这些参数都是很简单的:
CreateService proto hSCManager:HANDLE, lpServiceName:LPSTR, lpDisplayName:LPSTR, \ dwDesiredAccess:DWORD, dwServiceType:DWORD, dwStartType:DWORD, \ dwErrorControl:DWORD, lpBinaryPathName:LPSTR, lpLoadOrderGroup:LPSTR, \ lpdwTagId:LPDWORD, lpDependencies:LPSTR, lpServiceStartName:LPSTR, \ lpPassword:LPSTR
参数说明如下:
◎ hSCManager--不用说了吧?就是上一节中得到的SCM句柄
◎ lpServiceName--指向一个以0字符结尾的表示服务名称的字符串,字符串的最大长度是256个字符,名称中不允许使用/或者\字符(因为这些字符会和注册表的路径表示方式冲突),这个值和注册表中的键名是相对应的
◎ lpDisplayName--指向一个以0字符结尾表示服务名称的字符串,这个名称是供用户界面程序识别函数时使用的,同样,它的最大长度也是256个字符。这个值和注册表中的DisplayName键的值是相对应的
◎ dwDesiredAccess--指定需要访问服务的操作,可以有以下取值:
· SERVICE_ALL_ACCESS--可以进行所有操作
· SERVICE_START--允许调用StartService函数来启动服务
· SERVICE_STOP--允许调用ControlService函数来停止服务
· DELETE--允许调用DeleteService函数来删除服务
在这里我们只需要做两件事情:启动驱动和删除驱动,所以例子中使用了SERVICE_START和DELETE,我们不需要停止服务的操作,因为上面已经说过,这个驱动在初始化的时候就会返回错误(所以它不会有已经启动的状态)。
◎ dwServiceType--服务的类型,我们的教程中只用得到SERVICE_KERNEL_DRIVER,这个值和注册表中的Type键的值是相对应的
◎ dwStartType--表示在什么时候启动服务,如果我们需要手动启动驱动的话,那么使用SERVICE_DEMAND_START参数,如果驱动程 序需要在系统启动的时候就被启动,那么使用SERVICE_AUTO_START参数,这个取值和注册表中的Start键的取值是相对应的
◎ dwErrorControl--表示当驱动初始化的时候出错该如何处理,取值SERVICE_ERROR_IGNORE表示忽略错误,取值 SERVICE_ERROR_NORMAL表示将错误记录到系统日志中去,这个取值和注册表中的ErrorControl键值是相对应的
◎ lpBinaryPathName--指向以0结尾的表示驱动程序文件名的字符串,这个值和注册表中的ImagePath的键值是相对应的
◎ lpLoadOrderGroup--指向以0结尾的表示组名称的字符串,表示该驱动属于哪个组,既然我们的例子程序不属于任何组,那么这里就用NULL好了
◎ lpdwTagId--指向一个32位的缓冲区,用来接收驱动在lpLoadOrderGroup参数指定的组中的唯一的标识,我们的例子中不需要用到这个表示,所以参数指定为NULL
◎ lpDependencies--对于驱动程序来说,这个参数没什么用途,设置为NULL好了
◎ lpServiceStartName--指向一个以0结尾的表示帐号名称的字符串,用于指定服务允许在哪个帐号下运行,如果服务类型是 SERVICE_KERNEL_DRIVER的话,该帐号就是系统装入服务的模块名称,我们在这里使用NULL,表示由默认的模块装入
◎ lpPassword--对于驱动程序来说,这个参数没什么用途,设置为NULL好了
现在来总结一下,最后的5个参数总是设置为NULL,我们就把它抛到脑后去好了,第一个参数是SCM句柄,而dwDesiredAccess参数也是很好 理解的,剩下的参数是什么?聪明的你一定已经猜到了--它们实际上就是和注册表里面的键一一对应的!看看下表就明白了:

好了,现在回过头来看看例子代码:
invoke GetFullPathName,$CTA0("beeper.sys"),sizeof acDriverPath,addr acDriverPath,NULL
invoke CreateService, hSCManager, $CTA0("beeper"), $CTA0("Beeper"), \
SERVICE_START + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, \
SERVICE_ERROR_IGNORE, addr acDriverPath, NULL, NULL, NULL, NULL, NULL
mov hService, eax
首先,我们调用GetFullPathName函数来获取全路径的驱动程序文件名,并把它传递给CreateService函数。
然后CreateService函数将这个驱动程序加入到SCM的数据库中,并创建对应的注册表键,正如表5.1所示的,所有这些键将被 CreateService函数加入到注册表中,如果你在源代码中把DeleteService一行去掉,重新编译并执行,就可以验证我说的了。
不要认为使用RegXXX之类的函数将相同的信息写入注册表就可以达到相同的结果,这样操作的话,键值是写到注册表里面了,但是SCM的数据库里面可什么都没有哦!
如果SCM数据库中指定的设备驱动程序已经存在,那么CreateService函数会返回一个错误,这时可以调用GetLastError函数获取具体 原因,上例中会得到ERROR_SERVICE_EXISTS。如果CreateService函数成功地将驱动加入到了SCM数据库中,函数的返回值就 是驱动的句柄,这个句柄在后面的驱动管理函数中将会被用到。
5.3.3 打开已存在的驱动
如果SCM数据库中指定的设备驱动程序已经存在,我们可以调用OpenService函数来打开这个驱动,函数的返回值就是驱动的句柄,它的原型申明如下:
OpenService proto hSCManager:HANDLE, lpServiceName:LPSTR,dwDesiredAccess:DWORD
参数说明如下:
◎ hSCManager--前面提到的OpenSCManager返回的句柄
◎ lpServiceName--服务名字,对应上节CreateService的同名参数。
◎ dwDesiredAccess--指定需要访问服务的操作,对应上节CreateService的同名参数;
如果打开服务也失败,则意味错误。
5.3.4 停止驱动程序
如果SCM数据库中指定的设备驱动程序已经存在且已经标记为删除,说明服务还未停止,服务的句柄未被关闭,我们需要调用ControlService来停止驱动,它的原型申明如下:
ControlService proto hService:HANDLE,dwControl:DWORD,lpServiceStatus:LPDWORD
参数说明如下:
◎ hService--前面提到的CreateService或OpenService返回的句柄
◎ dwControl--控制类型,指定需要控制服务的操作,下面这些是我们关注的:
SERVICE_CONTROL_STOP 要服务停止
SERVICE_CONTROL_PAUSE 要服务暂停
SERVICE_CONTROL_CONTINUE 要服务继续
◎ lpServiceStatus--服务操作结果,指定一个变量地址用来装载返回信息。
我们回头来看下代码:
invoke GetLastError
.if eax==ERROR_SERVICE_MARKED_FOR_DELETE
invoke ControlService,hService,SERVICE_CONTROL_STOP, addr SvrSta
invoke CloseServiceHandle,hService
;再次创建
invoke CreateService, hSCManager, lpszDriverName, lpDisplayName, \
SERVICE_START + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, \
SERVICE_ERROR_IGNORE, addr acDriverPath, NULL, NULL, NULL, NULL, NULL
;如果打创建务再失败,则意味着错误
.if eax==NULL
PrintError
mov bRet,FALSE
jmp exit
.endif
.endif
我们先调用ControlService来停止服务,然后关闭服务句柄,最后再次创建服务,如果失败就代表错误。
5.3.5 启动驱动程序
下一步要调用的函数是StartService,它的原型申明如下:
StartService proto hService:HANDLE, dwNumServiceArgs:DWORD, lpServiceArgVectors:LPSTR
参数说明如下:
◎ hService--就是前面小节中由CreateService或OpenService返回的驱动的句柄
◎ dwNumServiceArgs--用于驱动程序的时候,这个参数总是设置为NULL
◎ lpServiceArgVectors--同上,也为NULL
启动驱动的方法就是这样的:
invoke StartService, hService, 0, NULL
StartService函数的执行过程和装入用户模式的DLL的过程类似,驱动程序文件的映像被装入到系统的地址空间中,文件可以被装入到任何地址中, 然后系统会根据PE文件中的重定位表对其进行重定位操作,这样驱动程序的内存映像就被准备好了,接下来系统调用驱动的入口函数,也就是 DriverEntry子程序,和装入DLL不同的是,DriverEntry子程序的执行是在系统进程的上下文中进行的。
StartService函数的调用是同步执行的,也就是说,只有驱动程序的DriverEntry过程返回后,函数才会返回(回想一下,如果函数不等人 家执行完就直接返回了,那叫什么~~~那是异步!)。如果驱动初始化成功,那么DriverEntry过程应该返回STATUS_SUCCESS,这样 StartService会返回一个非0值,这时,我们又回到了调用StartService的用户模式的上下文中了。
在这个例子中,我们并不关心StartService函数的返回值,理由前面已经说过了,那就是beeper驱动程序在DriverEntry中进行了发声音功能的演示,并返回一个错误码,后面再没有什么功能要做的了。
5.3.6 卸载驱动
怎样卸载驱动呢?
invoke DeleteService, hService
exit:
.if hService
invoke CloseServiceHandle ,hService ; 服务句柄
.endif
.if hSCManager
invoke CloseServiceHandle ,hSCManager; SCM句柄
.endif
现在我们需要将系统恢复到以前的状态,调用DeleteService函数就可以将驱动从SCM数据库中删除,比较奇怪的是,并不需要将SCM句柄传递给DeleteService函数。
DeleteService函数的原型申明如下:
DeleteService proto hService:HANDLE
参数hService就是需要被卸载的服务的句柄
严格地说,这个函数并不真正将服务删除,它仅仅是将服务做了一个删除标志,只有当服务已经停止,并且服务的句柄被关闭后,SCM才真正将服务删除。调用了 DeleteService函数后,我们还需要将服务的句柄保存以便在后面使用。如果再次调用DeleteService函数的话,函数会返回失败,这时 用GetLastError得到的错误代码是ERROR_SERVICE_MARKED_FOR_DELETE。
现在我们不再需要和驱动程序通讯了,所以需要使用CloseServiceHandle函数将句柄关闭:
CloseServiceHandle proto hSCObject:HANDLE
参数hSCObject可以是服务或驱动的句柄,也可以是SCM数据库的句柄,驱动的句柄被关闭后,我们再次调用CloseServiceHandle函数来关闭SCM句柄。
有关服务部分的介绍就到这里了,在完整的驱动程序中,我们还要调用DeviceIoControl函数与驱动程序进行通讯,完成后还要调用我们前面用到 ControlService函数来停止这个驱动再删除驱动,这方面在我们以后学到驱动程序通讯时会有详细介绍。
5.4 通用模板
下面是我写的一个驱动加载与卸载的框架,今天所学的内容全部体现在这个代码里了:
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; Service Control Program by nohacks
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.386
.model flat, stdcall
option casemap:none
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; I N C L U D E F I L E S
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
include windows.inc
include kernel32.inc
include user32.inc
include advapi32.inc
includelib kernel32.lib
includelib user32.lib
includelib advapi32.lib
include \RadASM\masm32\Macros\Strings.mac
includelib debug.lib
include debug.inc
include load.inc
; ----------------------debug-------------------------------
; PrintError
; PrintText<"debug message!">
; PrintString<driver>
; PrintStringByAddr<offset driver>
; PrintDword<hSCManager,driver>
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
; C O D E
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
.code
LoadDriver proc uses esi edi lpszDriverPath,lpszDriverName,lpDisplayName
local hSCManager:HANDLE,SvrSta,Error
local hService:HANDLE
local acDriverPath[MAX_PATH]:CHAR
LOCAL bRet:BOOL
mov bRet,FALSE
;打开服务管理器
invoke OpenSCManager, NULL, NULL, SC_MANAGER_CREATE_SERVICE
mov hSCManager, eax
.if ! hSCManager
PrintText "打开服务管理器出错"
PrintError
.endif
;得到完整的驱动路径
invoke GetFullPathName, lpszDriverPath,sizeof acDriverPath,addr acDriverPath,NULL
PrintString acDriverPath
;创建驱动所对应的服务
invoke CreateService, hSCManager, lpszDriverName, lpDisplayName, \
SERVICE_START + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, \
SERVICE_ERROR_IGNORE, addr acDriverPath, NULL, NULL, NULL, NULL, NULL
mov hService, eax
;创建成功就跳到运行服务处
.if hService
jmp RunServer
.endif
;没跳代表失败
invoke GetLastError
mov Error,eax
;创建服务失败,不是因为其他原因
.if Error!=ERROR_IO_PENDING && Error!=ERROR_SERVICE_EXISTS && Error!=ERROR_SERVICE_MARKED_FOR_DELETE
PrintText "注册驱动时出错,出错信息如下:"
PrintError
mov bRet,FALSE
jmp exit
.endif
; 服务创建失败,是由于服务已经创立过
invoke OpenService ,hSCManager,$CTA0("beeper"), SERVICE_ALL_ACCESS
mov hService, eax
;如果打开服务也失败,则意味错误
.if eax==NULL
PrintText "注册驱动时出错,出错信息如下:"
PrintError
mov bRet,FALSE
jmp exit
.endif
;服务创建失败,是由于服务已经标记删除
.if Error==ERROR_SERVICE_MARKED_FOR_DELETE
;停止服务
invoke ControlService,hService,SERVICE_CONTROL_STOP, addr SvrSta
invoke CloseServiceHandle,hService
;再次创建
invoke CreateService, hSCManager, lpszDriverName, lpDisplayName, \
SERVICE_START + DELETE, SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, \
SERVICE_ERROR_IGNORE, addr acDriverPath, NULL, NULL, NULL, NULL, NULL
;如果打创建务再失败,则意味着错误
.if eax==NULL
PrintText "注册驱动时出错,出错信息如下:"
PrintError
mov bRet,FALSE
jmp exit
.endif
.endif
;运行服务
RunServer:
PrintText "CreateService OK!"
invoke StartService, hService, 0, NULL
.if eax==NULL
invoke GetLastError
;其他错误
.if(eax!=ERROR_IO_PENDING && eax!=ERROR_SERVICE_ALREADY_RUNNING)
PrintText<"StartService error ,other error!">
PrintError
mov bRet,FALSE
jmp exit
.elseif eax==ERROR_IO_PENDING
; 设备被挂起
PrintText<"StartService error,设备被挂起 !">
PrintError
mov bRet,FALSE
jmp exit
.else
PrintText<"设备已经运行!">
mov bRet,TRUE
jmp exit
.endif
.endif
mov bRet,TRUE
exit:
.if hService
invoke CloseServiceHandle ,hService ; 服务句柄
.endif
.if hSCManager
invoke CloseServiceHandle ,hSCManager; SCM句柄
.endif
mov eax,bRet
ret
LoadDriver endp
UnloadDriver proc uses esi edi lpszSvrName
LOCAL bRet:BOOL
local hSCManager:HANDLE
local hService:HANDLE
LOCAL SrvStatus:SERVICE_STATUS
mov bRet,FALSE
; 打开SCM管理器
invoke OpenSCManager, NULL, NULL, SC_MANAGER_ALL_ACCESS
mov hSCManager,eax
;打开SCM管理器失败
.if !hSCManager
PrintText<"OpenSCManager err!">
invoke MessageBox, NULL, CTEXT("OpenSCManager err!"), NULL, MB_OK + MB_ICONSTOP
mov bRet,FALSE
jmp exit
.endif
;打开驱动服务
invoke OpenService,hSCManager,lpszSvrName,SERVICE_ALL_ACCESS ;
mov hService,eax
;打开驱动服务失败
.if !hService
PrintText<"OpenService error!">
PrintError
mov bRet,FALSE
jmp exit
.endif
; 停止驱动服务
invoke ControlService, hService, SERVICE_CONTROL_STOP, addr SrvStatus
; 停止驱动服务失败 只有重新启动才能,再动态加载
.if !eax
PrintText<"StopService error!">
PrintError
.endif
;动态卸载服务,只做标记,服务停止时才被删除
invoke DeleteService, hService
mov bRet,eax
;卸载服务失败
.if !eax
PrintText<"DeleteService error!">
PrintError
mov bRet,FALSE
jmp exit
.else
PrintText<"DeleteService OK!">
.endif
mov bRet,TRUE
exit:
.if hService
invoke CloseServiceHandle, hService
.endif
.if hSCManager
invoke CloseServiceHandle, hSCManager
.endif
mov eax,bRet
ret
UnloadDriver endp
start proc
invoke LoadDriver,$CTA0("beeper.sys"),$CTA0("beeper"),$CTA0("beeper")
invoke UnloadDriver,$CTA0("beeper")
invoke ExitProcess, 0
start endp
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
end start
调用很简单:;加载运行驱动
invoke LoadDriver,$CTA0("beeper.sys"),$CTA0("beeper"),$CTA0("beeper")
;停止删除驱动
invoke UnloadDriver,$CTA0("beeper")
在上面的代码中,大家可以看到我尽量避免了.if .endif 的套嵌,如果我不这样,在代码最后,大家就会看到这样的代码:
.endif
.endif
.endif
.endif
我想大家看到这样的代码应该很晕吧,修改代码起来也不方便的,所以这里建议尽量别使用这样的方式。
好了,题外话讲完了,我们来说下RamASM一个很方便我们的功能:模版
它有什么用了?比如今天我想写一个驱动加载的Win32 APP ,如果没有这个功能,我可能会复制前面的框架代码到我们的程序中或者复制一个我们事先保存的.asm模块到我们的工程中然后在头文件中包含,虽然也很方 便,但还不够,通过使用模板,我们只要创建好了,以后只要新建工程时选择模板时选择我们需要的模板就可以了。
5.4.1 创建模板
下面我介绍下RadASM模板建立的方法,,比如我要写一个驱动加载与卸载的通用模板,步骤如下:
1. 打开RadASM,新建一个"Win32 APP"的工程,自己定义,比如“load”,模板选择项里选无,在"Load.Asm"里插入之前的的框架代码。
注意: 我们建立模块时工程名称不能和代码中使用的名称(函数名,变量名等)重名,模块导入时RadASM会自动查找替换代码中工程名称为你新建的工程名称,如果我这里工程名称写“LoadDriver”保存模板后,新建工程“load”导入模板后,代码中的“LoadDriver”函数名会被替换为“load”。
2.在菜单工程里找到“创建模板”打开如下图:

写好说明,模块文件名起一个形象的名字,比如 “标准加载驱动.tpl”,注意,模块默认保存在“\RadASM\Masm\Templates\”里,我们保存模版以后,可以在这里找到共享给别人, 安装时只需要复制到这个目录,新建工程时在向导里就会看到这个模块,要注意的是附加的头文件名字RadASM也会帮我们自动转换成工程建立的名字,前面提 到的不能重名的原因就在这里了。
5.4.2 模板的导入
导入模板很简单,新建工程时选择模板就可以了,如下图:

5.5 本章小结
最后总结下驱动加载和卸载的流程:
*********** 加载NT驱动**********************
① 调用OpenSCManager,打开SCM管理器.如果返回NULL,则返回失败,否则继续
② 调用CreateService,创建服务,创建成功则转步骤 ⑥
③ 调用失败就用GetLastError的得到错误返回值
④ 返回值为ERROR_IO_PENDING或ERROR_SERVICE_EXISTS说明服务已经创建过,用OpenService打开此服务.
⑤ 返回值为其他值, 创建武服务失败,返回失败.
⑥ 调用StartService开启服务
⑦ 成功返回
****************卸载NT驱动******************
① 调用OpenSCManager,打开SCM管理器,如果返回NULL,则返回失败,否则继续.
② 调用OpenService.如果返回NULL,则返回失败,否则继续
③ 调用ControlService停止此项服务.
④ 调用DeleteService卸载此项服务.
⑤ 成功返回.
相关
【原创】Win32Asm 驱动学习笔记1-2 章
【原创】Win32Asm 驱动学习笔记<3>调试基础
【原创】Win32Asm 驱动学习笔记<4> 最简单的驱动程序
【原创】Win32Asm 驱动学习笔记<5> 驱动程序的安装与卸载
