7-DLL劫持注入(涉及白加黑)

一、前言

本节内容涉及 白加黑 技术,白加黑并不是一个很新的技术,但是在红队攻防中应用非常广泛,且十分有效,究其原因是一个白程序(有数字签名的),能加载你的黑DLL,这样AV/EDR都得有所顾忌,不敢乱杀。

1.1 DLL简介

在Windows中,许多应用程序并不是一个完整的可执行文件,一个大型项目的许多功能被拆分成相对独立的动态链接库,即DLL文件(也是PE文件的一种)。当一个程序需要到一个DLL里面的函数或者数据时,就会将相应的DLL加载到自己的虚拟地址空间中。

一个DLL文件可以被多个应用程序直接使用,这样的DLL文件被称为共享DLL文件

1.2 DLL的加载顺序

当应用程序动态加载动态链接库而不指定完全限定的路径名称时,Windows 会尝试通过按特定顺序搜索一组定义完善的目录来查找 DLL。如果攻击者控制 DLL 搜索路径上的某个目录,则可以在该目录中放置 DLL 的恶意副本

DLL劫持技术利用的是Windows对DLL访问时查找DLL位置的一个漏洞,Windows对DLL的默认查找顺序(XP SP2及之后)如下:

官方文档: 1、 Dynamic-link library search order - Win32 apps | Microsoft Learnarrow-up-right

2、动态链接库安全性 - Win32 apps | Microsoft Learnarrow-up-right

  1. 从中加载应用程序的目录。

  2. 系统目录。

  3. 16 位系统目录。

  4. Windows 目录。

  5. 当前目录。

  6. PATH 环境变量中列出的目录。

当应用程序发出 LoadLibrary 调用时,系统会搜索 DLL,在当前目录中查找 DLL 的恶意副本,然后加载它。 然后,DLL 的恶意副本在应用程序中运行,并获取用户的权限。

说的有点啰嗦了,实际的劫持中我们排除某些KnownDlls,排除某些绝对路径加载的DLL,找到可以被劫持的DLL,然后将恶意的DLL放置到原始DLL的路径即可。

二、流程和代码实现

DLL的导出函数:在 C++ 中,导出函数(Export Functions)通常是为了创建动态链接库(DLL)或共享库,以便其他程序或模块可以调用这些函数

导出方式

详见:C++开发基础之创建DLL的常见的6种导出函数(Export Functions)形式_c++ 导出函数-CSDN博客arrow-up-right

注意:DLL劫持注入中,恶意DLL应该与原始DLL某一个函数具有相同的导出函数声明,这样应用程序才能正确调用我们的恶意DLL里的函数。

2.1 DLL劫持测试

  1. 构造一个原始DLL

  2. 构造一个恶意DLL

  3. 构造一个应用程序exe

构造一个原始DLL

构造一个恶意DLL

应用程序代码

2.2 劫持特定函数接口型(Dll漏洞挖掘及白加黑利用)

  1. 寻找目标程序除微软DLL外的可被劫持的DLL

  2. 构造一个恶意DLL,并声明一个与原始DLL一样的导出函数声明

  3. 将恶意DLL放入到原始DLL目录中,并将恶意DLL的名称改为原始DLL的名称。

  4. 运行目标程序

现在有一个问题,就是我们如何知道被劫持的DLL里面的导出函数呢?手动去找不太现实,可以利用工具批量查找可利用的DLL。

工具:GitHub - HackerCalico/SkyShadow: Generate DLL Hijacking Payload in batches.arrow-up-right

将其中的scan.py分成两部分,一个命名为:scan_MSDLL.py,另一个命名为:scan_DLLHijack.py,首先使用 scan_MSDLL.py 来搜寻微软的DLL,接着使用 scan_DLLHijack.py 来搜寻可以劫持的DLL。

判断了一下获取到的dll如果开头为API以及结尾为HELP或API直接丢弃掉,然后就是判断导出的dll如果大于1个那么也丢弃掉,因为白加黑只需要一个dll,如果导出来太多dll的话没啥用。

注意:使用工具前,请将Tool目录添加到环境变量。

详见:白加黑详解arrow-up-right

scan_MSDLL.py

使用默认搜寻路径 python .\scan_MSDLL.py

scan_DLLHijack.py

python .\scan_DLLHijack.py "C:\\"

随便打开一个输出结果的txt,看看里面的导出函数声明,然后将代码复制到VS编译成一个恶意DLL,将恶意DLL的名称修改为 chrome_elf.dll

