关于作者

用户名:llf6
笔名:dge
地区: 北京-北京
行业:其他

日历  

快速登录

+ 用户名:
+ 密 码:

在线留言



友情链接

精彩站点

访问统计:
文章个数:21
评论个数:37
留言条数:8




Powered b BlogDriver 2.1

dge

 

文章

节日快乐
光棍节快乐

- 作者: D哥 2007年11月11日, 星期日 23:19  回复(0) |  引用(0) 加入博采

谈谈后门
					   谈谈后门
作者:yyt_hac	
主页:http://www.yythac.com	
Email:webmaster@yythac.com

	随着电脑技术的发展和网络的普及,黑客技术已经为越来越多的人所了解,好多网站也推出了黑客
培训班、VIP会员等一系列服务,这些服务必然会培养出好多黑客技术爱好者,而后门作为黑客必须的工具
必然会受到越来越多的关注。目前新后门增长的速度也很快,我接触后门很久了,也写过一些后门,现在想
谈谈对后门的一些看法,如果有错误的地方,欢迎指正。

	我想谈的第一个问题就是对一些概念的理解,那就是“远程控制软件”,“木马”,“后门”。

	一般“远程控制软件”是指用户便于远程管理而有意在自己机器上安装的软件,如pcanywhere,它
的特征是用户自己有意安装的软件,它的使用者是用户自己。不过现在有人也把“远程控制软件”的意义扩
充了,认为有远程控制功能的软件就叫远程控制软件,比如有些木马、后门有远程控制功能,也把它们叫远
程控制软件。

	“木马”全名叫“特洛伊木马”,它的使用者是黑客,更确切的说是“骇客”,它主要是“骇客”
通过欺骗用户的方法(包含捆绑,利用网页等)让用户不知不觉的安装到他们系统中的一类软件,主要功能
有远程控制,盗密码等。木马的名字就指出了它的特征,那就是具有欺骗性,软件的分类不应该看它的功能
,而是应该看它的特征。现在有些木马的开发者不喜欢把自己开发的木马叫作“木马”,而是叫“远程控制
软件”。原因很简单,“木马”是贬义的,“远程控制软件”就是褒义的了,这样做也可以理解,因为毕竟
“木马”也包含在扩充了意义的“远程控制软件”中了。可是还有人说它开发的木马是远程控制软件而不是
木马,这就不能理解了。是不是木马不是由你说的,而是要看它的特征。“木马”和常规远程控制软件的区
别就在于它能够通过欺骗的方式让用户不知不觉的安装到他的计算机中,它不是用户自己想安装的,而常规
远程控制软件是用户自己有意安装的。你能说“冰河”不是木马吗?不能,因为它有木马的特征。木马的特
征也是好多人讨厌它的原因,因为被人欺骗了总是很痛苦的,特别是被人欺骗了感情;好多木马都有盗密码
功能,这是让人讨厌它的另一个原因,因此水平比较高的黑客都不屑于用木马。

	“后门”是黑客在入侵了计算机以后为了以后能方便的进入该计算机而安装的一类软件,它的使用
者是水平比较高的黑客,他们入侵的机器都是一些性能比较好的服务器,而且这些计算机的管理员水平都比
较高,为了不让管理员发现,这就要求“后门”必须很隐蔽,因此后门的特征就是它的隐蔽性。木马的隐蔽
性也很重要,可是由于被安装了木马的机器的使用者一般水平都不高,因此相对来说就没有后门这么重要了。
后门和木马的区别就是它更注重隐蔽性但是没有欺骗性,因此它的危害性没有木马大,名声介于“远程控制
软件”和“木马”之间。

	通过上面的分析,我们就很容易区分这三类软件了。后门作为一种黑客工具,它的主要用途还是在
非法方面,把它说成是一种恶意程序也可以接受,可是却不能把它说成是病毒、木马,因为它们出来本质特
征是不同的。当然,任何事物都有两面性,有好的作用就有坏的作用,工具本身的好坏是一个方面,但是关
键是要看使用工具的人,刀可以用来杀人也可以用来救人,不能因为刀可以杀人就把刀说得一无是处,关键
是看好处多些还是坏处多些。后门也一样,不可否认,它的坏处要多些,可是它的破坏力不大,而且它也有
好的一方面,比如后门让大家增加网络安全意识,提高国家整体网络安全水平,可以增加杀毒软件的卖点,
也可以用它来控制国外的机器为祖国做贡献,是不是很夸张?

	下面我们谈谈什么样的后门才是好后门?也就是后门最重要的是什么?

	现在好多后门技术都公开了,也都有源代码,因此写一个后门并不困难,只要学会了编程,两个星
期就可以写出一个还可以的后门,这也是新后门不断增加的原因,可是要写一个真正好后门并不是一件容易
的事情。那什么样的后门才是真正好的后门呢?其实从上面可以知道后门的特征就是它的隐蔽性,因此隐蔽
性好的后门才是真正的好后门。我发现好多人在说这个后门的功能有多么多么的强大,那个后门有多么多么
难卸载,讨论这些有意义吗?只有在隐蔽性很好的情况下再来考虑它的功能和易用性,否则被发现了,你的
后门功能再多,再容易使用,也一点作用都没有。如果你只想找功能强大,容易使用的后门,我劝你还是使
用商业化的远程控制软件好了,如把pcanywhere改成能在命令行下可以安装的版本就可以了,它的功能够强
大了吧,使用也够方便了吧,根本用不到后门。难卸载性就更没意义了,如果我发现了我的机器被装了后门
,你再难卸载我重装系统还不行吗?不可能你发现有根刺在你肉里而不把它拔出来的。如果你发现你的系统
被装了后门却因为难卸载而能忍受它在你的系统中,那我只能说 ‘I 服了 YOU’。

	那么后门的隐蔽性主要体现在哪些方面呢?我觉得有下面三个方面:
	启动方式、存在方式和连接方式。

	启动方式是指当操作系统启动时怎样启动后门,目前决大多数后门都是采用加注册表启动项或者加
系统服务的方式,这些方式都是很容易发现的,而感染系统文件的启动方式是比较隐蔽的。

	存在方式是指当后门成功启动后,它在操作系统中的存在形式,目前主要有三种方式:进程、隐藏
进程和远程线程,进程和隐藏进程现在都很容易发现了,远程线程是一种比较隐蔽的方式。

	连接方式是指该后门的用户怎样通过客户端和安装了该后门的机器建立连接,从而控制该机器。开
tcp端口的方式太明显,fport就可以发现,udp端口也一样,使用icmp等数据包实现无连接传输不是很稳定,
目前端口复用技术和反向连接技术都是比较好的技术,各有所长,也可以两种技术相结合。

	如果一个后门能够把上面三个方面都做得很好,那才是一个真正的好后门,否则只能算是一个入门
级后门。

	那为什么要写后门呢?

	首先,写一个好的后门是一件很有挑战性的事情,毕竟它涉及到很多高级的编程技术,编写后门能
够增加你对操作系统底层的了解,大幅度的提高编程水平。从事一些工作的人水平到了一定的阶段就想有所
突破,做一些有挑战性的事情,比如登山运动员想登上珠穆朗玛峰,游戏运动员想游过太平洋,黑客程序员
嘛就是想写一个好后门了。
	其次,写后门可以提高你在黑客界的知名度。
	再次,写收费后门还可以带来经济效益。
	最后,后门虽然有很多坏处,但是毕竟破坏力不大,它也有些好的用途。

	写这篇文章是因为在聊天的过程中经常有人和我讨论这问题,也看到了好多人对这些问题的看法,
因此我也把我的一些观点写出来,以后有人和我讨论这些问题我就把这篇文章给他看就可以了。

	这篇文章有可能会冒犯一些人,如果你觉得我冒犯了你,那有可能是你水平比较低,因为我尊重的
是水平高的黑客,而不是骇客。如果你自认为水平很高却还是觉得我冒犯了你,那欢迎批评指正。

												
									2005-2-24

- 作者: D哥 2007年07月5日, 星期四 22:40  回复(0) |  引用(0) 加入博采

完美世界

- 作者: D哥 2007年06月30日, 星期六 09:33  回复(0) |  引用(0) 加入博采

推荐些好东西

由于水平的原因,我拿不出什么好东西给大家,而看到我的博客依然还是有很多人来.为了不让兄弟们白来一趟,我推荐一些东西给大家,算是弥补一下.这些东西特别适合象我一样喜欢驱动的朋友.

楚狂人大侠的系列教程:

《文件系统驱动开发教程》

Windows TDI过滤驱动开发(电子书)

DriverNetworks开发网络驱动教材(0-7课)

上面这些在驱动开发论坛上都可以找到http://bbs.driverdevelop.com/index.php

看这些书之前我感觉最好对WDM驱动模型有个最基本的了解.

关于WDM的好书:

Walter Oney写的 《Programming the Windows Driver Model》这个在网上可以找到中文版本.

Four-F的驱动开发教程.(这个是专门讲内核驱动的,在asm.yeah.net上可以找到).

好了,以后我发现什么好东西,再告诉大家,如果你发现了好东西,也要记得告诉我哦.

- 作者: D哥 2006年12月23日, 星期六 09:16  回复(3) |  引用(0) 加入博采

进程隐藏的两种方法

作者:dge
                              进程隐藏的两种方法
这两种都是很古老的方法,因为无聊,所以写了一下。代码在XP_SP2下调试通过.

(1).从活动进程链表(ActiveProcessLinks)中摘除自身,这种方法可以欺骗任务管理器,
下面这个程序做的就是双向链表的删除节点和插入节点,十分的简单。


;@echo off
;goto make

;----------------------------------------------------------------------------------------------------
.386
.model flat, stdcall
option casemap:none
;----------------------------------------------------------------------------------------------------
;                                 I N C L U D E   F I L E S                                       
;----------------------------------------------------------------------------------------------------
include d:\masm32\include\w2k\ntstatus.inc
include d:\masm32\include\w2k\ntddk.inc
include d:\masm32\include\w2k\ntoskrnl.inc
include d:masm32\include\w2k\w2kundoc.inc
includelib d:\masm32\lib\w2k\ntoskrnl.lib
include d:\masm32\Macros\Strings.mac

_DriverUnload proto :PDRIVER_OBJECT
_DispatchControlIo proto :PDEVICE_OBJECT,:PIRP

;----------------------------------------------------------------------------------------------------
;                                 C O N S T A N T S                                            
;----------------------------------------------------------------------------------------------------

.const
CCOUNTED_UNICODE_STRING "\\Device\\devHideprocess", g_usDeviceName, 4
CCOUNTED_UNICODE_STRING "\\??\\slHideprocess", g_usSymbolicLinkName, 4   
 
.data
szHide                  db   'explorer.exe',0
Flink                   dd   ?
Blink                   dd   ? 
Explorer                dd   ?
;----------------------------------------------------------------------------------------------------
;                                 C O D E                                 
;----------------------------------------------------------------------------------------------------

.code

DriverEntry proc uses ebx edi esi, pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
 local status:NTSTATUS
 local pDeviceObject:PDEVICE_OBJECT
 local   dwId,lpEprocess
        local   ListOffset,NameOffset
 local   IdOffset
 local   Version
        
;       int 3
; invoke DbgPrint,$CTA0("\n\nEntry  DriverEntry\n\n")
 mov status,STATUS_DEVICE_CONFIGURATION_ERROR

 invoke IoCreateDevice,pDriverObject,0,addr g_usDeviceName,FILE_DEVICE_UNKNOWN,0,FALSE,addr pDeviceObject
 .if     eax==STATUS_SUCCESS
         mov eax,pDriverObject
  assume eax:ptr DRIVER_OBJECT
  mov [eax].DriverUnload,  offset _DriverUnload
  assume eax:nothing
  ;获得得系统版本
  invoke  PsGetVersion,NULL,addr Version,NULL,NULL     
                mov     eax,Version
  cmp     eax,0
  jne     l1
  mov     ListOffset,0A0h
  mov     NameOffset,1fch
  jmp     l2
        l1:     cmp     eax,1
         jne     exit
  mov     ListOffset,88h
  mov     NameOffset,174h
        l2:     invoke  PsGetCurrentProcessId
         mov     dwId,eax
         invoke  PsLookupProcessByProcessId,dwId,addr lpEprocess
  mov     esi,lpEprocess
  add     esi,ListOffset
  mov     edi,esi
                assume  edi:PLIST_ENTRY
         assume  esi:PLIST_ENTRY   
        l3:     mov     edx,[esi].Flink
         ;比较是否为最后一个EPROCESS
                cmp     edx,edi                                      
  je      l4
  assume  esi:nothing
  sub     esi,ListOffset
  add     esi,NameOffset
  invoke  strcmp,esi,addr szHide
  .if     eax == 0
          sub     esi,NameOffset
          add     esi,ListOffset   
          mov     Explorer,esi
   assume  esi:PLIST_ENTRY
   assume  ebx:PLIST_ENTRY
   assume  eax:PLIST_ENTRY
                        ;删除节点
   mov     eax,[esi].Flink            
   mov     ebx,[esi].Blink
   mov     [ebx].Flink,eax
   mov     [eax].Blink,ebx
   mov     Flink,eax
   mov     Blink,ebx
   assume  eax:nothing
   assume  ebx:nothing
    
   invoke DbgPrint,$CTA0("\n\n************hide process successful ***********\n\n")
   jmp     l4
                .endif
  ;恢复EPROCESS指针
                sub     esi,NameOffset                                  
                add     esi,ListOffset                 
                assume  esi:PLIT_ENTRY
                mov     esi,[esi].Flink
                jmp     l3
l4:  
         assume  esi:nothing
                assume  edi:nothing                
                mov     status,STATUS_SUCCESS      
exit:      
 .endif
 mov     eax,status
 ret
        mov     eax,STATUS_DEVICE_CONFIGURATION_ERROR
        ret
DriverEntry endp

;----------------------------------------------------------------------------------------------------
;                                 D R I V E R U N L O A D                                          
;----------------------------------------------------------------------------------------------------

_DriverUnload proc pDriverObject:PDRIVER_OBJECT

;       int 3
; invoke DbgPrint,$CTA0("\n\nEntry DriverUnload\n\n")
        pushad

        mov     eax,Flink
 mov     ebx,Explorer
 assume  ebx:PLIST_ENTRY
 assume  eax:PLIST_ENTRY
 ;恢复被摘除的节点
 mov     [eax].Blink,ebx                                             
 mov     [ebx].Flink,eax

 mov     eax,Blink
 mov     [eax].Flink,ebx
 mov     [ebx].Blink,eax
 
 popad
 ;清除符号连接
 invoke IoDeleteSymbolicLink,addr g_usSymbolicLinkName                
 mov eax, pDriverObject   
 ;删除在初始化创建的设备
 invoke IoDeleteDevice,(DRIVER_OBJECT PTR [eax]).DeviceObject
 
 ret

_DriverUnload endp
;----------------------------------------------------------------------------------------------------
;                                 E N D                                          
;----------------------------------------------------------------------------------------------------
             
end DriverEntry


:make

set path=%path%;d:\masm32\bin
set drv=hideprocess
ml /nologo /c /coff %drv%.bat
link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native %drv%.obj
del %drv%.obj
echo.
pause

;****************************************************************************************************


(2).如果你反汇编taskmgr.exe,可以在发现taskmgr.exe是通过NtQuerySystemInformation枚举进程的,
因此可以通过挂钩系统服务NtQuerySystemInformation修改这个函数的行为,从而实现在任务管理器中隐藏进程的目的,下面就是实现代码。
 
;@echo off
;goto make

.386
.model flat, stdcall
option casemap:none
;----------------------------------------------------------------------------------------------------
;                                 I N C L U D E   F I L E S                                       
;----------------------------------------------------------------------------------------------------

include d:\masm32\include\w2k\ntstatus.inc
include d:\masm32\include\w2k\ntddk.inc
include d:\masm32\include\w2k\native.inc
include d:\masm32\include\w2k\ntoskrnl.inc
includelib d:\masm32\lib\w2k\ntoskrnl.lib
include d:\masm32\Macros\Strings.mac
;----------------------------------------------------------------------------------------------------
;                                 D A T A                                          
;----------------------------------------------------------------------------------------------------
.data
;保存地址
dwOldNtQuerySystemInformation  dd            ?
dwAddr                         dd            ?
;----------------------------------------------------------------------------------------------------
;                                 C O N S T A N T S                                            
;----------------------------------------------------------------------------------------------------
.const
CCOUNTED_UNICODE_STRING "\\Device\\devHideprocess", g_usDeviceName, 4
CCOUNTED_UNICODE_STRING "\\??\\slHideprocess", g_usSymbolicLinkName, 4
CCOUNTED_UNICODE_STRING "explorer.exe", processname, 4
;----------------------------------------------------------------------------------------------------
;                                 C O D E                nbsp;                
;----------------------------------------------------------------------------------------------------
.code

