通过APC卸载注入进程的DLL

admin 2025-12-25 03:11:28 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文介绍了通过APC注入自定义代码从目标进程卸载DLL以消除痕迹的方法。针对多函数调用及跨进程寻址难题,作者编写了调用GetModuleHandle和FreeLibrary的自定义函数,利用PE解析获取代码段大小,并采用绝对地址指针调用系统API。实验验证了该方法能成功卸载注入任务管理器的DLL。 综合评分: 90 文章分类: 红队,二进制安全,内网渗透


cover_image

通过 APC 卸载注入进程的 DLL

原创

MyStackTrace

MyStackTrace

2025年12月15日 00:00 上海

在前面我们实现了通过 APC 把 DLL 注入到指定进程中,但是在这个 DLL 做完该做的事之后,我们还需要把它从目标进程中给卸载了,这样才能做到不留痕迹。这篇文章我们就来说说如何通过 APC 把已经注入到目标进程中的 DLL 给卸载了。

在 Windows 上要想卸载进程中的某个 DLL,我们可以通过API FreeLibrary,这个函数需要一个指向该 DLL 的句柄,这个句柄就是加载 DLL 的 API LoadLibrary 返回的句柄。

但是 DLL 已经被注入到目标进程中了,LoadLibrary 已经调用过了,而且我们并没有记录该函数的返回值,也就是没有记录这个 DLL 对应的句柄,所以要想再次通过 APC 来卸载这个 DLL,我们就得想其他办法来获取这个 DLL 的句柄了,这里我们就需要另一个 API 了,这就是 GetModuleHandle。

GetModuleHandle 可以根据 DLL 的名字返回指定模块的句柄,有了这个句柄我们就可以通过 FreeLibrary 将其卸载了(需要该 DLL 模块没有其他的引用了)。

总结一下我们要做的事就是在注入到目标进程的 APC 中先调 GetModuleHandle 找到前面注入的 DLL 的句柄,然后调用 FreeLibrary 把这个 DLL 从目标进程中卸载了。但是这里有个问题需要我们考虑,前面通过 APC 把 DLL 注入到另一个进程的时候,我们直接使用了 LoadLibrary 函数作为 APC 的执行函数,但是这里在卸载 DLL 的时候,我们需要调用两个函数,所以需要我们单独写一个 APC 执行函数,在这个函数中先调 GetModuleHandle,再调 FreeLibrary。由于这个 APC 执行函数是我们自己写的函数,不像之前的 LoadLibrary 函数那样属于系统 DLL(Kernel32.dll)导出的函数,因此它原本是不会出现在目标程序中的,但是我们又需要在目标程序中执行这个 APC 函数,所以这里又有两个问题需要我们考虑了,第一个问题是我们如何把这个 APC 执行函数拷贝到目标进程中去执行,其实做法和我们前面把 DLL 的路径通过远程内存拷贝(WriteProcessMemory)到目标进程中是一样的,只不过这里我们要拷贝的是一个 APC 执行函数编译后的二进制代码。第二个问题是在我们写这个 APC 执行函数的时候我们需要调用 GetModuleHandle 和 FreeLibrary,在编译器编译这个 APC 执行函数时,会把对这两个函数的调用编译成 call 或者 jmp 指令,而且是使用相对地址的 call 或者 jmp 指令,这里说的相对地址是在注入程序中的相对地址,是 call 或者 jmp 指令所在的地址距离目标函数所在地址的相对地址,但是这个 APC 是要被拷贝到目标进程中去执行的,拷贝后 APC 执行函数在目标进程中的地址是不确定的,因此使用相对地址是会出问题的,所以我们不能在 APC 执行函数中直接调用 GetModuleHandle 和 FreeLibrary 这两个函数,而是需要采用函数指针的方式来调用这两个函数,这样在编译的时候编译器会把对这两个函数的调用编译成间接调用的 call 指令,而间接调用的目标地址需要使用绝对地址,又因为 GetModuleHandle 和 FreeLibrary 这两个函数都是系统动态链接库 Kernel32.dll 中导出的函数,它们地址在所有进程中都是一样的,所以我们只需要把这两个函数的地址传入 APC 执行函数,在 APC 执行函数中采用间接调用的方式就能解决这第二个问题。

拷贝函数其实就是拷贝函数编译之后的二进制代码,需要我们知道函数二进制代码的起始地址和长度,起始地址其实就是函数名对应的那个符号的地址,但是要获取函数的长度就比较麻烦,不过我们可以采用迂回的方法,把这个 APC 执行函数单独放到一个我们自定义的代码段中,在程序执行的时候,我们可以通过遍历这个程序的 Section Headers,找到这个自定义的代码段,这样就可以获取到这个代码段的长度,也就是这个 APC 执行函数的长度了。