来到原始DLL的路径 C:\Program Files\Google\Chrome\Application\131.0.6778.265。路径不一定相同,可以使用IDA PRO、windbg、x32/64dbg等工具去看 Chrome 真正加载的dll。

原始DLL:chrome_elf_old.dll 恶意DLL:chrome_elf.dll

来到Chrome.exe真正的目录 C:\Program Files\Google\Chrome\Application

点击运行exe,成功劫持

recording.gif

测试完之后,记得把恶意DLL chrome_elf.dll 给删除,原始DLL chrome_elf_old.dll 的名称修改为 chrome_elf.dll

2.3 DLL函数转发(也称DLL旁路加载)

  1. 寻找目标程序除微软DLL外的可被劫持的DLL

  2. 根据转发表,构造一个恶意DLL(用AheadLib工具)。

  3. 将恶意DLL放入到原始DLL目录中,并将恶意DLL的名称改为原始DLL的名称。

  4. 运行目标程序

可以看到上面的GIF图,我点击运行了Chrome,虽然成功执行了我们的恶意代码,但是Chrome并没有正常的执行。这样并不利于我们在实战中使用,所以在编写一个恶意DLL时,我们通常除了让dll的导出表与原dll一致,同时对函数进行向原dll的转发,这样我们就能在用户毫无察觉的情况下运行自定义的代码。

DLL函数转发

  1. A DLL,其导出函数有 funA,可以供主程序 exe 调用;

  2. B DLL,其导出函数有 funB1, funB2,但是开发者开发时,标记了 funB2 函数实际调用的是 A DLL 的 funA 函数,那么主程序在调用的时候:exe -> funB2 -> funA,即 funB2 只是起到一个中转的作用,前面讲到,我们在调用 DLL 的导出函数时,需要将对应的 DLL 载入内存,获取到其导出函数的内存地址,才能进行调用,所以 exe 在调用 B DLL的 funB1 和 funB2 函数时,不仅需要将 B DLL 载入内存,而且还需要将 A DLL 载入内存。

  3. 这个知识点对我们后续开发恶意DLL有很重要的作用,比如说我们可以在dllmain函数中编写恶意代码,而不影响程序的正常执行。

转发函数的形式:

如果被劫持的DLL拥有多个导出函数,则我们就需要全部转发,那么怎么批量生成转发函数表呢?可以使用 AheadLib 工具:[下载] AheadLib修改 支持x64支持类/命名空间-编程技术-看雪-安全社区|安全招聘|kanxue.comarrow-up-right

我这里选择的是直接转发函数,当然你也可以用即使调用函数,它们的原理不相同但是达到的效果差不多。

挑选一个可被劫持的DLL,如果要转发的函数太多了,选择一个合适的路径输出cpp文件。

dllmain和EvilFuntion的代码如下,然后将转发函数表代码复制到dllmain函数的下方中。

生成我们的恶意dll并改名,原始DLL改成以Org为后缀。

测试

recording.gif

可以看到,应用程序加载了恶意DLL和原始DLL,这样应用程序就能正常启动,达到隐蔽持久化的目的。

2.4 通用DLL劫持型

这是吾爱破解社区里面的一位大佬分享的技术。通用DLL劫持 ,这是一个 DLL 劫持的底层实现,主要通过直接操作 PEB(进程环境块)和 LDR(加载器)数据结构来替换已加载的 DLL。详细的原理介绍请看 参考文章-1

参考文章: 1、一种通用DLL劫持技术研究 - 吾爱破解 - 52pojie.cnarrow-up-right

2、 GitHub - anhkgg/SuperDllHijack: SuperDllHijack:A general DLL hijack technology, don't need to manually export the same function interface of the DLL, so easy! 一种通用Dll劫持技术,不再需要手工导出Dll的函数接口了arrow-up-right

3、PEB (winternl.h) - Win32 apps | Microsoft Learnarrow-up-right

4、PEB_LDR_DATA (winternl.h) - Win32 apps | Microsoft Learnarrow-up-right

5、Vergilius Project | Kernelsarrow-up-right

官方文档的数据结构中某些字段的含义并没有说明,详细字段含义请看参考文章-5

  1. 数据结构定义

  1. 获取 PEB 数据结构的地址

注意

  • 获取当前进程 PEB:使用 __readgsqword(0x60) 或(void*)__readfsdword(0x30)

  • 获取远程进程 PEB:使用上下文寄存器方法(进程镂空注入(Process Hollowing Injection) 有介绍)

  1. 获取 PEB 中的 Ldr 数据结构的地址

  1. 核心劫持函数

没成功,不过有些思路可以学习一下。

完整代码

Last updated