NewNtQuerySystemInformation     proc  SysInfoClass,lpSysInfo,SysInfoL,Return
                     
                    invoke NtQuerySystemInformation,SysInfoClass,lpSysInfo,SysInfoL,Return
                    pushad   
      test eax,eax
      jnz   exit
      .if    SysInfoClass == SystemProcessesAndThreadsInformation
             mov     esi,lpSysInfo
      mov     ebx,esi
             add     esi,[esi]                                     
     
         @@:               add     esi,38h                                        ;在38h偏移处取得进程名字。
                           invoke  RtlCompareUnicodeString,addr processname, esi, 1

      .if    eax== 0
             invoke DbgPrint, $CTA0("\nsuccessful \n")
             .if    dword ptr[esi-38h] == 0
             mov dword ptr[ebx],0
      jmp    exit
             .else    
             sub    esi,38h
             mov    edx,[esi]
                                         add    [ebx],edx
 
                                         add    esi,[esi]
             jmp    @B
                                  .endif
             .else
                                  sub    esi,38h
             cmp    dword ptr[esi],0
             jz     exit
             mov    ebx,esi
                                  add    esi,[esi]
      jmp    @B
             .endif        
      .endif
                
  exit:      popad

                    ret

NewNtQuerySystemInformation    endp

;----------------------------------------------------------------------------------------------------
;                                 H O O K F U N C                                         
;----------------------------------------------------------------------------------------------------
HookFunction        proc

                    pushad
;      int 3
;      invoke DbgPrint, $CTA0("\nEntry into hoookfunction\n")
                    ;下面是用KeServiceDescriptorTabled导出符号获得数组的基地址,这个数组中包含有NtXXXX函数的入口地址。
      mov eax, [KeServiceDescriptorTable]
             mov esi, [eax]
      mov esi, [esi]
                    ;下面五句为获取ZwQuerySystemInformation的地址
      mov eax,ZwQuerySystemInformation
      inc eax
      inc eax
      mov eax,[eax]
      mov eax,[eax]
      inc eax
      movzx ecx,byte ptr[eax]
      sal ecx,2                  
      add esi,ecx
      mov dwAddr,esi
      mov edi,dword ptr[esi]
      ;保存旧的函数地址。
      mov dwOldNtQuerySystemInformation,edi
                    mov edi,offset NewNtQuerySystemInformation
      ;修改入口地址
      cli
      mov dword ptr[esi],edi
      sti
      popad
                    mov eax, STATUS_SUCCESS

      ret

HookFunction     endp

;----------------------------------------------------------------------------------------------------
;                                 DriverUnload                                          
;----------------------------------------------------------------------------------------------------                               
DriverUnload        proc pDriverObject:PDRIVER_OBJECT
;必须保存环境,否则后果很严重。在这个函数中恢复被修改的地址。
 
                   pushad
;             int 3
;                   invoke DbgPrint, $CTA0("\nEntry into DriverUnload \n")
                    mov esi,dwAddr
             mov eax,dwOldNtQuerySystemInformation
                    cli
             mov dword ptr[esi],eax
             sti
                    invoke IoDeleteSymbolicLink, addr g_usSymbolicLinkName
             mov eax,pDriverObject
             invoke IoDeleteDevice, (DRIVER_OBJECT PTR [eax]).DeviceObject             
             popad

             ret

DriverUnload endp

;----------------------------------------------------------------------------------------------------
;                                 D R I V E R E N T R Y                                          
;----------------------------------------------------------------------------------------------------
DriverEntry         proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
                    local status:NTSTATUS
      local pDeviceObject:PDEVICE_OBJECT

;                   int 3
;             invoke DbgPrint, $CTA0("\nEntry into DriverEntry\n")
             mov status, STATUS_DEVICE_CONFIGURATION_ERROR
                    invoke IoCreateDevice, pDriverObject, 0, addr g_usDeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, addr pDeviceObject
             .if    eax == STATUS_SUCCESS
      mov eax, pDriverObject
      assume eax:ptr DRIVER_OBJECT
      mov [eax].DriverUnload,  offset DriverUnload
                    assume eax:nothing
                           invoke HookFunction       
      mov status, STATUS_SUCCESS
             .endif
             mov eax, status

             ret

DriverEntry         endp

end DriverEntry

;----------------------------------------------------------------------------------------------------
;                                 E N D                                         
;----------------------------------------------------------------------------------------------------

:make

set drv=hideprocess

d:\masm32\bin\ml /nologo /c /coff %drv%.bat
d:\masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native %drv%.obj

del %drv%.obj

echo.
pause


参考:

(1) Windows2000 内核级进程隐藏、侦测技术 
(2) A portable Win32 userland rootkit   

- 作者: D哥 2006年11月26日, 星期日 16:36  回复(10) |  引用(0) 加入博采

Windows NT/2000/XP下不用驱动的Ring0代码实现
摘:本文转载自http://webcrazy.yeah.net 查看全文

- 作者: D哥 2006年11月19日, 星期日 18:06  回复(1) |  引用(0) 加入博采

关于进入ring0的另一种方法
摘要:文章写的非常好,本文转载自http://www.vxer.cn的杂志NE365 magazine V0.1. 查看全文

- 作者: D哥 2006年11月19日, 星期日 17:43  回复(0) |  引用(0) 加入博采

总结进入RING0的方法
摘要:本文转载自CVC:http://www.retcvc.com/index.html的杂志cvc_issue1 查看全文

- 作者: D哥 2006年10月8日, 星期日 15:11  回复(2) |  引用(0) 加入博采

在Kernel mode下绕过Outpost Firewall 3.x和4.0
摘要:同样是greatdong翻译的文章。 查看全文

- 作者: D哥 2006年09月2日, 星期六 18:29  回复(0) |  引用(0) 加入博采

跟踪Native API函数调用
摘要:greatdong翻译的文章 查看全文

- 作者: D哥 2006年08月27日, 星期日 15:10  回复(0) |  引用(0) 加入博采

Hook API监视驱动的加载

;**************************************************************************************************
;Author:dge/D哥
;Date  :2006.7.20
;**************************************************************************************************
;@echo off
;goto make

.386
.model flat, stdcall
option casemap:none

;*************************************************************************************************
include d:\masm32\include\w2k\ntstatus.inc
include d:\masm32\include\w2k\ntddk.inc
include d:\masm32\include\w2k\ntoskrnl.inc
includelib d:\masm32\lib\w2k\ntoskrnl.lib
include d:\masm32\Macros\Strings.mac

;**************************************************************************************************
.data
;保存地址
dwOldNtLoadDriver   dd            ?
dwAddr              dd            ?
dwDriverName        ANSI_STRING 
.const
CCOUNTED_UNICODE_STRING "\\Device\\devHookApi", g_usDeviceName, 4
CCOUNTED_UNICODE_STRING "\\??\\slHookApi", g_usSymbolicLinkName, 4
CCOUNTED_UNICODE_STRING "ZwLoadDriver", g_usRoutineAddr, 4
  
;**************************************************************************************************
.code
;让这个函数在NtLoadDriver的调用时被执行以实现监视
NewNtLoadDriver     proc  lpDriverName:PUNICODE_STRING
                   
      pushad
;      int 3
;                   invoke DbgPrint, $CTA0("\nEntry into NEW\n")
      invoke RtlUnicodeStringToAnsiString, addr dwDriverName, lpDriverName,TRUE
      invoke DbgPrint, $CTA0("\nDriverName: %s.sys\n"), dwDriverName.Buffer
      popad
      ;调用原函数
      push   lpDriverName
      call   dwOldNtLoadDriver
   
                    ret

NewNtLoadDriver     endp

;**************************************************************************************************
HookFunction        proc

                    pushad
;      int 3
;      invoke DbgPrint, $CTA0("\nEntry into hoookfunction\n")
                    ;下面是用KeServiceDescriptorTabled导出符号获得数组的基地址,这个数组中包含有NtXXXX函数的入口地址。
      mov eax, KeServiceDescriptorTable
             mov esi, [eax]
      mov esi, [esi]
      ;用MmGetSystemRoutineAddress来获得函数ZwLoadDriver的地址。并从这个函数地址后面的第2个字节中取得服务号。从而
      ;获得以服务号为下标的数组元素。
             invoke MmGetSystemRoutineAddress,addr g_usRoutineAddr
      inc eax
      movzx ecx,byte ptr[eax]
      sal ecx,2                  
      add esi,ecx
      mov dwAddr,esi
      mov edi,dword ptr[esi]
      ;保存旧的函数地址。
      mov dwOldNtLoadDriver,edi
                    mov edi,offset NewNtLoadDriver
      ;修改入口地址
      cli
      mov dword ptr[esi],edi
      sti
      popad
                    mov eax, STATUS_SUCCESS

      ret

HookFunction     endp

;**************************************************************************************************                               
DispatchCreateClose proc pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP

             mov eax, pIrp
             assume eax:ptr _IRP
             mov [eax].IoStatus.Status, STATUS_SUCCESS
             and [eax].IoStatus.Information, 0
             assume eax:nothing

             invoke  IoCompleteRequest, pIrp, IO_NO_INCREMENT
             mov eax, STATUS_SUCCESS

             ret

DispatchCreateClose endp

;**************************************************************************************************
DriverUnload        proc pDriverObject:PDRIVER_OBJECT
;必须保存环境,否则后果很严重。在这个函数中恢复被修改的地址。
 
                   pushad
;             int 3
;                   invoke DbgPrint, $CTA0("\nEntry into DriverUnload \n")
                    mov esi,dwAddr
             mov eax,dwOldNtLoadDriver
                    cli
             mov dword ptr[esi],eax
             sti
                    invoke IoDeleteSymbolicLink, addr g_usSymbolicLinkName
             mov eax,pDriverObject
             invoke IoDeleteDevice, (DRIVER_OBJECT PTR [eax]).DeviceObject             
             popad

             ret

DriverUnload endp

;**************************************************************************************************
DriverEntry         proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING
              &bsp;     local status:NTSTATUS
      local pDeviceObject:PDEVICE_OBJECT

;                   int 3
;             invoke DbgPrint, $CTA0("\nEntry into DriverEntry\n")
             mov status, STATUS_DEVICE_CONFIGURATION_ERROR
                    invoke IoCreateDevice, pDriverObject, 0, addr g_usDeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, addr pDeviceObject
             .if eax == STATUS_SUCCESS
                 invoke IoCreateSymbolicLink, addr g_usSymbolicLinkName, addr g_usDeviceName
                 .if eax == STATUS_SUCCESS
                     mov eax, pDriverObject
                     assume eax:ptr DRIVER_OBJECT
                     mov [eax].DriverUnload,            offset DriverUnload
                     mov [eax].MajorFunction[IRP_MJ_CREATE*(sizeof PVOID)],        offset DispatchCreateClose
                     mov [eax].MajorFunction[IRP_MJ_CLOSE*(sizeof PVOID)],         offset DispatchCreateClose
          
       assume eax:nothing
                                          invoke HookFunction
           
                     mov status, STATUS_SUCCESS
                 .else
                                          invoke IoDeleteDevice, pDeviceObject
                 .endif
             .endif
             mov eax, status

             ret

DriverEntry         endp

end DriverEntry

;**************************************************************************************************

:make

set drv=HooKapi

d:\masm32\bin\ml /nologo /c /coff %drv%.bat
d:\masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native %drv%.obj

del %drv%.obj

echo.
pause
参考:Undocumented Windows NT
感谢:非常感谢cardmagic大侠的帮助

- 作者: D哥 2006年07月24日, 星期一 09:55  回复(16) |  引用(0) 加入博采

通过进程链枚举进程
摘要:做个练习 查看全文

- 作者: D哥 2006年05月3日, 星期三 21:27  回复(1) |  引用(0) 加入博采

Kmdtut 11---目录与文件

目录与文件

11.1 核心句柄表
11.2 FileWorks驱动程序源代码
11.3 创建目录与文件
11.4 文件对象
11.5 写入文件
11.6 修改文件属性
11.7 读取文件
11.8 向文件追加数据
11.9 截短文件
11.10 删除文件与目录
11.11 列举目录内容

 

源程序: KmdKit\examples\basic\FileWorks

提供对文件的读写功能是操作系统的一项重要任务。我们来看一下NT家族的操作系统都为我们提供了那些功能。

11.1 核心句柄表

在开始讨论本文的主题之前,我们先来讨论一个重要的问题,我们之前并未对其给予应有的注意。为了取得对象的句柄需要填充OBJECT_ATTRIBUTES结构体——我们已经做过很多遍了,其样子如下:

InitializeObjectAttributes addr oa, addr g_usName, OBJ_CASE_INSENSITIVE, NULL, NULL

初始化了OBJECT_ATTRIBUTES后,我们调用函数创建或打开这个对象并获得其句柄(handle)。但这个句柄进入的是得到上下文的那个进程的句柄表。因为对于进程句柄表是其特有的,所以使用这个句柄就只能在进程自己的上下文中。例如,若是试图在其它进程的上下文中打开这个句柄的话,好的情况下会操作失败,而运气不好的话,要是在这个进程句柄表中有取值相同的句柄——要知道句柄只是一个32位的数(准确地讲是一个位的结构体)——就可能闭其它对象。甚至如果获得的句柄是驱动句柄,但是是在用户进程中,句柄就会进入这个进程的句柄表并有可能有意或无意地在用户模式下使用对象。不希望的事情却总是发生,这样的情况常常出现。正是因此,内核组件和驱动程序有其特殊性,它们不喜欢使用句柄,而是使用reference to object,这样比较好,只需简单地使用指向内存中对象结构体的指针。为了统计对对象的引用,在对象的首部保存着一个引用计数(reference count)。如果需要像本例和上例中的那样访问对象,就要设计一个循环,在不同的上下文中对其进行访问,让系统将句柄放入核心句柄表中(kernel handle table)。

从Windows 2000开始,在系统中有了专门的核心句柄表。在这个表中的句柄只能内核模式下的任意进程上下文中访问,与进程特有的句柄不同。甚至于,比如说如果在System进程的上下文、在DriverEntry函数中获得句柄,则就不能在用户进程上下文中使用对象。System进程实现了自己私有的句柄表,其与核心句柄表不同。

对于在核心句柄表中的句柄,需要在调用InitializeObjectAttributes宏时显式地设置OBJ_KERNEL_HANDLE标志,形式如下:

InitializeObjectAttributes addr oa, addr g_usName, OBJ_KERNEL_HANDLE, NULL, NULL

11.2 FileWorks驱动程序源代码

就像上一例中的驱动程序,本例的驱动程序的代码也是由几个独立的函数构成的:CreateDirectory、CreateFile、WriteFile、MarkAsReadOnly、ReadFile、UnmarkAsReadOnly、AppendFile、TruncateFile、DeleteFile、DeleteDirectory和EnumerateFiles。几乎所有的函数都是独立工作的。

;@echo off
;goto make

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                                                                                  
;  FileWorks - Пример различных операций с файлами.                                                
;                                                                                                  
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.386
.model flat, stdcall
option casemap:none

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                               В К Л Ю Ч А Е М Ы Е    Ф А Й Л Ы                                   
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

include \masm32\include\w2k\ntstatus.inc
include \masm32\include\w2k\ntifs.inc
include \masm32\include\w2k\ntoskrnl.inc

includelib \masm32\lib\w2k\ntoskrnl.lib

include \masm32\Macros\Strings.mac

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                             Н Е И З М Е Н Я Е М Ы Е    Д А Н Н Ы Е                               
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.const

CCOUNTED_UNICODE_STRING "\\??\\c:\\FileWorks\\test.txt", g_usFileName, 4
CCOUNTED_UNICODE_STRING "\\??\\c:\\FileWorks", g_usDirName, 4

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                           К О Д                                                  
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.code

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                      CreateDirectory                                             
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

CreateDirectory proc

local oa:OBJECT_ATTRIBUTES
local iosb:IO_STATUS_BLOCK
local hDirectory:HANDLE

    ; 还记得吧, 传递给DbgPrint函数的用于格式化Unicode的代码(%C, %S, %lc, %ls, %wc, %ws, %wZ)只能在
    ; IRQL = PASSIVE_LEVEL下调用!
   
    invoke DbgPrint, $CTA0("\nFileWorks: Creating %ws directory\n"), g_usDirName.Buffer

    InitializeObjectAttributes addr oa, addr g_usDirName, \
                        OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

    invoke ZwCreateFile, addr hDirectory, SYNCHRONIZE, addr oa, addr iosb, 0, FILE_ATTRIBUTE_NORMAL, \
                        0, FILE_OPEN_IF, FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
    .if eax == STATUS_SUCCESS
        .if iosb.Information == FILE_CREATED
            invoke DbgPrint, $CTA0("FileWorks: Directory created\n")
        .elseif iosb.Information == FILE_OPENED
            invoke DbgPrint, $CTA0("FileWorks: Directory exists and was opened\n")
        .endif
  &nbs;     invoke ZwClose, hDirectory
    .else
        invoke DbgPrint, $CTA0("FileWorks: Can't create directory. Status: %08X\n"), eax
    .endif
   
    ret

