Windows下Dll注入与API拦截

一、使用注册表来注入Dll:

使用注册表来插入DLL:HKEY_LOCAL_MACHINE\Software\Microsoft \Windows NT\CurrentVersion\Windows\AppInit_DLLs该关键字的值包含一个D L L文件名或者一组D L L文件名(用空格或逗号隔开)。由于空格用来将文件名隔开,因此必须避免使用包含空格的文件名。列出的第一个D L L文件名可以包含一个路径,但是包含路径的其他D L L均被忽略。由于这个原因,最好将你的D L L放入Wi n d o w s的系统目录中,这样就不必设定路径。为了能够让系统使用这个注册表项,我们还必须创建一个名为LoadAppInit Dlls,类型为DWORD的注册表项并将它的值设为1

当重新启动计算机及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 H,这样,每个库就能够对自己进行初始化。由于插入的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的插入状态。

二、使用Windows挂钩来注入Dll:

我们可以用挂钩来将一个 DLL注入到进程的地址空间中。为了能让挂钩的工作方式与它们在16位 Windows中的工作方式相同,Microsof 被迫设计出一种机制,这种机制可以让我们将一个 DLL 注入到另一个进程的地址空间中。让我们来看一个例子。进程A(一个类似于 Microsof Spy++的工具)为了查看系统中各窗口处理了哪些消息,安装了一个WHGETMESSAGE挂钩。这个挂钩是通过调用SetWindowsHookEx来安装的,第1个参数WH GETMESSAGE表示要安装的挂钩的类型。第2个参数GetMsgProc是一个函数的地址(在我们的地址空间中),在窗口即将处理一条消息的时候,系统应该调用这个函数。第3个参数 hInstDll 标识一个 DLL,这个DLL中包含了 GetMsgProc 函数。在Windows中,hInstD!l 的值是进程地址空间中DLL,被映射到的虚拟内存地址。最后一个参数0表示要给哪个线程安装挂钩。一个线程可能会调用SetWindowsHookEx并传入系统中另一个线程的线程标识符。通过给这个参数传0,我们告诉系统要给系统中的所有GUI线程安装挂钩。

三、使用远程线程来注入Dll:
1、用 VirtualAllocEx函数在远程进程的地址空间中分配一块内存。
2、用 WriteProcessMemory 函数把 DLL的路径名复制到第1步分配的内存中。3、用 GetProcAddress 函数来得到LoadLibraryW或LoadLibraryA 函数(在 Kermel32.dll中)的实际地址。
4、用CreateRemoteThread函数在远程进程中创建一个线程,让新线程调用正确的LoadLibrary函数并在参数中传入第1步分配的内存地址。这时,DLL已经被注入到远程进程的地址空间中,DLL的DIMain数会收到DLLPROCESSATTACH通知并且可以执行我们想要执行的代码。当DMain返回的时候,远程线程会从LoadLibraryW/A调用返回到BaseThreadStart函数(在第6章中介绍)。
BaseThreadStart 然后调用ExitThread,使远程线程终止。现在远程进程中有一块内存,它是我们在第1步分配的,DLL也还在远程进程的地址空间中。为了对它们进行清理,我们需要在远程线程退出之后执行后续步骤。
5、用 VirtualFreeEx来释放第1步分配的内存。
6、用 GetProcAddress 来得到 FreeLibrary 函数(在 Kermel32.dll 中)的实际地址。
7、用CreateRemoteThread函数在远程进程中创建一个线程,让该线程调用FreeLibrary函数并在参数中传入远程 DLL的 HMODULE。

四、使用木马DLL来注入Dll:
1、注入 DLL的另一种方式是,把我们知道的进程必然会载入的一个 DLL替换掉。举个例子如果我们知道进程会载入Xyz.dl,则可以创建自己的 DLL 并给它起同样的文件名。当然必须将原来的 Xyz.dll 改成别的名称。
2、在我们的 Xyz.dll 的内部,我们导出原来的 Xyz.d 导出的所有符号。这用(第 20 章介绍的)函数转发器很容易实现,这样一来,给某些函数安装挂钩只不过是小事一桩。但是,由于这种方法不能自动适应版本变化,因此我们应该避免使用它。比如,如果替换的是一个系统 DLL,而 Microsof 后来又在该 DLL 中添加了新的函数,那么我们的 DLL中将不会有这些新函数的转发器。引用了这些新函数的应用程序将无法被载入和执行。
3、如果只想把这种方法用在一个应用程序中,则可以给我们的 DLL起一个独一无二的名称,并修改应用程序的.exe 模块的导入段。说得更具体一点,导入段包含了一个模块所需的所有 DLL的名称。我们可以在文件的导入段中找到那个要被替换的 DLL的名称,对它进行修改,这样载入程序就会载入我们自己的DLL。这种方法也不差,但前提是我们必须相当熟悉.exe 和 DLL 的文件格式。

