Skip to content

Commit 3304d5f

Browse files
committed
* library.cpp renamed to bootstrapper.cpp.
* Added `DllMain` for `bootstrapper.cpp` for windows. * Added `RUNTIME_CONFIG_PATH`, `ASSEMBLY_PATH`, `TYPE_NAME`, `METHOD_NAME` to call a specific patcher. * Added `wait_for_hostfxr` function to wait for `hostfxr` to load first before proceeding. Not sure it is required, but it is nice to have if the app takes long to initialize. * Extracted `HOSTFXR_TIMEOUT_MS`, `HOSTFXR_POLL_INTERVAL_MS`, `HOSTFXR_LIBRARY_NAME` macros. * Added `InitializeResult::HostFxrFptrLoadError` if function pointers from `hostfxr` were not loaded. * Added `exit_hooks` hooks to prevent the app from closing. * Added `logging` to a file and console for debugging. Added `log_printf` where makes sense to debug bootstrapping. * Added `module_dir` to get the directory of running process to load the patcher from and store logs in its folder. Usefull if runing target app from other directory. * Added `process_args` functions to extract command line, get its arguments and check for specific args. * Added `-LogBootstrapper` and `-HookExitProcess` flags to force enable logging and exit hook. Disabled from CMake side by default. Set `-DDEFAULT_LOGGING_ENABLED=ON`, `-DDEFAULT_EXIT_HOOKS_ENABLED=OFF` in `build.bat`. * Added `VERSION.dll` as injection entry point for windows. Proxies system `version.dll` (`c:\Windows\System32\version.dll`) Calls `Bootstrapper.dll` on `DllMain`.
1 parent 9b456fd commit 3304d5f

14 files changed

Lines changed: 884 additions & 146 deletions

Bootstrapper/CMakeLists.txt

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,34 @@ project(Bootstrapper)
44
set(CMAKE_CXX_STANDARD 23)
55
set(CMAKE_CXX_STANDARD_REQUIRED ON)
66

7-
add_library(${PROJECT_NAME} SHARED src/library.cpp)
7+
option(DEFAULT_LOGGING_ENABLED "Enable logging by default (overridden by -LogBootstrapper arg)" OFF)
8+
option(DEFAULT_EXIT_HOOKS_ENABLED "Enable exit hooks by default (overridden by -HookExitProcess arg)" OFF)
9+
10+
add_library(${PROJECT_NAME} SHARED src/bootstrapper.cpp src/module_dir.cpp src/logging.cpp src/exit_hooks.cpp src/process_args.cpp)
811
target_include_directories(${PROJECT_NAME} PRIVATE include)
12+
target_compile_definitions(${PROJECT_NAME} PRIVATE BOOTSTRAPPER_LOG_NAME="${PROJECT_NAME}.log")
13+
14+
if (DEFAULT_LOGGING_ENABLED)
15+
target_compile_definitions(${PROJECT_NAME} PRIVATE DEFAULT_LOGGING_ENABLED=1)
16+
else ()
17+
target_compile_definitions(${PROJECT_NAME} PRIVATE DEFAULT_LOGGING_ENABLED=0)
18+
endif ()
19+
20+
if (DEFAULT_EXIT_HOOKS_ENABLED)
21+
target_compile_definitions(${PROJECT_NAME} PRIVATE DEFAULT_EXIT_HOOKS_ENABLED=1)
22+
else ()
23+
target_compile_definitions(${PROJECT_NAME} PRIVATE DEFAULT_EXIT_HOOKS_ENABLED=0)
24+
endif ()
925

1026
if (NOT WIN32)
1127
target_link_libraries(${PROJECT_NAME} PRIVATE dl)
1228
endif ()
1329

30+
if (WIN32)
31+
add_library(VersionProxy SHARED src/version_proxy.cpp src/version_proxy.def)
32+
set_target_properties(VersionProxy PROPERTIES OUTPUT_NAME "VERSION")
33+
target_compile_definitions(VersionProxy PRIVATE BOOTSTRAPPER_DLL_NAME="$<TARGET_FILE_NAME:${PROJECT_NAME}>")
34+
install(TARGETS VersionProxy DESTINATION ${CMAKE_BINARY_DIR}/bin)
35+
endif ()
36+
1437
install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_BINARY_DIR}/bin)

Bootstrapper/build.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ set cmakeLookup=call %vswhere% -latest -requires Microsoft.VisualStudio.Componen
1010

1111
for /f "tokens=*" %%i in ('%cmakeLookup%') do set cmake="%%i"
1212

13-
%cmake% -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON ..
13+
%cmake% -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -DDEFAULT_LOGGING_ENABLED=ON -DDEFAULT_EXIT_HOOKS_ENABLED=OFF ..
1414
%cmake% --build . --config %CMAKE_BUILD_TYPE% --target INSTALL
1515

1616
cd ..

Bootstrapper/src/bootstrapper.cpp

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
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

Comments
 (0)