⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 011.txt

📁 会变语言实现的一些程序
💻 TXT
📖 第 1 页 / 共 5 页
字号:
    invoke lpIncCount,hWnd,IDC_COUNTER

  .endif

  .elseif ax == IDC_DEC

  .if lpDecCount

    invoke lpDecCount,hWnd,IDC_COUNTER

  .endif

  .endif

  .else

  mov eax,FALSE

  ret

  .endif

  mov eax,TRUE

  ret

 

_ProcDlgMain     endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

start:

      invoke  GetModuleHandle,NULL

      invoke  DialogBoxParam,eax,DLG_MAIN,NULL,\

      offset _ProcDlgMain,NULL

      invoke  ExitProcess,NULL

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

      end start

下面分析这个程序和UseDll1程序的不同点。

首先,程序的开始不再需要Counter.lib文件和Counter.inc文件,少了对应的include和includelib语句,因为并不需要链接器去定位函数的位置。

第二是 .const数据段中需要自己定义装入的库文件名和函数名:

szDll db   ~Counter.dll~,0   ;装入的动态链接库名称

szIncCount   db   ~_IncCount~,0     ;装入的函数名

szDecCount   db   ~_DecCount~,0   ;装入的函数名

这些信息原来是由链接器根据lib文件的信息写在可执行文件头的导入表中的,既然现在由程序自己来装入库函数,那么这些信息也就需要自己定义了。

第三是在使用库中的函数之前需要使用LoadLibrary将库装入,并使用GetProcAddress函数得到函数的入口地址。程序中将这个步骤安排在对话框初始化消息WM_INITDIALOG中完成,读者也可以在使用函数前的任何地方完成。LoadLibrary函数的使用方法是:

invoke  LoadLibrary,lpDllFileName

.if   eax

  mov   hDllInstance,eax

.endif

参数lpDllFileName指向需要装载的库文件名,库文件名是一个以NULL结尾的字符串。函数按下列顺序在不同目录中查找指定的库文件:当前目录、Windows系统目录和PATH环境变量列出的目录。如果这些目录中存在同名的库文件,那么先搜索到的库文件会被装入。

如果装载成功,函数返回库文件的实例句柄,装载失败则返回NULL。返回的实例句柄需要被保存起来,以后在获取库中的导出函数,装载库中的资源以及释放库的操作中都要用到它。

对于方法一中库文件丢失和库的入口函数返回FALSE告诉Windows初始化失败的情况,LoadLibrary函数均返回NULL,这样程序就可以根据情况决定该怎么做,程序可以显示一个提示信息并退出,也可以不使用这个库文件而继续执行。UseDll2程序的处理方法是显示一个“Counter.dll 文件丢失或装载失败,计数功能无法实现”提示信息,然后将对话框中的“增加”、“减少”两个按钮灰化后继续执行。这样,对话框可以正常显示出来,但是使用库函数的计数功能被屏蔽掉了。

如果装载动态链接库成功,下一步就是使用GetProcAddress函数来获取库中函数的地址。GetProcAddress函数的使用方法是:

invoke  GetProcAddress,hDllInstance,lpProcName

.if   eax

  mov   lpProc,eax

.endif

hDllInstance参数就是LoadLibrary函数返回的动态链接库的实例句柄,lpProcName指向要获取的函数名称,函数名也是用以NULL结尾的字符串来定义。有些系统DLL中的函数名称并不是字符串,而是使用数值编号,对于这种情况,lpProcName参数可以指定为函数的编号数值。

如果执行成功,返回值是要获取的函数的入口地址,程序可以保存它并在以后调用。如果执行失败,比如因为版本变化等原因导致需要获取的函数不存在,这时函数返回NULL。

在不再需要动态链接库的时候,为了释放库所占用的系统资源,需要使用FreeLibrary函数释放它。FreeLibrary函数的用法是:

invoke  FreeLibrary,hDllInstance

输入参数是LoadLibrary函数返回的实例句柄,函数导致系统以DLL_PROCESS_DETACH代码调用库的入口函数,这样库文件可以自己释放占用的一些资源,然后,整个库的代码和数据被从应用程序的地址空间中清除。 

但是在一个应用程序中使用FreeLibrary函数并不会影响另一个应用程序使用同一个库文件,当库文件还被另一个程序使用中的时候,它还是在物理内存中存在。实际上,操作系统为每个库文件维护一个装入计数器,每次使用LoadLibrary装载库文件(或者使用方法一由Windows来装入一个库)的时候,计数器递增;每次使用FreeLibrary函数将库释放的时候,计数器递减,只有到计数器减到零,也就是库文件没有被任何程序使用的时候,操作系统才会将它从物理内存中真正释放掉,否则仅是从某个进程的地址空间中解除了内存映射关系而已。

