首页
社区
课程
招聘
[原创]windows攻防对抗-dll侧载篇
发表于: 2024-5-18 01:23 14268

[原创]windows攻防对抗-dll侧载篇

2024-5-18 01:23
14268

介绍

最近来聊一聊在实战攻防对抗中比较常用的技术——dll侧载。从功能实现上来讲,该技术的实现就是倚托进程的白签名,通过劫持白进程的执行流来达到恶意代码执行的目的。但在实际应用的过程中会有一些坑点和骚操作的利用手法。而且作为安全产品对dll侧载的检测也没有很好的手段,一般都是类似设立黑名单的机制,无法对未知的样本进行一个有效的防御。可能对安全产品来讲也许是一个老生常谈的问题,设置强规则(诸如dll签名啊,文件落地啊等)就会产生大量误报,很难找到一个权衡误报与真实告警事件的一个边界。。。跑远了,这不是这篇文章该讨论的问题。

前置知识

动态库的加载方式

隐式加载

隐式加载动态库(也称为静态加载或预加载)是指在程序启动时,操作系统加载器自动加载程序所依赖的动态链接库。这种加载方式是在编译时确定的,因为依赖的库会在程序的链接阶段被指定。
隐式加载的优点是使用简单,程序启动时按照系统中一定的搜索顺序自动加载所有依赖,不需要在代码中进行额外的加载操作。缺点是程序启动时会加载所有依赖的库,即使这些库中的一些函数可能永远不会被调用,这可能会导致额外的内存消耗。此外,如果依赖的库在系统上不存在,程序将无法启动。
从PE文件格式对应的就是导入表,注意,导入表内的动态库在程序初始化的时候会被加载进内存,如果没找到就会报错。但动态库里面的导出函数并不一定会被调用。

显示加载

与隐式加载相对的是显式加载(也称为动态加载或运行时加载),这是指程序在运行时根据需要动态地加载和卸载动态链接库。在Windows上,这通常通过使用 LoadLibrary和GetProcAddress函数来实现。显式加载允 许程序更加灵活地控制何时加载库,以及如何处理库不可用的情况。

动态库的加载顺序


● 检测dll是否已被加载进内存
● 检测加载的dll是否在Known DLLs内
● 检测应用当前目录下是否存在dll
● 检测System32目录下是否存在dll
● 检测当前执行目录下是否存在dll
● 检测%PATH%环境变量下是否存在dll
其实在大多数的利用场景中(银狐黑灰,攻防演练,apt等)可以总结出,大部分利用都是依靠应用当前目录来做dll侧载的。
这里再介绍下Known DLLs
Known DLLs标记的dll默认会从system32路径下加载,注册表位置
\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs

动态库被加载的执行点

dllmain

参考文章:14aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6W2L8r3I4A6L8%4c8G2L8Y4y4W2j5%4g2J5K9i4c8&6i4K6u0W2j5$3!0E0i4K6u0r3M7r3g2J5k6X3g2U0N6q4)9J5k6r3c8D9L8q4)9J5k6r3S2A6K9X3q4U0K9$3W2F1k6#2)9J5c8R3`.`.
当动态库被加载时,会执行动态库中的dllmain函数。但当程序进入dllmain函数时,会被施加一个锁的状态,该锁的存在就是微软为了限制dllmain的行为做了一些安全限制。

后面就引用原文了
You should never perform the following tasks from within DllMain:
● Call LoadLibrary or LoadLibraryEx (either directly or indirectly). This can cause a deadlock or a crash.
● Call GetStringTypeA, GetStringTypeEx, or GetStringTypeW (either directly or indirectly). This can cause a deadlock or a crash.
● Synchronize with other threads. This can cause a deadlock.
● Acquire a synchronization object that is owned by code that is waiting to acquire the loader lock. This can cause a deadlock.
● Initialize COM threads by using CoInitializeEx. Under certain conditions, this function can call LoadLibraryEx.
● Call the registry functions.
● Call CreateProcess. Creating a process can load another DLL.
● Call ExitThread. Exiting a thread during DLL detach can cause the loader lock to be acquired again, causing a deadlock or a crash.
● Call CreateThread. Creating a thread can work if you do not synchronize with other threads, but it is risky.
● Call ShGetFolterPathW. Calling shell/known folder APIs can result in thread synchronization, and can therefore cause deadlocks.
● Create a named pipe or other named object (Windows 2000 only). In Windows 2000, named objects are provided by the Terminal Services DLL. If this DLL is not initialized, calls to the DLL can cause the process to crash.
● Use the memory management function from the dynamic C Run-Time (CRT). If the CRT DLL is not initialized, calls to these functions can cause the process to crash.
● Call functions in User32.dll or Gdi32.dll. Some functions load another DLL, which may not be initialized.
● Use managed code.
换算成攻击者可以理解场景就是你无法在dllmain中完成C2的上线。(具体的利用方式后面介绍)