CreateDirectory endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                        CreateFile                                                
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

CreateFile proc

local oa:OBJECT_ATTRIBUTES
local iosb:IO_STATUS_BLOCK
local hFile:HANDLE

    invoke DbgPrint, $CTA0("\nFileWorks: Creating %ws file\n"), g_usFileName.Buffer

    InitializeObjectAttributes addr oa, addr g_usFileName, \
                        OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

    invoke ZwCreateFile, addr hFile, SYNCHRONIZE, addr oa, addr iosb, 0, FILE_ATTRIBUTE_NORMAL, \
                        0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
    .if eax == STATUS_SUCCESS
        invoke DbgPrint, $CTA0("FileWorks: File created\n")
        invoke ZwClose, hFile
    .else
        invoke DbgPrint, $CTA0("FileWorks: Can't create file. Status: %08X\n"), eax
    .endif
   
    ret

CreateFile endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                            WriteFile                                             
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

WriteFile proc

local oa:OBJECT_ATTRIBUTES
local iosb:IO_STATUS_BLOCK
local hFile:HANDLE

    invoke DbgPrint, $CTA0("\nFileWorks: Opening file for writing\n")

    InitializeObjectAttributes addr oa, addr g_usFileName, \
                        OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL
   
    invoke ZwCreateFile, addr hFile, FILE_WRITE_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                        0, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
    .if eax == STATUS_SUCCESS
        invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

        CTA0 "Data can be written to an open file", g_szData, 4

        invoke ZwWriteFile, hFile, 0, NULL, NULL, addr iosb, \
                        addr g_szData, sizeof g_szData - 1, NULL, NULL
        .if eax == STATUS_SUCCESS
            invoke DbgPrint, $CTA0("FileWorks: File was written\n")
        .else
            invoke DbgPrint, $CTA0("FileWorks: Can't write to the file. Status: %08X\n"), eax
        .endif

        invoke ZwClose, hFile
    .else
        invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
    .endif

    ret

WriteFile endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                        MarkAsReadOnly                                            
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

MarkAsReadOnly proc
 
local oa:OBJECT_ATTRIBUTES
local iosb:IO_STATUS_BLOCK
local hFile:HANDLE
local fbi:FILE_BASIC_INFORMATION

    invoke DbgPrint, $CTA0("\nFileWorks: Opening file for changing attributes\n")

    InitializeObjectAttributes addr oa, addr g_usFileName, \
                        OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL
   
    invoke ZwCreateFile, addr hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES + SYNCHRONIZE, \
                        addr oa, addr iosb, 0, 0, FILE_SHARE_READ, \
                        FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
    .if eax == STATUS_SUCCESS
        invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

        invoke ZwQueryInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation
        .if eax == STATUS_SUCCESS
            invoke DbgPrint, $CTA0("FileWorks: File attributes were: %08X\n"), fbi.FileAttributes
            or fbi.FileAttributes, FILE_ATTRIBUTE_READONLY
            invoke ZwSetInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation
            .if eax == STATUS_SUCCESS
           &nbp;    invoke DbgPrint, $CTA0("FileWorks: Now file marked as read-only\n")
            .else
                invoke DbgPrint, $CTA0("FileWorks: Can't change file attributes. Status: %08X\n"), eax
            .endif
        .else
            invoke DbgPrint, $CTA0("FileWorks: Can't query file attributes. Status: %08X\n"), eax
        .endif

        invoke ZwClose, hFile
    .else
        invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
    .endif

    ret

MarkAsReadOnly endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                          ReadFile                                                
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

ReadFile proc

local oa:OBJECT_ATTRIBUTES
local iosb:IO_STATUS_BLOCK
local hFile:HANDLE
local p:PVOID
local cb:DWORD
local fsi:FILE_STANDARD_INFORMATION

    invoke DbgPrint, $CTA0("\nFileWorks: Opening file for reading\n")

    InitializeObjectAttributes addr oa, addr g_usFileName, \
                        OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

    invoke ZwOpenFile, addr hFile, FILE_READ_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_NONALERT
    .if eax == STATUS_SUCCESS

        invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

        invoke ZwQueryInformationFile, hFile, addr iosb, addr fsi, sizeof fsi, FileStandardInformation
        .if eax == STATUS_SUCCESS

            mov eax, fsi.EndOfFile.LowPart
            inc eax
            mov cb, eax

            invoke ExAllocatePool, PagedPool, cb
            .if eax != NULL
                mov p, eax

                invoke RtlZeroMemory, p, cb

                invoke ZwReadFile, hFile, 0, NULL, NULL, addr iosb, p, cb, 0, NULL
                .if eax == STATUS_SUCCESS
                    invoke DbgPrint, $CTA0("FileWorks: File content: \=%s\=\n"), p
                .else
                    invoke DbgPrint, $CTA0("FileWorks: Can't read from the file. Status: %08X\n"), eax
                .endif

                invoke ExFreePool, p

            .else
                invoke DbgPrint, $CTA0("FileWorks: Can't allocate memory. Status: %08X\n"), eax
            .endif
        .else
            invoke DbgPrint, $CTA0("FileWorks: Can't query file size. Status: %08X\n"), eax
        .endif

        invoke ZwClose, hFile

    .else
        invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
    .endif

    ret

ReadFile endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                        UnmarkAsReadOnly                                          
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

UnmarkAsReadOnly proc

local oa:OBJECT_ATTRIBUTES
local iosb:IO_STATUS_BLOCK
local hFile:HANDLE
local fbi:FILE_BASIC_INFORMATION

    invoke DbgPrint, $CTA0("\nFileWorks: Opening file for changing attributes\n")

    InitializeObjectAttributes addr oa, addr g_usFileName, \
                        OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL
   
    invoke ZwCreateFile, addr hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES + SYNCHRONIZE, \
                        addr oa, addr iosb, 0, 0, FILE_SHARE_READ, FILE_OPEN, \
                        FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
    .if eax == STATUS_SUCCESS
        invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

        invoke ZwQueryInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation
        .if eax == STATUS_SUCCESS
            invoke DbgPrint, $CTA0("FileWorks: File attributes were: %08X\n"), fbi.FileAttributes
            and fbi.FileAttributes, not FILE_ATTRIBUTE_READONLY
     &nbp;      invoke ZwSetInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation
            .if eax == STATUS_SUCCESS
                invoke DbgPrint, $CTA0("FileWorks: Now file can be written or deleted\n")
            .else
                invoke DbgPrint, $CTA0("FileWorks: Can't change file attributes. Status: %08X\n"), eax
            .endif
        .else
            invoke DbgPrint, $CTA0("FileWorks: Can't query file attributes. Status: %08X\n"), eax
        .endif

        invoke ZwClose, hFile
    .else
        invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
    .endif

    ret

UnmarkAsReadOnly endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                         AppendFile                                               
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

AppendFile proc

local oa:OBJECT_ATTRIBUTES
local iosb:IO_STATUS_BLOCK
local hFile:HANDLE

    invoke DbgPrint, $CTA0("\nFileWorks: Opening file to append data\n")

    InitializeObjectAttributes addr oa, addr g_usFileName, \
                        OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

    invoke ZwOpenFile, addr hFile, FILE_APPEND_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                                    FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT
    .if eax == STATUS_SUCCESS
        invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

        CTA0 " using ZwWriteFile", g_szDataToAppend, 4

        invoke ZwWriteFile, hFile, 0, NULL, NULL, addr iosb, \
                        addr g_szDataToAppend, sizeof g_szDataToAppend - 1, NULL, NULL
        .if eax == STATUS_SUCCESS
            invoke DbgPrint, $CTA0("FileWorks: Data appended to the file\n")
        .else
            invoke DbgPrint, $CTA0("FileWorks: Can't append data to file. Status: %08X\n"), eax
        .endif

        invoke ZwClose, hFile
   .else
        invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
    .endif

    ret

AppendFile endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                        TruncateFile                                              
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

TruncateFile proc
 
local oa:OBJECT_ATTRIBUTES
local iosb:IO_STATUS_BLOCK
local hFile:HANDLE
local fsi:FILE_STANDARD_INFORMATION
local feofi:FILE_END_OF_FILE_INFORMATION

    invoke DbgPrint, $CTA0("\nFileWorks: Opening file to truncate\n")

    InitializeObjectAttributes addr oa, addr g_usFileName, \
                        OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

    invoke ZwOpenFile, addr hFile, FILE_WRITE_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                        FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT
    .if eax == STATUS_SUCCESS
        invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

        invoke ZwQueryInformationFile, hFile, addr iosb, \
                        addr fsi, sizeof fsi, FileStandardInformation
        .if eax == STATUS_SUCCESS

            invoke DbgPrint, $CTA0("FileWorks: EOF was: %08X\n"), fsi.EndOfFile.LowPart

            and feofi.EndOfFile.HighPart, 0
            mov eax, fsi.EndOfFile.LowPart
            shr eax, 1
            mov feofi.EndOfFile.LowPart, eax
            invoke ZwSetInformationFile, hFile, addr iosb, \
                        addr feofi, sizeof feofi, FileEndOfFileInformation
            .if eax == STATUS_SUCCESS
                invoke DbgPrint, $CTA0("FileWorks: File truncated to its half size\n")
            .else
                invoke DbgPrint, $CTA0("FileWorks: Can't truncate file. Status: %08X\n"), eax      
            .endif

        .else
            invoke DbgPrint, $CTA0("FleWorks: Can't query file info. Status: %08X\n"), eax
        .endif

        invoke ZwClose, hFile
    .else
        invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
    .endif

    ret

TruncateFile endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                         DeleteFile                                               
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

DeleteFile proc

local oa:OBJECT_ATTRIBUTES
local iosb:IO_STATUS_BLOCK
local hFile:HANDLE
local fdi:FILE_DISPOSITION_INFORMATION

    invoke DbgPrint, $CTA0("\nFileWorks: Opening file for deletion")

    InitializeObjectAttributes addr oa, addr g_usFileName, \
                        OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

    invoke ZwCreateFile, addr hFile, DELETE + SYNCHRONIZE, addr oa, addr iosb, \
                        0, 0, FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
    .if eax == STATUS_SUCCESS
        invoke DbgPrint, $CTA0("FileWorks: File openeded\n")

        mov fdi.DeleteFile, TRUE
        invoke ZwSetInformationFile, hFile, addr iosb, addr fdi, sizeof fdi, FileDispositionInformation
        .if eax == STATUS_SUCCESS
            invoke DbgPrint, $CTA0("FileWorks: File has been marked for deletion\n")
            invoke DbgPrint, $CTA0("FileWorks: It should be deleted when the last open handle is closed\n")
        .else
            invoke DbgPrint, $CTA0("FileWorks: Can't mark file for deletion. Status: %08X\n"), eax
        .endif

        invoke ZwClose, hFile
    .else
        invoke DbgPrint, $CTA0("FileWorks: Can't open file. Status: %08X\n"), eax
    .endif

    ret

DeleteFile endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                       DeleteDirectory                                            
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

DeleteDirectory proc

local oa:OBJECT_ATTRIBUTES
local iosb:IO_STATUS_BLOCK
local hDirectory:HANDLE

    InitializeObjectAttributes addr oa, addr g_usDirName, \
                        OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

    invoke ZwDeleteFile, addr oa
    .if eax == STATUS_SUCCESS
        invoke DbgPrint, $CTA0("\nFileWorks: Directory should be deleted\n")           
    .else
        invoke DbgPrint, $CTA0("\nFileWorks: Can't delete directory. Status: %08X\n"), eax
    .endif

    ret

DeleteDirectory endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                      EnumerateFiles                                              
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

EnumerateFiles proc uses esi

local status:NTSTATUS
local oa:OBJECT_ATTRIBUTES
local hSystemRootDirectory:HANDLE
local hDriversDirectory:HANDLE
local as:ANSI_STRING
local us:UNICODE_STRING
local iosb:IO_STATUS_BLOCK
local tf:TIME_FIELDS
local cb:DWORD
local pfdi:PFILE_DIRECTORY_INFORMATION

    invoke DbgPrint, $CTA0("\nFileWorks: Opening directory to enumerate files\n")
   
    InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("\\SystemRoot"), \
                                OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

    invoke ZwOpenFile, addr hSystemRootDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE, addr oa, \
                        addr iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, \
                        FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT
    .if eax == STATUS_SUCCESS

        InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("system32\\drivers"), \
                            OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, hSystemRootDirectory, NULL

        invoke ZwOpenFile, addr hDriversDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE, addr oa, \
                            addr iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, \
                       &nbs;    FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT
        .if eax == STATUS_SUCCESS

            mov cb, sizeof FILE_DIRECTORY_INFORMATION + 256

            invoke ExAllocatePool, PagedPool, cb
            .if eax != NULL

                mov pfdi, eax
                mov esi, eax
                assume esi:ptr FILE_DIRECTORY_INFORMATION

                invoke DbgPrint, \
                        $CTA0("\nFileWorks: ---------- Starting enumerate files ----------\n")

                invoke ZwQueryDirectoryFile, hDriversDirectory, NULL, NULL, NULL, addr iosb, \
                            esi, cb, FileDirectoryInformation, \
                            TRUE, $CCOUNTED_UNICODE_STRING("c*"), TRUE

                .while eax != STATUS_NO_MORE_FILES

                    .if ( eax == STATUS_SUCCESS )
 
                        mov eax, [esi].FileNameLength
                        mov us._Length, ax
                        mov us.MaximumLength, ax
                        lea eax, [esi].FileName
                        mov us.Buffer, eax
                        invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
                        .if eax == STATUS_SUCCESS

                            invoke RtlTimeToTimeFields, addr [esi].CreationTime, addr tf
                            movzx eax, tf.Day
                            movzx ecx, tf.Month
                            movzx edx, tf.Year

                            invoke DbgPrint, $CTA0("    %s   size=%d   created on %d.%02d.%04d\n"), \
                                        as.Buffer, [esi].EndOfFile.LowPart, eax, ecx, edx

                            invoke RtlFreeAnsiString, addr as
                        .endif

                    .endif

                    invoke ZwQueryDirectoryFile, hDriversDirectory, NULL, NULL, NULL, addr iosb, \
                                esi, cb, FileDirectoryInformation, \
                                TRUE, NULL, FALSE
                .endw
                invoke DbgPrint, \
                    $CTA0("FileWorks: ------------------------------------------------\n")

                assume esi:nothing
                invoke ExFreePool, pfdi
            .endif
            invoke ZwClose, hDriversDirectory
        .else
            invoke DbgPrint, $CTA0("FileWorks: Can't open drivers directory. Status: %08X\n"), eax
        .endif
        invoke ZwClose, hSystemRootDirectory
    .else
        invoke DbgPrint, $CTA0("FileWorks: Can't open system root directory. Status: %08X\n"), eax
    .endif

    ret

EnumerateFiles endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                       DriverEntry                                                
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING

    invoke DbgPrint, $CTA0("\nFileWorks: Entering DriverEntry\n")

    invoke CeateDirectory
    invoke CreateFile
    invoke WriteFile
    invoke MarkAsReadOnly
    invoke ReadFile
    invoke UnmarkAsReadOnly
    invoke AppendFile
    invoke ReadFile
    invoke TruncateFile
    invoke ReadFile
    invoke DeleteFile
    invoke DeleteDirectory
    invoke EnumerateFiles

    invoke DbgPrint, $CTA0("\nFileWorks: Leaving DriverEntry\n")

    mov eax, STATUS_DEVICE_CONFIGURATION_ERROR
    ret

DriverEntry endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                                                                                  
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

end DriverEntry

:make

set drv=FileWorks

\masm32\bin\ml /nologo /c /coff %drv%.bat
\masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native %drv%.obj

del %drv%.obj

echo.
pause

在ntddk.inc中没有几个我们需要的常量和结构体。

include \masm32\include\w2k\ntifs.inc

在用于处理文件系统驱动的Installable File System (IFS) Kit中,有一个ntifs.h头文件,而我们这里则要包含ntifs.inc文件。在1.5版的KmdKit中我加入了这个文件。

11.3 创建目录与文件

    InitializeObjectAttributes addr oa, addr g_usDirName, \
                        OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

填充OBJECT_ATTRIBUTES结构体,不要忘了OBJ_KERNEL_HANDLE标志。我想要强调的是,在本例中这个操作并不是必需的,因为我们不会在其它进程中使用这些句柄。但是因为FileWorks驱动程序的函数可以很容易的移植到使用任意进程上下文的程序中,所以我决定设置这个标志。如果在您的程序中有驱动和用户进程的共用的句柄,则不应设置此标志。如果只在自己一个进程上下文中使用对象,也不必设置OBJ_KERNEL_HANDLE标志。

