原理

DLL Hijacking Vulnerability

如果在进程尝试加载一个dll时,没有指定DLL的绝对路径,那么Windows会尝试去按照顺序搜索这些特定目录来找到这个DLL,如果攻击者能够将恶意DLL放在优先于正常DLL所在目录,那么就能够欺骗系统去加载恶意的dll,形成”劫持”

当程序需要某些功能时候,通过DLL执行相应功能,叫做DLL调用

利用角度:
如果替换掉了这个DLL文件,或者导出了原DLL的导出函数并恶意构造,在原程序调用正常DLL前调用了我们预先构造好的恶意DLL,那就达到了劫持的效果
所以手段有多种,比如替换原DLL文件,黑DLL放置最先路径,DLL转发等.

条件

  1. 不在”HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs”注册表中
  2. 其DLL是exe程序首先加载的DLL,而不是依赖其他dll加载的
  3. DLL确实被加载进内存中

DLL路径搜索目录顺序

当loadlibrary()调用的时候,系统会从以下顺序路径寻找dll

正常程序运行时候,应用程序可以通过指定完整路径,使用DLL重定向或通过使用清单来控制DLL的加载位置 若未使用这些方法,则系统在加载时搜索DLL 如下

  • 如果内存中已经加载同一模块名称的DLL,则无论加载的DLL位于那个目录,系统都会使用加载的DLL,系统不搜索DLL
  • 如果DLL位于运行应用程序的windows版本的已知DLL列表中,则系统将使用其已知DLL(的副本和已知DLL的依赖DLL(如果有)).系统不搜索DLL

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

  1. 应用程序所在目录
  2. 当前目录,GetCurrentDirectory返回的目录
  3. 系统目录,GetSystemDirectory返回的目录,通常为系统盘\Windows\System32
  4. 16位系统目录,该项只是为了向前兼容处理,可以不考虑
  5. Windows目录,GetWindowsDirectory返回的目录,通常是系统盘\Windows当前目录,GetCurrentDirectory返回的目录
  6. 环境变量PATH中所有目录

    这样加载顺序很容易导致一个系统DLL被劫持,只要攻击者将目标文件和一个恶意dll放在一起,导致恶意dll优先于系统dll加载

后来为了减轻这种影响,默认情况下,从Windows xp service pack 2 (SP2)开始启动安全DLL搜索模式

  1. 应用程序所在目录
  2. 系统目录,GetSystemDirectory返回的目录,通常为系统盘\Windows\System32
  3. 16位系统目录,该项只是为了向前兼容处理,可以不考虑
  4. Windows目录,GetWindowsDirectory返回的目录,通常是系统盘\Windows
  5. 当前目录,GetCurrentDirectory返回的目录
  6. 环境变量PATH中所有目录

可以看到当前目录被放置到了后面,对系统dll起到了一定的保护作用

强制关闭SafeDLLSearchMode的方法 创建注册表项: 其值为0

  1. HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode

不过如果我们对程序安装目录拥有替换权限,比如装在了非系统盘,那么我们同样可以利用加载顺序的1来劫持系统的DLL

从Windows7之后,系统默认不存在SafeDllSearchMode
微软进一步加强了防御系统的DLL被劫持,将一些容易被劫持的系统DLL写进一个注册表项中,那么凡是此项下的DLL文件就会被禁止从exe自身所在的目录下调用,而只能从系统目录(System32)下调用
使用了KnownDLLs列表

  1. HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

image.png
image.png

这样可以进一步保护系统dll,防止这些常用的DLL被劫持加载

看完保护机制,如果想尝试进行DLL劫持,只需要放置黑DLL在优先与正常DLL的目录 并且DLL本身不在windows KnowsDLLs列表中,就能达到DLL劫持的效果

防御思路

  1. 调用第三方DLL的时候,使用绝对路径
  2. 调用API SetDllDirectory(L””)将当前目录从DLL加载顺序中移除

编写一个验证DLL

image.png
dllmain.cpp

  1. # include "pch.h"
  2. # include <stdlib.h>
  3. BOOL APIENTRY DllMain(HMODULE hModule,
  4. DWORD ul_reason_for_call,
  5. LPVOID lpReserved
  6. )
  7. {
  8. switch (ul_reason_for_call)
  9. {
  10. case DLL_PROCESS_ATTACH: //DLL进程装载
  11. system("calc");
  12. case DLL_THREAD_ATTACH: //DLL线程装载
  13. case DLL_THREAD_DETACH: //DLL线程卸载
  14. case DLL_PROCESS_DETACH: //DLL进程卸载
  15. break;
  16. }
  17. return TRUE;
  18. }

image.png

Process Monitor演示

https://docs.microsoft.com/zh-cn/sysinternals/downloads/procmon 这款工具是微软官方出的进程监视器,用于显示实时文件系统、注册表和进程/线程活动,这里用于观察进程运行过程的DLL调用。


已知目标存在必然会被使用的某个程序,希望劫持当前程序调用的DLL,所以在本地模拟了当前程序运行过程,发现了一个被调用的DLL。

设置过滤器

设置过滤的路径和开始名称

image.png

image.png

配置完过滤器以后,运行navicat程序

观察到当前libcurl.dll的stack,我们看到了LoadLibrary函数,说明当前DLL是被动态调用的,一般DLL中会存在多个导出函数,通过另一个工具api monitor我们可以观察到navicat.exe原程序,具体用了哪个导出函数,方便我们写一个转发DLL,或者也可以直接用工具转发。

image.png

通过判断使用了哪个导出函数可以定制修改指定的导出函数(在指定位置加入恶意代码)。
对于这类已经存在的DLL,也可以直接用工具生成转发DLL,这里使用了aheadlib生成转发DLL的cpp代码。

image.png

既然是劫持存在的DLL,通过DLL转发需要修改原DLL名称,如果转发了所有的导出函数,这里加入恶意代码可以直接在DllMain入口函数加入恶意代码。

需要注意DLL的位数。这里原来的libcurl.dll是x86位,生成x86 DLL,字符集多字节字符集,生成运行库无依赖多线程(MT)。

image.png

image.png
image.png
通过aheadlib生成的DLL指定的原DLL名加上了ORG,这里修改原DLL名为libcurlOrg.dll。修改生成的DLL名为libcurl.dll
image.png

运行navicat成功执行代码

image.png

劫持不存在的DLL
序在运行时,会尝试加载某些可能在当前环境不存在的DLL,通过放入我们的恶意DLL,在预先加载DLL的路径执行我们的DLL(多用于维权)。

procmon过滤规则

  • Process name is navicat.exe
  • Result is NAME NOT FOUND
  • Path ends with .dll

image.png

image.png
运行navicat.exe,复制提取procmon拦截的内容,最终提取下DLL,这里可以用一种批量的验证工具,感觉挺好用的,推荐ChkDllHijack。
image.png

image.png

选择劫持的程序,贴入需要验证的DLL即可,测试DLL倒也可以手工替换为自己的,或者干脆就自己手工测试。

image.png

image.png
image.png

sqlyog.exe

image.png