102 Patches: Detours to the Rescue
C reference for DttR maintainers and modders.
Loading...
Searching...
No Matches
load.c
Go to the documentation of this file.
1#include <dttr_bootstrap.h>
2#include <dttr_errors.h>
3#include <dttr_loader.h>
4#include <dttr_log.h>
5#include <dttr_path.h>
6#include <gen/asm.h>
7#include <sds.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <string.h>
11#include <windows.h>
12
13static const char *const SIDECAR_DLL_RELATIVE_PATH = "modules\\libdttr_sidecar.dll";
14static const char LOAD_LIBRARY_EX_NAME[] = "LoadLibraryExA";
15static const char EXIT_THREAD_NAME[] = "ExitThread";
16static const char GET_LAST_ERROR_NAME[] = "GetLastError";
17
18static const uintptr_t PEB_IMAGE_BASE_OFFSET = 0x8;
19
20static void log_win32_failure(const char *operation) {
21 const DWORD error_code = GetLastError();
22 if (error_code == ERROR_SUCCESS) {
23 DTTR_LOG_ERROR("%s failed", operation);
24 return;
25 }
26
27 LPSTR message = NULL;
28 FormatMessageA(
29 FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM
30 | FORMAT_MESSAGE_IGNORE_INSERTS,
31 NULL,
32 error_code,
33 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
34 (LPSTR)&message,
35 0,
36 NULL
37 );
38
40 "%s failed (Win32 API Error 0x%lX: %s)",
41 operation,
42 error_code,
43 message ? message : "unknown"
44 );
45
46 if (message) {
47 LocalFree(message);
48 }
49}
50
52 HANDLE process,
53 uintptr_t address,
54 void *out,
55 SIZE_T out_size,
56 const char *name
57) {
58 SIZE_T bytes_read = 0;
59
60 if (!ReadProcessMemory(process, (LPCVOID)address, out, out_size, &bytes_read)
61 || bytes_read != out_size) {
62 log_win32_failure("ReadProcessMemory");
64 "Could not read %s from child process (address=0x%08X, expected=%u, "
65 "got=%u)",
66 name,
67 (unsigned)address,
68 (unsigned)out_size,
69 (unsigned)bytes_read
70 );
71 return false;
72 }
73
74 return true;
75}
76
78 HANDLE process,
79 const CONTEXT *thread_context,
80 uintptr_t *out_image_base
81) {
82 const uintptr_t peb_address = (uintptr_t)thread_context->Ebx;
83 uintptr_t image_base = 0;
84
85 DTTR_LOG_DEBUG("Reading image base from PEB at 0x%08X", (unsigned)peb_address);
86
88 process,
89 peb_address + PEB_IMAGE_BASE_OFFSET,
90 &image_base,
91 sizeof(image_base),
92 "PEB image base"
93 )) {
94 return false;
95 }
96
97 DTTR_LOG_DEBUG("Image base: 0x%08X", (unsigned)image_base);
98 *out_image_base = image_base;
99 return true;
100}
101
103 HANDLE process,
104 uintptr_t image_base,
105 uintptr_t *out_entry_point_rva
106) {
107 IMAGE_DOS_HEADER dos = {0};
108 if (!read_remote_bytes(process, image_base, &dos, sizeof(dos), "remote DOS header")) {
109 return false;
110 }
111
112 if (dos.e_magic != IMAGE_DOS_SIGNATURE) {
113 DTTR_LOG_ERROR("Invalid DOS header in child process image");
114 return false;
115 }
116
117 if (dos.e_lfanew <= 0) {
118 DTTR_LOG_ERROR("Invalid NT header offset in child process image");
119 return false;
120 }
121
122 const uintptr_t nt_headers_address = image_base + (uintptr_t)dos.e_lfanew;
124 "DOS header valid; remote NT headers at 0x%08X",
125 (unsigned)nt_headers_address
126 );
127
128 IMAGE_NT_HEADERS32 nt = {0};
130 process,
131 nt_headers_address,
132 &nt,
133 sizeof(nt),
134 "remote NT headers"
135 )) {
136 return false;
137 }
138
139 if (nt.Signature != IMAGE_NT_SIGNATURE) {
140 DTTR_LOG_ERROR("Invalid NT header in child process image");
141 return false;
142 }
143
144 if (nt.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
145 DTTR_LOG_ERROR("Unsupported PE optional header in child process image");
146 return false;
147 }
148
149 const uintptr_t rva = (uintptr_t)nt.OptionalHeader.AddressOfEntryPoint;
150 DTTR_LOG_DEBUG("Entry point RVA: 0x%08X", (unsigned)rva);
151 *out_entry_point_rva = rva;
152 return true;
153}
154
155static bool resolve_sidecar_dll_path(char *out_path, size_t out_path_size) {
157 const bool copied = DTTR_Path_CopySds(out_path, out_path_size, sidecar_path);
158 sdsfree(sidecar_path);
159 if (!copied) {
160 DTTR_LOG_ERROR("Sidecar DLL path is too long");
161 return false;
162 }
163
164 return true;
165}
166
168 DTTR_LoaderShellcodePayload *out_payload,
169 const char *dll_path,
170 uintptr_t original_entry
171) {
172 static const WCHAR KERNEL32_NAME[] = L"kernel32.dll";
173
174 memset(out_payload, 0, sizeof(*out_payload));
175
176 const size_t dll_path_len = strlen(dll_path);
177 if (dll_path_len >= sizeof(out_payload->dll_path)) {
178 DTTR_LOG_ERROR("Sidecar DLL path is too long for shellcode buffer: %s", dll_path);
179 return false;
180 }
181
182 memcpy(out_payload->dll_path, dll_path, dll_path_len + 1);
183 memcpy(out_payload->kernel32_name, KERNEL32_NAME, sizeof(out_payload->kernel32_name));
184 memcpy(
185 out_payload->loadlibraryex_name,
187 sizeof(out_payload->loadlibraryex_name)
188 );
189 memcpy(
190 out_payload->exitthread_name,
192 sizeof(out_payload->exitthread_name)
193 );
194 memcpy(
195 out_payload->getlasterror_name,
197 sizeof(out_payload->getlasterror_name)
198 );
199 out_payload->original_entry = (uint32_t)original_entry;
200 return true;
201}
202
204 const DTTR_LoaderShellcodePayload *payload,
205 uint8_t **out_buffer,
206 uint32_t *out_buffer_size
207) {
208 const size_t out_size = (size_t)dttr_sidecar_shellcode_len + sizeof(*payload);
209 if (out_size > UINT32_MAX) {
210 DTTR_LOG_ERROR("Shellcode payload is too large");
211 return false;
212 }
213
214 uint8_t *const buffer = malloc(out_size);
215 if (!buffer) {
216 DTTR_LOG_ERROR("Could not allocate shellcode payload");
217 return false;
218 }
219
220 *out_buffer = buffer;
221 *out_buffer_size = (uint32_t)out_size;
222 memcpy(buffer, dttr_sidecar_shellcode, dttr_sidecar_shellcode_len);
223 memcpy(buffer + dttr_sidecar_shellcode_len, payload, sizeof(*payload));
224
226 "Shellcode payload built (bytes=%u, shellcode=%u, payload=%u)",
227 *out_buffer_size,
228 dttr_sidecar_shellcode_len,
229 (unsigned)sizeof(*payload)
230 );
231
232 return true;
233}
234
236 HANDLE process,
237 const void *buffer,
238 SIZE_T buffer_size,
239 LPVOID *out_remote_buffer
240) {
241 DTTR_LOG_DEBUG("Allocating %u bytes in remote process", (unsigned)buffer_size);
242
243 LPVOID remote_buffer = VirtualAllocEx(
244 process,
245 NULL,
246 buffer_size,
247 MEM_COMMIT | MEM_RESERVE,
248 PAGE_READWRITE
249 );
250 if (!remote_buffer) {
251 log_win32_failure("VirtualAllocEx");
252 return false;
253 }
254
255 DTTR_LOG_DEBUG("Remote allocation at 0x%08X", (unsigned)(uintptr_t)remote_buffer);
256
257 SIZE_T bytes_written = 0;
258 if (!WriteProcessMemory(process, remote_buffer, buffer, buffer_size, &bytes_written)
259 || bytes_written != buffer_size) {
260 log_win32_failure("WriteProcessMemory");
262 "Could not write shellcode to child process (expected=%u, wrote=%u)",
263 (unsigned)buffer_size,
264 (unsigned)bytes_written
265 );
266 VirtualFreeEx(process, remote_buffer, 0, MEM_RELEASE);
267 return false;
268 }
269
270 DTTR_LOG_DEBUG("Shellcode written to remote process");
271
272 DWORD old_protect = 0;
273 if (!VirtualProtectEx(
274 process,
275 remote_buffer,
276 buffer_size,
277 PAGE_EXECUTE_READWRITE,
278 &old_protect
279 )) {
280 log_win32_failure("VirtualProtectEx");
281 VirtualFreeEx(process, remote_buffer, 0, MEM_RELEASE);
282 return false;
283 }
284
285 DTTR_LOG_DEBUG("Remote memory protection set to PAGE_EXECUTE_READWRITE");
286
287 *out_remote_buffer = remote_buffer;
288 return true;
289}
290
291bool DTTR_Loader_InjectSidecar(const PROCESS_INFORMATION *child_info) {
292 CONTEXT child_thread_context = {0};
293 child_thread_context.ContextFlags = CONTEXT_INTEGER | CONTEXT_CONTROL;
294 if (!GetThreadContext(child_info->hThread, &child_thread_context)) {
295 log_win32_failure("GetThreadContext");
296 return false;
297 }
298
299 uintptr_t image_base = 0;
301 child_info->hProcess,
302 &child_thread_context,
303 &image_base
304 )) {
305 return false;
306 }
307
308 uintptr_t entry_point_rva = 0;
310 child_info->hProcess,
311 image_base,
312 &entry_point_rva
313 )) {
314 return false;
315 }
316
317 const uintptr_t original_entry = image_base + entry_point_rva;
318
320 "Resolved original entry point: 0x%08X (base=0x%08X + RVA)",
321 (unsigned)original_entry,
322 (unsigned)image_base
323 );
324
325 char sidecar_dll_path[MAX_PATH];
326 if (!resolve_sidecar_dll_path(sidecar_dll_path, sizeof(sidecar_dll_path))) {
327 return false;
328 }
329
330 DTTR_LOG_DEBUG("Sidecar DLL path: %s", sidecar_dll_path);
331
332 DTTR_LoaderShellcodePayload payload = {0};
333 if (!initialize_shellcode_payload(&payload, sidecar_dll_path, original_entry)) {
334 return false;
335 }
336
337 uint8_t *shellcode_buffer = NULL;
338 uint32_t shellcode_buffer_len = 0;
339 if (!build_sidecar_shellcode(&payload, &shellcode_buffer, &shellcode_buffer_len)) {
340 return false;
341 }
342
343 LPVOID payload_buffer = NULL;
344 const bool wrote_payload = write_remote_payload(
345 child_info->hProcess,
346 shellcode_buffer,
347 shellcode_buffer_len,
348 &payload_buffer
349 );
350 free(shellcode_buffer);
351 if (!wrote_payload) {
352 return false;
353 }
354
355 child_thread_context.Eip = (DWORD)(uintptr_t)payload_buffer;
356 if (!SetThreadContext(child_info->hThread, &child_thread_context)) {
357 log_win32_failure("SetThreadContext");
358 VirtualFreeEx(child_info->hProcess, payload_buffer, 0, MEM_RELEASE);
359 return false;
360 }
361
363 "Thread context updated: EIP=0x%08X",
364 (unsigned)(uintptr_t)payload_buffer
365 );
366
367 if (ResumeThread(child_info->hThread) == (DWORD)-1) {
368 log_win32_failure("ResumeThread");
369 VirtualFreeEx(child_info->hProcess, payload_buffer, 0, MEM_RELEASE);
370 return false;
371 }
372
373 DTTR_LOG_DEBUG("Resumed thread; game process is running");
374 return true;
375}
void DWORD DWORD * free
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
#define DTTR_LOG_DEBUG(...)
Definition dttr_log.h:28
#define DTTR_LOG_ERROR(...)
Definition dttr_log.h:31
sds DTTR_Path_ModuleSibling(void *module, const char *relative_path)
Definition path.c:217
bool DTTR_Path_CopySds(char *out, size_t out_size, sds value)
Definition path.c:72
static bool resolve_sidecar_dll_path(char *out_path, size_t out_path_size)
Definition load.c:155
static bool read_remote_image_base_from_thread_context(HANDLE process, const CONTEXT *thread_context, uintptr_t *out_image_base)
Definition load.c:77
static const char *const SIDECAR_DLL_RELATIVE_PATH
Definition load.c:13
bool DTTR_Loader_InjectSidecar(const PROCESS_INFORMATION *child_info)
Definition load.c:291
static bool initialize_shellcode_payload(DTTR_LoaderShellcodePayload *out_payload, const char *dll_path, uintptr_t original_entry)
Definition load.c:167
static const char EXIT_THREAD_NAME[]
Definition load.c:15
static const char GET_LAST_ERROR_NAME[]
Definition load.c:16
static const uintptr_t PEB_IMAGE_BASE_OFFSET
Definition load.c:18
static bool read_remote_bytes(HANDLE process, uintptr_t address, void *out, SIZE_T out_size, const char *name)
Definition load.c:51
static bool build_sidecar_shellcode(const DTTR_LoaderShellcodePayload *payload, uint8_t **out_buffer, uint32_t *out_buffer_size)
Definition load.c:203
static bool write_remote_payload(HANDLE process, const void *buffer, SIZE_T buffer_size, LPVOID *out_remote_buffer)
Definition load.c:235
static void log_win32_failure(const char *operation)
Definition load.c:20
static bool read_entry_point_rva_from_remote_image(HANDLE process, uintptr_t image_base, uintptr_t *out_entry_point_rva)
Definition load.c:102
static const char LOAD_LIBRARY_EX_NAME[]
Definition load.c:14
WCHAR kernel32_name[sizeof(L"kernel32.dll")/sizeof(WCHAR)]
char getlasterror_name[sizeof("GetLastError")]
char loadlibraryex_name[sizeof("LoadLibraryExA")]
char exitthread_name[sizeof("ExitThread")]