创建目录和文件用的都是ZwCreateFile函数。从系统角度看,目录也是文件,所以创建目录的函数与创建文件的函数没有本质上的差别。所以,创建目录与创建文件使用同一个函数,只是创建目录要用FILE_DIRECTORY_FILE标志。

    invoke ZwCreateFile, addr hDirectory, SYNCHRONIZE, addr oa, addr iosb, 0, FILE_ATTRIBUTE_NORMAL, \
                        0, FILE_OPEN_IF, FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0

ZwCreateFile函数有相当多的参数,所以我在下面给出了它的原型,而且不得不使用了C语言的语义,以显示出输入(IN)、输出(OUT)和可选的参数。

NTSTATUS 
  ZwCreateFile(
    OUT PHANDLE             FileHandle,
    IN  ACCESS_MASK         DesiredAccess,
    IN  POBJECT_ATTRIBUTES  ObjectAttributes,
    OUT PIO_STATUS_BLOCK    IoStatusBlock,
    IN  PLARGE_INTEGER      AllocationSize  OPTIONAL,
    IN  ULONG               FileAttributes,
    IN  ULONG               ShareAccess,
    IN  ULONG               CreateDisposition,
    IN  ULONG               CreateOptions,
    IN  PVOID               EaBuffer        OPTIONAL,
    IN  ULONG               EaLength
    );

函数成功完成后,参数FileHandle取得所建立的目录的句柄。DesiredAccess定义了对目录的三种访问请求。我们用标志SYNCHRONIZE来定义,这个值的含义在后面会清楚。ObjectAttributes想必您已经知道了。函数成功完成后,参数IoStatusBlock就是指向IO_STATUS_BLOCK结构体的指针,从中可以取出相关的的信息。参数FileAttributes定义了所创建目录的属性(只读、隐藏等)。我们使用FILE_ATTRIBUTE_NORMAL,本例中不需要为目录指定什么特别的属性。可选参数AllocationSize为0定义了所创建文件的大小为0。对于目录来说这是很自然的。参数ShareAccess定义为什么样的值,就要以什么样的权限访问目录。在本例中,我们不允许其它程序访问目录,故将此参数设为0。参数CreateDisposition定义了该目录已经存在,或者相反,文件不存在时系统的行为。我们使用标志FILE_OPEN_IF,这个标志表示如果目录已经存在,就将其打开。我们向参数CreateOptions传递的是标志FILE_DIRECTORY_FILE和FILE_SYNCHRONOUS_IO_NONALERT的组合。第一个表示要建立的是目录而非文件,无需特别解释。FILE_SYNCHRONOUS_IO_NONALERT定义了()对文件所有的操作都要是同步的,例如,调用ZwReadFile后在没有实际读取完文件数据时函数不会返回。在I/O管理器中为文件维护了一个当前文件位置上下文(file position context)。如果设置了标志FILE_SYNCHRONOUS_IO_NONALERT,则在参数DesiredAccess里应该定义为标志SYNCHRONIZE。最后两个参数不用于驱动程序。

    .if eax == STATUS_SUCCESS
        .if iosb.Information == FILE_CREATED
        .elseif iosb.Information == FILE_OPENED
        .endif

正如我所讲的,在IO_STATUS_BLOCK结构体中会有额外的信息。

还记得我们是如何处理I/O请求的(见前面的章节)。例如,在驱动程序SharingMemory(第9章)中对IRP_MJ_CREATE和IRP_MJ_CLOSE的处理如下:

mov eax, pIrp
mov (_IRP PTR [eax]).IoStatus.Status, STATUS_SUCCESS
and (_IRP PTR [eax]).IoStatus.Information, 0
fastcall IofCompleteRequest, pIrp, IO_NO_INCREMENT

本例的驱动程序也大致相同,即将完成创建文件的请求。只有放入IO_STATUS_BLOCK结构体的域中的值会依赖于请求的类型。

因为我们定义了FILE_OPEN_IF标志,通过iosb.Information的值,我们可以知道是该建立新的目录还是因该目录已经存在而将其打开。

        invoke ZwClose, hDirectory
    .endif

我再重复一遍,在内核模式下一定要显式地关闭所有打开的句柄。

    invoke ZwCreateFile, addr hFile, SYNCHRONIZE, addr oa, addr iosb, 0, FILE_ATTRIBUTE_NORMAL, \
                        0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0

如您所见,文件的创建实际上是相同的,只是要去掉FILE_DIRECTORY_FILE标志。而FILE_CREATE标志我将其用于多种情况。它表示只可以创建文件。如果文件已经存在,则对ZwCreateFile会以失败结束。

11.4 文件对象

每一个打开的文件句柄都对应着一个文件对象(file object),在内核内存区中有FILE_OBJECT结构体。

FILE_OBJECT STRUCT                              ; sizeof = 070h
    _Type                   SWORD      ?       ; 0000h  IO_TYPE_FILE
    _Size                   SWORD       ?       ; 0002h
    DeviceObject            PVOID       ?       ; 0004h  PTR DEVICE_OBJECT
    Vpb                     PVOID       ?       ; 0008h  PTR VPB
    FsContext               PVOID       ?       ; 000Ch
    FsContext2              PVOID       ?       ; 0010h
    SectionObjectPointer    PVOID       ?       ; 0014h  PTR SECTION_OBJECT_POINTERS
    PrivateCacheMap         PVOID       ?       ; 0018h
    FinalStatus             SDWORD      ?       ; 001Ch
    RelatedFileObject       PVOID       ?       ; 0020h  PTR FILE_OBJECT
    LockOperation           BYTE        ?       ; 0024h  BOOLEAN
    DeletePending           BYTE        ?       ; 0025h  BOOLEAN
    ReadAccess              BYTE        ?       ; 0026h  BOOLEAN
    WriteAccess             BYTE        ?       ; 0027h  BOOLEAN
    DeleteAccess            BYTE        ?       ; 0028h  BOOLEAN
    SharedRead              BYTE        ?       ; 0029h  BOOLEAN
    SharedWrite             BYTE        ?       ; 002Ah  BOOLEAN
    SharedDelete            BYTE        ?       ; 002Bh  BOOLEAN
    Flags                   DWORD       ?       ; 002Ch
    FileName                UNICODE_STRING  <>  ; 0030h
    CurrentByteOffset       LARGE_INTEGER   <>  ; 0038h
    Waiters                 DWORD       ?       ; 0040h
    Busy                    DWORD       ?       ; 0044h
    LastLock                PVOID       ?       ; 0048h
    _Lock                   KEVENT      <>      ; 004Ch
    Event                   KEVENT      <>      ; 005Ch
    CompletionContext       PVOID       ?       ; 006Ch  PTR IO_COMPLETION_CONTEXT
FILE_OBJECT ENDS
PFILE_OBJECT typedef ptr FILE_OBJECT

例如,我们可以两次打开同一个文件,但是两次的访问请求却不相同:第一次读(FILE_READ_DATA)而第二次写(FILE_WRITE_DATA)。结果内核会建立两个FILE_OBJECT结构体,每一个都对应于自己相应的文件句柄。但是两个句柄和相对应的两个FILE_OBJECT结构体都对应着同一个磁盘文件。第一个FILE_OBJECT结构体将设置ReadAccess域,第二个则设置WriteAccess域。例如,试图写入第一个句柄指向的文件时,系统会发现域WriteAccess = FALSE并结束请求。

DeviceObject域将包含指向设备对象\Device\HarddiskVolume1的指针,因为这个设备是对\??\c:符号链接,我们创建文件时在文件名中使用了\??\c:。

在读完本文后您可能会进行文件操作的实验并会发现系统是如何填充并管理FILE_OBJECT结构体的。如果在内存中定位它时发生问题,可以使用ObReferenceObjectByHandle函数,形式大致如下:

local pFileObject:PFILE_OBJECT
. . .
invoke ObReferenceObjectByHandle, hFile, FILE_READ_DATA, NULL, KernelMode, addr pFileObject, NULL
.if eax == STATUS_SUCCESS
    ; pFileObject指向对应于hFile的FILE_OBJECT
    fastcall ObfDereferenceObject, pFileObject
.endif

ObReferenceObjectByHandle向参数pFileObject中返回指向对应于该文件句柄的FILE_OBJECT结构体的指针。这时,引用计数会增一。之后一定要调用ObfDereferenceObject来使其复原。

11.5 写入文件

到这里我们已经有了大小为0目录和文件。该向文件中写点东西了。

    invoke ZwCreateFile, addr hFile, FILE_WRITE_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                        0, 0, FILE_SHARE_READ, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0

借助于ZwCreateFile不止可以创建文件,还可以打开已有的文件(恰好,不只是文件,其它某些对象也是这样)。为此需要指定标志FILE_OPEN。

为了写入文件,需要相应的访问权——使用FILE_WRITE_DATA。

在头文件中定义了几个常量以用于对文件的一般访问。例如,FILE_GENERIC_WRITE不仅允许向文件中写入数据,还可以更改其属性并添加数据。

FILE_GENERIC_WRITE equ (STANDARD_RIGHTS_WRITE or FILE_WRITE_DATA or FILE_WRITE_ATTRIBUTES or FILE_WRITE_EA or FILE_APPEND_DATA or SYNCHRONIZE)

但对于给定的情况我总是只使用最低限度必需的标志,因为不管是否需要总是使用例如FILE_ALL_ACCESS之类的标志并不是一种好的编程习惯。

显然,当我们写文件,其它的程序不应向该文件写入。使用FILE_SHARE_READ标志,则没有程序能写入或是删除文件,而只能读取文件。

    .if eax == STATUS_SUCCESS

        CTA0 "Data can be written to an open file", g_szData, 4

        invoke ZwWriteFile, hFile, 0, NULL, NULL, addr iosb, \
        &nbs;               addr g_szData, sizeof g_szData - 1, NULL, NULL

函数ZwWriteFile见名则知意。

NTSTATUS
  ZwWriteFile(
    IN  HANDLE            FileHandle,
    IN  HANDLE            Event       OPTIONAL,
    IN  PIO_APC_ROUTINE   ApcRoutine  OPTIONAL,
    IN  PVOID             ApcContext  OPTIONAL,
    OUT PIO_STATUS_BLOCK  IoStatusBlock,
    IN  PVOID             Buffer,
    IN  ULONG             Length,
    IN  PLARGE_INTEGER    ByteOffset  OPTIONAL,
    IN  PULONG            Key  OPTIONAL
    );

ZwWriteFile的参数至少要有文件句柄、IO_STATUS_BLOCK结构体指针——在该结构体中将放置额外的信息(其中包括向文件写入的字节数)、指向需要写入文件的数据的指针和其大小。

11.6 修改文件属性

我们需要防止我们的文件被删除。

    invoke ZwCreateFile, addr hFile, FILE_READ_ATTRIBUTES + FILE_WRITE_ATTRIBUTES + SYNCHRONIZE, \
                        addr oa, addr iosb, 0, 0, FILE_SHARE_READ, \
                        FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0

标志FILE_READ_ATTRIBUTES和FILE_WRITE_ATTRIBUTES用于取得和更改相应的文件属性。

    .if eax == STATUS_SUCCESS
        invoke ZwQueryInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation
        .if eax == STATUS_SUCCESS

我们需要建立只读属性,但由于文件有其它的属性需要保留,故有:

            or fbi.FileAttributes, FILE_ATTRIBUTE_READONLY
            invoke ZwSetInformationFile, hFile, addr iosb, addr fbi, sizeof fbi, FileBasicInformation

要向文件属性中添加属性,我们必须要用标志。当需要根改文件时,我们用UnmarkAsReadOnly函数去掉这个属性,有:

            and fbi.FileAttributes, not FILE_ATTRIBUTE_READONLY


11.7 读取文件

现在,为了各种操作,我们用ZwOpenFile函数打开文件来从中读取。所需要的参数与ZwCreateFile的类似。

NTSTATUS
  ZwOpenFile(
    OUT PHANDLE             FileHandle,
    IN  ACCESS_MASK         DesiredAccess,
    IN  POBJECT_ATTRIBUTES  ObjectAttributes,
    OUT PIO_STATUS_BLOCK    IoStatusBlock,
    IN  ULONG               ShareAccess,
    IN  ULONG               OpenOptions
    );

    invoke ZwOpenFile, addr hFile, FILE_READ_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_NONALERT

FILE_READ_DATA——在这里我们只从文件中读取数据。标志FILE_SHARE_READ、FILE_SHARE_WRITE和FILE_SHARE_DELETE的组合允许对文件进行完全的访问。

    .if eax == STATUS_SUCCESS
        invoke ZwQueryInformationFile, hFile, addr iosb, addr fsi, sizeof fsi, FileStandardInformation
        .if eax == STATUS_SUCCESS

我们以准备好读取文件的所有数据,但是用于这个请求的缓冲区的大小依赖于文件的大小。文件的大小可以用信息类FileStandardInformation由函数ZwQueryInformationFile获得,并将其传递给FILE_BASIC_INFORMATION指针。

            mov eax, fsi.EndOfFile.LowPart
            inc eax
            mov cb, eax

我们来为最后的零添加一个字节。

            invoke ExAllocatePool, PagedPool, cb
            .if eax != NULL
                mov p, eax

                invoke RtlZeroMemory, p, cb

                invoke ZwReadFile, hFile, 0, NULL, NULL, addr iosb, p, cb, 0, NULL
                .if eax == STATUS_SUCCESS
                    invoke DbgPrint, $CTA0("FileWorks: File content: \=%s\=\n"), p
                .endif

                invoke ExFreePool, p
            .endif

分配所需的缓冲区,将其清零并传递给ZwReadFile函数。这个函数的原型与ZwWriteFile函数的原型类似,只是缓冲区用处不同。因为在使用之前我们清零了缓冲区,并且其大小比文件的内容多一个字节,所以不会发生将其内容输出到调试信息的问题。

11.8 向文件追加数据

向文件中追加数据有几种方法。可以用标志FILE_WRITE_DATA将其打开,在当前位置建立指向文件末尾的指针,在函数ZwWriteFile的参数ByteOffset中传递偏移量并写入数据。指示文件当前位置的指针保存在FILE_OBJECT.CurrentByteOffset中。我们还可以用标志FILE_APPEND_DATA打开文件,文件当前位置指针会自动指向其末尾。

    invoke ZwOpenFile, addr hFile, FILE_APPEND_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                                    FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT
    .if eax == STATUS_SUCCESS

        CTA0 " using ZwWriteFile", g_szDataToAppend, 4

        invoke ZwWriteFile, hFile, 0, NULL, NULL, addr iosb, \
                        addr g_szDataToAppend, sizeof g_szDataToAppend - 1, NULL, NULL

文件的大小会自动增加。

11.9 截短文件

假设我们需要截短文件,去掉不需要的数据。在本例中,我为了简单起见,将文件缩减为原来的一半。

    invoke ZwOpenFile, addr hFile, FILE_WRITE_DATA + SYNCHRONIZE, addr oa, addr iosb, \
                        FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT

我们用写访问来打开文件。

    .if eax == STATUS_SUCCESS
        invoke ZwQueryInformationFile,hFile, addr iosb, \
                        addr fsi, sizeof fsi, FileStandardInformation
        .if eax == STATUS_SUCCESS

我们在FILE_STANDARD_INFORMATION结构体的成员中可以获得文件当前的大小。

            and feofi.EndOfFile.HighPart, 0
            mov eax, fsi.EndOfFile.LowPart
            shr eax, 1
            mov feofi.EndOfFile.LowPart, eax
            invoke ZwSetInformationFile, hFile, addr iosb, \
                        addr feofi, sizeof feofi, FileEndOfFileInformation

使用信息类FileEndOfFileInformation,我们设置新的大小等于当前大小的一半。

11.10 删除文件与目录

小事一桩——将c:恢复为初始的状态。令人奇怪的是,2000 DDK里没有提到存在ZwDeleteFile函数。在XP DDK中说系统终归提供了这个函数,但是是从Windows XP开始的。但事实并非如此。Windows 2000甚至Windows NT4中就有ZwDeleteFile。但对于文件的删除,我们用几中更为复杂的方法,而对目录的删除则借助于ZwDeleteFile。

    invoke ZwCreateFile, addr hFile, DELETE + SYNCHRONIZE, addr oa, addr iosb, \
                        0, 0, FILE_SHARE_DELETE, FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0

我们打开要文件以进行删除。

    .if eax == STATUS_SUCCESS

        mov fdi.DeleteFile, TRUE
        invoke ZwSetInformationFile, hFile, addr iosb, addr fdi, sizeof fdi, FileDispositionInformation

使用信息类FileDispositionInformation并设置文件删除标志。这时在FILE_OBJECT结构体的DeletePending域中的值由FALSE变为TRUE。这表示文件被标记为删除。这样只要存在该文件的一个打开的句柄,文件就不会被删除。

        invoke ZwClose, hFile

