11.动态代码注入技术详解

1.前言

注入技术中,最常用的实际上是dll注入,其好处在于方便省事,你只需要关注你dll中本身的代码逻辑即可。

一个很常见的例子是,你会看到很多破解软件的方式,都是让你将一个dll文件放在软件安装目录下完成破解。

其原理并不复杂,因为该dll一般就是该程序必加载的系统dll,一般放在系统目录下而不是在软件安装目录下。

可windows系统加载dll的机制却是先在当前目录下找,找不到才去找系统目录,找到了就直接加载,这样就轻松完成了dll注入操作。

但各种安全管家都会将这种行为视作一种病毒行为,会拦截这样的操作,现象便是你一复制进安装目录,安全管家就给你删掉了。

这种便是静态注入的方式。

而本文要介绍的动态注入就要复杂的多了,它涉及到非常多Windows机制原理,其最终能够实现的效果便是,无需理会其原程序,我们直接在自己的程序中控制目标程序的各种行为。

比如常见的外挂软件,就是一个独立的程序,但它却能在游戏启动的时候识别、并修改游戏中的各种属性数据。

2.理论知识

想要做到上面这样的效果,我们必须要了解一些底层知识。

我们在C/C++中经常使用指针,而指针实际上就是一个存储地址的变量,那么你有没有想过,如果两个exe程序中都去修改、读取同一个地址上的变量,那么它们实际操作的是同一个内存数据吗?

显然,根据我们的经验就能推测出来,这肯定是不行的,毕竟我们电脑上运行了这么多软件,软件的作者不尽相同,怎么可能知道别人用了哪些内存地址上的数据?

为了防止这样混乱的行为发生,windows系统就提供了进程空间的机制。

虽然电脑所有软件用的都是相同的电脑内存,但软件不能直接通过地址访问到实际的内存,而是要通过一个中转站。

类似下面这样:

graph TD;
	A[进程1]--访问0x123456地址-->C[Windows内存管理器];
	B[进程2]--访问0x123456地址-->C;
	C--进程1访问0x222地址-->D[电脑真实内存];
	C--进程2访问0x333地址-->D;

虽然两个进程访问的都是同一个地址,但经过了中转站后,它们访问的真实内存地址并不相同。

而中转站在这里仅仅起到了一个映射作用,比如此时上图的映射关系为:进程1 0x123456 == 0x222进程2 0x123456 == 0x333

这样做的好处就在于,不同进程之间完全隔离开了,无论怎么访问地址,都无法操作其它进程中的数据。

而我们之所以要进行代码注入,就是为了能让我们的代码运行在目标进程空间内部,这样代码操作的内存数据才是目标内存中的数据。

dll注入就是最简便的注入方式,只要目标进程加载了dll,那么dll就存在于目标进程之中,此时就可以随意修改数据信息了。

而本文要介绍的方式便是更为高级、复杂的手段,让我们可以在自己的进程中去操作目标进程中的数据,无需dll注入。

3.从零开始

经过前面的学习我们已经知道了,Windows系统提供了我们以下几个win api可以让我们控制其它进程:

  1. 在本进程中通过OpenProcress函数打开想要Hook的程序
  2. 使用VirutalAllocEx在目标进程中分配一块空间。
  3. WriteProcessMemory函数在分配的空间中写上我们想要让其执行的代码,以及在哪里进行跳转等等
  4. 一般还需要用ReadProcessMemory函数读取、保留其原本位置的数据,用于后面的恢复
  5. 可能还需要用VirutalProtectEx函数更改目标内存的权限,使其允许我们读写目标进程的内存。
  6. 可能还需要使用CreateRemoteThread函数在目标进程内新创建一个线程执行我们复制过去的代码

但现在问题来了,我们能复制过去的代码有哪些?怎么复制?难不成要自己手搓二进制然后复制过去不成?

这显然是不现实的,大多数时候,我们更倾向于使用vs编写C/C++函数,然后直接将编译好的整个函数二进制都复制到目标进程中去。

就像下面这样:

image.png

这里的get_kernel_addr就是一个函数,而这个函数名实际上就是这个函数在本进程中的内存地址,直接将这个函数整个复制到目标进程中即可。

所以现在我们已经知道了如何将一个函数复制到目标进程中去,那么下一个问题便是,这个复制过去的函数里面我们能写什么东西?

因为此时你要知道,由于该函数是在本进程中进行编译运行的,所以该函数内部如果调用任何函数,比如即使是最简单的printf函数,该函数的地址也属于本进程,一旦将其复制到目标进程,就会在相应的地址上找不到函数,一运行就会崩溃。

流程如下:

sequenceDiagram
	participant A as 进程1
	participant B as 进程2
	A-->>A: printf函数的地址为0x123456
	B-->>B: printf函数的地址为0x654321
	A->>B: 复制含printf函数地址0x123456的代码
	B-->>B: 由于0x123456地址上不是printf函数,程序运行该函数会崩溃

这样一来就出现了一个非常严重的问题:我们可以将代码复制过去,但这段代码中不能调用任何函数。

可一旦无法调用任何函数,那么我们将这段代码复制过去的意义是什么呢?根本什么都干不了!

这便是本节从零开始的含义,我们需要从零在目标进程中找到我们所需要的函数地址,然后就能调用它们!

这一切的源头便在于kernel32.dll这个Windows动态库,这是windows的核心动态库,windows系统中所有的exe程序,只要运行起来,都会默认自动加载该dll。

并且最重要的是,几乎所有重要的winapi,都存放在了这个dll中,因此只要我们能够找到这个dll在目标进程中的地址,我们就能够从该dll中解析出来其中win api的地址,然后我们就能在目标进程中调用这些win api!

此时流程如下:

作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux