以文本方式查看主题 - 计算机科学论坛 (http://bbs.xml.org.cn/index.asp) -- 『 C/C++编程思想 』 (http://bbs.xml.org.cn/list.asp?boardid=61) ---- Kmd教程----用C写驱动的辅助教程 (http://bbs.xml.org.cn/dispbbs.asp?boardid=61&rootid=&id=51989) |
-- 作者:卷积内核 -- 发布时间:8/30/2007 2:24:00 PM -- Kmd教程----用C写驱动的辅助教程 0.前言 0.1 关于KmdTut KmdTut是一份以Win32汇编语言来开发Kernel mode驱动程序的教程,读者可以从http://www.freewebs.com/four-f/下载最新的英文版本,为了使用本教程,你还需要下载Kernel mode驱动程序开发包KmdKit,更多信息请参考http://www.wasm.ru/(很不幸,是俄文的,倒ing~~~) 0.2 关于KmdKit KmdKit的全称是Kernel Mode Driver development Kit for assembly language programmers,即内核模式驱动程序汇编开发包,KmdKit中包括了用宏汇编编译器开发驱动程序需要用到的所有东西。 0.3 关于KmdTut中文版 本中文版由罗云彬和刘松翻译,首先发表在http://asm.yeah.net(罗云彬的编程乐园)中,如果需要转载或者引用,请注明出处。 ◎ 将汇编代码和编译命令一同放在BAT文件中的缺点: ◎ 在代码中使用很多宏的缺点: 所以对文中的这些非常个性化的习惯,读者请根据自己的爱好自由取舍。 另外,毕竟翻译网上教程并不如出书一样经过层层审校,所以翻译有误或者不贴切的地方是难免的,特别是错别字,本人是打拼音的,空格按得快了,往往全拼的词组就可能选错了,所以如果有明显不对的地方,请读者发挥您的丰富想像力谐音一把,如"寄出"说不定是"基础","乘虚"说不定就是"程序"等等,如果发现错误,请邮件告知asm@zj165.com 再者:本篇教程英文版只有这么多,已经全部翻译了,但是俄文版还有后续的章节,可惜大家都不懂俄文了,有没有懂俄文的自告奋勇翻译一番呢~~~~俄文原文见http://www.wasm.ru/ |
-- 作者:卷积内核 -- 发布时间:8/30/2007 2:25:00 PM -- 1.Kernel Mode驱动程序基础 本教程讲述了如何在Windows NT为基础的操作系统上用Win32汇编开发KMD,包括NT4.0、2000、XP和2003等操作系统。开发Windows 95/98/ME使用的VxD驱动程序方面的知识并不在本教程讲述的范围内,另外,毫无疑问本教程并不那么完美,可能还包含了诸多未发现的错误,如果您发现了问题,请告知作者,毕竟作者的母语并不是英语,把它翻译成英文已经够难为我了(注:原作者是俄国人也),也感谢masquer和Volodya的校对工作。 1.1 KMD结构概述 1.1.1 主要组成部分 根据地址空间、代码权限和职责的不同,Windows NT内部划分为两个截然不同的部分。 下面就是用户模式进程的一些简单分类: 而下面是内核模式的一些模块: 图1.1 Windows NT结构简图 1.1.2 内核模式和用户模式 Intel x86体系结构的处理器定义了4个级别的权限(称为Ring),Windows系统使用了Ring0(供特权模式使用)和Ring3(供用户模式使用),Windows系统只使用了2个级别的权限级别的原因是为了和其他一些硬件系统兼容,这些硬件系统只有2个级别的权限,如Compaq Alpha和Silicon Graphics MIPS等。 1.2 Windows NT设备驱动程序 1.2.1 设备驱动程序的分类 Windows NT支持的设备驱动程序的范围很广,它们的分类如下: 用户模式的驱动程序: 内核模式驱动程序: 在不同的资料中,对驱动程序的分类方法可能完全不同,但这并不是问题。 1.2.2 分层的和单层的设备驱动程序 大部分控制硬件设备的驱动程序是分层的驱动程序,分层驱动的概念就是当用户模式发出一个请求时,每个请求从高层次的驱动程序逐层处理并流传到低层次的驱动程序中,一个I/O请求的处理可能分步在多个驱动程序中,例如,如果一个应用程序发出读盘请求,处理请求会在多个驱动程序中流过,在其中你也可以再加入n多个过滤驱动程序(比如插入一个加解密的模块)。 1.3 线程上下文(Thread Context) 在大多数情况下,我们的系统中只安装了一个CPU,所以,对于所有这些运行中的程序来说,操作系统对每个进程中的线程所使用的CPU时间进行调度,循环为每个线程分配时间片,这就造成了多个程序同时执行的假象。如果系统中安装了多个CPU,那么操作系统的调度算法将复杂得多,因为它要将各CPU上的线程进行平衡。如果Windows检测到一个新线程要开始运行了,它将进行一次上下文切换(context switch)(注:上下文(Content)实际上就是线程运行的环境,也就是运行时各寄存器和其他东东的状态,更自然的理解就是"线程状态")。所谓上下文切换就是保存线程运行时的机器状态,然后将另一个线程的状态恢复并重新开始执行。如果重新开始执行的线程属于另一个进程,那么该进程的地址空间也将被同时切换过来(通过在CR3寄存器中装入页表)。 1. 在发起I/O请求的用户线程中运行 在处理I/O请求包(IRPs)时,我们总是运行在和用户模式的调用者相同的进程上下文中运行,这样我们就能对用户程序的地址空间进行寻址。但是当驱动程序被加载或者卸载的时候,我们将在系统进程中运行,这时存取的只能是系统的地址空间。 1.4 中断请求级别 中断是任何操作系统都少不了的组成部分,中断使处理器打断正常的程序流程来首先处理它们,中断分硬件中断和软件中断两种,中断是分优先级的,一个高优先级的中断可以打断低优先级的中断的执行。 1.5 系统崩溃 我想每个人都见过著名的蓝屏死机画面,即"Blue Screen Of Death",简称为BSOD,也许根本不需要解释它是怎么出现或者在什么时候出现的,因为在后面的KMD开发过程中,你会很频繁地遇到它们。 1.6 Driver Development Kit Windows DDK是MSDN专业版和宇宙版的一部分,它也可以从http://www.microsoft.com/ddk/下载,对于开发设备驱动程序来说,DDK是关于Windows NT内部信息,包括系统函数、数据结构等的丰富资源,不幸的是,微软已经停止了免费发放DDK,所以现在只好去买正版的CD了(没有枪,没有炮,盗版游击队给我们造~~~) 1.7 汇编程序员使用的KmdKit KmdKit包含了所有用汇编开发KMD所需要的东西:include文件、lib文件、宏定义、例子文件、工具和一些文章,你可以自己在软件包中找到更多的东西,下一节我们将从这个软件包中包括的一些例子开始学习KMD的编程。 1.8 驱动程序的调试 调试内核模式的代码需要合适的调试器,Compuware的SoftIce是个不错的选择(见 http://www.compuware.com/products/numega/index.htm),当然你也可以使用Microsoft Kernel Debugger,它需要两台计算机:主机和目标机器,目标机器是被调试的机器,主机是运行调试软件的机器。Mark Russinovich ( http://www.sysinternals.com/ ) 也写了一个工具,叫做LiveKd,它允许在单台机器上运行Microsoft Kernel Debugger,而不再需要两台机器了。 1.9 其他参考资料 1. David Solomon, Mark Russinovich, "Inside Microsoft Windows 2000. Third Edition", Microsoft Press, 2000 这里列出的并不是全部,但这些似乎都有点儿看看的必要性。 |
-- 作者:卷积内核 -- 发布时间:8/30/2007 2:28:00 PM -- 2. 服务 ※ 和本节对应的例子代码见KmdKit\examples\simple\Beeper 读者也许有点疑惑:用户模式的服务关内核模式的驱动程序什么事呀?事实上,两者的确风马牛不相及,但是如果我们要和设备驱动程序通讯的话,我们必须首先安装它,启动它,而和设备驱动程序通讯的界面刚好和服务通讯的界面是类似的。 2.1 Windows服务 Windows NT使用某种机制来启动进程,并让它们不和某个具体的交互式的用户界面相关联,这些进程就被称为服务(service),服务的一个很好的例子就是Web服务器,这些Web服务都没有用户界面,服务是唯一以这种方式运行的应用程序(注:指没有用户界面,当然,严格地说病毒、木马以及所有不想见光的程序也是这样的~~),服务可以在系统启动的时候自动启动,也可以被手工启动,从这一点来看,设备驱动程序和服务是类似的。 (咦!第三个哪里去了,我也不知道,原文就这么两个呀,可能后面会提到吧~~) 服务程序中包含可执行代码,这两个组件对服务和驱动程序的处理方式是相同的。我们先来看看前面两个组件,在后面再讲述驱动程序。 2.2 服务控制管理器(SCM) SCM的代码位于\%SystemRoot%\System32\Services.exe中,当系统启动的时候,SCM被WinLogon进程启动,然后它扫描注册表中HKLM\SYSTEM\CurrentControlSet\Services键下的相关内容,根据这些内容创建一个服务数据库,数据库中包括所有服务的相关参数,如果服务或者驱动被标为自动启动的,那么启动它们并检测启动中是否出错。 图2.1 beeper.sys驱动的注册表键值 这些参数的含义如下: ◎ DisplayName--用户程序访问服务时使用的名称,如果为空,那么注册表的键名会被作为它的名称 你可以在控制面板中的"管理工具"中选择"事件查看器"来查看系统日志,例如,beeper.sys驱动在初始化的时候做完了所有该做的事(这个例子会让喇叭发声音,但是发声功能是在初始化函数DriverEntry中做的,初始化函数执行完,后面就没什么事了),所以它就返回一个错误,系统就会将它从内存中卸载。但是这里的ErrorControl参数等于SERVICE_ERROR_IGNORE,所以系统日志中并没有错误记录。 ◎ ImagePath--指驱动文件的全路径文件名,如果该参数没有指定路径,那么系统会在\%SystemRoot%\Drivers目录下查找 如果驱动的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) 仔细观察图2.1后,你对beeper.sys有什么要说的吗?好的,我们看到beeper这个内核模式驱动程序位于C:\masm32\Ring0\Kmd\Article2\beeper目录下,它的名称为"Nice Melody Beeper",由用户控制启动,出错信息不被记录。 2.3 服务控制程序(SCP) 从名称理解,服务控制程序(service control program/SCP)可以控制服务或者设备驱动程序,这些功能是在SCM的管理下,通过调用适当的函数来完成的,这些函数位于\%SystemRoot%\System32\advapi.dll (Advanced API)中。 ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 在上面的例子中,我们首先要做的事情是使用OpenSCManager函数来建立到SCM的连接,以便在指定的计算机上打开服务数据库。 OpenSCManager proto lpMachineName:LPSTR, lpDatabaseName:LPSTR, dwDesiredAccess:DWORD 函数使用的参数说明如下: .const 现在我们要打开的就是这个当前被激活的数据库,所以我们使用了NULL参数 ◎ dwDesiredAccess--指定访问SCM的权限,这个参数告诉SCM我们需要进行什么样的操作,常用的取值有三个: 我们可以使用下面的代码连接到SCM: invoke OpenSCManager, NULL, NULL, SC_MANAGER_CREATE_SERVICE |
-- 作者:卷积内核 -- 发布时间:8/30/2007 2:28:00 PM -- 2.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句柄 在这里我们只需要做两件事情:启动驱动和删除驱动,所以例子中使用了SERVICE_START和DELETE,我们不需要停止服务的操作,因为上面已经说过,这个驱动在初始化的时候就会返回错误(所以它不会有已经启动的状态)。 现在来总结一下,最后的5个参数总是设置为NULL,我们就把它抛到脑后去好了,第一个参数是SCM句柄,而dwDesiredAccess参数也是很好理解的,剩下的参数是什么?聪明的你一定已经猜到了--它们实际上就是和注册表里面的键一一对应的!看看下表就明白了: CreateService函数的参数 注册表 好了,现在回过头来看看例子代码: push eax 2.3.3 启动驱动程序 下一步要调用的函数是StartService,它的原型申明如下: StartService proto hService:HANDLE, dwNumServiceArgs:DWORD, lpServiceArgVectors:LPSTR 参数说明如下: ◎ hService--就是上一小节中由CreateService返回的驱动的句柄 启动驱动的方法就是这样的: invoke StartService, hService, 0, NULL StartService函数的执行过程和装入用户模式的DLL的过程类似,驱动程序文件的映像被装入到系统的地址空间中,文件可以被装入到任何地址中,然后系统会根据PE文件中的重定位表对其进行重定位操作,这样驱动程序的内存映像就被准备好了,接下来系统调用驱动的入口函数,也就是DriverEntry子程序,和装入DLL不同的是,DriverEntry子程序的执行是在系统进程的上下文中进行的。 2.3.4 卸载驱动 怎样卸载驱动呢? invoke DeleteService, hService DeleteService proto hService:HANDLE 参数hService就是需要被卸载的服务的句柄 CloseServiceHandle proto hSCObject:HANDLE 参数hSCObject可以是服务或驱动的句柄,也可以是SCM数据库的句柄,驱动的句柄被关闭后,我们再次调用CloseServiceHandle函数来关闭SCM句柄。 2.4 字符串操作的宏 最后来解释一下源代码中的$CTA0是什么东东--这是一个宏,用来在只读数据段中定义一个以0结尾的字符串,它可以在invoke宏指令中使用,这不是唯一用到的宏,在\Macros\Strings.mac文件中还包括很多其他有用的宏,这些宏都是用于定义字符串的,文件中也有怎样使用它们的详细的解释。既然本教程的重点是讲述KMD的编程,那么我就不在这些宏上面做过多的解释了,但是后面的程序中有很多地方会用到它们。 |
-- 作者:卷积内核 -- 发布时间:8/30/2007 2:29:00 PM -- 3-最简单的设备驱动程序 3. 最简单的设备驱动程序 ※ 和本章内容相关的源代码见: 3.1 如何编译和链接内核模式驱动程序 我总是把驱动程序的汇编源代码放到批处理文件中,这样的文件从内容上看是.asm和.bat文件的混合体,但是扩展名是.bat(注:读者在实际使用的时候是不是这样做完全可以根据个人喜好而定) ;@echo off .386 ; driver's code start :make 如果你运行这个"自编译"的批处理文件的话,系统会做以下的事情: set drv=drvname 这里定义一个环境变量,具体使用的时候用驱动的文件名来代替,下面的链接选项的含义是: ◎ /driver--告诉链接器创建Windows NT内核模式驱动程序,这个选项造成的最重要的影响是文件中会多出一个"INIT"节区(有关PE文件中节区等概念可以参考《Windows环境下32位汇编语言程序设计》一书的第17章:PE文件),另外还有".idata"节区,里面包含了一些IMAGE_IMPORT_DESCRIPTOR结构,指出了需要导入的函数和模块的名称。"INIT"节区的属性被标志为可丢弃,这样装载程序获取了相关的导入信息后,这个节区的内容即被丢弃 3.2 最简单的内核模式驱动程序 3.2.1 源代码 这里是一个最简单的内核模式驱动程序的源代码 ;@echo off mov eax, STATUS_DEVICE_CONFIGURATION_ERROR DriverEntry endp 3.2.2 DriverEntry过程 就像其他的可执行程序一样,每个驱动程序也有一个入口点,这是当驱动被装载到内存中时首先被调用的,驱动的入口点是DriverEntry过程(注:过程也就是子程序),DriverEntry这个名称只是一个标记而已,你可以把它命名为其他任何名字--只要它是入口点就行了。DriverEntry过程用来对驱动程序的一些数据结构进行初始化,它的函数原型定义如下: DriverEntry proto DriverObject:PDRIVER_OBJECT, RegistryPath:PUNICODE_STRING 不幸的是,Charles Simonyi创造的著名的"匈牙利表示法"并没有在DDK中使用,但是我还是在任何可能的地方使用,所以我为DriverObject和RegistryPath参数都加上了前缀。 PDRIVER_OBJECT typedef PTR DRIVER_OBJECT 当I/O管理器调用DriverEntry过程的时候,它会传过来两个指针类型的参数,说明如下: ◎ pDriverObject--指向用于描述当前驱动的对象(所谓对象,在内存中也就表现为一个结构而已),这个对象刚被系统初始化。由于Windows NT是一个面向对象的操作系统,因此,驱动也是被作为一个对象来描述的,当驱动被装载到内存中的时候,系统会创建一个对象来描述这个驱动,对象在内存中的表示方式就是一个DRIVER_OBJECT结构(在\include\w2k\ntddk.inc中定义),pDriverObject参数指向这个对象,以便让驱动有存取它的机会,但我们现在还没必要用到它 定长的Unicode字符串是用UNICODE_STRING结构来表示的,和用户模式代码不同,内核模式的代码往往采用用UNICODE_STRING结构定义的字符串,该结构在\include\w2k\ntdef.inc中定义如下: UNICODE_STRING STRUCT 结构中的各字段含义如下: ◎ _Length--字符串的长度,以字节表示(而不是以字符数量表示),这个长度不包括末尾的0字符,由于Length是汇编的保留字,所以我不得不在前面加了一个下划线 这种结构的优点在于它清楚地表现出了字符串的当前长度和最大的可能长度,这样就允许对它进行一些运算(比如在后面加上一些字符等)。 |
-- 作者:卷积内核 -- 发布时间:8/30/2007 2:29:00 PM -- 3.3 Beeper驱动程序 3.3.1 源代码 现在来看看Beeper驱动程序的源代码,在"服务"一节中我们已经看到过它的控制程序了。 ;@echo off ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: TONE_1 equ TIMER_FREQUENCY/(PITCH_C*OCTAVE) DELAY equ 1800000h ; for my ~800mHz box ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: cli sti MakeBeep1 endp ; Hardware access using WRITE_PORT_UCHAR and READ_PORT_UCHAR mov eax, dwPitch ; Turn speaker ON sti ; Turn speaker OFF MakeBeep2 endp invoke MakeBeep1, TONE_1 ; Hardware access using hal.dll HalMakeBeep function DriverEntry endp :make 这个驱动程序会使用主板上的扬声器来播放C大调的几个音符,为了实现这个功能,程序使用了IN和OUT指令来访问I/O端口。众所周知的是,Windows NT把I/O端口当作重要的资源来保护,任何用户模式的程序如果使用了IN或者OUT指令来访问端口的话,会被Windows立马枪毙掉。但事实上还是有办法绕过这个限制使用户模式的程序直接访问I/O端口,我们马上会谈到这一点。 3.3.2 控制系统定时器 计算机中有三个定时器,分别是定时器0、1和2,由可编程定时芯片(Programmable Interval Timer/PIT)实现,其中定时器2用于发声,发声的频率取决于定时器计数器的初始设置值,定时器会将计数值从初始值开始递减到0,然后将计数值复原为初始值,以此循环。计数值的递减由频率为1,193,180 Hz的系统振荡器控制着,该频率值在所有的PC家族的机器中是固定的。振荡器每产生一个脉冲,计数值就减一,为了发出不同频率的声音,我们只需要设定不同的初始值即可,发声频率和初始值的关系是:声音频率=1193180/初始值。关于这方面更进一步的知识,读者可以在网上搜到更多的内容。 mov al, 10110110y 首先,我们要设置定时器的控制寄存器,也就是将2进制值的10110110送到43h端口。 mov eax, dwPitch mov al, ah 然后,我们用两个连续的操作将初始值的低位字节和高位字节送到42h端口。 in al, 61h 现在要将扬声器打开,这可以通过将61h端口的位0和位1设置为1来完成,不出意外的话,现在应该能够听到声音了。 DO_DELAY MACRO 为了让声音延续一段时间,我们用DO_DELAY宏来进行一些延时,虽然这种延时方法有点过时,但还是很有效的。 in al, 61h 现在可以关闭扬声器了,千万别忘了扬声器是整个系统的资源哦,这只要将端口61h的位0和位1清零就好了。在程序中我们用了cli指令清除中断允许标志来关闭中断,这在多处理器的机器上会对其他程序有所影响的。 |
-- 作者:卷积内核 -- 发布时间:8/30/2007 2:30:00 PM -- 3.4.3 存取CMOS 计算机主板上有块小芯片是用来存放系统配置信息的,如硬盘参数、内存配置以及系统时间等等,这块芯片一般被叫做"CMOS"(CMOS是Complementary Metal Oxide Semiconductor的缩写)。CMOS芯片由电池供电,内部建有一个实时时钟(RTC),我们可以通过存取70h和71h号I/O端口来获取其中的数据,"Ralf Brown's Interrupt List"里面有其详细的格式说明,网址见http://www-2.cs.cmu.edu/afs/cs/user/ralf/pub/WWW/files.html(注:http://asm.yeah.net上面有已经整理成单个hlp文件的全部上述资料)。 mov al, 0Bh ; status register B push eax ; save old data format invoke wsprintf, addr acOut, $CTA0("Date:\t%s\nTime:\t%s"), addr acDate, addr acTime 图3.2 DateTime.exe程序的输出结果 这个程序中最奇怪的事情就是我们竟然可以直接访问CMOS而不被系统阻止,前面已经说过,如果用户模式程序在Windows NT操作系统下使用IN或者OUT指令来存取I/O端口的话会被系统终止,但是我们却可以,这怎么可能呢?这是因为我运行程序前刚喝了脑x金!广告里面天天说,脑x金无所不能!~~~~~~呵呵,开个玩笑,这当然是因为有了Giveio驱动程序。 3.5 Giveio设备驱动程序 3.5.1 Giveio驱动程序的源代码 ;@echo off ;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: local status:NTSTATUS invoke DbgPrint, $CTA0("giveio: Entering DriverEntry") ; I/O access for 71h port invoke Ke386SetIoAccessMap, 1, pIopm invoke DbgPrint, $CTA0("giveio: Leaving DriverEntry") DriverEntry endp 3.5.2 I/O许可位图 谜底揭晓了:我们的驱动程序修改了I/O许可位图(I/O permission bit map,IOPM),这样进程就被允许自由地存取I/O端口,这方面详细资料见http://www.intel.com/design/intarch/techinfo/pentium/PDF/inout.pdf Ke386QueryIoAccessMap proto stdcall dwFlag:DWORD, pIopm:PVOID Ke386QueryIoAccessMap函数从TSS中拷贝2000h字节的当前IOPM到指定的内存缓冲区中,缓冲区指针由pIopm参数指定。 如果函数执行成功的话会在al中返回非0值(不是eax);如果执行失败会在al(不是eax)中返回零。 Ke386SetIoAccessMap proto stdcall dwFlag:DWORD, pIopm:PVOID Ke386SetIoAccessMap函数刚好相反,它从pIopm参数指定的缓冲区中拷贝2000h字节的IOPM到TSS中去。 如果函数执行成功的话会在al中返回非0值(不是eax);如果执行失败会在al(不是eax)中返回零。 Ke386IoSetAccessProcess proto stdcall pProcess:PTR KPROCESS, dwFlag:DWORD Ke386IoSetAccessProcess允许或者禁止对进程使用IOPM。其参数说明如下: 如果函数执行成功的话会在al中返回非0值(不是eax);如果执行失败会在al(不是eax)中返回零。 3.5.3 从注册表中读取信息 在调用Ke386IoSetAccessProcess的时候要用到进程对象的指针,有好几种办法可以获得该指针,我选择了最简单的办法--使用进程ID,这就是DateTime.exe程序首先获取当前进程的ID并把它保存在注册表中的原因。在这里我们使用注册表在用户模式的代码以及内核模式的驱动程序之间传递参数。由于DriverEntry过程是在系统进程环境中运行的,所以不这样做的话,我们无法得知它究竟是被哪个进程所启动的。 lea ecx, oa 在调用ZwOpenKey函数前,我们必须先初始化OBJECT_ATTRIBUTES结构(在\include\w2k\ntdef.inc中定义),我用InitializeObjectAttributes宏来完成这个功能,但读者以后最好手工来完成这个工作,因为InitializeObjectAttributes宏可能并不像你预料的那样运行(注:宏中间可能用到很多寄存器,但是有些寄存器的值可能是你需要保存的,过多的使用宏以后,寄存器在哪里被修改了你都不知道),你也可以这样做: lea ecx, oa ZwOpenKey函数的第一个参数指向一个变量,用来返回注册表键的句柄;第二个参数是存取注册表的权限,你应该注意到ecx中保存有指向已经初始化的该注册表键的对象属性结构的指针。 invoke ZwOpenKey, addr hKey, KEY_READ, ecx usz dw 'U', 'n', 'i', 'c', 'o', 'd', 'e', ' ', 's', 't', 'r', 'i', 'n', 'g', 0 但是我可不喜欢这么麻烦,所以我写了下面这些宏:COUNTED_UNICODE_STRING、$COUNTED_UNICODE_STRING、CCOUNTED_UNICODE_STRING和$CCOUNTED_UNICODE_STRING(定义在\Macros\Strings.mac中)。 (注:原文作者这里用的一个小技巧可以学习一下,前面一句push eax,然后后面调用的最后一个参数是esp,在这里压入堆栈的堆栈指针正是指向push eax指令保留的堆栈空间,所以函数执行的时候在这里返回一个值,后面一句pop ecx实际上是弹出了函数返回在里面的数值,千万不要认为弹出的原始的eax值,这种方法可以免去定义一个临时使用的变量的麻烦,当然限制条件就是该临时变量的指针参数必须是函数的最后一个参数才行。这个技巧在整个教程中多次用到,请注意理解!) 3.5.4 让用户模式的进程可以存取I/O端口 .if ( eax != STATUS_OBJECT_NAME_NOT_FOUND ) && ( ecx != 0 ) lea ecx, kvpi .if al != 0 mov ecx, pIopm mov ecx, pIopm invoke Ke386SetIoAccessMap, 1, pIopm invoke ObDereferenceObject, pProcess invoke MmFreeNonCachedMemory, pIopm, IOPM_SIZE invoke MmAllocateNonCachedMemory, IOPM_SIZE 3.6 关于驱动程序的调试 现在可以更详细地讨论一下驱动的调试了,前面已经提到过,我们最好用SoftICE来调试驱动程序。 |
W 3 C h i n a ( since 2003 ) 旗 下 站 点 苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》 |
282.227ms |