现在文件唯一的句柄被关闭,文件被删除。

    invoke ZwDeleteFile, addr oa

使用ZwDeleteFile删除目录很简单,我就不多说了。

11.11 列举目录内容

一般说来,有两种方法可以用来定义创建/打开的对象的名字,而文件却特别。访问命名对象可以使用完整的路径,也可以使用符号链接。这里我们只使用绝对路径。例如,路径\??\c:\FileWorks\test.txt就是绝对路径。在本例中,使用InitializeObjectAttributes宏填充InitializeObjectAttributes结构体,其形式如下:

InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("\??\c:\FileWorks\test.txt"), OBJ_CASE_INSENSITIVE, NULL, NULL

倒数第二个参数RootDirectory为NULL。宏InitializeObjectAttributes的参数RootDirectory和该宏填充的OBJECT_ATTRIBUTES的同名域定义了目录容器对象的句柄。

OBJECT_ATTRIBUTES STRUCT
. . .
    RootDirectory               HANDLE          ?
. . .
OBJECT_ATTRIBUTES ENDS

如果目录容器对象已经打开,则已经取得了句柄,就可以用相对于目录容器的路径来使用对象。这时目录容器的句柄应该放置在RootDirectory中。例如,如果我们已经打开了目录\??\c:\FileWorks\并将其句柄放入了变量hDirectory中,则对test.txt的文件路径我们可以如下使用:

InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("test.txt"), OBJ_CASE_INSENSITIVE, hDirectory, NULL

在目录容器下就意味着目录不只是在磁盘上,还在对象管理器的名字空间中。

对于列出系统目录\%SystemRoot%\System32\Drivers\的内容,我们来使用相对路径。

    InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("\\SystemRoot"), \
                                OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, NULL, NULL

我们来填充OBJECT_ATTRIBUTES结构体,使用符号链接\SystemRoot——这个是绝对路径。

    invoke ZwOpenFile, addr hSystemRootDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE, addr oa, \
                        addr iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, \
                        FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT

我们打开目录\%SystemRoot%\。FILE_LIST_DIRECTORY标志用于列举目录内容。

    .if eax == STATUS_SUCCESS

        InitializeObjectAttributes addr oa, $CCOUNTED_UNICODE_STRING("system32\\drivers"), \
                            OBJ_CASE_INSENSITIVE + OBJ_KERNEL_HANDLE, hSystemRootDirectory, NULL

如果目录打开成功,从变量hSystemRootDirectory中我们可以取得其句柄,这个句柄将用作目录容器的句柄。记着OBJECT_ATTRIBUTES结构体使用相对路径“system32\drivers”。

        invoke ZwOpenFile, addr hDriversDirectory, FILE_LIST_DIRECTORY + SYNCHRONIZE, addr oa, \
                            addr iosb, FILE_SHARE_READ + FILE_SHARE_WRITE + FILE_SHARE_DELETE, \
                            FILE_DIRECTORY_FILE + FILE_SYNCHRONOUS_IO_NONALERT
        .if eax == STATUS_SUCCESS

我们来用相对路径打开目录\%SystemRoot%\System32\Drivers\。

            mov cb, sizeof FILE_DIRECTORY_INFORMATION + 256

            invoke ExAllocatePool, PagedPool, cb
            .if eax != NULL

分配必要的缓冲区,在缓冲区中应放得下FILE_DIRECTORY_INFORMATION结构体和文件名。

                mov pfdi, eax
                mov esi, eax
                assume esi:ptr FILE_DIRECTORY_INFORMATION

                invoke ZwQueryDirectoryFile, hDriversDirectory, NULL, NULL, NULL, addr iosb, \
                            esi, cb, FileDirectoryInformation, \
                            TRUE, $CCOUNTED_UNICODE_STRING("c*"), TRUE

我们开始列举目录文件,这里用的是信息类FileDirectoryInformation。函数ZwQueryDirectoryFile的原型大概是多余的。

NTSTATUS
  ZwQueryDirectoryFile(
    IN  HANDLE                  FileHandle,
    IN  HANDLE                  Event       OPTIONAL,
    IN  PIO_APC_ROUTINE         ApcRoutine  OPTIONAL,
    IN  PVOID                   ApcContext  OPTIONAL,
    OUT PIO_STATUS_BLOCK        IoStatusBlock,
    OUT PVOID                   FileInformation,
    IN  ULONG                   Length,
    IN  FILE_INFORMATION_CLASS  FileInformationClass,
    IN  BOOLEAN                 ReturnSingleEntry,
    IN  PUNICODE_STRING         FileName    OPTIONAL,
    IN  BOOLEAN                 RestartScan
    );

但这个函数在2000 DDK中也没有被提到。XP DDK说这个函数只从Windows XP开始提供。这同样也不是真的。

我们令参数ReturnSingleEntry为TRUE,这就使ZwQueryDirectoryFile函数只返回一个文件的信息,而且是第一个文件的。参数FileName指向带有搜索条件“c*”的字符串,即只列出文件名以“c”开头的文件。这样能缩短输出的调试信息。在第一次调用ZwQueryDirectoryFile时,我们将参数RestartScan设为TRUE。这就使得ZwQueryDirectoryFile函数开始检查目录的内容。

                .while eax != STATUS_NO_MORE_FILES

循环调用ZwQueryDirectoryFile直到ZwQueryDirectoryFile返回STATUS_NO_MORE_FILES,即目录中所有文件都已经列举。

                    .if ( eax == STATUS_SUCCESS )

如果突然发现有文件的文件名超过256字节(实际上是不会的,因为驱动的文件名不会超过8个字符),ZwQueryDirectoryFile返回非STATUS_NO_MORE_FILES的错误代号。这时,我们只简单的过滤掉这个文件。

                        mov eax, [esi].FileNameLength
                        mov us._Length, ax
                        mov us.MaximumLength, ax
                        lea eax, [esi].FileName
                        mov us.Buffer, eax
                        invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
                        .if eax == STATUS_SUCCESS

                            invoke RtlTimeToTimeFields, addr [esi].CreationTime, addr tf
                            movzx eax, tf.Day
                            movzx ecx, tf.Month
                            movzx edx, tf.Year

                            invoke DbgPrint, $CTA0("    %s   size=%d   created on %d.%02d.%04d\n"), \
                                        as.Buffer, [esi].EndOfFile.LowPart, eax, ecx, edx

                            invoke RtlFreeAnsiString, addr as
                        .endif

格式化获得的信息,输出文件名、大小以及创建时间。

                    .endif

                    invoke ZwQueryDirectoryFile, hDriversDirectory, NULL, NULL, NULL, addr iosb, \
                                esi, cb, FileDirectoryInformation, \
                                TRUE, NULL, FALSE
                .endw

在调用ZwQueryDirectoryFile的循环中,参数ReturnSingleEntry、FileName和RestartScan分别为为TRUE、NULL和FALSE。这就使得ZwQueryDirectoryFile能继续列举文件。

                invoke ExFreePool, pfdi
            .endif
            invoke ZwClose, hDriversDirectory
        .endif
        invoke ZwClose, hSystemRootDirectory
    .endif

回收所有用过的资源。

--------------------------------------------------------------------------------

Copyright © 2002-2004 Four-F, four-f@mail.ru

董岩 译
http://greatdong.blog.edu.cn

- 作者: D哥 2006年02月6日, 星期一 16:42  回复(6) |  引用(0) 加入博采

Kmdtut 10---注册表

注册表

董岩 译

10.1 注册表的结构
10.2 在驱动程序中访问注册表
10.3 RegistryWorks驱动程序源代码
   10.3.1 注册表键的创建与打开
   10.3.2 创建注册表键值
   10.3.3 访问注册表键值
   10.3.4 删除注册表键
   10.3.5 更新注册表键
 
源代码: KmdKit\examples\basic\RegistryWorks


10.1 注册表的结构

注册表(Registry)是基本数据的中心,在系统的设置和管理方面扮演着重要的角色。注册表的结构类似于磁盘的逻辑结构,但是注册表的内容不是磁盘数据的静态组合,而是随系统的工作进程而动态改变。注册表由keys构成,键就像磁盘的目录。最上层的keys叫做root keys。keys本身就是一个容器,装着其它的keys,装在里面的这些keys叫做subkeys或是values,就像磁盘上的文件。values保存着实际数据。对注册表的操作与管理是由Configuration Manager负责的。

root keys有六个:

HKEY_USER
 包含所有注册信息;
 
HKEY_CURRENT_USER
 保存着当前用户的注册信息;
 
HKEY_LOCAL_MACHINE
 保存着系统配置信息:硬件设备支持记录,安全策略,用户口令,应用程序设置和服务及驱动程序配置。
 
HKEY_CURRENT_CONFIG
 包含当前硬件配置信息;
 
HKEY_CLASSES_ROOT
 保存着文件关联和COM类的注册数据;

HKEY_PERFORMANCE_DATA
 包含着性能信息。
 
HKEY_PERFORMANCE_DATA 是一个特殊的键,未必会直接用到。在用户模式下,关于性能统计的信息是通过Performance Data Helper库来访问的,这个库实现在模块pdh.dll里。标准程序Performance Monitor正是利用了这个库。除性能统计之外这个键还包含着许多补充信息。例如,用于枚举进程、线程和模块等等的Process Status函数(在psapi.dll里实现)正是从HKEY_PERFORMANCE_DATA键中获得的信息。注册表编辑器Regedit和Regedt32并不能显示出这个键的内容,因此无法编辑。

根键HKEY_CURRENT_USER、HKEY_CURRENT_CONFIG和HKEY_CLASSES_ROOT并不包含什么信息。它们只是链接到注册表其它的子键。

HKEY_CURRENT_USER
 链接到子键HKEY_USER\  系统用户中对应于当前用户的注册信息。
 
HKEY_CURRENT_CONFIG
 链接到子键HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current.
 
HKEY_CLASSES_ROOT
 链接到子键HKEY_LOCAL_MACHINE\SOFTWARE\Classes和HKEY_CURRENT_USER\SOFTWARE\Classes.
 

10.2 在驱动程序中访问注册表

如何在内核模式下访问注册表?对注册表的访问和访问其它命名对象完全相同,即通过对象管理器的名字空间(详细方法见第三章)来访问。为了将注册表名字空间与对象管理器名字空间集成起来,配置管理器(Configuration Manager)在建立了一个名为“Registry”注册键类型的对象(key object)并将其放在对象管理器名字空间的根目录下。对于内核模式组件,有进入注册表的入口点。

所有的内核函数,对命名对象的访问要获得其名称,这个名称位于结构体OBJECT_ATRIBUTES的一个成员中,这个我们以前就知道了。如果对象类型为注册表键,则对象名字应该起始于“\Registry”。我要说的是,我不知道打开HKEY_PERFORMANCE_DATA根键要用什么名字,以及更一般的说,对某个键要用哪个名字。我在这方面作出过努力,但都是白费。如果您了解这方面的东西,还请您教我一下。有两个根键还算简单。

键                     名称
HKEY_USER              "\Registry\User"
HKEY_LOCAL_MACHINE     "\Registry\Machine"
 
对于三个链接到其它地方的键还需要额外的操作。比如说,操作HKEY_CURRENT_CONFIG键需要知道它链接到了子键HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Hardware Profiles\Current上并用这个根键名字代替。这样就得到了\Registry\Machine\SYSTEM\CurrentControlSet\Hardware Profiles\Current这个名字。很可惜,我们不能使用CTW0和$CTW0宏来定义这个长长的unicode字符串,这个字符串超出了47个符号的限制。对此您可以用自己的方案来解决。


10.3 RegistryWorks驱动程序源代码

现在,当我们熟知根键名字的时候,理解它们就不会太困难。

;@echo off
;goto make

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                                                                                  
;  RegistryWorks - Пример работы с реестром                                                        
;                                                                                                  
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.386
.model flat, stdcall
option casemap:none

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                              В К Л Ю Ч А Е М Ы Е    Ф А Й Л Ы                                    
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

include \masm32\include\w2k\ntstatus.inc
include \masm32\include\w2k\ntddk.inc
include \masm32\include\w2k\ntoskrnl.inc

includelib \masm32\lib\w2k\ntoskrnl.lib

include \masm32\Macros\Strings.mac

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                             Н Е И З М Е Н Я Е М Ы Е    Д А Н Н Ы Е                               
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.const

CCOUNTED_UNICODE_STRING "\\Registry\\Machine\\Software\\CoolApp", g_usMachineKeyName, 4
CCOUNTED_UNICODE_STRING "SomeData", g_usValueName, 4

CTW0 "It's just a string", g_wszStringData, 4

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;      &bsp;                                    К О Д                                                  
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.code

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                         CreateKey                                                
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

CreateKey proc

local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
local dwDisposition:DWORD

    invoke DbgPrint, $CTA0("\nRegistryWorks: *** Creating registry key\n")

    lea ecx, oa
    InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
    invoke ZwCreateKey, addr hKey, KEY_WRITE, addr oa, 0, NULL, \
                                      REG_OPTION_VOLATILE, addr dwDisposition
    .if eax == STATUS_SUCCESS

        .if dwDisposition == REG_CREATED_NEW_KEY
            invoke DbgPrint, \
                $CTA0("RegistryWorks: Registry key \\Registry\\Machine\\Software\\CoolApp created\n")
        .elseif dwDisposition == REG_OPENED_EXISTING_KEY
            invoke DbgPrint, \
                $CTA0("RegistryWorks: Registry key \\Registry\\Machine\\Software\\CoolApp opened\n")
        .endif

        invoke ZwClose, hKey
        invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed\n")
    .else
        invoke DbgPrint, $CTA0("RegistryWorks: Can't create registry key. Status: %08X\n"), eax
    .endif

    ret

CreateKey endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                       SetValueKey                                                
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

SetValueKey proc

local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE

    invoke DbgPrint, $CTA0("\nRegistryWorks: *** Opening registry key to set new value\n")
    lea ecx, oa
    InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
    invoke ZwOpenKey, addr hKey, KEY_SET_VALUE, ecx

    .if eax == STATUS_SUCCESS
        invoke DbgPrint, $CTA0("RegistryWorks: Registry key openeded\n")

        invoke ZwSetValueKey, hKey, addr g_usValueName, 0, REG_SZ, \
                                addr g_wszStringData, sizeof g_wszStringData
        .if eax == STATUS_SUCCESS
            invoke DbgPrint, $CTA0("RegistryWorks: Registry key value added\n")
        .else
            invoke DbgPrint, \
                    $CTA0("RegistryWorks: Can't set registry key value. Status: %08X\n"), eax
        .endif

        invoke ZwClose, hKey
        invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed\n")
    .else
        invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X\n"), eax
    .endif

    ret

SetValueKey endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                      QueryValueKey                                               
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

QueryValueKey proc

local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
local cb:DWORD
local ppi:PKEY_VALUE_PARTIAL_INFORMATION
local as:ANSI_STRING
local us:UNICODE_STRING

    invoke DbgPrint, $CTA0("\nRegistryWorks: *** Opening registry key to read value\n")
    lea ecx, oa
    InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
    invoke ZwOpenKey, addr hKey, KEY_QUERY_VALUE, ecx

    .if eax == STATUS_SUCCESS
        invoke DbgPrint, $CTA0("RegistryWorks: Registry key openeded\n")

        invoke ZwQueryValueKey, hKey, addr g_usValueName, \
                                KeyValuePartialInformation, NULL, 0, addr cb
        .if cb != 0
nbsp;           invoke ExAllocatePool, PagedPool, cb
            .if eax != NULL
                mov ppi, eax

                invoke ZwQueryValueKey, hKey, addr g_usValueName, \
                                    KeyValuePartialInformation, ppi, cb, addr cb
                .if ( eax == STATUS_SUCCESS ) && ( cb != 0 )

                    mov eax, ppi
                    .if [KEY_VALUE_PARTIAL_INFORMATION PTR [eax]]._Type == REG_SZ
                        lea eax, (KEY_VALUE_PARTIAL_INFORMATION PTR [eax]).Data
                        invoke RtlInitUnicodeString, addr us, eax
                        invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
                        .if eax == STATUS_SUCCESS
                            invoke DbgPrint, \
                                $CTA0("RegistryWorks: Registry key value is: \=%s\=\n"), as.Buffer
                            invoke RtlFreeAnsiString, addr as
                        .endif
                    .endif
                .else
                    invoke DbgPrint, \
                            $CTA0("RegistryWorks: Can't query registry key value. Status: %08X\n"), eax
                .endif
                invoke ExFreePool, ppi
            .else
                invoke DbgPrint, $CTA0("RegistryWorks: Can't allocate memory. Status: %08X\n"), eax
            .endif
        .else
            invoke DbgPrint, \
            $CTA0("RegistryWorks: Can't get bytes count needed for key partial information. Status: %08X\n"), eax
        .endif
        invoke ZwClose, hKey
        invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed\n")
    .else
        invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X\n"), eax
    .endif

    ret

