通过挂钩插入DLL(转载)

类别:编程语言 点击:0 评论:0 推荐:
可以使用挂钩将D L L插入进程的地址空间。为了使挂钩能够像它们在1 6位Wi n d o w s中那样工作,M i c r o s o f t不得不设计了一种方法,使得D L L能够插入另一个进程的地址空间中。

  下面让我们来看一个例子。进程A(类似Microsoft Spy++的一个实用程序)安装了一个挂钩W N _ G E T M E S S A G E,以便查看系统中的各个窗口处理的消息。该挂钩是通过调用下面的S e t Wi n d o w s H o o k E x函数来安装的:

  第一个参数W H _ G E T M E S S A G E用于指明要安装的挂钩的类型。第二个参数G e t M s g P r o c用于指明窗口准备处理一个消息时系统应该调用的函数的地址(在你的刂房占渲校5三个参数h i n s t D l l用于指明包含G e t M s g P r o c函数的D L L。在Wi n d o w s中,D L L的h i n s t D l l的值用于标识DLL被映射到的进程的地址空间中的虚拟内存地址。最后一个参数0用于指明要挂接的线程。

  对于一个线程来说,它可以调用S e t Wi n d o w s H o o k E x函数,传递系统中的另一个线程的I D。通过为这个参数传递0,就告诉系统说,我们想要挂接系统中的所有G U I线程。

  现在让我们来看一看将会发生什么情况:

  1) 进程B中的一个线程准备将一条消息发送到一个窗口。

  2) 系统查看该线程上是否已经安装了W H _ G E T M E S S A G E挂钩。

  3) 系统查看包含G e t M s g P r o c函数的D L L是否被映射到进程B的地址空间中。

  4) 如果该D L L尚未被映射,系统将强制该D L L映射到进程B的地址空间,并且将进程B中的D L L映像的自动跟踪计数递增1。

  5) 当D L L的h i n s t D l l用于进程B时,系统查看该函数,并检查该D L L的h i n s t D l l是否与它用于进程A时所处的位置相同。

  如果两个h i n s t D l l是在相同的位置上,那么G e t M s g P r o c函数的内存地址在两个进程的地址空间中的位置也是相同的。在这种情况下,系统只需要调用进程A的地址空间中的G e t M s g P r o c函数即可。

  如果h i n s t D l l的位置不同,那么系统必须确定进程B的地址空间中G e t M s g P r o c函数的虚拟内存地址。这个地址可以使用下面的公式来确定:

  将GetMsgProc A的地址减去hinstDll A的地址,就可以得到G e t M s g P r o c函数的地址位移(以字节为计量单位)。将这个位移与hinstDll B的地址相加,就得出G e t M s g P r o c函数在用于进程B的地址空间中该D L L的映像时它的位置。

  6) 系统将进程B中的D L L映像的自动跟踪计数递增1。

  7) 系统调用进程B的地址空间中的G e t M s g P r o c函数。

  8) 当G e t M s g P r o c函数返回时,系统将进程B中的D L L映像的自动跟踪计数递减1。

  注意,当系统插入或者映射包含挂钩过滤器函数的D L L时,整个D L L均被映射,而只是挂钩过滤器函数被映射。这意味着D L L中包含的任何一个函数或所有函数现在都存在,并且可以从进程B的环境下运行的线程中调用。

  若要为另一个进程中的线程创建的窗口建立子类,首先可以在创建该窗口的挂钩上设置一个W H _ G E T M E S S A G E挂钩,然后,当G e t M s g P r o c函数被调用时,调用S e t Wi n d o w L o n g P t r函数 来建立窗口的子类。当然,子类的过程必须与G e t M s g P r o c函数位于同一个D LL中。

  与插入D L L的注册表方法不同,这个方法允许你在另一个进程的地址空间中不再需要DL L时删除该D L L的映像,方法是调用下面的函数:

  当一个线程调用U n h o o k Wi n d o w s H o o k E x函数时,系统将遍历它必须将D L L插入到的各个进程的内部列表,并且对D L L的自动跟踪计数进行递减。当自动跟踪计数时,D L L就自动从进程的地址空间中被删除。应该记得,就在系统调用G e t M s g P r o c前,它对D L L的自动跟踪计数进行了递增(见上面的第6个步骤)。

  该自动跟踪计数没有递增,那么当进程B的线程试图执行G e t M s g P r o c函数中的代胧,系统中运行的另一个线程就可以调用U n l o o k Wi n d o w s H o o k E x函数。

  这一切意味着不能撤消该窗口的子类并且立即撤消该挂钩。该挂钩必须在该子类的寿命期内保持有效状态。

  3.使用远程线程插入DLL

  插入D L L的另一个方法是使用远程线程。这种方法具有更大的灵活性。它要求你懂得若干个Wi n d o w s特性、如进程、线程、线程同步、虚拟内存管理、D L L和U n i c o d e等(如果对这些特性不清楚,请参阅本书中的有关章节)。Wi n d o w s的大多数函数市斫讨欢宰己进行操作。

  这是很好的一个特性,因为它能够防止一个进程破坏另一个进程的运行。但是,有些函数却允许一个进程对另一个进程进行操作。这些函数大部分最初是为调试程序和其他工具杓的。不过任何函数都可以调用这些函数。

  这个D L L插入方法基本上要求目标进程中的线程调用L o a d L i b r a r y函数来载必要的D L L。

  由于除了自己进程中的线程外,我们无法方便地控制其他进程中的线程,因此这种解决方案要求我们在目标进程中创建一个新线程。由于是自己创建这个线程,因此我们能够控扑执行什么代码。幸好,Wi n d o w s提供了一个称为C r e a t e R e m o t e T h r e a d的函数,使我们能够非常容易地在另一个进程中创建线程:

  C r e a t e R e m o t e T h r e a d与C r e a t e T h r e a d很相似,差别在于它增加了一个参数h P r o c e s s。该参数指明拥有新创建线程的进程。参数p f n S t a r t A d d r指明线程函数的内存地址。当然,该内存地址与远程进程是相关的。线程函数的代码不能位于你自己进程的地址空间中。

  Windows 98 在Windows 98中,C r e a t e R e m o t e T h r e a d函数不存在有用的实现代码,它只是返回N U L L。调用G e t L a s t E r r o r函数将返回E R R O R _ C A L L _ N O T _ I M P L E M E N T E D(C r e a t e T h r e a d函数包含用于在调用进程中创建线程的完整的实现代码)。由于C r e a t e R e m o t e T h r e a d没有实现,因此,在Windows 98下,不能使用本方法来插入D L L。

  好了,现在你已经知道如何在另一个进程中创建线程了,但是,如何才能让该线程加载我们的D L L呢?答案很简单,那就是需要该线程调用L o a d L i b r a r y函数。

  我们将详细步骤说明如下:

  1) 使用Vi r t u a l A l l o c E x函数,分配远程进程的地址空间中的内存。

  2) 使用Wr i t e P r o c e s s M e m o r y函数,将D L L的路径名拷贝到第一个步骤中已经分配的内存中。

  3) 使用G e t P r o c A d d r e s s函数,获取L o a d L i b r a r y A或L o a dL i b r a t y W函数的实地址(在K e r n e l 3 2 . d l l中)。

  4) 使用C r e a t e R e m o t e T h r e a d函数,在远程进程中创建一个线程,它调用正确的L o a d L i b r a r y函数,为它传递第一个步骤中分配的内存的地址。这时, D L L已经被插入远程进程的地址空间中,同时D L L的D l l M a i n函数接收到一个D L L _ P R O C E S S _ AT TA C H通知,并且能够执行需要的代码。当D l l M a in函数返回时,远程线程

  从它对L o a d L i b r a r y的调用返回到B a s e T h r e a d S t a r t 函数(第6 章中已经介绍。然后B a s e T h r e a d S t a r t调用E x i t T h r e a d,使远程线程终止运行。

  现在远程进程拥有第一个街柚蟹峙涞哪诖婵椋鳧 L L则仍然保留在它的地址空间中。

  若要将它删除,需要在远程线程退出后执行下面的步骤:

  5) 使用Vi r t u a l F r e e E x函数,释放第一个步骤中分配的内存。

  6) 使用G e t P r o c A d d r e s s函数,获得F r e e L i b r a r y函数的实地址(在K e r n e l 3 2 . d l l中)。

  7) 使用C r e a t e R e m o t e T h r e a d函数,在远程进程中创建一个线程,它调用F r e e L i b r a r y函数,传递远程D L L的H I N S TA N C E。

  这就是它的基本操作步骤。这种插入D L L的方法存在的唯一一个不足是, Windows 98并不支持这样的函数。只能在Windows 2000上使用这种方法。

本文地址:http://com.8s8s.com/it/it26384.htm