0x00 Preface
---
In Cobalt Strike 3.11, a command named "execute-assembly" was introduced, capable of loading .NET assemblies from memory. This feature does not require writing files to the hard disk, making it highly stealthy. Moreover, existing PowerShell exploitation scripts can be easily converted to C# code, offering great convenience.
This article will introduce the principles of "execute-assembly", combine multiple open-source codes to explain implementation methods, analyze exploitation approaches, and finally provide defense recommendations.
0x01 Introduction
---
This article will cover the following topics:
- Basic Knowledge
- Normal Implementation Methods
- Analysis of Open-Source Exploitation Code
- Exploitation Approaches
- Defense Recommendations
0x02 Basic Knowledge
---
1.CLR
Full name Common Language Runtime, is a runtime environment that can be used by multiple programming languages.
CLR is the main execution engine of the .NET Framework, one of its functions is to monitor the operation of programs:
- Programs running under the supervision of CLR belong to "managed" code.
- Applications or components that run directly on bare metal without CLR belong to "unmanaged" code.
2.Unmanaged API
Reference:
https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/
API for loading .NET assemblies into arbitrary programs.
Supports two interfaces:
- ICorRuntimeHost Interface
- ICLRRuntimeHost Interface
3.ICorRuntimeHost Interface
Reference:
https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/icorruntimehost-interface
Supports v1.0.3705, v1.1.4322, v2.0.50727, and v4.0.30319
4. ICLRRuntimeHost Interface
References:
https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/iclrruntimehost-interface
Supports v2.0.50727 and v4.0.30319
In .NET Framework 2.0, ICLRRuntimeHost is used to replace ICorRuntimeHost
In actual program development, .NET Framework 1.0 is rarely considered, so both interfaces can be used
0x03 Normal Implementation Method
---
Example code used:
https://code.msdn.microsoft.com/windowsdesktop/CppHostCLR-e6581ee0#content
Here, we will reference the example code and provide additional details
The general implementation method is as follows:
1. Load the CLR into the process
(1) Call the CLRCreateInstance function to obtain the ICLRMetaHost or ICLRMetaHostPolicy interface
(2) Call ICLRMetaHost::EnumerateInstalledRuntimes, ICLRMetaHost::GetRuntime, or ICLRMetaHostPolicy::GetRequestedRuntime method to obtain a valid ICLRRuntimeInfo pointer
Choose any one of the three
(3) Use ICorRuntimeHost or ICLRRuntimeHost
Both call the ICLRRuntimeInfo::GetInterface method, but with different parameters
ICorRuntimeHost:
Supports v1.0.3705, v1.1.4322, v2.0.50727, and v4.0.30319
Specify CLSID_CorRuntimeHost as the rclsid parameter
Specify IID_ICorRuntimeHost as the RIID parameter
ICLRRuntimeHost:
Supports v2.0.50727 and v4.0.30319
Specify CLSID_CLRRuntimeHost as the rclsid parameter
Specify IID_ICLRRuntimeHost as the RIID parameter
2. Load .NET assembly and call static methods
In code implementation, using ICLRRuntimeHost is much simpler than using ICorRuntimeHost
3. Clean up CLR
Release the pointer from step 1
Below, use ICLRMetaHost::GetRuntime to obtain a valid ICLRRuntimeInfo pointer, then use ICLRRuntimeHost to load a .NET assembly from a file and invoke a static method. The implementation code is as follows:
#include "stdafx.h" |
The code will load ClassLibrary1.dll (developed with .NET 4.0) from the same directory, with class name Class1, method TestMethod, and passed parameter argstring.
The code for ClassLibrary1.dll is as follows:
using System; |
0x04 Open Source Exploit Code Analysis
---
1. Unmanaged CLR Hosting Assembly Loader
https://github.com/caseysmithrc/AssemblyLoader
Utilizes CLR to read shellcode from a predefined array in the code, loads it into memory, and executes it
Implementation method is as follows:
1. Load CLR into the process
(1) Call the CLRCreateInstance function to obtain the ICLRMetaHost or ICLRMetaHostPolicy interface
(2) Call the ICLRMetaHost::GetRuntime method to obtain a valid ICLRRuntimeInfo pointer
(3) Use ICorRuntimeHost
Note:
When using ICorRuntimeHost, a reference to mscorlib.tlb needs to be added. The C++ code is as follows:
// Import mscorlib.tlb (Microsoft Common Language Runtime Class Library). |
In ICorRuntimeHost, the method for reading and loading .NET assemblies from files is defined as follows:
virtual HRESULT __stdcall Load_2 ( |
The method for reading and loading .NET assemblies from memory is defined as follows:
virtual HRESULT __stdcall Load_3 ( |
Note:
Method definitions are from mscorlib.tlh
Here Load_3(...) is used, first reading shellcode from the array, then loading the .NET assembly
2. Load .NET assembly and invoke static method
3. Clean CLR
2. Executing a .NET Assembly from C++ in Memory (CLR Hosting)
https://github.com/etormadiv/HostingCLR
The method is essentially the same as caseysmith's: both call the ICLRMetaHost::GetRuntime method to obtain a valid ICLRRuntimeInfo pointer, use the ICorRuntimeHost interface, and use Load_3(...) to read and load the .NET assembly from memory.
3. CLR via native code
https://gist.githubusercontent.com/xpn/e95a62c6afcf06ede52568fcd8187cc2/raw/f3498245c8309d44af38502a2cc7090c318e8adf/clr_via_native.c
It is noteworthy that here, ICLRMetaHost::EnumerateInstalledRuntimes is called to obtain a valid ICLRRuntimeInfo pointer.
Then, ICLRRuntimeHost is used to load the .NET assembly from a file and call static methods.
4. metasploit-execute-assembly
https://github.com/b4rtik/metasploit-execute-assembly
First, create a notepad.exe process, then inject HostingCLRx64.dll into notepad.exe. HostingCLRx64.dll implements in-memory loading of .NET assemblies.
Here, we only focus on the details of in-memory loading of .NET assemblies. Code location:
https://github.com/b4rtik/metasploit-execute-assembly/blob/master/HostingCLR_inject/HostingCLR/HostingCLR.cpp
Details are as follows:
- Using .Net v4.0.30319
- Call the ICLRMetaHost::GetRuntime method to obtain a valid ICLRRuntimeInfo pointer
- Use the ICorRuntimeHost interface
- Use Load_3(...) to read and load a .NET assembly from memory
Essentially the same as 1 and 2
0x05 Exploitation Approach
---
Based on the open-source code in 0x04, execute-assembly typically has the following two exploitation approaches:
1. Read shellcode from memory and load a .NET assembly
- Call the ICLRMetaHost::EnumerateInstalledRuntimes, ICLRMetaHost::GetRuntime, or ICLRMetaHostPolicy::GetRequestedRuntime method to obtain a valid ICLRRuntimeInfo pointer
- Use the ICorRuntimeHost interface
- Use Load_3(...) to read and load a .NET assembly from memory
- Call static methods
2. Read and load a .NET assembly from disk
- Call the ICLRMetaHost::EnumerateInstalledRuntimes, ICLRMetaHost::GetRuntime, or ICLRMetaHostPolicy::GetRequestedRuntime method to obtain a valid ICLRRuntimeInfo pointer
- Use the ICorRuntimeHost (using Load_2(...)) or ICLRRuntimeHost interface
- Load .NET assembly and invoke static methods
The first exploitation approach is superior to the second; the complete exploitation process is as follows:
- Create a normal process
- Inject DLL into the process via DLL reflection
- The DLL implements reading shellcode from memory and loading the final .NET assembly
Advantages are as follows:
- The entire process executes in memory without writing to the file system
- The payload exists as a DLL, avoiding suspicious processes
- The final payload is a C# program, making conversion from existing PowerShell exploitation scripts to C# code convenient
0x06 Defense Recommendations
---
The entire exploitation process requires DLL injection; common DLL injection methods (especially DLL reflection) can be intercepted
Regarding the DLL itself, when using CLR, system DLLs such as the following will be loaded:
- mscoree.dll
- mscoreei.dll
- mscorlib.dll
This can be monitored
0x07 Summary
---
This article combines multiple open-source codes to summarize the implementation methods and exploitation ideas of "execute-assembly", analyzes its advantages, and finally provides defense recommendations