QueryValueKey endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                        DeleteKey                                                 
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

DeleteKey proc

local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE

    invoke DbgPrint, $CTA0("\nRegistryWorks: *** Deleting registry key\n")

    lea ecx, oa
    InitializeObjectAttributes ecx, offset g_usMachineKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
    invoke ZwOpenKey, addr hKey, KEY_ALL_ACCESS, ecx

    .if eax == STATUS_SUCCESS
        invoke DbgPrint, $CTA0("RegistryWorks: Registry key opened\n")
        invoke ZwDeleteKey, hKey
        .if eax == STATUS_SUCCESS
            invoke DbgPrint, $CTA0("RegistryWorks: Registry key deleted\n")
        .else
            invoke DbgPrint, $CTA0("RegistryWorks: Can't delete registry key. Status: %08X\n"), eax
        .endif
        invoke ZwClose, hKey
        invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed\n")
    .else
        invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X\n"), eax
    .endif

    ret

DeleteKey endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                      EnumerateKey                                                
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

EnumerateKey proc

local oa:OBJECT_ATTRIBUTES
local hKey:HANDLE
local cb:DWORD
local pbi:PKEY_BASIC_INFORMATION
local pfi:PKEY_FULL_INFORMATION
local as:ANSI_STRING
local us:UNICODE_STRING
local dwSubKeys:DWORD
local pwszKeyName:PWCHAR

  &nbs; invoke DbgPrint, $CTA0("\nRegistryWorks: *** Opening \\Registry\\User key to enumerate\n")

    CCOUNTED_UNICODE_STRING "\\Registry\\User", g_usUserKeyName, 4

    lea ecx, oa
    InitializeObjectAttributes ecx, offset g_usUserKeyName, OBJ_CASE_INSENSITIVE, NULL, NULL
    invoke ZwOpenKey, addr hKey, KEY_ENUMERATE_SUB_KEYS, ecx

    .if eax == STATUS_SUCCESS
        invoke DbgPrint, $CTA0("RegistryWorks: Registry key openeded\n")

        invoke ZwQueryKey, hKey, KeyFullInformation, NULL, 0, addr cb
        .if cb != 0

            invoke ExAllocatePool, PagedPool, cb
            .if eax != NULL
                mov pfi, eax

                invoke ZwQueryKey, hKey, KeyFullInformation, pfi, cb, addr cb
                .if ( eax == STATUS_SUCCESS ) && ( cb != 0 )

                    mov eax, pfi
                    push (KEY_FULL_INFORMATION PTR [eax]).SubKeys
                    pop dwSubKeys

                    invoke DbgPrint, \
                        $CTA0("RegistryWorks: ---------- Starting enumerate subkeys ----------\n")

                    push ebx
                    xor ebx, ebx
                    .while ebx < dwSubKeys
                        invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, NULL, 0, addr cb
                        .if cb != 0
                            invoke ExAllocatePool, PagedPool, cb
                            .if eax != NULL
                                mov pbi, eax

                                invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, pbi, cb, addr cb
                                .if ( eax == STATUS_SUCCESS ) && ( cb != 0 )

                                    mov eax, pbi
                                    mov eax, (KEY_BASIC_INFORMATION PTR [eax]).NameLength
                                    add eax, sizeof WCHAR
                                    mov cb, eax
                                    invoke ExAllocatePool, PagedPool, cb
                                    .if eax != NULL
                                        mov pwszKeyName, eax

                                        invoke memset, pwszKeyName, 0, cb

                                        mov ecx, pbi
                                        mov eax, (KEY_BASIC_INFORMATION PTR [ecx]).NameLength
                                        shr eax, 1
                                        lea ecx, (KEY_BASIC_INFORMATION PTR [ecx])._Name
                                        invoke wcsncpy, pwszKeyName, ecx, eax
 
                                        invoke RtlInitUnicodeString, addr us, pwszKeyName
                                        invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
             &nsp;                          .if eax == STATUS_SUCCESS
                                            invoke DbgPrint, $CTA0("RegistryWorks: \=%s\=\n"), as.Buffer
                                            invoke RtlFreeAnsiString, addr as
                                        .endif

                                        invoke ExFreePool, pwszKeyName
                                    .endif
                                .else
                                    invoke DbgPrint, \
                                        $CTA0("RegistryWorks: Can't enumerate registry keys. Status: %08X\n"), eax                             
                                .endif
                                invoke ExFreePool, pbi
                            .endif
                        .endif
                        inc ebx
                    .endw
                    pop ebx

                    invoke DbgPrint, \
                        $CTA0("RegistryWorks: ------------------------------------------------\n")

                .else
                    invoke DbgPrint, \
                        $CTA0("RegistryWorks: Can't query registry key information. Status: %08X\n"), eax
                .endif
                invoke ExFreePool, pfi
            .else
                invoke DbgPrint, $CTA0("RegistryWorks: Can't allocate memory. Status: %08X\n"), eax
            .endif
        .endif

        invoke ZwClose, hKey
        invoke DbgPrint, $CTA0("RegistryWorks: Registry key handle closed\n")

    .else
        invoke DbgPrint, $CTA0("RegistryWorks: Can't open registry key. Status: %08X\n"), eax
    .endif

    ret

EnumerateKey endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                       DriverEntry                                                
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING

    invoke DbgPrint, $CTA0("\nRegistryWorks: Entering DriverEntry\n")
       

    ;:::::::::::::::::::::::::::::::::::::::
    ; Создаём новый подраздел              ;
    ;:::::::::::::::::::::::::::::::::::::::

    invoke CreateKey

    ;:::::::::::::::::::::::::::::::::::::::
    ; Создаем в этом подразделе параметр   ;
    ;:::::::::::::::::::::::::::::::::::::::

    invoke SetValueKey

    ;:::::::::::::::::::::::::::::::::::::::
    ; Получаем значение параметра          ;
    ;:::::::::::::::::::::::::::::::::::::::

    invoke QueryValueKey

    ;:::::::::::::::::::::::::::::::::::::::
    ; Удаляем подраздел                    ;
    ;:::::::::::::::::::::::::::::::::::::::

    invoke DeleteKey

    ;:::::::::::::::::::::::::::::::::::::::
    ; Перечисляем содержимое раздела       ;
    ;:::::::::::::::::::::::::::::::::::::::

    invoke EnumerateKey


    invoke DbgPrint, $CTA0("\nRegistryWorks: Leavig DriverEntry\n")

    mov eax, STATUS_DEVICE_CONFIGURATION_ERROR
    ret

DriverEntry endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                                                                                  
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

end DriverEntry

:make

set drv=RegistryWorks

\masm32\bin\ml /nologo /c /coff %drv%.bat
\masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native %drv%.obj

del %drv%.obj

echo.
pause

驱动程序的代码由几个独立的函数构成:CreateKey、SetValueKey、QueryValueKey、DeleteKey和EnumerateKey,每一个都是“从零开始”操作注册表的,对于学习来说,例子总是最直观的。


10.3.1 注册表键的创建与打开

在CreateKey函数中调用了函数ZwCreateKey,建立新的Registry\Machine\Software\CoolApp键。

    invoke ZwCreateKey, addr hKey, KEY_WRITE, addr oa, 0, NULL, REG_OPTION_VOLATILE, addr dwDisposition

标志REG_OPTION_VOLATILE不准将建立的子键写入hive——磁盘上的一个注册表文件。这样这个子键只存在到系统下一次加载。在本例中,并不一定非要使用这个标志,我们可以自己删除所有子键。如果想在注册表中较长时间注册这个子键,就不要用这个标志了。

    .if eax == STATUS_SUCCESS
        .if dwDisposition == REG_CREATED_NEW_KEY
        .elseif dwDisposition == REG_OPENED_EXISTING_KEY
        .endif

在成功调用ZwCreateKey后,变量dwDisposition的值定义了是否已创建新的子键(REG_CREATED_NEW_KEY),要是这个子键已在注册表中存在(REG_OPENED_EXISTING_KEY),就已被打开。

    invoke ZwOpenKey, addr hKey, KEY_SET_VALUE, ecx
    invoke ZwOpenKey, addr hKey, KEY_QUERY_VALUE, ecx
    invoke ZwOpenKey, addr hKey, KEY_ALL_ACCESS, ecx
    invoke ZwOpenKey, addr hKey, KEY_ENUMERATE_SUB_KEYS, ecx

在剩下的函数中,我们调用ZwOpenKey函数来打开已经存在的键,对相应的访问类型只使用相应的标志。


10.3.2 创建注册表键值

现在,我们来在我们的子键里创建一个字符串键值“SomeData”。

. . .
CCOUNTED_UNICODE_STRING "SomeData", g_usValueName, 4
CTW0 "It's just a string", g_wszStringData, 4
. . .
        invoke ZwSetValueKey, hKey, addr g_usValueName, 0, REG_SZ, \
                                addr g_wszStringData, sizeof g_wszStringData

常量REG_SZ定义了所创建键值的类型:以零结尾的unicode字符串。系统还有许多其它的类型——DDK中有详细描述。


10.3.3 访问注册表键值

我们来获取我们的SomeData的值。

        invoke ZwQueryValueKey, hKey, addr g_usValueName, \
                                KeyValuePartialInformation, NULL, 0, addr cb

函数ZwQueryValueKey的第三个参数定义了请求信息的类型。在DDK中定义了三种值:KeyValueBasicInformation、KeyValueFullInformation 和KeyValuePartialInformation,每一个值都有自己相应的结构体:KEY_VALUE_BASIC_INFORMATION、KEY_VALUE_FULL_INFORMATION和KEY_VALUE_PARTIAL_INFORMATION。在本例中,我们想获得键值的内容。对此KeyValuePartialInformation完全适合。

我们事先不知道所获取信息的大小,所以我们调用ZwQueryValueKey时,第四第五个参数分别使用NULL(缓冲区指针)和0(缓冲区大小)。函数ZwQueryValueKey计并算出缓冲区所需的大小并将这个值返回到变量cb中(很多但非全部的Zw*函数都是这个样子处理的)。

        .if cb != 0
            invoke ExAllocatePool, PagedPool, cb
            .if eax != NULL
                mov ppi, eax

                invoke ZwQueryValueKey, hKey, addr g_usValueName, \
                                    KeyValuePartialInformation, ppi, cb, addr cb

分配必需的内存空间,再次调用ZwQueryValueKey——现在已经有缓冲区指针了。

                .if ( eax == STATUS_SUCCESS ) && ( cb != 0 )
                    mov eax, ppi
                    .if [KEY_VALUE_PARTIAL_INFORMATION PTR [eax]]._Type == REG_SZ

在任何情况下,我们都要检查键值类型。

                        lea eax, (KEY_VALUE_PARTIAL_INFORMATION PTR [eax]).Data

如果是REG_SZ类型的键值,则KEY_VALUE_PARTIAL_INFORMATION结构体的Data域就包含了以零结尾的unicode字符串。我们需要在调试信息中输出这个字符串,所以要将它转换为ansi字符串。

                        invoke RtlInitUnicodeString, addr us, eax

函数RtlInitUnicodeString初始化unicode字符串,其地址在第二个参数中,并填充UNICODE_STRING结构体,其地址在第一个参数中。

                        invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE

函数RtlUnicodeStringToAnsiString将unicode字符串转换为ansi字符串。如果最后的参数设置为TRUE,则函数自己分配缓冲区,向那里写入转换的字符串并填充ANSI_STRING结构体(在我们这里是变量as)。ANSI_STRING的Buffer域将指向所分配的装有转换好的ansi字符串的缓冲区。如果最后一个参数为FALSE,则用于保存ansi字符串的缓冲区就需要事先分配好并将指针放在我们的ANSI_STRING结构体的Buffer域中。在本例中,我们让RtlUnicodeStringToAnsiString函数为我们分配缓冲区。

                        .if eax == STATUS_SUCCESS
                            invoke DbgPrint, \
                                $CTA0("RegistryWorks: Registry key value is: \=%s\=\n"), as.Buffer
                            invoke RtlFreeAnsiString, addr as
                        .endif

DDK描述了RtlUnicodeStringToAnsiString函数,而我却昏了头,说得模模糊的。IFS DDK更好的描述了这个函数的操作。为了处理“i上面的一点”我们来看个简单的例子。

wsz db 'a', 0, 'b', 0, 'c', 0, 0, 0
us  UNICODE_STRING <>
as  ANSI_STRING    <>

变量us和as最初没有定义。wsz为unicode字符串,要被转换为ANSI格式。

us._Length        = ?
us.MaximumLength  = ?
us.Buffer         = ?

as._Length        = ?
as.MaximumLength  = ?
as.Buffer         = ?

RtlInitUnicodeString 用字符串wsz的大小填充变量us。

invoke RtlInitUnicodeString, addr us, addr wsz

us._Length        = 6
us.MaximumLength  = 8
us.Buffer         = offset wsz

as._Length        = ?
as.MaximumLength  = ?
as.Buffer         = ?

从RtlUnicodeStringToAnsiString的最后一个参数可以看到,它分配的缓冲区大小应该为us.MaximumLength / sizeof WCHAR。分配缓冲区并将指针放在域as.Buffer中后,函数RtlUnicodeStringToAnsiString开始真正转换字符串。如果这个操作成功完成,则变量as将包含转换好的字符串的完整的描述。

invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE

us._Length        = 6
us.MaximumLength  = 8
us.Buffer         = offset wsz

as._Length        = 3
as.MaximumLength  = 4
as.Buffer         = -> 'a', 'b', 'c', 0  ; 指向RtlUnicodeStringToAnsiString,,,TRUE函数分配的缓冲区的指针
                                         ; 缓冲区包含有转换为ANCI格式的wsz字符串的

在使用了RtlUnicodeStringToAnsiString函数分配的缓冲区之后,还需要调用RtlFreeAnsiString释放掉它。而且充当其参数的不是指向函数自己缓冲区的指针,而是指向ANSI_STRING结构体的指针。

RtlFreeAnsiString释放掉缓冲区as.Buffer并清零变量as。

invoke RtlFreeAnsiString, addr as

us._Length        = 6
us.MaximumLength  = 8
us.Buffer         = offset wsz

as._Length        = 0
as.MaximumLength  = 0
as.Buffer         = NULL

现在应该全明白了吧。

10.3.4 删除注册表键

我想,这部分不用我讲您也能搞定。

10.3.5 更新注册表键

现在我们来看\Registry\User键下都有些什么。

        invoke ZwQueryKey, hKey, KeyFullInformation, NULL, 0, addr cb
        .if cb != 0

            invoke ExAllocatePool, PagedPool, cb
            .if eax != NULL
                mov pfi, eax

                invoke ZwQueryKey, hKey, KeyFullInformation, pfi, cb, addr cb
                .if ( eax == STATUS_SUCCESS ) && ( cb != 0 )

                    mov eax, pfi
                    push (KEY_FULL_INFORMATION PTR [eax]).SubKeys
                    pop dwSubKeys

要组织键的内容就需要知道其下子键/键值的数量。对此我们要使用KeyFullInformation信息类。分配必需的内存并将它传给ZwQueryKey,我们就在变量dwSubKeys中得到了子键/键值的数量。

                    push ebx
                    xor ebx, ebx
                    .while ebx < dwSubKeys
                        invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, NULL, 0, addr cb
                        .if cb != 0

                            invoke ExAllocatePool, PagedPool, cb
                            .if eax != NULL
                                mov pbi, eax

                                invoke ZwEnumerateKey, hKey, ebx, KeyBasicInformation, pbi, cb, addr cb
                                .if ( eax == STATUS_SUCCESS ) && ( cb != 0 )

我们在一个循环中使用KeyBasicInformation信息类来调用ZwEnumerateKey。就像前面那样,我们对其调用两次,第一次是为了得知信息的大小,第二次是为了获得这项信息本身。

                                    mov eax, pbi
                                    mov eax, (KEY_BASIC_INFORMATION PTR [eax]).NameLength
                                    add eax, sizeof WCHAR
                                    mov cb, eax
                                    invoke ExAllocatePool, PagedPool, cb

在KEY_BASIC_INFORMATION结构体的_Name域返回的是子键/键值的名称,其形式为unicode字符串,但是这个字符串不是以零结尾的。为了在后面将其转换为ansi字符串(为了在调试信息中输出),我们应该将其完善——为其分配一个临时缓冲区。

                                    .if eax != NULL
                             &nsp;          mov pwszKeyName, eax

                                        invoke memset, pwszKeyName, 0, cb

                                        mov ecx, pbi
                                        mov eax, (KEY_BASIC_INFORMATION PTR [ecx]).NameLength
                                        shr eax, 1
                                        lea ecx, (KEY_BASIC_INFORMATION PTR [ecx])._Name
                                        invoke wcsncpy, pwszKeyName, ecx, eax