方法二和方法一的最后一个不同点是调用函数的方法,在使用GetProcAddress函数获取了库中导出函数的入口点以后,程序在调用的时候一般使用将参数手工入栈的方法,如对_IncCount函数的调用可以写为:

push     IDC_COUNTER

push     hWnd

call     lpIncCount    ;lpIncCount保存有GetProcAddress获取的地址

这样写法的缺点是无法使用invoke伪指令来进行参数检验,容易引发错误。实际上还有一个变通的方法,可以将一个变量定义为子程序入口指针,并为它定义参数个数,方法是两次使用typedef伪操作:

_PROCVAR2 typedef proto :dword,:dword

PROCVAR2   typedef ptr _PROCVAR2

如上面的第一句将_PROCVAR2类型定义为使用两个参数的函数类型,第二句将PROCVAR类型定义为_PROCVAR2类型的指针,这样,在数据段中就可以将保存函数入口地址的变量使用PROCVAR2类型来定义了,得到的好处就是可以用invoke语句来调用这个变量中的指针:

    .data?

lpIncCount   PROCVAR2   ?

lpDecCount   PROCVAR2   ?

有人曾询问笔者这样一个问题:如果既没有导入库,也没有资料,该如何使用DLL中的函数?答案是,函数名是没有问题的,通过一些工具查看导出表就可以得知库中所有的导出函数列表,但是有关调用函数使用参数的数量和参数的定义方法等资料就成问题了,惟一的办法就是通过反汇编或者跟踪来找出参数的数量和含义后再通过本节介绍的方法调用。

 使用方法二时要注意:不管是使用LoadLibrary函数还是GetProcAddress函数,对返回值必须要检查,否则一旦失败的话,很容易引发调用NULL指针的错误。

 


11.1 动态链接库(6)

11.1.4  动态链接库中的数据共享 
当多个应用程序同时使用同一个动态链接库的时候,这些动态链接库在系统中是存在于不同进程的地址空间中的,它们代表“宿主”程序工作,互相之间没有任何联系。这一点可以通过一个简单的实验来演示:当我们多次运行UseDll1.exe(或UseDll2.exe),按动不同对话框中的“增加”或“减少”按钮的时候,每个对话框中的计数值按照自己的规律增减,不会受到其他对话框中计数值的影响。这就是说,虽然Counter.dll被多个进程同时装入,但是操作系统为它们映射了各不相同的数据段,使它们工作起来互不影响。

但是需要在进程间进行数据共享的时候,这种互相隔离的特征就不是我们所需要的了,当然,解决的方法之一就是使用第10章中介绍的内存映射文件,但是更简单的办法是通过构造特殊的动态链接库来实现。

再次以前面的Counter.dll为例,现在将它的计数器改成是全局的,也就是说运行UseDll1.exe的多个拷贝的时候,不同对话框增减的是同一个计数器。

回顾第2章对Link.exe程序的介绍,会发现链接器有一个/SECTION选项,可以将某个节区的属性自行定义,选项中有一个S属性,代表将节区的属性设置为共享,这就是我们需要的,实际上不必修改Counter.asm源程序,只需要把Makefile文件中的Link选项修改一下:

DLL = Counter

 

ML_FLAG = /c /coff

LINK_FLAG = /subsystem:windows /Dll /section:.bss,S

 

$(DLL).dll: $(DLL).obj $(DLL).def

  Link  $(LINK_FLAG) /Def:$(DLL).def $(DLL).obj

.asm.obj:

  ml $(ML_FLAG) $<

.rc.res:

  rc $<

未初始化数据段 .data?的节区名称为 .bbs,加上/section:.bss,S选项就可以将这个段的属性改为共享,这样,当DLL被不同应用程序装载的时候,不但映射到不同进程地址空间中的代码段来自同一段物理内存,.data?段的映射也来自同一段物理内存。

修改Makefile以后来验证一下,使用nmake /a将DLL重新编译并将Counter.dll文件拷贝到UseDll1程序所在目录,然后多次执行UseDll1程序以产生多个对话框,当在一个对话框中按下“增加”按钮将计数增加到x的时候,再换到另一个对话框中再按“增加”按钮,会发现出现的值是x+1而不是原来应该出现的1,表示这些对话框操作的是一个共享的计数器。

如果不希望全部的数据都共享,如hInstance等私有的数据,可以把这些数据放在初始化数据段 .data中,它的节区名称不同于 .data?段,在将 .data?段的属性修改为共享的时候并不会影响 .data段的属性。



11.2 Windows钩子(1)


11.2.1  什么是Windows钩子 
1. Windows钩子简介

