VirtualBox

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

Last change on this file since 3199 was 3179, checked in by bird, 7 years ago

kmk_redirect,winchildren: WOW64 standard handle injection fixes.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 18.8 KB
Line 
1/* $Id: nt_child_inject_standard_handles.c 3179 2018-03-22 19:50:04Z bird $ */
2/** @file
3 * Injecting standard handles into a child process.
4 */
5
6/*
7 * Copyright (c) 2004-2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
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 uintptr_t uBasicInfoPeb;
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 ( BasicInfo.Wow64.PebBaseAddress < 0x1000
256 || BasicInfo.Wow64.PebBaseAddress > ~(uintptr_t)0x1000)
257 {
258 _snprintf(pszErr, cbErr, "NtWow64QueryInformationProcess64 returned bad PebBaseAddress: %#llx",
259 BasicInfo.Wow64.PebBaseAddress);
260 return ERROR_INVALID_ADDRESS;
261 }
262 uBasicInfoPeb = (uintptr_t)BasicInfo.Wow64.PebBaseAddress;
263 }
264 else
265#endif
266 {
267 rcNt = pfnNtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo.Natural,
268 sizeof(BasicInfo.Natural), &cbActual1);
269 if (!NT_SUCCESS(rcNt))
270 {
271 _snprintf(pszErr, cbErr, "NtQueryInformationProcess failed: %#x", rcNt);
272 return rcNt;
273 }
274 if ((uintptr_t)BasicInfo.Natural.PebBaseAddress < 0x1000)
275 {
276 _snprintf(pszErr, cbErr, "NtQueryInformationProcess returned bad PebBaseAddress: %#llx",
277 BasicInfo.Natural.PebBaseAddress);
278 return ERROR_INVALID_ADDRESS;
279 }
280 uBasicInfoPeb = (uintptr_t)BasicInfo.Natural.PebBaseAddress;
281 }
282
283 /*
284 * Get the 32-bit PEB if it's a WOW64 process.
285 * This query should return 0 for non-WOW64 processes, but we quietly
286 * ignore failures and assume non-WOW64 child.
287 */
288#if K_ARCH_BITS != 64
289 if (!s_fHostIs64Bit)
290 ullPeb32 = uBasicInfoPeb;
291 else
292#endif
293 {
294 ULONG_PTR uPeb32Ptr = 0;
295 cbActual1 = 0;
296 rcNt = pfnNtQueryInformationProcess(hProcess, ProcessWow64Information, &uPeb32Ptr, sizeof(uPeb32Ptr), &cbActual1);
297 if (NT_SUCCESS(rcNt) && uPeb32Ptr != 0)
298 {
299 ullPeb32 = uPeb32Ptr;
300 ullPeb64 = uBasicInfoPeb;
301#if K_ARCH_BITS != 64
302 assert(ullPeb64 != ullPeb32);
303 if (ullPeb64 == ullPeb32)
304 ullPeb64 = 0;
305#endif
306 }
307 else
308 {
309 assert(NT_SUCCESS(rcNt));
310 ullPeb64 = uBasicInfoPeb;
311 }
312 }
313
314 /*
315 * Read the process parameter pointers.
316 */
317 if (ullPeb32)
318 {
319 DWORD uProcParamPtr = 0;
320 SIZE_T cbRead = 0;
321 if ( MyReadProcessMemory(hProcess, ullPeb32 + s_offProcessParametersInPeb32,
322 &uProcParamPtr, sizeof(uProcParamPtr), &cbRead)
323 && cbRead == sizeof(uProcParamPtr))
324 ullProcParams32 = uProcParamPtr;
325 else
326 {
327 DWORD dwErr = GetLastError();
328 _snprintf(pszErr, cbErr, "Failed to read PEB32!ProcessParameter at %#llx: %u/%#x (%u read)",
329 ullPeb32 + s_offProcessParametersInPeb32, dwErr, dwErr, cbRead);
330 return dwErr ? dwErr : -1;
331 }
332 if (uProcParamPtr < 0x1000)
333 {
334 _snprintf(pszErr, cbErr, "Bad PEB32!ProcessParameter value: %#llx", ullProcParams32);
335 return ERROR_INVALID_ADDRESS;
336 }
337 }
338
339 if (ullPeb64)
340 {
341 ULONGLONG uProcParamPtr = 0;
342 SIZE_T cbRead = 0;
343 if ( MyReadProcessMemory(hProcess, ullPeb64 + s_offProcessParametersInPeb64,
344 &uProcParamPtr, sizeof(uProcParamPtr), &cbRead)
345 && cbRead == sizeof(uProcParamPtr))
346 ullProcParams64 = uProcParamPtr;
347 else
348 {
349 DWORD dwErr = GetLastError();
350 _snprintf(pszErr, cbErr, "Failed to read PEB64!ProcessParameter at %p: %u/%#x (%u read)",
351 ullPeb64 + s_offProcessParametersInPeb64, dwErr, dwErr, cbRead);
352 return dwErr ? dwErr : -1;
353 }
354 if (uProcParamPtr < 0x1000)
355 {
356 _snprintf(pszErr, cbErr, "Bad PEB64!ProcessParameter value: %#llx", uProcParamPtr);
357 return ERROR_INVALID_ADDRESS;
358 }
359 }
360
361 /*
362 * If we're replacing standard input and standard error but not standard
363 * output, we must read the standard output handle. We ASSUME that in
364 * WOW64 processes the two PEBs have the same value, saving a read.
365 */
366 if (iFirstToInject == 0 && cHandlesToInject == 3 && !pafReplace[1])
367 {
368 if (ullProcParams64)
369 {
370 SIZE_T cbRead = 0;
371 if ( MyReadProcessMemory(hProcess, ullProcParams64 + s_offStandardInputInProcParams64 + sizeof(au64Handles[0]),
372 &au64Handles[1], sizeof(au64Handles[1]), &cbRead)
373 && cbRead == sizeof(au64Handles[1]))
374 au32Handles[1] = (DWORD)au64Handles[1];
375 else
376 {
377 DWORD dwErr = GetLastError();
378 _snprintf(pszErr, cbErr, "Failed to read ProcessParameter64!StandardOutput at %#llx: %u/%#x (%u read)",
379 ullProcParams64 + s_offStandardInputInProcParams64 + sizeof(au64Handles[0]), dwErr, dwErr, cbRead);
380 return dwErr ? dwErr : -1;
381 }
382 }
383 else if (ullProcParams32)
384 {
385 SIZE_T cbRead = 0;
386 if ( !MyReadProcessMemory(hProcess, ullProcParams32 + s_offStandardInputInProcParams32 + sizeof(au32Handles[0]),
387 &au32Handles[1], sizeof(au32Handles[1]), &cbRead)
388 || cbRead != sizeof(au32Handles[1]))
389 {
390 DWORD dwErr = GetLastError();
391 _snprintf(pszErr, cbErr, "Failed to read ProcessParameter32!StandardOutput at %#llx: %u/%#x (%u read)",
392 ullProcParams32 + s_offStandardInputInProcParams32 + sizeof(au32Handles[0]), dwErr, dwErr, cbRead);
393 return dwErr ? dwErr : -1;
394 }
395 }
396 }
397
398 /*
399 * Duplicate the handles into process, preparing the two handle arrays
400 * that we'll write to the guest afterwards.
401 */
402 for (i = iFirstToInject; i < 3; i++)
403 if (pafReplace[i])
404 {
405 HANDLE hInChild = pahHandles[i];
406 if ( hInChild == NULL
407 || hInChild == INVALID_HANDLE_VALUE
408 || DuplicateHandle(GetCurrentProcess(), pahHandles[i], hProcess, &hInChild,
409 0 /*fDesiredAccess*/, TRUE /*fInheritable*/, DUPLICATE_SAME_ACCESS))
410 {
411 au32Handles[i] = (DWORD)(uintptr_t)hInChild;
412 au64Handles[i] = (uintptr_t)hInChild;
413 }
414 else
415 {
416 DWORD dwErr = GetLastError();
417 _snprintf(pszErr, cbErr, "Failed to duplicate handle %p into the child as %s: %u",
418 pahHandles[i], s_apszNames[i], dwErr);
419 return dwErr ? dwErr : -1;
420 }
421 }
422
423 /*
424 * Write the handle arrays to the child.
425 *
426 * If we're a WOW64 we need to use NtWow64WriteVirtualMemory64 instead of
427 * WriteProcessMemory because the latter fails with ERROR_NOACCESS (998).
428 * So, we use a wrapper for doing the writing.
429 */
430 if (ullProcParams32)
431 {
432 ULONGLONG ullDst = ullProcParams32 + s_offStandardInputInProcParams32 + iFirstToInject * sizeof(au32Handles[0]);
433 SIZE_T cbToWrite = cHandlesToInject * sizeof(au32Handles[0]);
434 SIZE_T cbWritten = 0;
435 if ( !MyWriteProcessMemory(hProcess, ullDst, &au32Handles[iFirstToInject], cbToWrite, &cbWritten)
436 || cbWritten != cbToWrite)
437 {
438 DWORD dwErr = GetLastError();
439 _snprintf(pszErr, cbErr, "Failed to write handles to ProcessParameter32 (%#llx LB %u): %u/%#x (%u written)",
440 ullDst, cbToWrite, dwErr, dwErr, cbWritten);
441 return dwErr ? dwErr : ERROR_MORE_DATA;
442 }
443 }
444
445 if (ullProcParams64)
446 {
447 ULONGLONG ullDst = ullProcParams64 + s_offStandardInputInProcParams64 + iFirstToInject * sizeof(au64Handles[0]);
448 SIZE_T cbToWrite = cHandlesToInject * sizeof(au64Handles[0]);
449 SIZE_T cbWritten = 0;
450 if ( !MyWriteProcessMemory(hProcess, ullDst, &au64Handles[iFirstToInject], cbToWrite, &cbWritten)
451 || cbWritten != cbToWrite)
452 {
453 DWORD dwErr = GetLastError();
454 _snprintf(pszErr, cbErr, "Failed to write handles to ProcessParameter64 (%#llx LB %u): %u/%#x (%u written)",
455 ullDst, cbToWrite, dwErr, dwErr, cbWritten);
456 return dwErr ? dwErr : ERROR_MORE_DATA;
457 }
458 }
459
460 /* Done successfully! */
461 return 0;
462}
463
Note: See TracBrowser for help on using the repository browser.

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