导出函数

通过重写动态链接库中的导出函数劫持可执行程序的劫持流是一个非常有效的手段。但前提是可执行程序确实会调用你的导出函数。这个的利用场景也很多。

隐式加载的劫持方式

OEP劫持

对于需要隐式加载的dll,由于其加载过程由系统接管。即隐式加载的dll是在exe主体模块加载过程中进来的,执行顺序上是先通过PE文件的导入表加载隐式的dll,然后再运行EXE的入口点。玩过runpe的应该知道pe在内存中的装载流程中,会填充iat,而iat的填充就是需要动态链接库里面的函数地址。所以导入表内的动态链接库会在可执行文件初始化的过程中被装载进内存,从而触发dllmain函数的执行。
具体利用方式参考:811K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1j5&6j5X3W2W2i4K6u0r3K9h3q4@1d9r3W2B7j5h3y4C8c8$3g2F1k6i4u0S2N6r3f1`. (很粗暴)
伪代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BOOL WINAPI DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        MODULEINFO moduleInfoe;
        SIZE_T bytesWritten;
        GetModuleInformation(GetCurrentProcess(), GetModuleHandle(NULL), &moduleInfoe, sizeof(moduleInfoe));
        unsigned char shellcode[] = { xx, xx...}
        int shellcode_size = xx;
        HANDLE currentProcess = GetCurrentProcess();
        WriteProcessMemory(currentProcess, moduleInfoe.EntryPoint, (LPCVOID)&shellcode, shellcode_size, &bytesWritten);
    }
    else if (dwReason == DLL_PROCESS_DETACH){}
    return TRUE;
}

● 缺点:直接覆盖OEP,如果使用stageless(极大的shellcode),有可能覆盖到其他区段,导致PE文件执行出现问题。
● 改良方案:申请新内存,将shellcode代码拷贝过去,修改oep处代码跳转执行或找到E8、E9指令,修改偏移地址劫持执行流


[注意]看雪招聘,专注安全领域的专业人才平台!

收藏
免费 9
支持
分享
最新回复 (6)
雪    币: 1148
活跃值: (4521)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
一行代码修改返回地址:
*(size_t *)_AddressOfReturnAddress() = (size_t)ExecutionFlowHijackTrampoline;
2024-5-24 10:18
0
雪    币: 3500
活跃值: (805)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
3
appview 一行代码修改返回地址: *(size_t *)_AddressOfReturnAddress() = (size_t)ExecutionFlowHijackTrampoline;
这个需要遍历调用栈,判断返回地址所在模块,根据所在模块来获取真正要被修改的返回地址。而不是直接覆盖当前函数的返回地址
2024-5-24 14:42
0
雪    币: 169
活跃值: (343)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
就是dll注入技术
2024-5-28 17:37
0
雪    币: 4140
活跃值: (5872)
能力值: ( LV8,RANK:120 )
在线值:
发帖
回帖
粉丝
5
AppInit_DLLs 现在都被安全启动了,要从BIOS里关闭。
2024-5-29 22:05
0
雪    币: 3500
活跃值: (805)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
6
badboyl AppInit_DLLs 现在都被安全启动了,要从BIOS里关闭。
其实appinit只是次选方案,并不能做到全进程hook。我记得好像只有加载user32的进程才会加载appinit注册的dll
2024-5-29 23:06
0
雪    币: 3018
活跃值: (988)
能力值: ( LV7,RANK:100 )
在线值:
发帖
回帖
粉丝
7

这就是白加黑吧?只不过增添了些上线相关的技术细节

最后于 2024-6-20 10:57 被天堂猪0ink编辑 ,原因:
2024-6-20 10:55
0
游客
登录 | 注册 方可回帖
返回