五、把Dll作为调试器来注入:
1、调试器可以在被调试进程中执行许多特殊的操作。系统载入一个被调试程序(debuggee)的时候,会在被调试程序的地址空间准备完毕之后,但被调试程序的主线程尚未开始执行任何代码之前,自动通知调试器。这时,调试器可以强制将一些代码注入到被调试程序的地址空间中(比如使用 WriteProcessMemory),然后让被调试程序的主线程去执行这些代码。
2、这种方法要求我们对被调试线程的CONTEXT结构进程操作,这也意味着我们必须编写与CPU相关的代码。为了让这种方法能在不同的CPU平台上正常工作,我们必须对源代码进行修改。此外,我们可能还必须手工编写一些我们想让被调试程序执行的机器语言指令。在默认的情况下,如果调试器终止,那么 Windows会自动终止被调试程序。
3、但是,调试器可以通过调用 DebugSetProcessKiOnExit 并传入FALSE 来改变默认的行为。在不终止一个进程的前提下停止调试该进程也是有可能的,这要归功于DebugActiveProcessStop函数

六、使用 CreateProcess 来注入代码:
1、如果要注入代码的进程是由我们的进程生成(spawn)的,那么事情就比较好办了。举个例子我们的进程(父进程)可以在创建新进程的时候将它挂起。这种方法允许我们改变子进程的状态,同时又不影响它的执行,因为它根本还没有开始执行。但是,父进程也会得到子进程的主线程的句柄。通过这个句柄,可以对线程执行的代码进行修改。由于我们可以设置线程的指令指针,让它执行内存映射文件中的代码,因此我们可以解决前一节提到的问题。
2、下面是让进程对它的子进程的主线程执行的代码进行控制的一种方法。

  • 让进程生成一个被挂起的子进程。
  • 从.exe 模块的文件头中取得主线程的起始内存地址。
  • 将位于该内存地址处的机器指令保存起来。
  • 强制将一些手工编写的机器指令写入到该内存地址处。这些指令应该调用LoadLibrary 来载入一个 DLL。
  • 让子进程的主线程恢复运行,从而让这些指令得到执行。
  • 把保存起来的原始指令恢复到起始地址处。
  • 让进程从起始地址继续执行,就好像什么都没有发生过一样。

3、要正确地实现第6步和第7步是比较棘手的,因为必须修改正在执行的代码。但这并非没有可能,我就曾见过这样的实现。这种方法有很多好处。首先,它在应用程序开始执行之前得到地址空间。其次,由于我们的应用程序不是调试器,因此我们可以非常容易地对应用程序和注入的 DLL进行调试。最后,这种方法同时适用于控制台应用程序和 GUI应用程序。当然,这种方法也有一些缺点。只有当我们的代码在父进程中的时候,我们才能用这种方法来注入 DLL。当然,这种方法还与CPU相关,我们必须为不同的CPU平台做相应的修改。

七、使用 Detours来注入代码:
这是微软研究院发布的,可以用来注入Win32API,非常好用。
八、Windows相关书籍阅读所得:

  1. Explorer 进程是由 UserInit 程序创建的,UserInit 是由 WinLogon 进程创建的,以下是我在“Windows 启动过程”讲义中的描述(增加解释):
  • 当用户输入用户名和密码后,负责登录的系统进程 WinLogon 将用户名和
    密码发给负责安全认证的另一个系统进程 LSASS。
  • LSASS 调用验证模块对用户名和密码进行验证,如果通过,则创建一个访
    问令牌对象。
  • WinLogon 启动 HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ Winlogon 的 UserInit 表键下指定的程序(默认为 userinit.exe)。
  • UserInit 进程执行登录和初始化脚本,然后启动 Shell 表键中定义的 Shell
    程序,默认即是 Explorer.exe。
    因为这台电脑没有设置密码,开机后自动登录,所以前两个步骤应该都通过了,很可能是在执行 UserInit 时出了问题。
  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值