插入DLL和挂接API  

  在Microsoft Windows中,每个进程都有它自己的私有地址空间。当使用指针来引用内存时,指针的值将引用你自己进程的地址空间中的一个内存地址。你的进程不能创建一个其引用属于另一个进程的内存指针。因此,如果你的进程存在一个错误,改写了一个随机地址上的内存,那么这个错误不会影响另一个进程使用的内存。

  独立的地址空间对于编程人员和用户来说都是非常有利的。对于编程人员来说,系统更容易捕获随意的内存读取和写入操作。对于用户来说,操作系统将变得更加健壮,因为一个应用程序无法破坏另一个进程或操作系统的运行。当然,操作系统的这个健壮特性是要付出代价的,因为要编写能够与其他进程进行通信,或者能够对其他进程进行操作的应用程序将要困难得多。
有些情况下,必须打破进程的界限,访问另一个进程的地址空间,这些情况包括:
• 当你想要为另一个进程创建的窗口建立子类时。
• 当你需要调试帮助时(例如,当你需要确定另一个进程正在使用哪个DLL时)。
• 当你想要挂接其他进程时。
本章将介绍若干种方法,可以用来将DLL插入到另一个进程的地址空间中。一旦你的DLL进入另一个进程的地址空间,就可以对另一个进程为所欲为。这一定会使你非常害怕,因此,究竟应该怎样做,要三思而后行。

1.插入DLL:一个例子

  假设你想为由另一个进程创建的窗口建立一个子类。你可能记得,建立子类就能够改变窗口的行为特性。若要建立子类,只需要调用SetWindowLongPtr函数,改变窗口的内存块中的窗口过程地址,指向一个新的(你自己的)WndProc。Platform SDK文档说,应用程序不能为另一个进程创建的窗口建立子类。这并不完全正确。为另一个进程的窗口建立子类的关键问题与进程地址空间的边界有关。
  当调用下面所示的SetWindowsLongPtr函数,建立一个窗口的子类时,你告诉系统,发送到或者显示在hwnd设定的窗口中的所有消息都应该送往MySubclassProc,而不是送往窗口的正常窗口过程:

SetWindowLongPtr(hwnd, nIndex, MySubclassProc);

参数说明编辑

hWnd:窗口句柄,间接给出窗口所属的类。
nlndex:指定将设定的大于等于0的偏移值。有效值的范围从0到额外类的存储空间的字节数减去一个整型的大小(-sizeof(int))。要设置其他任何值,可以指定下面值之一:[1]
nlndex 说明
GWL_EXSTYLE
设定一个新的扩展风格。更多信息,请见CreateWindowEx
GWL_STYLE 设定一个新的窗口风格。
GWL_WNDPROC 为窗口过程设置一个新的地址。
GWL_HINSTANCE 设置一个新的应用程序实例句柄。
GWL_ID 设置一个新的窗口标识符。
GWL_USERDATA
设置与该窗口相关的用户数据。这些用户数据可以在程序创建该窗口时被使用。用户数据的初始值为0。
当hWnd参数标识了一个对话框时,也可使用下列值:
DWL_DLGPROC 设置对话框过程的新地址。
DWL_MSGRESULT 设置对话框中的消息处理程序的返回值。
DWL_USER 设置的应用程序所私有的新的额外信息,例如句柄或指针。

dwNewLong:指定的替换值。

  换句话说,当系统需要将消息发送到指定窗口的WndProc时,要查看它的地址,然后直接调用WndProc。在本例中,系统发现MySubclassProc函数的地址与窗口相关联,因此就直接调用MySubclassProc函数。

  由于对这个问题的解决并没有什么万全之策,因此Microsoft决定不让SetWindowsLongPtr改变另一个进程创建的窗口过程。
  不过仍然可以为另一个进程创建的窗口建立子类—只需要用另一种方法来进行这项操作。这并不是建立子类的问题,而是进程的地址空间边界的问题。如果能将你的子类过程的代码放入进程A的地址空间,就可以方便地调用SetWindowsLongPtr函数,将进程A的地址传递给MySubclassProc函数。我将这个方法称为将DLL“插入”进程的地址空间。有若干种方法可以用来进行这项操作。下面将逐个介绍它们。