将临时缓冲区清零,并向其中拷贝子键/键值的名称。

                                        invoke RtlInitUnicodeString, addr us, pwszKeyName
                                        invoke RtlUnicodeStringToAnsiString, addr as, addr us, TRUE
                                        .if eax == STATUS_SUCCESS
                                            invoke DbgPrint, $CTA0("RegistryWorks: \=%s\=\n"), as.Buffer
                                            invoke RtlFreeAnsiString, addr as
                                        .endif

我们来做必要的处理并在调试信息中输出子键/键值的名称。

                                        invoke ExFreePool, pwszKeyName
                                    .endif
                                .endif
                                invoke ExFreePool, pbi
                            .endif
                        .endif
                        inc ebx
                    .endw
                    pop ebx
                .endif
                invoke ExFreePool, pfi
            .endif
        .endif

进行必要的资源回收。

- 作者: D哥 2006年02月6日, 星期一 16:38  回复(1) |  引用(0) 加入博采

Kmdtut 9---共享内存

共享内存

董岩 译

9.1 SharingMemory驱动程序源代码

9.1.1 DriverEntry函数
9.1.2 DispatchControl函数
9.1.3 Memory Descriptor List
9.1.4 Cleanup函数

9.2 SharingMemory应用程序源代码
 
源代码:KmdKit\examples\basic\MemoryWorks\SharingMemory

在上一个例子SharedSection中,我们使用section进行通讯,驱动程序被硬性限制在具体进程的地址上下文中,即驱动程序所使用的虚拟地址位于此进程的地址空间中。我们在本例中使用的方法将没有这个缺点。对于驱动程序来说,这种方法更为自然些。

9.1 SharingMemory驱动程序源代码

我们首先从驱动程序开始分析。

;@echo off
;goto make

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                                                                                  
; SharingMemory - 示例程序,驱动程序使用用户进程的一块内存向用户进程中传递数据             
;                
;                                                                                                  
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.386
.model flat, stdcall
option casemap:none

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                              В К Л Ю Ч А Е М Ы Е    Ф А Й Л Ы                                    
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

include \masm32\include\w2k\ntstatus.inc
include \masm32\include\w2k\ntddk.inc

include \masm32\include\w2k\ntoskrnl.inc
include \masm32\include\w2k\hal.inc

includelib \masm32\lib\w2k\ntoskrnl.lib
includelib \masm32\lib\w2k\hal.lib

include \masm32\Macros\Strings.mac

include ..\common.inc
include seh0.inc

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                             Н Е И З М Е Н Я Е М Ы Е    Д А Н Н Ы Е                               
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.const
CCOUNTED_UNICODE_STRING "\\Device\\SharingMemory", g_usDeviceName, 4
CCOUNTED_UNICODE_STRING "\\DosDevices\\SharingMemory", g_usSymbolicLinkName, 4

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                    Н Е И Н И Ц И А Л И З И Р О В А Н Н Ы Е    Д А Н Н Ы Е                        
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.data?
g_pSharedMemory     PVOID   ?
g_pMdl              PVOID   ?
g_pUserAddress      PVOID   ?

g_fTimerStarted     BOOL    ?

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                           К О Д                                                  
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.code

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                        UpdateTime                                                
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

UpdateTime proc

local SysTime:LARGE_INTEGER

    invoke KeQuerySystemTime, addr SysTime
    invoke ExSystemTimeToLocalTime, addr SysTime, g_pSharedMemory

    ret

UpdateTime endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                       TimerRoutine                                               
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

TimerRoutine proc pDeviceObject:PDEVICE_OBJECT, pContext:PVOID

    invoke UpdateTime

    ret

TimerRoutine endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                          Cleanup                                                 
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