好了,所有的问题都有了解决办法,下面开始写代码。首先我们来定义这个 APC 执行函数 ApcFunc 以及它的参数类型 APC_PARAMETER。结构体 APC_PARAMETER 有三个参数,分别是要卸载的 DLL 的名字(DllName),指向函数 GetModuleHandleA 的函数指针和指向函数 FreeLibrary 的函数指针。函数 ApcFunc 在执行的时候,先调用 GetModuleHandleA 函数指针,根据传入的 DllName 找到目标进程中该 DLL 的句柄,然后把该句柄传入 FreeLibrary 函数中,这个 DLL 就被从目标进程中卸载了。

上面的这个 ApcFunc 函数和我们平时写代码时定义的函数有点不同的是我们为这个函数单独分配了一个自定义的代码段 .MyCode,这就是那个 #pragma code_seg 的作用,这是微软的编译器 MSVC 的预处理指令,作用是在编译的时候把它修饰的函数编进指定的自定义代码段中(不在 .text 中)。后面那句 #pragma commet 的作用是指定这个代码段的属性是可执行和可读的,其实只有一个可读属性就行,因为我们不需要在我们的程序中来执行这个函数,只需要能够把它拷贝到目标进程中去执行就可以了。

下面是获取这个 APC 执行函数所在的这个自定义代码段(.MyCode)长度的函数。首先通过函数 GetModuleHandle (设置 ModuleName 参数为 NULL)获取当前程序对应模块句柄,然后将其转化为 PE (Windows 可执行文件格式 Portable Executable)的 DOS 头,然后进一步找到 NT 头,最后找到 Section Headers,并且对所有的 Section 进行遍历直到找到名字为 .MyCode 的 Section,我们使用这个 Section 的 SizeOfRawData 作为 APC 执行函数拷贝的长度(其实要比 APC 执行函数本身的长度要大,因为这是个按照扇区大小对齐的长度)。

下面是初始化 APC 参数 APC_PARAMETER 的函数。首先我们需要在目标进程(hProcess)中分配远程内存用来存放要卸载的 DLL 的名字,然后使用函数 WriteProcessMemory 把 DLL 名字从注入程序拷贝到目标进程中,这样 APC 就能够访问到这个 DLL 的名字字符串了。最后我们把函数 GetModuleHandle 和 FreeLibrary 的(绝对)地址分别赋给 APC_PARAMETER 中的 GetModuleHandleFuncPtr 和 FreeLibraryFuncPtr 这两个函数指针,这样 APC 执行函数 ApcFunc 在目标进程中执行的时候就可以通过这两个函数指针来调用 GetModuleHandle 和 FreeLibrary 了。

最后我们来看下完整的代码,其实就是把上面那几个函数组合起来,首先获取 APC 执行函数所在的自定义代码段(.MyCode)的长度,然后初始化好 APC 参数结构体 APC_PARAMETER,其中包括要卸载的 DLL 的名字以及 GetModuleHandle 和 FreeLibrary 的函数指针。接下来就是为 APC 执行函数和 APC 参数在目标进程中分配远程内存,并且把 APC 执行函数和 APC 参数拷贝到这两块远程内存中。后面的操作就和前面使用 APC 注入 DLL 的操作都是一样的,遍历目标进程中的所有线程,向这些线程注入卸载 DLL 的 APC,这里就不再赘述了。

下面来看下执行效果,使用这个程序能否把我们注入到目标进程的 DLL 给卸载掉。首先使用之前写的 InjectDllByAPC.exe 把那个 SetAntiScreenShot.dll 注入到任务管理器中。

我们使用 ProcessExplorer 捕捉到任务管理器进程加载  SetAntiScreenShot.dll 的瞬间,就是下面图中绿色的那一行,所以 DLL 注入是成功的。

最后我们使用这次写的 UnloadDllbyAPC.exe 程序,把 SetAntiScreenShot.dll 从任务管理器进程中给卸载了。

我们使用 ProcessExplorer 捕捉到任务管理器进程卸载 SetAntiScreenShot.dll 的瞬间,就是下图中 SetAntiScreenShot.dll 那一行变红的样子,所以 DLL 的卸载是成功的。

最后总结一下,其实这篇文章主要讲了如何在目标进程中通过 APC 注入一段自定义的代码,这个方法也同样适用于通过创建远程线程的方式在目标进程中注入自定义的代码。这个方法麻烦的点在于需要把要注入执行的代码拷贝到目标进程中,还要保证这个代码中所有的 call/jmp 指令能够在目标进程中找到正确的跳转地址,最简单的办法就是采用间接调用或者跳转的方式,以绝对地址的方式指定跳转目标。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:MyStackTrace MyStackTrace《通过 APC 卸载注入进程的 DLL》

评论:0   参与:  3