Skip to content

Instantly share code, notes, and snippets.

@NotMedic
Forked from Arno0x/TestAssembly.cs
Last active July 23, 2020 15:39
Show Gist options
  • Save NotMedic/21e8e6aec91bf248e6e74f759a2beb46 to your computer and use it in GitHub Desktop.
Save NotMedic/21e8e6aec91bf248e6e74f759a2beb46 to your computer and use it in GitHub Desktop.
This code shows how to load a CLR in an unmanaged process, then load an assembly from memory (not from a file) and execute a method
/*
====== x86 (32 bits) EXE:
1/ Run the following bat file:
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars32.bat"
2/ Then compile it using
c:\> cl.exe loadAssemblyFromMemory.cpp /o loadAssemblyFromMemory.exe
====== x64 (64 bits) EXE:
1/ Run the following bat file:
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"
2/ Then compile it using
c:\> cl.exe loadAssemblyFromMemory.cpp /o loadAssemblyFromMemory.exe
*/
#pragma region Includes and Imports
#include <metahost.h>
#pragma comment(lib, "mscoree.lib")
// Import mscorlib.tlb (Microsoft Common Language Runtime Class Library).
#import <mscorlib.tlb> raw_interfaces_only \
high_property_prefixes("_get","_put","_putref") \
rename("ReportEvent", "InteropServices_ReportEvent") \
rename("or", "InteropServices_or")
using namespace mscorlib;
#pragma endregion
// This is the TestAssembly.dll managed DLL represented as an array of bytes.
// Use the following PowerShell snippet to convert the dll file to the expect hex array and get the assembly size:
/*
$Bytes = Get-Content .\TestAssembly.dll -Encoding Byte
$Bytes.Length
$HexString = [System.Text.StringBuilder]::new($Bytes.Length * 4)
ForEach($byte in $Bytes) { $HexString.AppendFormat("\x{0:x2}", $byte) | Out-Null }
$HexString.ToString()
*/
unsigned char assemblyDLL[] = "\x4d\x5a[...]";
const unsigned int assemblyDLL_len = 3072;
int main()
{
//------------------------------------
// Code from https://code.msdn.microsoft.com/windowsdesktop/CppHostCLR-e6581ee0
//------------------------------------
HRESULT hr;
ICLRRuntimeInfo* pRuntimeInfo = NULL;
ICorRuntimeHost* pCorRuntimeHost = NULL;
//----------------------------------------------------------------------
// Load the CLR runtime
ICLRMetaHost* pMetaHost = NULL;
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
if (FAILED(hr)) {
return -1;
}
// Get the ICLRRuntimeInfo corresponding to a particular CLR version. It
// supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE.
hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
if (FAILED(hr)) {
return -1;
}
// Check if the specified runtime can be loaded into the process. This
// method will take into account other runtimes that may already be
// loaded into the process and set pbLoadable to TRUE if this runtime can
// be loaded in an in-process side-by-side fashion.
BOOL fLoadable;
hr = pRuntimeInfo->IsLoadable(&fLoadable);
if (FAILED(hr)) {
return -1;
}
if (!fLoadable) {
return -1;
}
// Load the CLR into the current process and return a runtime interface
// pointer. ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting
// interfaces supported by CLR 4.0. Here we demo the ICorRuntimeHost
// interface that was provided in .NET v1.x, and is compatible with all
// .NET Frameworks.
hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_PPV_ARGS(&pCorRuntimeHost));
if (FAILED(hr)) {
return -1;
}
//----------------------------------------------------------------------
// Start the CLR
hr = pCorRuntimeHost->Start();
if (FAILED(hr)) {
return -1;
}
//----------------------------------------------------------------------
// Get the default AppDomain for this Runtime host
IUnknownPtr spAppDomainThunk = NULL;
_AppDomainPtr spDefaultAppDomain = NULL;
// Get a pointer to the default AppDomain in the CLR.
hr = pCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk);
if (FAILED(hr)) {
return -1;
}
hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spDefaultAppDomain));
if (FAILED(hr)) {
return -1;
}
//----------------------------------------------------------------------
// Load the assembly from memory, declared as an unsigned char array
SAFEARRAYBOUND bounds[1];
bounds[0].cElements = assemblyDLL_len;
bounds[0].lLbound = 0;
SAFEARRAY* arr = SafeArrayCreate(VT_UI1, 1, bounds);
SafeArrayLock(arr);
memcpy(arr->pvData, assemblyDLL, assemblyDLL_len);
SafeArrayUnlock(arr);
_AssemblyPtr spAssembly = NULL;
hr = spDefaultAppDomain->Load_3(arr, &spAssembly);
if (FAILED(hr)) {
return -1;
}
// Get the Type (ie: Namespace and Class type) to be instanciated from the assembly
bstr_t bstrClassName("TestNamespace.TestClass");
_TypePtr spType = NULL;
hr = spAssembly->GetType_2(bstrClassName, &spType);
if (FAILED(hr)) {
return -1;
}
//----------------------------------------------------------------------
// Finally, invoke the method passing it some arguments as a single string
bstr_t bstrStaticMethodName(L"EntryPoint");
SAFEARRAY* psaStaticMethodArgs = NULL;
variant_t vtStringArg(L"these|are|arguments|passed|to|the|dotNet|method");
variant_t vtPSEntryPointReturnVal;
variant_t vtEmpty;
psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
LONG index = 0;
hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg);
if (FAILED(hr)) {
return -1;
}
// Invoke the method from the Type interface.
hr = spType->InvokeMember_3(
bstrStaticMethodName,
static_cast<BindingFlags>(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public),
NULL,
vtEmpty,
psaStaticMethodArgs,
&vtPSEntryPointReturnVal);
if (FAILED(hr)) {
return -1;
}
SafeArrayDestroy(psaStaticMethodArgs);
psaStaticMethodArgs = NULL;
}
/*
Compiles to a DLL that can be injected into another process.
====== x64 (64 bits) DLL:
1/ Run the following bat file:
"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"
2/ Then compile it using
cl.exe /LD loadDotNetAssemblyFromMemory.cpp /o loadDotNetAssemblyFromMemory.dll
*/
#pragma region Includes and Imports
#include <metahost.h>
#pragma comment(lib, "mscoree.lib")
// Import mscorlib.tlb (Microsoft Common Language Runtime Class Library).
#import <mscorlib.tlb> raw_interfaces_only \
high_property_prefixes("_get","_put","_putref") \
rename("ReportEvent", "InteropServices_ReportEvent") \
rename("or", "InteropServices_or")
using namespace mscorlib;
#pragma endregion
/*
// This is the TestAssembly.dll managed DLL represented as an array of bytes.
// Use the following PowerShell snippet to convert the dll file to the expect hex array and get the assembly size:
$Bytes = Get-Content .\TestAssembly.dll -Encoding Byte
$Bytes.Length
$HexString = [System.Text.StringBuilder]::new($Bytes.Length * 4)
ForEach($byte in $Bytes) { $HexString.AppendFormat("\x{0:x2}", $byte) | Out-Null }
$HexString.ToString()
*/
unsigned char assemblyDLL[] = "\x4d\x5a\...";
const unsigned int assemblyDLL_len = 3072;
DWORD WINAPI RunDotNet(LPVOID lpParam)
{
//------------------------------------
// Code from https://code.msdn.microsoft.com/windowsdesktop/CppHostCLR-e6581ee0
//------------------------------------
HRESULT hr;
ICLRRuntimeInfo* pRuntimeInfo = NULL;
ICorRuntimeHost* pCorRuntimeHost = NULL;
//----------------------------------------------------------------------
// Load the CLR runtime
ICLRMetaHost* pMetaHost = NULL;
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
if (FAILED(hr)) {
return -1;
}
// Get the ICLRRuntimeInfo corresponding to a particular CLR version. It
// supersedes CorBindToRuntimeEx with STARTUP_LOADER_SAFEMODE.
hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
if (FAILED(hr)) {
return -1;
}
// Check if the specified runtime can be loaded into the process. This
// method will take into account other runtimes that may already be
// loaded into the process and set pbLoadable to TRUE if this runtime can
// be loaded in an in-process side-by-side fashion.
BOOL fLoadable;
hr = pRuntimeInfo->IsLoadable(&fLoadable);
if (FAILED(hr)) {
return -1;
}
if (!fLoadable) {
return -1;
}
// Load the CLR into the current process and return a runtime interface
// pointer. ICorRuntimeHost and ICLRRuntimeHost are the two CLR hosting
// interfaces supported by CLR 4.0. Here we demo the ICorRuntimeHost
// interface that was provided in .NET v1.x, and is compatible with all
// .NET Frameworks.
hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_PPV_ARGS(&pCorRuntimeHost));
if (FAILED(hr)) {
return -1;
}
//----------------------------------------------------------------------
// Start the CLR
hr = pCorRuntimeHost->Start();
if (FAILED(hr)) {
return -1;
}
//----------------------------------------------------------------------
// Get the default AppDomain for this Runtime host
IUnknownPtr spAppDomainThunk = NULL;
_AppDomainPtr spDefaultAppDomain = NULL;
// Get a pointer to the default AppDomain in the CLR.
hr = pCorRuntimeHost->GetDefaultDomain(&spAppDomainThunk);
if (FAILED(hr)) {
return -1;
}
hr = spAppDomainThunk->QueryInterface(IID_PPV_ARGS(&spDefaultAppDomain));
if (FAILED(hr)) {
return -1;
}
//----------------------------------------------------------------------
// Load the assembly from memory, declared as an unsigned char array
SAFEARRAYBOUND bounds[1];
bounds[0].cElements = assemblyDLL_len;
bounds[0].lLbound = 0;
SAFEARRAY* arr = SafeArrayCreate(VT_UI1, 1, bounds);
SafeArrayLock(arr);
memcpy(arr->pvData, assemblyDLL, assemblyDLL_len);
SafeArrayUnlock(arr);
_AssemblyPtr spAssembly = NULL;
hr = spDefaultAppDomain->Load_3(arr, &spAssembly);
if (FAILED(hr)) {
return -1;
}
// Get the Type (ie: Namespace and Class type) to be instanciated from the assembly
bstr_t bstrClassName("TestNamespace.TestClass");
_TypePtr spType = NULL;
hr = spAssembly->GetType_2(bstrClassName, &spType);
if (FAILED(hr)) {
return -1;
}
//----------------------------------------------------------------------
// Finally, invoke the method passing it some arguments as a single string
bstr_t bstrStaticMethodName(L"EntryPoint");
SAFEARRAY* psaStaticMethodArgs = NULL;
variant_t vtStringArg(L"these|are|arguments|passed|to|the|dotNet|method");
variant_t vtPSEntryPointReturnVal;
variant_t vtEmpty;
psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
LONG index = 0;
hr = SafeArrayPutElement(psaStaticMethodArgs, &index, &vtStringArg);
if (FAILED(hr)) {
return -1;
}
// Invoke the method from the Type interface.
hr = spType->InvokeMember_3(
bstrStaticMethodName,
static_cast<BindingFlags>(BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public),
NULL,
vtEmpty,
psaStaticMethodArgs,
&vtPSEntryPointReturnVal);
if (FAILED(hr)) {
return -1;
}
SafeArrayDestroy(psaStaticMethodArgs);
psaStaticMethodArgs = NULL;
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CreateThread(NULL, NULL, RunDotNet, NULL, NULL, NULL);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
/*
================================ Compile as a .Net DLL ==============================
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\csc.exe /target:library /out:TestAssembly.dll TestAssembly.cs
*/
using System.Windows.Forms;
namespace TestNamespace
{
public class TestClass
{
public static int EntryPoint (string arguments)
{
MessageBox.Show(arguments);
return 0;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment