|
| 1 | +// ============================================================================= |
| 2 | +// Platform includes & macros |
| 3 | +// ============================================================================= |
| 4 | + |
| 5 | +#ifdef _WIN32 |
| 6 | +#define EXPORT __declspec(dllexport) |
| 7 | +#include <windows.h> |
| 8 | +#else |
| 9 | +#define EXPORT [[gnu::visibility("default")]] |
| 10 | +#include <dlfcn.h> |
| 11 | +#endif |
| 12 | + |
| 13 | +#include <string> |
| 14 | +#include <chrono> |
| 15 | +#include <thread> |
| 16 | +#include <cstdlib> |
| 17 | + |
| 18 | +#include <hostfxr.h> |
| 19 | +#include <coreclr_delegates.h> |
| 20 | + |
| 21 | +#include "logging.h" |
| 22 | +#include "module_dir.h" |
| 23 | +#include "exit_hooks.h" |
| 24 | +#include "process_args.h" |
| 25 | + |
| 26 | +// ============================================================================= |
| 27 | +// Configuration - predefined paths for automatic initialization |
| 28 | +// ============================================================================= |
| 29 | + |
| 30 | +#ifndef RUNTIME_CONFIG_PATH |
| 31 | +#define RUNTIME_CONFIG_PATH "RuntimePatcher.runtimeconfig.json" |
| 32 | +#endif |
| 33 | + |
| 34 | +#ifndef ASSEMBLY_PATH |
| 35 | +#define ASSEMBLY_PATH "RuntimePatcher.dll" |
| 36 | +#endif |
| 37 | + |
| 38 | +#ifndef TYPE_NAME |
| 39 | +#define TYPE_NAME "RuntimePatcher.Main, RuntimePatcher" |
| 40 | +#endif |
| 41 | + |
| 42 | +#ifndef METHOD_NAME |
| 43 | +#define METHOD_NAME "InitializePatchesUnmanaged" |
| 44 | +#endif |
| 45 | + |
| 46 | +#ifndef HOSTFXR_TIMEOUT_MS |
| 47 | +#define HOSTFXR_TIMEOUT_MS 30000 |
| 48 | +#endif |
| 49 | + |
| 50 | +#ifndef HOSTFXR_POLL_INTERVAL_MS |
| 51 | +#define HOSTFXR_POLL_INTERVAL_MS 100 |
| 52 | +#endif |
| 53 | + |
| 54 | +#ifdef _WIN32 |
| 55 | +#define HOSTFXR_LIBRARY_NAME "hostfxr.dll" |
| 56 | +#else |
| 57 | +#define HOSTFXR_LIBRARY_NAME "libhostfxr.so" |
| 58 | +#endif |
| 59 | + |
| 60 | +// ============================================================================= |
| 61 | +// Module - cross-platform shared library helpers |
| 62 | +// ============================================================================= |
| 63 | + |
| 64 | +class Module { |
| 65 | +public: |
| 66 | + static void *get_base_address(const char *library) { |
| 67 | +#ifdef _WIN32 |
| 68 | + auto base = GetModuleHandleA(library); |
| 69 | +#else |
| 70 | + auto base = dlopen(library, RTLD_LAZY); |
| 71 | +#endif |
| 72 | + return reinterpret_cast<void *>(base); |
| 73 | + } |
| 74 | + |
| 75 | + static void *get_export_by_name(void *module, const char *name) { |
| 76 | +#ifdef _WIN32 |
| 77 | + auto address = GetProcAddress((HMODULE) module, name); |
| 78 | +#else |
| 79 | + auto address = dlsym(module, name); |
| 80 | +#endif |
| 81 | + return reinterpret_cast<void *>(address); |
| 82 | + } |
| 83 | + |
| 84 | + template<typename T> |
| 85 | + static T get_function_by_name(void *module, const char *name) { |
| 86 | + return reinterpret_cast<T>(get_export_by_name(module, name)); |
| 87 | + } |
| 88 | +}; |
| 89 | + |
| 90 | +// ============================================================================= |
| 91 | +// Core - .NET assembly loading via hostfxr |
| 92 | +// ============================================================================= |
| 93 | + |
| 94 | +enum class InitializeResult : uint32_t { |
| 95 | + Success, |
| 96 | + HostFxrLoadError, |
| 97 | + HostFxrFptrLoadError, |
| 98 | + InitializeRuntimeConfigError, |
| 99 | + GetRuntimeDelegateError, |
| 100 | + EntryPointError, |
| 101 | +}; |
| 102 | + |
| 103 | +extern "C" EXPORT InitializeResult bootstrapper_load_assembly( |
| 104 | + const char_t *runtime_config_path, |
| 105 | + const char_t *assembly_path, |
| 106 | + const char_t *type_name, |
| 107 | + const char_t *method_name |
| 108 | +) { |
| 109 | + void *module = Module::get_base_address(HOSTFXR_LIBRARY_NAME); |
| 110 | + if (!module) { |
| 111 | + log_printf("[-] Failed to load %s\n", HOSTFXR_LIBRARY_NAME); |
| 112 | + return InitializeResult::HostFxrLoadError; |
| 113 | + } |
| 114 | + |
| 115 | +#ifdef _WIN32 |
| 116 | + log_printf("[*] runtime_config_path: %ls\n", runtime_config_path); |
| 117 | + log_printf("[*] assembly_path: %ls\n", assembly_path); |
| 118 | + log_printf("[*] type_name: %ls\n", type_name); |
| 119 | + log_printf("[*] method_name: %ls\n", method_name); |
| 120 | +#else |
| 121 | + log_printf("[*] runtime_config_path: %s\n", runtime_config_path); |
| 122 | + log_printf("[*] assembly_path: %s\n", assembly_path); |
| 123 | + log_printf("[*] type_name: %s\n", type_name); |
| 124 | + log_printf("[*] method_name: %s\n", method_name); |
| 125 | +#endif |
| 126 | + |
| 127 | + auto hostfxr_initialize_for_runtime_config_fptr = |
| 128 | + Module::get_function_by_name<hostfxr_initialize_for_runtime_config_fn>(module, "hostfxr_initialize_for_runtime_config"); |
| 129 | + |
| 130 | + auto hostfxr_get_runtime_delegate_fptr = |
| 131 | + Module::get_function_by_name<hostfxr_get_runtime_delegate_fn>(module, "hostfxr_get_runtime_delegate"); |
| 132 | + |
| 133 | + auto hostfxr_close_fptr = |
| 134 | + Module::get_function_by_name<hostfxr_close_fn>(module, "hostfxr_close"); |
| 135 | + |
| 136 | + if (!hostfxr_initialize_for_runtime_config_fptr || !hostfxr_get_runtime_delegate_fptr || !hostfxr_close_fptr) { |
| 137 | + log_printf("[-] Failed to resolve hostfxr exports (init=%p, delegate=%p, close=%p)\n", |
| 138 | + (void*)hostfxr_initialize_for_runtime_config_fptr, |
| 139 | + (void*)hostfxr_get_runtime_delegate_fptr, |
| 140 | + (void*)hostfxr_close_fptr); |
| 141 | + return InitializeResult::HostFxrFptrLoadError; |
| 142 | + } |
| 143 | + |
| 144 | + hostfxr_handle ctx = nullptr; |
| 145 | + int rc = hostfxr_initialize_for_runtime_config_fptr(runtime_config_path, nullptr, &ctx); |
| 146 | + log_printf("[*] hostfxr_initialize_for_runtime_config => 0x%08X\n", (unsigned int)rc); |
| 147 | + |
| 148 | + /// Success = 0x00000000 |
| 149 | + /// Success_HostAlreadyInitialized = 0x00000001 |
| 150 | + /// @see https://github.com/dotnet/runtime/blob/main/docs/design/features/host-error-codes.md |
| 151 | + if (rc != 1 || ctx == nullptr) { |
| 152 | + hostfxr_close_fptr(ctx); |
| 153 | + return InitializeResult::InitializeRuntimeConfigError; |
| 154 | + } |
| 155 | + |
| 156 | + void *delegate = nullptr; |
| 157 | + int ret = hostfxr_get_runtime_delegate_fptr(ctx, hostfxr_delegate_type::hdt_load_assembly_and_get_function_pointer, |
| 158 | + &delegate); |
| 159 | + |
| 160 | + if (ret != 0 || delegate == nullptr) { |
| 161 | + log_printf("[-] hostfxr_get_runtime_delegate failed => 0x%08X\n", (unsigned int)ret); |
| 162 | + return InitializeResult::GetRuntimeDelegateError; |
| 163 | + } |
| 164 | + |
| 165 | + auto load_assembly_fptr = reinterpret_cast<load_assembly_and_get_function_pointer_fn>(delegate); |
| 166 | + |
| 167 | + typedef void (CORECLR_DELEGATE_CALLTYPE *custom_entry_point_fn)(); |
| 168 | + custom_entry_point_fn custom = nullptr; |
| 169 | + |
| 170 | + ret = load_assembly_fptr(assembly_path, type_name, method_name, UNMANAGEDCALLERSONLY_METHOD, nullptr, |
| 171 | + (void **) &custom); |
| 172 | + |
| 173 | + if (ret != 0 || custom == nullptr) { |
| 174 | + log_printf("[-] load_assembly_and_get_function_pointer failed => 0x%08X\n", (unsigned int)ret); |
| 175 | + return InitializeResult::EntryPointError; |
| 176 | + } |
| 177 | + |
| 178 | + log_printf("[+] Invoking managed entry point\n"); |
| 179 | + custom(); |
| 180 | + |
| 181 | + hostfxr_close_fptr(ctx); |
| 182 | + |
| 183 | + return InitializeResult::Success; |
| 184 | +} |
| 185 | + |
| 186 | +// ============================================================================= |
| 187 | +// Initialization - hostfxr polling & assembly loading orchestration |
| 188 | +// ============================================================================= |
| 189 | + |
| 190 | +static bool wait_for_hostfxr(int timeout_ms) { |
| 191 | + int elapsed = 0; |
| 192 | + while (!Module::get_base_address(HOSTFXR_LIBRARY_NAME) && elapsed < timeout_ms) { |
| 193 | + std::this_thread::sleep_for(std::chrono::milliseconds(HOSTFXR_POLL_INTERVAL_MS)); |
| 194 | + elapsed += HOSTFXR_POLL_INTERVAL_MS; |
| 195 | + } |
| 196 | + |
| 197 | + if (!Module::get_base_address(HOSTFXR_LIBRARY_NAME)) { |
| 198 | + log_printf("[-] hostfxr not loaded after %dms, aborting\n", timeout_ms); |
| 199 | + return false; |
| 200 | + } |
| 201 | + |
| 202 | + log_printf("[+] hostfxr found after ~%dms\n", elapsed); |
| 203 | + return true; |
| 204 | +} |
| 205 | + |
| 206 | +static std::string get_env_var(const char *name) { |
| 207 | + auto val = std::getenv(name); |
| 208 | + return val == nullptr ? std::string() : std::string(val); |
| 209 | +} |
| 210 | + |
| 211 | +static void initialize_from_env(int timeout_ms) { |
| 212 | + auto runtime_config_path = get_env_var("RUNTIME_CONFIG_PATH"); |
| 213 | + auto assembly_path = get_env_var("ASSEMBLY_PATH"); |
| 214 | + auto type_name = get_env_var("TYPE_NAME"); |
| 215 | + auto method_name = get_env_var("METHOD_NAME"); |
| 216 | + |
| 217 | + if (runtime_config_path.empty() || assembly_path.empty() || type_name.empty() || method_name.empty()) { |
| 218 | + return; |
| 219 | + } |
| 220 | + |
| 221 | + if (!wait_for_hostfxr(timeout_ms)) return; |
| 222 | + |
| 223 | + auto ret = bootstrapper_load_assembly( |
| 224 | +#ifdef _WIN32 |
| 225 | + std::wstring(runtime_config_path.begin(), runtime_config_path.end()).c_str(), |
| 226 | + std::wstring(assembly_path.begin(), assembly_path.end()).c_str(), |
| 227 | + std::wstring(type_name.begin(), type_name.end()).c_str(), |
| 228 | + std::wstring(method_name.begin(), method_name.end()).c_str() |
| 229 | +#else |
| 230 | + runtime_config_path.c_str(), |
| 231 | + assembly_path.c_str(), |
| 232 | + type_name.c_str(), |
| 233 | + method_name.c_str() |
| 234 | +#endif |
| 235 | + ); |
| 236 | + log_printf("[+] bootstrapper_load_assembly() => %d\n", (uint32_t) ret); |
| 237 | +} |
| 238 | + |
| 239 | +static void initialize_from_constants(int timeout_ms) { |
| 240 | + if (!wait_for_hostfxr(timeout_ms)) return; |
| 241 | + |
| 242 | + auto dir = get_module_directory(); |
| 243 | + |
| 244 | +#ifdef _WIN32 |
| 245 | + auto runtime_config_path = dir + L"\\" + L"" RUNTIME_CONFIG_PATH; |
| 246 | + auto assembly_path = dir + L"\\" + L"" ASSEMBLY_PATH; |
| 247 | +#else |
| 248 | + auto runtime_config_path = dir + "/" + RUNTIME_CONFIG_PATH; |
| 249 | + auto assembly_path = dir + "/" + ASSEMBLY_PATH; |
| 250 | +#endif |
| 251 | + |
| 252 | + auto ret = bootstrapper_load_assembly( |
| 253 | +#ifdef _WIN32 |
| 254 | + runtime_config_path.c_str(), |
| 255 | + assembly_path.c_str(), |
| 256 | + L"" TYPE_NAME, |
| 257 | + L"" METHOD_NAME |
| 258 | +#else |
| 259 | + runtime_config_path.c_str(), |
| 260 | + assembly_path.c_str(), |
| 261 | + TYPE_NAME, |
| 262 | + METHOD_NAME |
| 263 | +#endif |
| 264 | + ); |
| 265 | + log_printf("[+] bootstrapper_load_assembly() => %d\n", (uint32_t) ret); |
| 266 | +} |
| 267 | + |
| 268 | +// ============================================================================= |
| 269 | +// Entry point - DllMain / constructor |
| 270 | +// ============================================================================= |
| 271 | + |
| 272 | +#ifndef DEFAULT_LOGGING_ENABLED |
| 273 | +#define DEFAULT_LOGGING_ENABLED 1 // Provided by CMake; this is the IntelliSense fallback |
| 274 | +#endif |
| 275 | + |
| 276 | +#ifndef DEFAULT_EXIT_HOOKS_ENABLED |
| 277 | +#define DEFAULT_EXIT_HOOKS_ENABLED 1 // Provided by CMake; this is the IntelliSense fallback |
| 278 | +#endif |
| 279 | + |
| 280 | +/// Applies CMake defaults, then overrides with command-line args if present. |
| 281 | +static void configure_features() { |
| 282 | + log_set_enabled(DEFAULT_LOGGING_ENABLED); |
| 283 | + exit_hooks_set_enabled(DEFAULT_EXIT_HOOKS_ENABLED); |
| 284 | + |
| 285 | + if (has_process_arg("-LogBootstrapper")) log_set_enabled(true); |
| 286 | + if (has_process_arg("-HookExitProcess")) exit_hooks_set_enabled(true); |
| 287 | + |
| 288 | + log_init(); |
| 289 | + hook_exit_process(); |
| 290 | + |
| 291 | + // Log the command line after logging is initialized |
| 292 | + auto args = get_process_args(); |
| 293 | + log_printf("[*] Command line (%zu args):\n", args.size()); |
| 294 | + for (size_t i = 0; i < args.size(); i++) { |
| 295 | + log_printf("[*] [%zu] %s\n", i, args[i].c_str()); |
| 296 | + } |
| 297 | +} |
| 298 | + |
| 299 | +#ifdef _WIN32 |
| 300 | +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { |
| 301 | + if (fdwReason == DLL_PROCESS_ATTACH) { |
| 302 | + DisableThreadLibraryCalls(hinstDLL); |
| 303 | + set_module_handle(hinstDLL); |
| 304 | + |
| 305 | + configure_features(); |
| 306 | + |
| 307 | + CreateThread(nullptr, 0, [](LPVOID) -> DWORD { |
| 308 | + initialize_from_constants(HOSTFXR_TIMEOUT_MS); |
| 309 | + return 0; |
| 310 | + }, nullptr, 0, nullptr); |
| 311 | + } |
| 312 | + return TRUE; |
| 313 | +} |
| 314 | +#else |
| 315 | +[[gnu::constructor]] |
| 316 | +void initialize_library() { |
| 317 | + configure_features(); |
| 318 | + |
| 319 | + std::thread thread([] { |
| 320 | + initialize_from_constants(HOSTFXR_TIMEOUT_MS); |
| 321 | + }); |
| 322 | + thread.detach(); |
| 323 | +} |
| 324 | +#endif |
0 commit comments