2.使用注册表来插入DLL

  如果你曾经多少使用过Windows操作系统,你肯定熟悉注册表的情况。整个系统的配置都是在注册表中维护的,可以通过调整它的设置来改变系统的行为特性。

  下图显示了使用Registry Editor(注册表编辑器)时该关键字中的各个项目的形式。该关键字的值包含一个D L L文件名或者一组D L L文件名(用空格或逗号隔开)。由于空格用来将文件名隔开,因此必须避免使用包含空格的文件名。列出的第一个D L L文件名可以包含一个路径,但是包含路径的其他D L L均被忽略。由于这个原因,最好将你的D L L放入Wi n d o w s的系统目录中,这样就不必设定路径。在窗口中,我将该值设置为单个D L L路径名C : \ M y L i b . d l l。

                         图 注册表窗口

  当重新启动计算机及Wi n d o w s进行初始化时,系统将保存这个关键字的值。然后,当U s e r 3 2 . d l l库被映射到进程中时,它将接收到一个D L L P R O C E S S AT TA C H通知。当这个通知被处理时,U s e r 3 2 . d l l便检索保存的这个关键字中的值,并且为字符串中指定的每个D L L调用L o a d L i b r a r y函数。当每个库被加载时,便调用与该库相关的D l l M a i n函数,其f d w R e a s o n的值是D L L P R O C E S S AT TA C ,这样,每个库就能够对自己进行初始化。由于插入的D L L在进程的寿命期中早早地就进行了加载,因此在调用函数时应该格外小心。调用k e r n e l 3 2 . d l l中的函数时应该不会出现什么问题,不过调用其他D L L中的函数时就可能产生一些问题。U s e r 3 2 . d l l并不检查每个库是否已经加载成功,或者初始化是否取得成功。

  在插入D L L时所用的所有方法中,这是最容易的一种方法。要做的工作只是将一个值添加到一个已经存在的注册表关键字中。不过这种方法也有它的某些不足:
  • 由于系统在初始化时要读取这个关键字的值,因此在修改这个值后必须重新启动你的计算机—即使退出后再登录,也不行。当然,如果从这个关键字的值中删除D L L,那么在计算机重新启动之前,系统不会停止对库的映射操作。
  • 你的D L L只会映射到使用U s e r 3 2 . d l l的进程中。所有基于G U I的应用程序均使用U s e r 3 2 . d l l,不过大多数基于C U I的应用程序并不使用它。因此,如果需要将D L L插入编译器或链接程序,这种方法将不起作用。
  • 你的D L L将被映射到每个基于G U I的应用程序中,但是必须将你的库插入一个或几个进程中。你的D L L映射到的进程越多,“容器”进程崩溃的可能性就越大。毕竟在这些进程中运行的线程是在执行你的代码。如果你的代码进入一个无限循环,或者访问的内存不正确,就会影响代码运行时所在进程的行为特性和健壮性。因此,最好将你的库插入尽可能少的进程中。
  • 你的D L L将被映射到每个基于G U I的应用程序中。这与上面的问题相类似。在理想的情况下,你的D L L只应该映射到需要的进程中,同时,它应该以尽可能少的时间映射到这些进程中。假设在用户调用你的应用程序时你想要建立Wo r d P a d的主窗口的子类。在用户调用你的应用程序之前,你的D L L不必映射到Wo r d P a d的地址空间中。如果用户后来决定终止你的应用程序的运行,那么你必须撤消Wo r d P a d的主窗口。在这种情况下,你的D L L将不再需要被插入Wo r d P a d的地址空间。最好是仅在必要时保持D L L的插入状态。

3.使用Windows挂钩来插入DLL

  可以使用挂钩将DLL插入进程的地址空间。为了使挂钩能够像它们在16位Windows中那样工作,Microsoft不得不设计了一种方法,使得DLL能够插入另一个进程的地址空间中。
  下面让我们来看一个例子。进程A(类似Microsoft Spy++的一个实用程序)安装了一个挂钩WNGETMESSAGE,以便查看系统中的各个窗口处理的消息。该挂钩是通过调用下面的SetWindowsHookEx函数来安装的:

HHook hHook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, hinstDll, );

  第一个参数W H G E T M E S S A G E用于指明要安装的挂钩的类型。第二个参数G e t M s g P r o c用于指明窗口准备处理一个消息时系统应该调用的函数的地址(在你的地址空间中)。第三个参数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的值用于标识D L L被映射到的进程的地址空间中的虚拟内存地址。最后一个参数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 B = hinstDll B + (GetMsgProc A - hinstDll A)
将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的环境下运行的线程中调用。

未完待续...

