Running managed executables inside an unmanaged executable in C++

1

For the past few days, I've been trying to run a .NET executable inside an unmanaged executable made in C++.

I made a simple MessageBox program to test if my code worked, and indeed the application ran fine:

using System;
using System.Windows.Forms;

namespace Test2Lol
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            MessageBox.Show("I am alive!", "Hello!", 0, MessageBoxIcon.Information);
        }
    }
}

The application works without throwing any exceptions, here's the output log of the unmanaged application running the managed one. The latter's shellcode is inside the unmanaged application.

[main][116] Phase 3: Loading data...
[MDLL_OpenProcess][75] Process opened sucessfully.
[MDLL_VirtualAlloc][40] Sucessfully allocated memory.
[DLL_RunImageDotNet][662] Loading .NET application: BaseAddress 0x04980000, Size: 6656.
[DLL_RunImageDotNet][671] Done 6655 cycles.
[DLL_RunImageDotNet][688] [+] ICLRMetaHost: CLRCreateInstance(...) succeeded
[DLL_RunImageDotNet][701] [+] ICLRMetaHostPolicy: CLRCreateInstance(...) succeeded
[DLL_RunImageDotNet][714] [+] ICLRDebugging: CLRCreateInstance(...) succeeded
[DLL_RunImageDotNet][729] [+] pMetaHost->GetRuntime(...) succeeded
[DLL_RunImageDotNet][744] [+] pRuntimeInfo->IsLoadable(...) succeeded
[DLL_RunImageDotNet][759] [+] pRuntimeInfo->GetInterface(...) succeeded
[DLL_RunImageDotNet][772] [+] pRuntimeHost->Start() succeeded
[DLL_RunImageDotNet][785] [+] pRuntimeHost->GetDefaultDomain(...) succeeded
[DLL_RunImageDotNet][800] [+] pAppDomainThunk->QueryInterface(...) succeeded
[DLL_RunImageDotNet][823] [+] SafeArrayAccessData(...) succeeded
[DLL_RunImageDotNet][836] [+] SafeArrayUnaccessData(...) succeeded
[DLL_RunImageDotNet][838] pSafeArray: 00ABD488, cDims: 1, fFeatures: 128, cbElements: 1, cLocks: 0, pvData: 00AF8C88, cElements 6656, lLBound 0.
[DLL_RunImageDotNet][849] [+] pDefaultAppDomain->Load_3(...) succeeded
[DLL_RunImageDotNet][862] [+] pAssembly->get_EntryPoint(...) succeeded

enter image description here

So, where is the problem you guys might ask? Well, while it works for that very simple messagebox program, it does not work whenever I attempt to load a different application, crashing at Load_3() with the following output log:

[main][116] Phase 3: Loading data...
[MDLL_OpenProcess][75] Process opened sucessfully.
[MDLL_VirtualAlloc][40] Sucessfully allocated memory.
[DLL_RunImageDotNet][662] Loading .NET application: BaseAddress 0x05000000, Size: 514048.
[DLL_RunImageDotNet][671] Done 514047 cycles.
[DLL_RunImageDotNet][688] [+] ICLRMetaHost: CLRCreateInstance(...) succeeded
[DLL_RunImageDotNet][701] [+] ICLRMetaHostPolicy: CLRCreateInstance(...) succeeded
[DLL_RunImageDotNet][714] [+] ICLRDebugging: CLRCreateInstance(...) succeeded
[DLL_RunImageDotNet][729] [+] pMetaHost->GetRuntime(...) succeeded
[DLL_RunImageDotNet][744] [+] pRuntimeInfo->IsLoadable(...) succeeded
[DLL_RunImageDotNet][759] [+] pRuntimeInfo->GetInterface(...) succeeded
[DLL_RunImageDotNet][772] [+] pRuntimeHost->Start() succeeded
[DLL_RunImageDotNet][785] [+] pRuntimeHost->GetDefaultDomain(...) succeeded
[DLL_RunImageDotNet][800] [+] pAppDomainThunk->QueryInterface(...) succeeded
[DLL_RunImageDotNet][823] [+] SafeArrayAccessData(...) succeeded
[DLL_RunImageDotNet][836] [+] SafeArrayUnaccessData(...) succeeded
[DLL_RunImageDotNet][838] pSafeArray: 0137E298, cDims: 1, fFeatures: 128, cbElements: 1, cLocks: 0, pvData: 0137E9D0, cElements 514048, lLBound 0.
code: 0xE06D7363 (0x00000001)
addr: 75D74662

As you guys might notice (and will notice after reading the code below): execution halts at Load_3() throwing an error with code 0xE06D7363 (0x0000001) (E_UNKNOWN?), after I loaded the executable shellcode inside the SafeArray. I also tried with a smaller .NET application (46KB in size) and still no luck. I'm quite lost on why is this happening, by the way here is the code that executes the .NET shellcode:

