上篇文章详细描述了DLL 侧载的定义以及如何实现来隐匿执行我们自己的代码,但也抛出了一个问题,那就是需要去找到被劫持的DLL文件的导出函数以及确定哪个导出函数在DLL被加载时会被执行。这个问题,可以通过DLL Proxying技术去解决,该技术在保证自己想要执行的代码被执行的情况下简化这一查找的步骤又能保持原有二进制文件正常的调用被劫持DLL文件里的导出函数,进一步实现隐匿化。
一、DLL Proxying介绍
在传统编程意义下的DLL Proxying是一种拦截和重定向对某个DLL调用的技术,这种技术通常用于以下几种场景:
1.功能扩展:通过代理某个现有的DLL,开发者可以在不修改现在DLL的情况下添加新的功能或者修改现有功能。
2.调试与测试:代理DLL可以用于调试和测试,帮助开发者追踪和记录函数的调用及其参数。
3.安全与保护:通过代理DLL,可以对某些函数的调用进行验证和过滤,提高系统的安全性。
从以上功能场景不难看出,该技术在传统编程意义下有点类似hook的作用:扩展,监控,过滤。但在恶意代码编程的上下文中,DLL Proxying更加类似请求转发,也可以看做一种DLL劫持技术。它具体的实现逻辑是,以上篇文章中提及到的Dism.exe为例:
1.我们将Dism.exe执行要调用的dismcore.dll这个合法的dll文件重命名为任意名字,比如whatever.dll。
2.创建我们自己的Dll文件,导出所有的whatever.dll中的导出函数,命名为dismcore.dll。
3.当我们自己创建的dismcore.dll被dism.exe载入进执行进程时,dismcore.dll中的恶意代码得到执行。
4.当Dism.exe要执行DllGetObjectClass这个函数时,dismcore.dll将调用请求转发给whatever.dll文件,然后由whatever.dll执行对应要被调用的函数。
可以参考如下流程图:
二、DLL Proxying实现
从以上的逻辑流程中,我们不难看出,dismcore.dll是DLL Proxying技术中的核心,主要执行有两个功能:一是承载我们自己的恶意代码,当被主进程加载时执行我们自己的代码;二是转发whatever.dll的导出函数调用请求给whatever.dll。下面我们依次来说明这两点如何实现。
2.1 进程加载自动执行代码
这个相对来说比较简单,我们可以使用DllMain这个函数,来让操作系统自己加载执行我们的代码,代码如下:
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
switch (fdwReason) {
case DLL_PROCESS_ATTACH:
// 执行代码
break;
case DLL_PROCESS_DETACH:
// 执行代码
break;
case DLL_THREAD_ATTACH:
// 执行代码
break;
case DLL_THREAD_DETACH:
// 执行代码
break;
}
return TRUE;
}
代码解释:
DllMain: DllMain 是 Windows DLL 中的一个特殊函数。它是 DLL 入口点,当 DLL 被加载、卸载或者线程附加/分离时,操作系统会自动调用这个函数。
DLL_PROCESS_ATTACH: 当 DLL 被加载到进程中时,操作系统会调用 DllMain 函数,并传入此参数。
DLL_PROCESS_DETACH: 当 DLL 即将从进程中卸载时,操作系统会调用 DllMain 函数,并传入此参数。
DLL_THREAD_ATTACH: 当一个新的线程被创建并附加到 DLL 所在的进程时,操作系统会调用 DllMain 函数,并传入此参数。
DLL_THREAD_DETACH: 当一个线程终止并从 DLL 所在的进程中分离时,操作系统会调用 DllMain 函数,并传入此参数。
因此,我们可以定义一个我们想要执行的函数,然后将该函数放在“case DLL_PROCESS_ATTACH:”这个条件下,这样当进程加载执行我们自定义编写的DLL时,我们要执行的代码就会自动执行。我们可以使用以下代码进行测试:
BOOL WINAPI DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, L"Dll Proxying!", L"Test", MB_OK);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
使用rundll32.exe来模拟二进制文件加载执行DLL文件:
这就实现了当进程加载DLL时,代码自动执行的功能。
2.2 函数调用转发
我们先看下面一行代码:
其中关键的点其实就是#pragma comment这条c/c++预编译指令,它告诉链接器导出/转发DllGetObjectClass这个函数到whatever.dll这个文件。这种情况下,当主进程调用这个函数时,我们恶意的dll文件就会转发这个调用请求到原始的dll文件。我们创建一个实验性的原始Dll文件来测试这样是否可行,原始dll文件代码如下:
恶意的dll文件如下:
将两个dll文件放在同一目录,使用rundll32来调用恶意dll文件中的whatever1函数:
可以看到,不仅whatever1函数被转发执行了,DllMain函数也自动被加载执行了。这就成功执行了Dll Proxying。大家可以使用dism.exe + dismcore.dll练练手。
2.3 自动生成代理Dll文件
在我们执行 DLL Proxying的过程中,有些被劫持的DLL文件可能有几十个导出函数,那这种情况下手动去添加转发预编译指令就显得很费时间。Github上有个工具可以帮我们节省这个过程: https://github.com/Flangvik/SharpDllProxy
第一个红框是被重命名后的原始dll文件,第二个红框则是代理dll文件的原始代码。我们看下代理dll的源码:
从代码中可以看出,DismCore.dll文件的四个导出函数都已被导出。这时,我们只需要编写自己的代码,放入DllMain函数中执行,就可以隐匿实现执行自定义代码。
下面的文章系列我们将会介绍一些shellcode loader的代码实现。
原文始发于微信公众号(TrustedSec):DLL Proxying