Cleanup proc pDeviceObject:PDEVICE_OBJECT

    .if g_fTimerStarted
        invoke IoStopTimer, pDeviceObject
        invoke DbgPrint, $CTA0("SharingMemory: Timer stopped\n")
    .endif

    .if ( g_pUserAddress != NULL ) && ( g_pMdl != NULL )
        invoke MmUnmapLockedPages, g_pUserAddress, g_pMdl
        invoke DbgPrint, $CTA0("SharingMemory: Memory at address %08X unmapped\"), g_pUserAddress
        and g_pUserAddress, NULL
    .endif

    .if g_pMdl != NULL
        invoke IoFreeMdl, g_pMdl
        invoke DbgPrint, $CTA0("SharingMemory: MDL at address %08X freed\n"), g_pMdl
        and g_pMdl, NULL
    .endif

    .if g_pSharedMemory != NULL
        invoke ExFreePool, g_pSharedMemory
        invoke DbgPrint, $CTA0("SharingMemory: Memory at address %08X released\n"), g_pSharedMemory
        and g_pSharedMemory, NULL
    .endif

    ret

Cleanup endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                     DispatchCleanup                                              
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

DispatchCleanup proc pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP

    invoke DbgPrint, $CTA0("\nSharingMemory: Entering DispatchCleanup\n")

    invoke Cleanup, pDeviceObject

    mov eax, pIrp
    mov (_IRP PTR [eax]).IoStatus.Status, STATUS_SUCCESS
    and (_IRP PTR [eax]).IoStatus.Information, 0

    fastcall IofCompleteRequest, pIrp, IO_NO_INCREMENT

    invoke DbgPrint, $CTA0("SharingMemory: Leaving DispatchCleanup\n")

    mov eax, STATUS_SUCCESS
    ret

DispatchCleanup endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                   DispatchCreateClose                                            
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

DispatchCreateClose proc pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP

    mov eax, pIrp
    mov (_IRP PTR [eax]).IoStatus.Status, STATUS_SUCCESS
    and (_IRP PTR [eax]).IoStatus.Information, 0

    fastcall IofCompleteRequest, pIrp, IO_NO_INCREMENT

    mov eax, STATUS_SUCCESS
    ret

DispatchCreateClose endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                     DispatchControl                                              
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

DispatchControl proc uses esi edi pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP

local dwContext:DWORD

    invoke DbgPrint, $CTA0("\nSharingMemory: Entering DispatchControl\n")

    mov esi, pIrp
    assume esi:ptr _IRP

    mov [esi].IoStatus.Status, STATUS_UNSUCCESSFUL
    and [esi].IoStatus.Information, 0

    IoGetCurrentIrpStackLocation esi
    mov edi, eax
    assume edi:ptr IO_STACK_LOCATION

    .if [edi].Parameters.DeviceIoControl.IoControlCode == IOCTL_GIVE_ME_YOUR_MEMORY
        .if [edi].Parameters.DeviceIoControl.OutputBufferLength >= sizeof PVOID

            invoke ExAllocatePool, NonPagedPool, PAGE_SIZE
            .if eax != NULL
                mov g_pSharedMemory, eax

                invoke DbgPrint, \
                $CTA0("SharingMemory: %X bytes of nonpaged memory allocated at address %08X\n"), \
                PAGE_SIZE, g_pSharedMemory

                invoke IoAllocateMdl, g_pSharedMemory, PAGE_SIZE, FALSE, FALSE, NULL
                .if eax != NULL
                    mov g_pMdl, eax

                    invoke DbgPrint, \
                            $CTA0("SharingMemory: MDL allocated at address %08X\n"), g_pMdl

                    invoke MmBuildMdlForNonPagedPool, g_pMdl

                    _try

                    invoke MmMapLockedPagesSpecifyCache, g_pMdl, UserMode, MmCached, \
                                        NULL, FALSE, NormalPagePriority
                    .if eax != NULL

                        mov g_pUserAddress, eax

                        invoke DbgPrint, \
                        $CTA0("SharingMemory: Memory mapped into user space at address %08X\n"), \
                        g_pUserAddress

 nbsp;                      mov eax, [esi].AssociatedIrp.SystemBuffer
                        push g_pUserAddress
                        pop dword ptr [eax]

                        invoke UpdateTime

                        invoke IoInitializeTimer, pDeviceObject, TimerRoutine, addr dwContext
                        .if eax == STATUS_SUCCESS

                            invoke IoStartTimer, pDeviceObject
                            inc g_fTimerStarted

                            invoke DbgPrint, $CTA0("SharingMemory: Timer started\n")

                            mov [esi].IoStatus.Information, sizeof PVOID
                            mov [esi].IoStatus.Status, STATUS_SUCCESS

                        .endif
                    .endif

                    _finally

                .endif
            .endif

        .else
            mov [esi].IoStatus.Status, STATUS_BUFFER_TOO_SMALL
        .endif
    .else
        mov [esi].IoStatus.Status, STATUS_INVALID_DEVICE_REQUEST
    .endif

    assume edi:nothing

    .if [esi].IoStatus.Status != STATUS_SUCCESS

        invoke DbgPrint, $CTA0("SharingMemory: Something went wrong\:\n")

        invoke Cleanup, pDeviceObject

    .endif

    push [esi].IoStatus.Status

    assume esi:nothing

    fastcall IofCompleteRequest, esi, IO_NO_INCREMENT

    invoke DbgPrint, $CTA0("SharingMemory: Leaving DispatchControl\n")

    pop eax
    ret

DispatchControl endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                       DriverUnload                                               
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

DriverUnload proc pDriverObject:PDRIVER_OBJECT

    invoke IoDeleteSymbolicLink, addr g_usSymbolicLinkName

    mov eax, pDriverObject
    invoke IoDeleteDevice, (DRIVER_OBJECT PTR [eax]).DeviceObject

    ret

DriverUnload endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;               В Ы Г Р У Ж А Е М Ы Й   П Р И   Н Е О Б Х О Д И М О С Т И   К О Д                  
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.code INIT

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                       DriverEntry                                                
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

DriverEntry proc pDriverObject:PDRIVER_OBJECT, pusRegistryPath:PUNICODE_STRING

local status:NTSTATUS
local pDeviceObject:PDEVICE_OBJECT

    mov status, STATUS_DEVICE_CONFIGURATION_ERROR

    and g_pSharedMemory, NULL
    and g_pMdl, NULL
    and g_pUserAddress, NULL
    and g_fTimerStarted, FALSE

    invoke IoCreateDevice, pDriverObject, 0, addr g_usDeviceName, FILE_DEVICE_UNKNOWN, \
                                     0, TRUE, addr pDeviceObject
    .if eax == STATUS_SUCCESS
        invoke IoCreateSymbolicLink, addr g_usSymbolicLinkName, addr g_usDeviceName
        .if eax == STATUS_SUCCESS
            mov eax, pDriverObject
            assume eax:ptr DRIVER_OBJECT
            mov [eax].MajorFunction[IRP_MJ_CREATE*(sizeof PVOID)],          offset DispatchCreateClose
            mov [eax].MajorFunction[IRP_MJ_CLEANUP*(sizeof PVOID)],         offset DispatchCleanup
            mov [eax].MajorFunction[IRP_MJ_CLOSE*(sizeof PVOID)],           offset DispatchCreateClose
            mov [eax].MajorFunction[IRP_MJ_DEVICE_CONTROL*(sizeof PVOID)],  offset DispatchControl
            mov [eax].DriverUnload,                                         offset DriverUnload
            assume eax:nothing
            mov status, STATUS_SUCCESS
        .else
            invoke IoDeleteDevice, pDeviceObject
        .endif
    .endif

    mov eax, status
    ret

DriverEntry endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                                                                                  
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

end DriverEntry

:make

set drv=SharingMemory

\masm32\bin\ml /nologo /c /coff %drv%.bat
\masm32\bin\link /nologo /driver /base:0x10000 /align:32 /out:%drv%.sys /subsystem:native /ignore:4078 %drv%.obj

del %drv%.obj
move %drv%.sys ..

echo.
pause

9.1.1 DriverEntry函数

            mov [eax].MajorFunction[IRP_MJ_CREATE*(sizeof PVOID)],          offset DispatchCreateClose
            mov [eax].MajorFunction[IRP_MJ_CLEANUP*(sizeof PVOID)],         offset DispatchCleanup
            mov [eax].MajorFunction[IRP_MJ_CLOSE*(sizeof PVOID)],           offset DispatchCreateClose
            mov [eax].MajorFunction[IRP_MJ_DEVICE_CONTROL*(sizeof PVOID)],  offset DispatchControl

除了通常的IRP_MJ_CREATE、IRP_MJ_CLOSE和IRP_MJ_DEVICE_CONTROL之外,这里还要处理IRP_MJ_CLEANUP。当用户模式代码调用了CloseHandle时,驱动程序开始发出IRP_MJ_CLEANUP,通知系统设备驱动将要关闭句柄。在此之后句柄真正关闭,驱动收到IRP_MJ_CLOSE。在本例中我们希望释放掉之前使用的资源,所以需要处理IRP_MJ_CLEANUP。

9.1.2 DispatchControl函数

            invoke ExAllocatePool, NonPagedPool, PAGE_SIZE
            .if eax != NULL
                mov g_pSharedMemory, eax

获得了控制代码IOCTL_GIVE_ME_YOUR_MEMORY,我们来从非分页内存中分配一个内存页。驱动应该将这部分内存映射到用户进程的地址空间中,在接收到请求的进程上下文中,即在我们的应用程序的地址空间中。使用非分页内存以及使用一个内存页的原因后面会介绍到。

ExAllocatePool返回系统空间中的地址,也就是说驱动程序是与当前上下文无关的。现在需要将这块内存映射到这个进程的地址空间中去,使之被共享。我们的驱动程序是单层的,所以对IRP_MJ_DEVICE_CONTROL的处理我们想放在我们应用程序的地址上下文中。在我们将分配的一个内存页映射到进程地址空间之前必须先分配MDL(Memory Descriptor List。我还真不知道怎么翻译成俄语)

9.1.3 Memory Descriptor List

MDL是一个结构体,用于描述一片内存区域中的物理内存页。

MDL STRUCT
    Next            PVOID       ?
    _Size           SWORD       ?
    MdlFlags        SWORD       ?
    Process         PVOID       ?
    MappedSystemVa  PVOID       ?
    StartVa         PVOID       ?
    ByteCount       DWORD       ?
    ByteOffset      DWORD       ?
MDL ENDS
PMDL typedef PTR MDL

更准确地讲,MDL结构体是一个首部(header)。紧随首部之后的是许多物理页的页号(page frame number, PFN)。但是MDL所描述的内存区域在虚拟地址空间中是连续不间断的,而它们所占据的物理页所在的物理内存却可能是按任意的顺序排列。正是因为如此再加上页的数量较大,我们需要维护一个表来记录该内存区中所有的物理页。同时这也用在了直接内存访问(Direct Memory Access, DMA)中。在我们这里物理页总共就一个。


                invoke IoAllocateMdl, g_pSharedMemory, PAGE_SIZE, FALSE, FALSE, NULL
                .if eax != NULL
                    mov g_pMdl, eax

函数IoAllocateMdl前两个参数定义了虚拟地址和内存区的大小,是建立MDL所必须的。如果MDL不与IRP相关联(我们这里正是这样),则第三个参数就为FALSE。第四个参数定义是否需要减少进程的份额,并只用于位于驱动程序链最上层的驱动程序或是单层的驱动程序(我们这里正是这样)。每一个进程都要获得一定份额的系统资源。当进程为自己分配资源时,这个份额就会减小。如果份额用完,就不能再为其分配相应的资源。我们可不想减少这个用于分配内存的进程份额,所以第四个参数设为FALSE。最后一个参数定义了一个非必要的指向IRP的指针,通过这个指针MDL可以与IRP关联。例如,对于直接I/O,I/O管理器为用户缓冲区建立MDL,并将其地址送至IRP.MdlAddress。我们弄这个MDL可不是用来搞I/O的,所以就没有什么IRP,最后一个参数也就设为NULL。

函数IoAllocateMdl为MDL分配内存并初始化首部。

                invoke MmBuildMdlForNonPagedPool, g_pMdl

MmBuildMdlForNonPagedPool填充物理页号并更新MDL首部的某些范围。

                _try

如果我们将要调用的函数MmMapLockedPagesSpecifyCache的参数AccessMode为UserMode且调用失败,系统会抛出一个异常(这是DDK公开说明的),这个异常我们能够处理,我们来建立SEH-frame。

                invoke MmMapLockedPagesSpecifyCache, g_pMdl, UserMode, MmCached, \
                                                     NULL, FALSE, NormalPagePriority

将MDL所描述的内存映射到我们应用程序的地址空间中。

MDL的第一个参数为描述所要映射的内存区域的MDL。第二个参数定义了是否要从用户模式下访问这块内存。第三个参数定义了这块内存被处理器缓存的方式。如果第四个参数为NULL,则系统会自己从用户空间中挑选虚拟地址。第五个参数定义了如果万一系统不能完成请求,是否要出现BSOD,但是这只用在第二个参数为KernelMode时。我们可不想让系统死掉,于是将这个参数赋值为FALSE。最后一个参数定义了成功调用MmMapLockedPagesSpecifyCache的重要性。

在Windows NT4,函数MmMapLockedPagesSpecifyCache并未实现,代之以MmMapLockedPages,形式如下:

                invoke MmMapLockedPages, g_pMdl, UserMode

MmMapLockedPages在Windows的后续版本中也是存在的,且只是对MmMapLockedPagesSpecifyCache,但是不能使用MmMapLockedPagesSpecifyCache的后面四个参数。

借助于MDL,在用户地址空间中只能映射锁定的内存,即位于非分页池中的内存(对于使用分页内存的所有情况我并不全都知道)。这是使用非分页内存的第一个理由。

映射的内存不能少于一页,所以我们需要完整的一个内存页,但是实际上总共只用其中的几个字节。

                        .if eax != NULL
                        mov g_pUserAddress, eax

                        mov eax, [esi].AssociatedIrp.SystemBuffer
                        push g_pUserAddress
                        pop dword ptr [eax]

MmMapLockedPagesSpecifyCache返回我们的内存页映射到用户空间中的地址。我们将这个地址传递到应用程序中。从这一刻起该内存页就成为共享的了,并且驱动程序对其的使用不依赖于当前的地址上下文,而用户进程也能以自己的地址来访问。

为了直观起见,函数UpdateTime将把当前系统时间放在我们的内存页中。

UpdateTime proc

local SysTime:LARGE_INTEGER

    invoke KeQuerySystemTime, addr SysTime
    invoke ExSystemTimeToLocalTime, addr SysTime, g_pSharedMemory

    ret

UpdateTime endp

KeQuerySystemTime取得的是格林威治时间。再用ExSystemTimeToLocalTime将其转换为本地时间。

                        invoke IoInitializeTimer, pDeviceObject, TimerRoutine, addr dwContext

初始化Timer,Timer将与设备对象建立关联。DEVICE_OBJECT结构体中有一个Timer域,其中有指向IO_TIMER结构体的指针。函数IoInitializeTimer的第一个参数定义了Timer要和哪一个设备对象关联。第二个参数是一个指向系统启用Timer时要调用的函数的指针。TimerRoutine函数将调用UpdateTime,在我们的内存页中更新系统时间。TimerRoutine运行在IRQL = DISPATCH_LEVEL(DDK中有记载)。这就是我们使用非分页内存的第一个也是最主要的原因。IoInitializeTimer的最后一个参数是一个指向任意数据的指针。这个指针将被传递到TimerRoutine中。我们这里不需要指定这个值,所以只是随便虚构一个变量。

                            .if eax == STATUS_SUCCESS

                            invoke IoStartTimer, pDeviceObject
                            inc g_fTimerStarted

启动Timer。现在函数TimerRoutine大约每秒被调用一次。这个时间间隔是不能修改的。

        .if [esi].IoStatus.Status != STATUS_SUCCESS
        invoke Cleanup, pDeviceObject
    .endif

如果上述各阶段有一个发生问题,就要收回资源。

9.1.4 Cleanup函数

Cleanup proc pDeviceObject:PDEVICE_OBJECT

    .if g_fTimerStarted
        invoke IoStopTimer, pDeviceObject
    .endif

    .if ( g_pUserAddress != NULL ) && ( g_pMdl != NULL )
        invoke MmUnmapLockedPages, g_pUserAddress, g_pMdl
        and g_pUserAddress, NULL
    .endif

    .if g_pMdl != NULL
        invoke IoFreeMdl, g_pMdl
        and g_pMdl, NULL
    .endif

    .if g_pSharedMemory != NULL
        invoke ExFreePool, g_pSharedMemory
        and g_pSharedMemory, NULL
    .endif

    ret

Cleanup endp

这里进行的工作都是很显然的,不用过多解释。唯一的奥妙在于将内存映射到用户空间和还原操作是借助于MmUnmapLockedPages函数实现的,应该在进程定义的地址上下文中进行,这是很自然的。

9.2 SharingMemory应用程序源代码

;@echo off
;goto make

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                                                                                  
; Клиент драйвера SharingMemory                                                                    
;                                                                                                  
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.386
.model flat, stdcall
option casemap:none

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                              В К Л Ю Ч А Е М Ы Е    Ф А Й Л Ы                                    
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

include \masm32\include\windows.inc

include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\advapi32.inc

includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\advapi32.lib

include \masm32\include\winioctl.inc

include \masm32\Macros\Strings.mac

include ..\common.inc

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;  nbsp;                                   E Q U A T E S                                               
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

IDD_MAIN            equ 1000
IDC_TIME            equ 1001
IDI_ICON            equ 1002

TIMER_ID            equ     100

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                    Н Е И Н И Ц И А Л И З И Р О В А Н Н Ы Е    Д А Н Н Ы Е                        
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.data?
g_hDevice           HANDLE      ?
g_hInstance         HINSTANCE   ?
g_hDlg              HWND        ?
g_pSharedMemory     LPVOID      ?

g_hSCManager        HANDLE      ?
g_hService          HANDLE      ?


;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                           К О Д                                                  
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

.code

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                             MyUnhandledExceptionFilter                                           
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

MyUnhandledExceptionFilter proc lpExceptionInfo:PTR EXCEPTION_POINTERS

local _ss:SERVICE_STATUS

    invoke KillTimer, g_hDlg, TIMER_ID
    invoke CloseHandle, g_hDevice
    invoke ControlService, g_hService, SERVICE_CONTROL_STOP, addr _ss
    invoke DeleteService, g_hService
    invoke CloseServiceHandle, g_hService
    invoke CloseServiceHandle, g_hSCManager

    mov eax, EXCEPTION_EXECUTE_HANDLER
    ret

MyUnhandledExceptionFilter endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                              UpdateTime                                          
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

UpdateTime proc

local stime:SYSTEMTIME
local buffer[64]:CHAR

    .if g_pSharedMemory != NULL
        invoke FileTimeToSystemTime, g_pSharedMemory, addr stime
        movzx eax, stime.wHour
        movzx ecx, stime.wMinute
        movzx edx, stime.wSecond

        invoke wsprintf, addr buffer, $CTA0("%02d:%02d:%02d"), eax, ecx, edx

        invoke SetDlgItemText, g_hDlg, IDC_TIME, addr buffer
    .endif

    ret

UpdateTime endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                               D I A L O G     P R O C E D U R E                                  
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

DlgProc proc uses esi edi hDlg:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

    mov eax, uMsg
    .if eax == WM_TIMER

        invoke UpdateTime

    .elseif eax == WM_INITDIALOG

        push hDlg
        pop g_hDlg

        invoke LoadIcon, g_hInstance, IDI_ICON
        invoke SendMessage, hDlg, WM_SETICON, ICON_BIG, eax

        invoke SetWindowText, hDlg, $CTA0("Kernel Timer")

        invoke UpdateTime

        invoke SetTimer, hDlg, TIMER_ID, 1000, NULL

    .elseif eax == WM_COMMAND

        mov eax, wParam
        .if ax == IDCANCEL
            invoke EndDialog, hDlg, 0
        .endif

    .elseif eax == WM_DESTROY

        invoke KillTimer, hDlg, TIMER_ID

    .else

        xor eax, eax
        ret
   
    .endif

    xor eax, eax
    inc eax
    ret
   
DlgProc endp

;::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                       start                                                      
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

start proc uses esi edi

local acModulePath[MAX_PATH]:CHAR
local _ss:SERVICE_STATUS
local dwBytesReturned:DWORD

    and g_pSharedMemory, NULL

    invoke SetUnhandledExceptionFilter, MyUnhandledExceptionFilter

    invoke OpenSCManager, NULL, NULL, SC_MANAGER_ALL_ACCESS
    .if eax != NULL
        mov g_hSCManager, eax

        push eax
        invoke GetFullPathName, $CTA0("SharingMemory.sys"), sizeof acModulePath, addr acModulePath, esp
        pop eax

        invoke CreateService, g_hSCManager, $CTA0("SharingMemory"), \
            $CTA0("Another way how to share memory"), SERVICE_START + SERVICE_STOP + DELETE, \
            SERVICE_KERNEL_DRIVER, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, addr acModulePath, \
            NULL, NULL, NULL, NULL, NULL

        .if eax != NULL
            mov g_hService, eax

            invoke StartService, g_hService, 0, NULL
            .if eax != 0

                invoke CreateFile, $CTA0("\\\\.\\SharingMemory"), GENERIC_READ, \
                                0, NULL, OPEN_EXISTING, 0, NULL

                .if eax != INVALID_HANDLE_VALUE
                    mov g_hDevice, eax

                    ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

                    invoke DeviceIoControl, g_hDevice, IOCTL_GIVE_ME_YOUR_MEMORY, NULL, 0, \
                                addr g_pSharedMemory, sizeof g_pSharedMemory, \
                                addr dwBytesReturned, NULL

                    .if ( eax != 0 ) && ( dwBytesReturned == sizeof g_pSharedMemory )

                        invoke GetModuleHandle, NULL
                        mov g_hInstance, eax
                        invoke DialogBoxParam, g_hInstance, IDD_MAIN, NULL, addr DlgProc, 0

                    .else
                        invoke MessageBox, NULL, $CTA0("Can't send control code to device."), \
                                                    NULL, MB_OK + MB_ICONSTOP
                    .endif

                    ;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

                    invoke CloseHandle, g_hDevice
                .else
                    invoke MessageBox, NULL, $CTA0("Device is not present."), NULL, MB_ICONSTOP
                .endif
                invoke ControlService, g_hService, SERVICE_CONTROL_STOP, addr _ss
            .else
                invoke MessageBox, NULL, $CTA0("Can't start driver."), NULL, MB_OK + MB_ICONSTOP
            .endif
            invoke DeleteService, g_hService
            invoke CloseServiceHandle, g_hService
        .else
            invoke MessageBox, NULL, $CTA0("Can't register driver."), NULL, MB_OK + MB_ICONSTOP
        .endif
        invoke CloseServiceHandle, g_hSCManager
    .else
        invoke MessageBox, NULL, $CTA0("Can't connect to Service Control Manager."), NULL, MB_ICONSTOP
    .endif

    invoke ExitProcess, 0

start endp

;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
;                                                                                &nbp;                 
;:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::

end start

:make

set exe=SharingMemory

if exist ..\%scp%.exe del ..\%scp%.exe

if exist rsrc.obj goto final
    \masm32\bin\rc /v rsrc.rc
    \masm32\bin\cvtres /machine:ix86 rsrc.res
    if errorlevel 0 goto final
        pause
        exit

:final
if exist rsrc.res del rsrc.res

\masm32\bin\ml /nologo /c /coff %exe%.bat
\masm32\bin\link /nologo /subsystem:windows %exe%.obj rsrc.obj

del %exe%.obj
move %exe%.exe ..
if exist %exe%.exe del %exe%.exe

echo.
pause

用户进程的每一个线程都处在SEH-frame中,SEH处理在线程中出现的所有异常。如果线程没有建立额外的异常处理,系统就会叫出那个臭名昭著的对话框并启动调试器。调用函数SetUnhandledExceptionFilter可以用自己的处理代替系统的异常处理。

    invoke SetUnhandledExceptionFilter, MyUnhandledExceptionFilter

事实上我们没有建立SEH-frame,在发生异常的情况下我们必须回收资源。后面我们稍微看一下MyUnhandledExceptionFilter进行的处理。

                    invoke DeviceIoControl, g_hDevice, IOCTL_GIVE_ME_YOUR_MEMORY, NULL, 0, \
                                addr g_pSharedMemory, sizeof g_pSharedMemory, \
                                addr dwBytesReturned, NULL

如果驱动程序正常启动,我们就向其发送控制代码IOCTL_GIVE_ME_YOUR_MEMORY。驱动将地址返回到变量g_pSharedMemory中,这个地址就是驱动程序映射内存缓冲区的地址。对其大小我们这里不感兴趣,足够我们用的。其中头8个字节为当前时间,每一秒钟由驱动程序更新一次。

                    .if ( eax != 0 ) && ( dwBytesReturned == sizeof g_pSharedMemory )

                        invoke GetModuleHandle, NULL
                        mov g_hInstance, eax
                        invoke DialogBoxParam, g_hInstance, IDD_MAIN, NULL, addr DlgProc, 0

如果一切正常,我们就来启动对话框。下面的东西都是很基础的。

    .elseif eax == WM_INITDIALOG
        . . .
        invoke UpdateTime
        invoke SetTimer, hDlg, TIMER_ID, 1000, NULL

在对WM_INITDIALOG消息的处理中,我们调用了函数UpdateTime。对于在对话框出现后立即显示当前时间,这是必须的。之后我们来启动Timer,Timer每秒钟启动一次。

    .if eax == WM_TIMER
        invoke UpdateTime

在对WM_TIMER的处理中,我们调用UpdateTime来更新时间。

UpdateTime proc

local stime:SYSTEMTIME
local buffer[64]:CHAR

    .if g_pSharedMemory != NULL
        invoke FileTimeToSystemTime, g_pSharedMemory, addr stime
        movzx eax, stime.wHour
        movzx ecx, stime.wMinute
        movzx edx, stime.wSecond

        invoke wsprintf, addr buffer, $CTA0("%02d:%02d:%02d"), eax, ecx, edx

        invoke SetDlgItemText, g_hDlg, IDC_TIME, addr buffer
    .endif

    ret

UpdateTime endp

函数UpdateTime的任务是将当前时间格式化为小时:分钟:秒钟的形式并将其输出。

这样驱动程序每秒钟向分配的内存页写一次当前时间,将其虚拟地址视为系统地址空间的地址,而应用程序每秒钟一次地获取此信息,将虚地址视为用户地址空间的地址。但是物理上是同一个内存页。这样时钟每秒滴答一次。顺便说一句,函数KeQuerySystemTime取得当前时间,同时在内核和用户模式页间共享,这个内存页在内核模式下地址为0FFDF0000h,而在用户模式下为7FFE0000h(用户函数GetSystemTime和内核函数KeQuerySystemTime读取的都是这个字节),之后函数将其写入KUSER_SHARED_DATA结构体(见ntddk.inc)。从这个结构体的名字可以看出,它是由内核模式与用户模式共享的。

MyUnhandledExceptionFilter proc lpExceptionInfo:PTR EXCEPTION_POINTERS
 
local _ss:SERVICE_STATUS

    invoke KillTimer, g_hDlg, TIMER_ID
    invoke CloseHandle, g_hDevice
    invoke ControlService, g_hService, SERVICE_CONTROL_STOP, addr _ss
    invoke DeleteService, g_hService
    invoke CloseServiceHandle, g_hService
    invoke CloseServiceHandle, g_hSCManager

    mov eax, EXCEPTION_EXECUTE_HANDLER
    ret

MyUnhandledExceptionFilter endp

如果在应用程序的任何一个地方发生异常,系统都会调用我们的MyUnhandledExceptionFilter处理程序。所有我们所能做的就是释放所有分配的资源。最重要的是关闭设备句柄。当驱动程序收到IRP_MJ_CLEANUP并随后收到IRP_MJ_CLOSE而进行清理时,最主要的就是解除对用户地址空间的内存映射。在这些操作中甚至可能会没有异常处理。如果应用程序崩溃,系统就要自己关闭所有打开的句柄和设备句柄。我们在对IRP_MJ_CLEANUP的处理中解除我们的内存共享仅仅是希望能将过去可能分配过的资源全部释放掉。在本例中这项工作还可以在对IRP_MJ_CLOSE的处理中进行。一般情况下,MmUnmapLockedPages应该在用户进程中止后调用。

本例与上例的差别是,这里我们有两个线程使用共享的内存资源。这时我们就应该考虑同步的问题了。读线程工作在用户模式下,因而总是处于IRQL = PASSIVE_LEVEL下。写线程位于系统进程空间并执行TimerRoutine函数,其地址定义在IoInitializeTimer调用中。TimerRoutine函数调用系统函数的环境是IRQL = DISPATCH_LEVEL(DDK中有准确的叙述)并由idle进程的线程执行,在我所试验过的所有情况下,都是由这个线程执行的。它的优先级要比用户线程的优先级低,所以在从共享内存页读取数据时它不可能使应用程序中断。在IRQL = DISPATCH_LEVEL下调度线程不执行,这样在系统向共享内存页中写入当前时间时用户线程不可能使系统中断。所以在单处理器机器上应该不会出现任何同步上的问题。在多处理器机器上这些线程则有可能同时工作。所以在类似的情形下需要考虑同步问题。在本例中我们就不在这上下功夫了,在后面有文章专门讨论。这个程序最不好的一点是时间上有误差,不过在这里不算什么。

- 作者: D哥 2006年02月6日, 星期一 16:34  回复(0) |  引用(0) 加入博采

Kmdtut 8---共享Section通讯
摘要:这是俄语版的那几篇内核驱动文章。 查看全文

- 作者: D哥 2006年02月6日, 星期一 16:06  回复(0) |  引用(0) 加入博采