Windows API学习---插入DLL和挂接API的更多相关文章

  1. 淘宝API学习之道:淘宝API相关了解

    淘宝API开发平台,经过两年多的升级一系列动作,提供的api接口日渐稳定.看到淘宝api开发的浏览量还是较大,但那会写的DEMO如今已不能执行,淘宝改了链接地址,改了加密算法,为了不让大家浪费时间,特 ...

  2. Windows Dll Injection、Process Injection、API Hook、DLL后门/恶意程序入侵技术

    catalogue 1. 引言2. 使用注册表注入DLL3. 使用Windows挂钩来注入DLL4. 使用远程线程来注入DLL5. 使用木马DLL来注入DLL6. 把DLL作为调试器来注入7. 使用c ...

  3. Windows API 学习

    Windows API学习 以下都是我个人一些理解,笔者不太了解windows开发,如有错误请告知,非常感谢,一切以microsoft官方文档为准. https://docs.microsoft.co ...

  4. Windows录音API学习笔记(转)

    源:Windows录音API学习笔记 Windows录音API学习笔记 结构体和函数信息  结构体 WAVEINCAPS 该结构描述了一个波形音频输入设备的能力. typedef struct { W ...

  5. Windows录音API学习笔记

    Windows录音API学习笔记 结构体和函数信息  结构体 WAVEINCAPS 该结构描述了一个波形音频输入设备的能力. typedef struct { WORD      wMid; 用于波形 ...

  6. Windows录音API学习笔记--转

    Windows录音API学习笔记 结构体和函数信息  结构体 WAVEINCAPS 该结构描述了一个波形音频输入设备的能力. typedef struct { WORD      wMid; 用于波形 ...

  7. nodejs利用windows API读取文件属性(dll)

    nodejs调用delphi编写的dll中,使用了dll调用windows api转读取文件属性,感觉使用nodejs也可直接调用windows api. 此处需用到windows系统的version ...

  8. windows进程/线程创建过程 --- windows操作系统学习

    有了之前的对进程和线程对象的学习的铺垫后,我们现在可以开始学习windows下的进程创建过程了,我将尝试着从源代码的层次来分析在windows下创建一个进程都要涉及到哪些步骤,都要涉及到哪些数据结构. ...

  9. Native Application 开发详解(直接在程序中调用 ntdll.dll 中的 Native API,有内存小、速度快、安全、API丰富等8大优点)

    文章目录:                   1. 引子: 2. Native Application Demo 展示: 3. Native Application 简介: 4. Native Ap ...

随机推荐

  1. scala高级内容(一) Case Class

    一. 操作符 自定义操作符 操作付默认左结合调用.除了以:结尾的操作符是右结合调用 object OperaterTest extends App{ val a: myInt = new myInt( ...

  2. CF 461B Appleman and Tree 树形DP

    Appleman has a tree with n vertices. Some of the vertices (at least one) are colored black and other ...

  3. php命名空间学习

    在一个命名空间中,当 PHP 遇到一个非限定的类.函数或常量名称时,它使用不同的优先策略来解析该名称. 1.类名称总是解析到当前命名空间中的名称.因此在访问系统内部或不包含在命名空间中的类名称时,必须 ...

  4. memcache 内部原理实现

    Lazy Expiration memcached 内部不会监视记录是否过期,而是在 get 时查看记录的时间戳,检查记录是否过期.这 种技术被称为 lazy(惰性)expiration.因此,mem ...

  5. phpStudy Linux安装集成环境 (CentOS--7)

    phpStudy for Linux (lnmp+lamp一键安装包) phpStudy for Linux 支持Apache/Nginx/Tengine/Lighttpd, 支持php5.2/5.3 ...

  6. chrome调试js工具的使用

    Audits标签页 这个对于优化前端页面.加速网页加载速度很有用哦(相当与Yslow): 点击run按钮,就可以开始分析页面,分析完了就可以看到分析结果了: 它甚至可以分析出页面上样式表中有哪些CSS ...

  7. Jmeter监控系统等资源,ServerAgent端口的修改

    默认情况下在下载的ServerAgent下,如果服务是windows系统,则直接启动"startAgent.bat"即可,如果是Linux系统,则直接启动"./start ...

  8. oracle新建数据库时怎么选择编码格式

    源地址:https://zhidao.baidu.com/question/2009631596107727508.html 启动database configuration assistant,创建 ...

  9. UTF-8 GB2321

    已知:汉字“你”UTF8编码是 0xe4bda0,GBK编码是 0xc4e3,求证. 0xe4bda0 → 2进制11100100 10111101 10100000 → UTF8 3字节111001 ...

  10. 最大子序列和(O(n))

    下面介绍一个线性的算法,这个算法是许多聪明算法的典型:运行时间是明显的,但是正确性则很不明显(不容易理解). //线性的算法O(N) long maxSubSum4(const vector<i ...