VirtualBox

source: kBuild/trunk/src/lib/nt/nt_child_inject_standard_handles.c

Last change on this file was 3584, checked in by bird, 2 years ago

nt/nt_child_inject_standard_handles.c: Fixed starting 64-bit binaries from a WOW64 process.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 18.7 KB
Line 
1/* $Id: nt_child_inject_standard_handles.c 3584 2023-01-20 15:29:36Z bird $ */
2/** @file
3 * Injecting standard handles into a child process.
4 */
5
6/*
7 * Copyright (c) 2004-2018 knut st. osmundsen <[email protected]>
8 *
9 * This file is part of kBuild.
10 *
11 * kBuild is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * kBuild is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with kBuild. If not, see <http://www.gnu.org/licenses/>
23 *
24 */
25
26
27/*********************************************************************************************************************************
28* Header Files *
29*********************************************************************************************************************************/
30#include <Windows.h>
31#include <Winternl.h>
32#include <stdio.h>
33#include <assert.h>
34#include <k/kDefs.h>
35#include "nt_child_inject_standard_handles.h"
36
37/**
38 * Wrapper around ReadProcessMemory in case WOW64 tricks are needed.
39 *
40 * @returns Success indicator.
41 * @param hProcess The target process.
42 * @param ullSrc The source address (in @a hProcess).
43 * @param pvDst The target address (this process).
44 * @param cbToRead How much to read.
45 * @param pcbRead Where to return how much was actually read.
46 */
47static BOOL MyReadProcessMemory(HANDLE hProcess, ULONGLONG ullSrc, void *pvDst, SIZE_T cbToRead, SIZE_T *pcbRead)
48{
49#if K_ARCH_BITS != 64
50 if (ullSrc + cbToRead - 1 > ~(uintptr_t)0)
51 {
52 typedef NTSTATUS(NTAPI *PFN_NtWow64ReadVirtualMemory64)(HANDLE, ULONGLONG, PVOID, ULONGLONG, PULONGLONG);
53 static PFN_NtWow64ReadVirtualMemory64 volatile s_pfnNtWow64ReadVirtualMemory64= NULL;
54 static BOOL volatile s_fInitialized = FALSE;
55 PFN_NtWow64ReadVirtualMemory64 pfnNtWow64ReadVirtualMemory64 = s_pfnNtWow64ReadVirtualMemory64;
56 if (!pfnNtWow64ReadVirtualMemory64 && !s_fInitialized)
57 {
58 *(FARPROC *)&pfnNtWow64ReadVirtualMemory64 = GetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtWow64ReadVirtualMemory64");
59 s_pfnNtWow64ReadVirtualMemory64 = pfnNtWow64ReadVirtualMemory64;
60 }
61 if (pfnNtWow64ReadVirtualMemory64)
62 {
63 struct
64 {
65 ULONGLONG volatile ullBefore;
66 ULONGLONG cbRead64;
67 ULONGLONG volatile ullAfter;
68 } Wtf = { ~(ULONGLONG)0, 0, ~(ULONGLONG)0 };
69 NTSTATUS rcNt = pfnNtWow64ReadVirtualMemory64(hProcess, ullSrc, pvDst, cbToRead, &Wtf.cbRead64);
70 *pcbRead = (SIZE_T)Wtf.cbRead64;
71 SetLastError(rcNt); /* lazy bird */
72 return NT_SUCCESS(rcNt);
73 }
74 }
75#endif
76 return ReadProcessMemory(hProcess, (void *)(uintptr_t)ullSrc, pvDst, cbToRead, pcbRead);
77}
78
79
80/**
81 * Wrapper around WriteProcessMemory in case WOW64 tricks are needed.
82 *
83 * @returns Success indicator.
84 * @param hProcess The target process.
85 * @param ullDst The target address (in @a hProcess).
86 * @param pvSrc The source address (this process).
87 * @param cbToWrite How much to write.
88 * @param pcbWritten Where to return how much was actually written.
89 */
90static BOOL MyWriteProcessMemory(HANDLE hProcess, ULONGLONG ullDst, void const *pvSrc, SIZE_T cbToWrite, SIZE_T *pcbWritten)
91{
92#if K_ARCH_BITS != 64
93 if (ullDst + cbToWrite - 1 > ~(uintptr_t)0)
94 {
95 typedef NTSTATUS (NTAPI *PFN_NtWow64WriteVirtualMemory64)(HANDLE, ULONGLONG, VOID const *, ULONGLONG, PULONGLONG);
96 static PFN_NtWow64WriteVirtualMemory64 volatile s_pfnNtWow64WriteVirtualMemory64= NULL;
97 static BOOL volatile s_fInitialized = FALSE;
98 PFN_NtWow64WriteVirtualMemory64 pfnNtWow64WriteVirtualMemory64 = s_pfnNtWow64WriteVirtualMemory64;
99 if (!pfnNtWow64WriteVirtualMemory64 && !s_fInitialized)
100 {
101 *(FARPROC *)&pfnNtWow64WriteVirtualMemory64 = GetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtWow64WriteVirtualMemory64");
102 s_pfnNtWow64WriteVirtualMemory64 = pfnNtWow64WriteVirtualMemory64;
103 }
104 if (pfnNtWow64WriteVirtualMemory64)
105 {
106 struct
107 {
108 ULONGLONG volatile ullBefore;
109 ULONGLONG cbWritten64;
110 ULONGLONG volatile ullAfter;
111 } Wtf = { ~(ULONGLONG)0, 0, ~(ULONGLONG)0 };
112 NTSTATUS rcNt = pfnNtWow64WriteVirtualMemory64(hProcess, ullDst, pvSrc, cbToWrite, &Wtf.cbWritten64);
113 *pcbWritten = (SIZE_T)Wtf.cbWritten64;
114 SetLastError(rcNt); /* lazy bird */
115 return NT_SUCCESS(rcNt);
116 }
117 }
118#endif
119 return WriteProcessMemory(hProcess, (void *)(uintptr_t)ullDst, pvSrc, cbToWrite, pcbWritten);
120}
121
122
123/**
124 * Injects standard handles into a child process (created suspended).
125 *
126 * @returns 0 on success. On failure a non-zero windows error or NT status,
127 * with details in @a pszErr.
128 * @param hProcess The child process (created suspended).
129 * @param pafReplace Selects which handles to actually replace (TRUE) and
130 * which to leave as-is (FALSE). The first entry is
131 * starndard input, second is standard output, and the
132 * final is standard error.
133 * @param pahHandles The handle in the current process to inject into the
134 * child process. This runs parallel to pafReplace. The
135 * values NULL and INVALID_HANDLE_VALUE will be written
136 * directly to the child without duplication.
137 * @param pszErr Pointer to error message buffer.
138 * @param cbErr Size of error message buffer.
139 */
140int nt_child_inject_standard_handles(HANDLE hProcess, BOOL pafReplace[3], HANDLE pahHandles[3], char *pszErr, size_t cbErr)
141{
142 typedef NTSTATUS (NTAPI *PFN_NtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
143 static PFN_NtQueryInformationProcess volatile s_pfnNtQueryInformationProcess = NULL;
144 PFN_NtQueryInformationProcess pfnNtQueryInformationProcess;
145#if K_ARCH_BITS != 64
146 static PFN_NtQueryInformationProcess volatile s_pfnNtWow64QueryInformationProcess64 = NULL;
147 PFN_NtQueryInformationProcess pfnNtWow64QueryInformationProcess64;
148
149 static BOOL s_fHostIs64Bit = K_ARCH_BITS == 64;
150 static BOOL volatile s_fCheckedHost = FALSE;
151#endif
152
153 static const unsigned s_offProcessParametersInPeb32 = 0x10;
154 static const unsigned s_offProcessParametersInPeb64 = 0x20;
155 static const unsigned s_offStandardInputInProcParams32 = 0x18;
156 static const unsigned s_offStandardInputInProcParams64 = 0x20;
157 static const char * const s_apszNames[3] = { "standard input", "standard output", "standard error" };
158
159
160 ULONG cbActual1 = 0;
161 union
162 {
163 PROCESS_BASIC_INFORMATION Natural;
164 struct
165 {
166 NTSTATUS ExitStatus;
167 ULONGLONG PebBaseAddress;
168 ULONGLONG AffinityMask;
169 ULONG BasePriority;
170 ULONGLONG UniqueProcessId;
171 ULONGLONG InheritedFromUniqueProcessId;
172 } Wow64;
173 } BasicInfo = { { 0, 0, } };
174 ULONGLONG ullBasicInfoPeb;
175 NTSTATUS rcNt;
176 ULONGLONG ullPeb32 = 0;
177 ULONGLONG ullPeb64 = 0;
178 ULONGLONG ullProcParams32 = 0;
179 ULONGLONG ullProcParams64 = 0;
180 DWORD au32Handles[3] = { 0, 0, 0 };
181 ULONGLONG au64Handles[3] = { 0, 0, 0 };
182 unsigned iFirstToInject;
183 unsigned cHandlesToInject;
184 unsigned i;
185
186 /*
187 * Analyze the input to figure out exactly what we need to do.
188 */
189 iFirstToInject = 0;
190 while (iFirstToInject < 3 && !pafReplace[iFirstToInject])
191 iFirstToInject++;
192 if (iFirstToInject >= 3)
193 return 0;
194
195 cHandlesToInject = 3 - iFirstToInject;
196 while ( cHandlesToInject > 1
197 && !pafReplace[iFirstToInject + cHandlesToInject - 1])
198 cHandlesToInject--;
199
200#if K_ARCH_BITS != 64
201 /*
202 * Determine host bit count first time through.
203 */
204 if (!s_fCheckedHost)
205 {
206 BOOL fAmIWow64 = FALSE;
207 if ( IsWow64Process(GetCurrentProcess(), &fAmIWow64)
208 && fAmIWow64)
209 s_fHostIs64Bit = TRUE;
210 else
211 s_fHostIs64Bit = FALSE;
212 s_fCheckedHost = TRUE;
213 }
214#endif
215
216 /*
217 * Resolve NT API first time through.
218 */
219 pfnNtQueryInformationProcess = s_pfnNtQueryInformationProcess;
220#if K_ARCH_BITS != 64
221 pfnNtWow64QueryInformationProcess64 = s_pfnNtWow64QueryInformationProcess64;
222#endif
223 if (!pfnNtQueryInformationProcess)
224 {
225 HMODULE hmodNtDll = GetModuleHandleA("NTDLL.DLL");
226#if K_ARCH_BITS != 64
227 *(FARPROC *)&pfnNtWow64QueryInformationProcess64 = GetProcAddress(hmodNtDll, "NtWow64QueryInformationProcess64");
228 s_pfnNtWow64QueryInformationProcess64 = pfnNtWow64QueryInformationProcess64;
229#endif
230 *(FARPROC *)&pfnNtQueryInformationProcess = GetProcAddress(hmodNtDll, "NtQueryInformationProcess");
231 if (!pfnNtQueryInformationProcess)
232 {
233 _snprintf(pszErr, cbErr, "The NtQueryInformationProcess API was not found in NTDLL");
234 return ERROR_PROC_NOT_FOUND;
235 }
236 s_pfnNtQueryInformationProcess = pfnNtQueryInformationProcess;
237 }
238
239 /*
240 * Get the PEB address.
241 *
242 * If we're a WOW64 process, we must use NtWow64QueryInformationProcess64
243 * here or the PEB address will be set to zero for 64-bit children.
244 */
245#if K_ARCH_BITS != 64
246 if (s_fHostIs64Bit && pfnNtWow64QueryInformationProcess64)
247 {
248 rcNt = pfnNtWow64QueryInformationProcess64(hProcess, ProcessBasicInformation, &BasicInfo.Wow64,
249 sizeof(BasicInfo.Wow64), &cbActual1);
250 if (!NT_SUCCESS(rcNt))
251 {
252 _snprintf(pszErr, cbErr, "NtWow64QueryInformationProcess64 failed: %#x", rcNt);
253 return rcNt;
254 }
255 if ((ULONGLONG)BasicInfo.Wow64.PebBaseAddress < 0x1000)
256 {
257 _snprintf(pszErr, cbErr, "NtWow64QueryInformationProcess64 returned bad PebBaseAddress: %#llx",
258 BasicInfo.Wow64.PebBaseAddress);
259 return ERROR_INVALID_ADDRESS;
260 }
261 ullBasicInfoPeb = BasicInfo.Wow64.PebBaseAddress;
262 }
263 else
264#endif
265 {
266 rcNt = pfnNtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo.Natural,
267 sizeof(BasicInfo.Natural), &cbActual1);
268 if (!NT_SUCCESS(rcNt))
269 {
270 _snprintf(pszErr, cbErr, "NtQueryInformationProcess failed: %#x", rcNt);
271 return rcNt;
272 }
273 if ((uintptr_t)BasicInfo.Natural.PebBaseAddress < 0x1000)
274 {
275 _snprintf(pszErr, cbErr, "NtQueryInformationProcess returned bad PebBaseAddress: %#llx",
276 (ULONGLONG)BasicInfo.Natural.PebBaseAddress);
277 return ERROR_INVALID_ADDRESS;
278 }
279 ullBasicInfoPeb = (uintptr_t)BasicInfo.Natural.PebBaseAddress;
280 }
281
282 /*
283 * Get the 32-bit PEB if it's a WOW64 process.
284 * This query should return 0 for non-WOW64 processes, but we quietly
285 * ignore failures and assume non-WOW64 child.
286 */
287#if K_ARCH_BITS != 64
288 if (!s_fHostIs64Bit)
289 ullPeb32 = ullBasicInfoPeb;
290 else
291#endif
292 {
293 ULONG_PTR uPeb32Ptr = 0;
294 cbActual1 = 0;
295 rcNt = pfnNtQueryInformationProcess(hProcess, ProcessWow64Information, &uPeb32Ptr, sizeof(uPeb32Ptr), &cbActual1);
296 if (NT_SUCCESS(rcNt) && uPeb32Ptr != 0)
297 {
298 ullPeb32 = uPeb32Ptr;
299 ullPeb64 = ullBasicInfoPeb;
300#if K_ARCH_BITS != 64
301 assert(ullPeb64 != ullPeb32);
302 if (ullPeb64 == ullPeb32)
303 ullPeb64 = 0;
304#endif
305 }
306 else
307 {
308 assert(NT_SUCCESS(rcNt));
309 ullPeb64 = ullBasicInfoPeb;
310 }
311 }
312
313 /*
314 * Read the process parameter pointers.
315 */
316 if (ullPeb32)
317 {
318 DWORD uProcParamPtr = 0;
319 SIZE_T cbRead = 0;
320 if ( MyReadProcessMemory(hProcess, ullPeb32 + s_offProcessParametersInPeb32,
321 &uProcParamPtr, sizeof(uProcParamPtr), &cbRead)
322 && cbRead == sizeof(uProcParamPtr))
323 ullProcParams32 = uProcParamPtr;
324 else
325 {
326 DWORD dwErr = GetLastError();
327 _snprintf(pszErr, cbErr, "Failed to read PEB32!ProcessParameter at %#llx: %u/%#x (%u read)",
328 ullPeb32 + s_offProcessParametersInPeb32, dwErr, dwErr, cbRead);
329 return dwErr ? dwErr : -1;
330 }
331 if (uProcParamPtr < 0x1000)
332 {
333 _snprintf(pszErr, cbErr, "Bad PEB32!ProcessParameter value: %#llx", ullProcParams32);
334 return ERROR_INVALID_ADDRESS;
335 }
336 }
337
338 if (ullPeb64)
339 {
340 ULONGLONG uProcParamPtr = 0;
341 SIZE_T cbRead = 0;
342 if ( MyReadProcessMemory(hProcess, ullPeb64 + s_offProcessParametersInPeb64,
343 &uProcParamPtr, sizeof(uProcParamPtr), &cbRead)
344 && cbRead == sizeof(uProcParamPtr))
345 ullProcParams64 = uProcParamPtr;
346 else
347 {
348 DWORD dwErr = GetLastError();
349 _snprintf(pszErr, cbErr, "Failed to read PEB64!ProcessParameter at %p: %u/%#x (%u read)",
350 ullPeb64 + s_offProcessParametersInPeb64, dwErr, dwErr, cbRead);
351 return dwErr ? dwErr : -1;
352 }
353 if (uProcParamPtr < 0x1000)
354 {
355 _snprintf(pszErr, cbErr, "Bad PEB64!ProcessParameter value: %#llx", uProcParamPtr);
356 return ERROR_INVALID_ADDRESS;
357 }
358 }
359
360 /*
361 * If we're replacing standard input and standard error but not standard
362 * output, we must read the standard output handle. We ASSUME that in
363 * WOW64 processes the two PEBs have the same value, saving a read.
364 */
365 if (iFirstToInject == 0 && cHandlesToInject == 3 && !pafReplace[1])
366 {
367 if (ullProcParams64)
368 {
369 SIZE_T cbRead = 0;
370 if ( MyReadProcessMemory(hProcess, ullProcParams64 + s_offStandardInputInProcParams64 + sizeof(au64Handles[0]),
371 &au64Handles[1], sizeof(au64Handles[1]), &cbRead)
372 && cbRead == sizeof(au64Handles[1]))
373 au32Handles[1] = (DWORD)au64Handles[1];
374 else
375 {
376 DWORD dwErr = GetLastError();
377 _snprintf(pszErr, cbErr, "Failed to read ProcessParameter64!StandardOutput at %#llx: %u/%#x (%u read)",
378 ullProcParams64 + s_offStandardInputInProcParams64 + sizeof(au64Handles[0]), dwErr, dwErr, cbRead);
379 return dwErr ? dwErr : -1;
380 }
381 }
382 else if (ullProcParams32)
383 {
384 SIZE_T cbRead = 0;
385 if ( !MyReadProcessMemory(hProcess, ullProcParams32 + s_offStandardInputInProcParams32 + sizeof(au32Handles[0]),
386 &au32Handles[1], sizeof(au32Handles[1]), &cbRead)
387 || cbRead != sizeof(au32Handles[1]))
388 {
389 DWORD dwErr = GetLastError();
390 _snprintf(pszErr, cbErr, "Failed to read ProcessParameter32!StandardOutput at %#llx: %u/%#x (%u read)",
391 ullProcParams32 + s_offStandardInputInProcParams32 + sizeof(au32Handles[0]), dwErr, dwErr, cbRead);
392 return dwErr ? dwErr : -1;
393 }
394 }
395 }
396
397 /*
398 * Duplicate the handles into process, preparing the two handle arrays
399 * that we'll write to the guest afterwards.
400 */
401 for (i = iFirstToInject; i < 3; i++)
402 if (pafReplace[i])
403 {
404 HANDLE hInChild = pahHandles[i];
405 if ( hInChild == NULL
406 || hInChild == INVALID_HANDLE_VALUE
407 || DuplicateHandle(GetCurrentProcess(), pahHandles[i], hProcess, &hInChild,
408 0 /*fDesiredAccess*/, TRUE /*fInheritable*/, DUPLICATE_SAME_ACCESS))
409 {
410 au32Handles[i] = (DWORD)(uintptr_t)hInChild;
411 au64Handles[i] = (uintptr_t)hInChild;
412 }
413 else
414 {
415 DWORD dwErr = GetLastError();
416 _snprintf(pszErr, cbErr, "Failed to duplicate handle %p into the child as %s: %u",
417 pahHandles[i], s_apszNames[i], dwErr);
418 return dwErr ? dwErr : -1;
419 }
420 }
421
422 /*
423 * Write the handle arrays to the child.
424 *
425 * If we're a WOW64 we need to use NtWow64WriteVirtualMemory64 instead of
426 * WriteProcessMemory because the latter fails with ERROR_NOACCESS (998).
427 * So, we use a wrapper for doing the writing.
428 */
429 if (ullProcParams32)
430 {
431 ULONGLONG ullDst = ullProcParams32 + s_offStandardInputInProcParams32 + iFirstToInject * sizeof(au32Handles[0]);
432 SIZE_T cbToWrite = cHandlesToInject * sizeof(au32Handles[0]);
433 SIZE_T cbWritten = 0;
434 if ( !MyWriteProcessMemory(hProcess, ullDst, &au32Handles[iFirstToInject], cbToWrite, &cbWritten)
435 || cbWritten != cbToWrite)
436 {
437 DWORD dwErr = GetLastError();
438 _snprintf(pszErr, cbErr, "Failed to write handles to ProcessParameter32 (%#llx LB %u): %u/%#x (%u written)",
439 ullDst, cbToWrite, dwErr, dwErr, cbWritten);
440 return dwErr ? dwErr : ERROR_MORE_DATA;
441 }
442 }
443
444 if (ullProcParams64)
445 {
446 ULONGLONG ullDst = ullProcParams64 + s_offStandardInputInProcParams64 + iFirstToInject * sizeof(au64Handles[0]);
447 SIZE_T cbToWrite = cHandlesToInject * sizeof(au64Handles[0]);
448 SIZE_T cbWritten = 0;
449 if ( !MyWriteProcessMemory(hProcess, ullDst, &au64Handles[iFirstToInject], cbToWrite, &cbWritten)
450 || cbWritten != cbToWrite)
451 {
452 DWORD dwErr = GetLastError();
453 _snprintf(pszErr, cbErr, "Failed to write handles to ProcessParameter64 (%#llx LB %u): %u/%#x (%u written)",
454 ullDst, cbToWrite, dwErr, dwErr, cbWritten);
455 return dwErr ? dwErr : ERROR_MORE_DATA;
456 }
457 }
458
459 /* Done successfully! */
460 return 0;
461}
462
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette