-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
====== 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
================================ 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