在DOS操作系统下编程的时候,如果想截获某种系统功能,可以采取截获中断的办法,比如要获取击键的动作可以截获9号中断,要获取应用程序对文件操作功能的调用可以截获21h号DOS中断,由于DOS是单任务系统,所以这些操作几乎全部是内存驻留程序做的。DOS下截获中断的方法是这样的简单和随意,不管在驱动程序层次还是在应用程序层次都可以完成,以至于到最后大半的截获操作是由病毒使用的。

在Windows下就不同了,我们已经知道保护模式下的中断描述符表是受系统保护的,在应用程序层次是不可能再通过修改中断向量来截获系统中断了,但这样也对一些应用造成了不便,当做一种变通的措施,Windows提供了钩子来完成类似的功能。那么,钩子是什么呢?Win32 API手册中是这样描述的:

“A hook is a point in the Microsoft Windows message-handling mechanism where an application can install a subroutine to monitor the message traffic in the system and process certain types of messages before they reach the target window procedure.”

翻译过来就是:“钩子是Windows的消息处理机制中的一个监视点,应用程序可以在这里安装一个监视子程序,这样就可以在系统中的消息流到达目的窗口过程前监控它们。”

也就是说,钩子可以用来截获系统中的消息流,显然,钩子不是像截获中断一样用来随心所欲地截获系统底层功能的,那么钩子能够用来做什么事情呢?(我仿佛听到了一些阴险的笑声……)不用笑得这么阴险嘛!大家想得没错,如果把钩子用在后台执行的程序中,就能够偷偷检查任何程序中发生的WM_CHAR消息,这样用户输入的任何内容:账号、密码、情书——不管是什么,不管是否显示在屏幕上——都可以被记录下来。事实上,很多木马程序就是这样做的,像冰河一类的木马程序就可以在后台记录用户的击键并偷偷发送到人家的信箱中去。

2. 钩子的类型

钩子是Windows消息机制中的监视点,应用程序可以在这里安装一个监视函数,这样就可以捕捉自己进程或者其他进程发生的事件。通过SetWindowsHookEx函数就可以做到这一点。SetWindowsHookEx函数定义了监视函数的位置和监视消息的类型,这样,每当发生我们感兴趣的消息时,Windows就会将消息发送给监视函数,监视函数是一个处理消息的回调函数,也称为“钩子函数”。

Windows安装的钩子有两种类型:局部的和远程的。它们处理消息的范围不同。局部钩子仅钩挂属于自身进程的事件;远程钩子除了可以钩挂自身进程的事件,还可以钩挂其他进程中发生的事件。远程钩子又分两种:基于线程的和系统范围的。基于线程的远程钩子用来捕获其他进程中某一特定线程的事件;而系统范围的远程钩子将捕捉系统中所有进程中发生的事件消息。

安装钩子会影响系统的性能,因为系统在处理所有的相关事件时都会调用钩子函数,特别是监视范围是整个系统范围的全局钩子。如果钩子函数中的处理代码过多的话,系统运行速度将会明显减慢,所以对于全局钩子一定要小心使用,不需要的时候应该立刻卸载。在 DOS操作系统下编写中断服务程序的时候,如果代码有错误的话会影响其他调用它的程序。同样,由于钩子函数可以预先截获其他进程的消息,所以一旦钩子函数存在问题的话,也会影响其他进程的运行。

可以把钩子想像成钓鱼钩,不同鱼钩用来钓的鱼是不同的,大钩钓大鱼,小钩钓小鱼,不同钩子钓的消息也是不同的,没有必要每次钓来所有的消息,根据监视的消息类型和时机的不同,钩子可以分为如表11.1所示的几种。

表11.1  钩子的类型

钩 子 名 称
 监视消息的类型和时机
 
WH_CALLWNDPROC
 每当调用SendMessage函数时,函数将消息发送给目标窗口过程前首先调用钩子函数
 
WH_CALLWNDPROCRET
 每当调用SendMessage函数时,函数将消息发送给目标窗口过程后再调用钩子函数
 
WH_GETMESSAGE
 每当调用GetMessage或PeekMessage函数时,函数从程序的消息队列中获取一个消息后调用钩子函数
 
WH_KEYBOARD
 每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是WM_KEYUP或WM_KEYDOWN消息,则调用钩子函数
 
WH_MOUSE
 每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是鼠标消息,则调用钩子函数
 
WH_HARDWARE
 每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是非鼠标和键盘消息,则调用钩子函数
 
WH_MSGFILTER
 当用户对对话框、菜单和滚动条有所操作时,系统在发送对应的消息之前调用钩子函数,这种钩子只能是局部的
 
WH_SYSMSGFILTER
 同WH_MSGFILTER,不过是系统范围的
 