#include <iostream>
#include <Windows.h>
#include <wchar.h>
#include <TlHelp32.h>

#include <metahost.h>
#pragma comment(lib, "MSCorEE.lib")

#import "C:\Windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.tlb" raw_interfaces_only \
    high_property_prefixes("_get","_put","_putref")     \
    rename("ReportEvent", "InteropServices_ReportEvent") auto_rename

using namespace mscorlib;

int main()
{
    // Open the file that needs to be executed
    HANDLE FileHandle = CreateFileA("C:\\Users\\MyUsername\\OneDrive\\Documenti\\Desktop\\schifo\\test_net.exe", 
        GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);

    DWORD FileSize = GetFileSize(FileHandle, 0);
    BYTE* Buffer = (BYTE*)VirtualAlloc(0, FileSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

    ReadFile(FileHandle, Buffer, FileSize, 0, 0);

    // Load the CLR in the parent process
    ICLRMetaHost* pMetaHost = NULL;
    HRESULT hr;

    hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (VOID**)&pMetaHost);

    ICLRMetaHostPolicy* pMetaHostPolicy = NULL;
    hr = CLRCreateInstance(CLSID_CLRMetaHostPolicy, IID_ICLRMetaHostPolicy, (VOID**)&pMetaHostPolicy);

    ICLRDebugging* pCLRDebugging = NULL;
    hr = CLRCreateInstance(CLSID_CLRDebugging, IID_ICLRDebugging, (VOID**)&pCLRDebugging);

    ICLRRuntimeInfo* pRuntimeInfo = NULL;
    hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (VOID**)&pRuntimeInfo);

    /* Check if the specified runtime can be loaded */
    BOOL bLoadable;
    hr = pRuntimeInfo->IsLoadable(&bLoadable);

    ICorRuntimeHost* pRuntimeHost = NULL;
    hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (VOID**)&pRuntimeHost);

    hr = pRuntimeHost->Start();

    IUnknownPtr pAppDomainThunk = NULL;
    hr = pRuntimeHost->GetDefaultDomain(&pAppDomainThunk);

    _AppDomainPtr pDefaultAppDomain = NULL;
    hr = pAppDomainThunk->QueryInterface(__uuidof(_AppDomain), (VOID**) &pDefaultAppDomain);

    // This is where everything goes wrong

    _AssemblyPtr pAssembly = NULL;
    SAFEARRAYBOUND rgsabound[1];
    rgsabound[0].cElements = FileSize;
    rgsabound[0].lLbound   = 0;
    SAFEARRAY* pSafeArray  = SafeArrayCreate(VT_UI1, 1, rgsabound);
    void* pvData = NULL;
    hr = SafeArrayAccessData(pSafeArray, &pvData);

    // Copy the executable in the safearray
    memcpy(pvData, Buffer, FileSize);

    hr = SafeArrayUnaccessData(pSafeArray);

    // the bastard function
    hr = pDefaultAppDomain->Load_3(pSafeArray, &pAssembly);

    if (FAILED(hr))
        printf("Load_3() failed.\n");

    // Now I get the entry point and I invoke the main function of the executable
    
    _MethodInfoPtr pMethodInfo = NULL;
    hr = pAssembly->get_EntryPoint(&pMethodInfo);

    VARIANT retVal;
    ZeroMemory(&retVal, sizeof(VARIANT));

    VARIANT obj;
    ZeroMemory(&obj, sizeof(VARIANT));
    obj.vt = VT_NULL;

    SAFEARRAY *psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);

    hr = pMethodInfo->Invoke_3(obj, psaStaticMethodArgs, &retVal);

}

I have no idea on why Load_3() fails whenever I try to load a different executable than normal. My theories are:

  1. Load_3() crashes if I try to load files bigger than a certain amount -> not true because I tried with smaller applications and still no luck, also I saw blogs of people loading shellcode even bigger than 42KB using Load_3() successfully.

  2. The SafeArray is being created in a wrong way -> I double checked the output with the working and not-working application and the SafeArray looks the same.

  3. I am loading the data incorrectly -> Tried tons of different methods, none of them worked.

  4. Switching to /SUBSYSTEM:WINDOWS in the linker options didn't solve anything.

I'm quite out of ideas on why such a thing happens with different executables, and sadly I couldn't find any documentation regarding Load_3() whatsoever.

What may be causing Load_3() to fail with different executables from the one I created?

Thanks to everyone willing to help me out!

c++
clr
shellcode
mscorlib
asked on Stack Overflow Dec 31, 2020 by Yung Lew • edited Jan 3, 2021 by Yung Lew

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0