WH_SHELL
 当Windows shell程序准备接收一些通知事件前调用钩子函数,如shell被激活和重画等
 
WH_DEBUG
 用来给其他钩子函数除错
 
WH_CBT
 当基于计算机的训练(CBT)事件发生时调用钩子函数
 
WH_JOURNALRECORD
 日志记录钩子,用来记录发送给系统消息队列的所有消息
 
WH_JOURNALPLAYBACK
 日志回放钩子,用来回放日志记录钩子记录的系统事件
 
WH_FOREGROUNDIDLE
 系统空闲钩子,当系统空闲的时候调用钩子函数,这样就可以在这里安排一些优先级很低的任务
 

在这些钩子中,有些只能当做局部钩子使用,如WH_MSGFILTER钩子;有些只能当做系统范围的远程钩子使用,如WH_JOURNALRECORD 和 WH_JOURNALPLAYBACK钩子;而大多数的钩子可以在任何范围内使用。

对于不同的钩子,由于它们处理的消息类型不同,所以钩子函数的参数定义也是不同的,在具体的编程中,需要查看Win32 API手册来了解各种钩子函数的参数定义。

另外,远程钩子和局部钩子的程序结构也是不同的。当安装了一个局部钩子时,每当指定的事件发生,Windows就可以调用进程中的钩子函数;但是若安装的是远程钩子,系统不能从其他进程的地址空间中调用钩子函数,因为两个进程的地址空间是隔离的,由于系统中只有DLL程序是可以插入到其他进程的地址空间中去的,所以远程钩子的钩子函数必须位于一个动态链接库中,而且必须是共享数据段的动态链接库(因为写远程钩子要用到动态链接库,所以本书中将两部分内容合在一章中介绍)。

但是也有两个例外:日志记录钩子和日志回放钩子虽然属于远程钩子,但是它们的钩子函数却可以放在安装钩子的程序中,并不需要单独放在一个动态链接库中。Microsoft并没有说明为什么有这样的例外,笔者认为其中的原因是这两个钩子是用来监控比较底层的硬件事件的,所以钩子函数的调用并不是从其他进程的地址空间中发起的,而是从Windows内部发起的,所以不存在不同进程之间地址空间隔离的问题。(猜想而已,如果读者有明确的资料请告知笔者。)

下面的11.2.2节以键盘钩子为例来说明系统范围远程钩子的安装和使用,局部钩子的使用步骤与之类似,只不过不必将钩子函数放在动态链接库中而已,使用起来更加简单,读者可以举一反三自己尝试一下。11.2.3节中演示日志钩子的使用方法。

11.2.2  远程钩子的安装和使用

1. 钩子程序的结构

钩子程序一般包括3个功能模块:

(1)主程序——用来实现界面或者其他功能。

(2)钩子回调函数——用来接收系统发过来的消息。

(3)钩子的安装和卸载程序。

对于局部钩子来说,这些模块可以处在同一个可执行文件中。而对于远程钩子来说,第2部分必须放在一个动态链接库中,第3部分虽然没有要求,但一般也放在动态链接库中,这是因为钩子创建以后得到一个钩子句柄,这个句柄要在钩子回调函数中以及卸载钩子的时候用到,如果把这部分代码放在主程序中的话,还需要创建一个函数将它传回给动态链接库,所以还不如直接放到库中。

所附光盘的Chapter11\KeyHook目录中的例子采用的就是这样的结构。目录中包括两部分文件:HookDll.asm和HookDll.def文件用来生成动态链接库;Main.asm和Main.rc是主程序部分。程序用一个系统范围的远程钩子来实现监视所有键盘输入的功能。由于安装钩子回调函数的动态链接库要求是共享数据段的,所以请读者注意Makefile中dll文件的链接选项,它使用了/section:.bss,S选项。

 


11.2 Windows钩子(2)


NAME = Main

DLL = Hookdll

 

ML_FLAG = /c /coff

LINK_FLAG = /subsystem:windows

DLL_LINK_FLAG = /subsystem:windows /section:.bss,S

 

$(DLL).dll $(NAME).exe:

$(DLL).dll: $(DLL).obj $(DLL).def

  Link  $(DLL_LINK_FLAG) /Def:$(DLL).def /Dll $(DLL).obj

$(NAME).exe: $(NAME).obj $(NAME).res

  Link  $(LINK_FLAG) $(NAME).obj $(NAME).res

.asm.obj:

  ml $(ML_FLAG) $<

.rc.res:

  rc $<

clean:

  del *.obj

  del *.res

  del *.exp

  del *.lib

HookDll.asm文件的内容如下:

  .386

  .model flat, stdcall

  option casemap :none

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; Include 文件定义

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -