summaryrefslogtreecommitdiffstats
path: root/src/lib/nt/nt_child_inject_standard_handles.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/nt/nt_child_inject_standard_handles.c')
-rw-r--r--src/lib/nt/nt_child_inject_standard_handles.c462
1 files changed, 462 insertions, 0 deletions
diff --git a/src/lib/nt/nt_child_inject_standard_handles.c b/src/lib/nt/nt_child_inject_standard_handles.c
new file mode 100644
index 0000000..93b139d
--- /dev/null
+++ b/src/lib/nt/nt_child_inject_standard_handles.c
@@ -0,0 +1,462 @@
+/* $Id: nt_child_inject_standard_handles.c 3584 2023-01-20 15:29:36Z bird $ */
+/** @file
+ * Injecting standard handles into a child process.
+ */
+
+/*
+ * Copyright (c) 2004-2018 knut st. osmundsen <bird-kBuild-spamx@anduin.net>
+ *
+ * This file is part of kBuild.
+ *
+ * kBuild is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * kBuild is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with kBuild. If not, see <http://www.gnu.org/licenses/>
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <Windows.h>
+#include <Winternl.h>
+#include <stdio.h>
+#include <assert.h>
+#include <k/kDefs.h>
+#include "nt_child_inject_standard_handles.h"
+
+/**
+ * Wrapper around ReadProcessMemory in case WOW64 tricks are needed.
+ *
+ * @returns Success indicator.
+ * @param hProcess The target process.
+ * @param ullSrc The source address (in @a hProcess).
+ * @param pvDst The target address (this process).
+ * @param cbToRead How much to read.
+ * @param pcbRead Where to return how much was actually read.
+ */
+static BOOL MyReadProcessMemory(HANDLE hProcess, ULONGLONG ullSrc, void *pvDst, SIZE_T cbToRead, SIZE_T *pcbRead)
+{
+#if K_ARCH_BITS != 64
+ if (ullSrc + cbToRead - 1 > ~(uintptr_t)0)
+ {
+ typedef NTSTATUS(NTAPI *PFN_NtWow64ReadVirtualMemory64)(HANDLE, ULONGLONG, PVOID, ULONGLONG, PULONGLONG);
+ static PFN_NtWow64ReadVirtualMemory64 volatile s_pfnNtWow64ReadVirtualMemory64= NULL;
+ static BOOL volatile s_fInitialized = FALSE;
+ PFN_NtWow64ReadVirtualMemory64 pfnNtWow64ReadVirtualMemory64 = s_pfnNtWow64ReadVirtualMemory64;
+ if (!pfnNtWow64ReadVirtualMemory64 && !s_fInitialized)
+ {
+ *(FARPROC *)&pfnNtWow64ReadVirtualMemory64 = GetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtWow64ReadVirtualMemory64");
+ s_pfnNtWow64ReadVirtualMemory64 = pfnNtWow64ReadVirtualMemory64;
+ }
+ if (pfnNtWow64ReadVirtualMemory64)
+ {
+ struct
+ {
+ ULONGLONG volatile ullBefore;
+ ULONGLONG cbRead64;
+ ULONGLONG volatile ullAfter;
+ } Wtf = { ~(ULONGLONG)0, 0, ~(ULONGLONG)0 };
+ NTSTATUS rcNt = pfnNtWow64ReadVirtualMemory64(hProcess, ullSrc, pvDst, cbToRead, &Wtf.cbRead64);
+ *pcbRead = (SIZE_T)Wtf.cbRead64;
+ SetLastError(rcNt); /* lazy bird */
+ return NT_SUCCESS(rcNt);
+ }
+ }
+#endif
+ return ReadProcessMemory(hProcess, (void *)(uintptr_t)ullSrc, pvDst, cbToRead, pcbRead);
+}
+
+
+/**
+ * Wrapper around WriteProcessMemory in case WOW64 tricks are needed.
+ *
+ * @returns Success indicator.
+ * @param hProcess The target process.
+ * @param ullDst The target address (in @a hProcess).
+ * @param pvSrc The source address (this process).
+ * @param cbToWrite How much to write.
+ * @param pcbWritten Where to return how much was actually written.
+ */
+static BOOL MyWriteProcessMemory(HANDLE hProcess, ULONGLONG ullDst, void const *pvSrc, SIZE_T cbToWrite, SIZE_T *pcbWritten)
+{
+#if K_ARCH_BITS != 64
+ if (ullDst + cbToWrite - 1 > ~(uintptr_t)0)
+ {
+ typedef NTSTATUS (NTAPI *PFN_NtWow64WriteVirtualMemory64)(HANDLE, ULONGLONG, VOID const *, ULONGLONG, PULONGLONG);
+ static PFN_NtWow64WriteVirtualMemory64 volatile s_pfnNtWow64WriteVirtualMemory64= NULL;
+ static BOOL volatile s_fInitialized = FALSE;
+ PFN_NtWow64WriteVirtualMemory64 pfnNtWow64WriteVirtualMemory64 = s_pfnNtWow64WriteVirtualMemory64;
+ if (!pfnNtWow64WriteVirtualMemory64 && !s_fInitialized)
+ {
+ *(FARPROC *)&pfnNtWow64WriteVirtualMemory64 = GetProcAddress(GetModuleHandleA("NTDLL.DLL"), "NtWow64WriteVirtualMemory64");
+ s_pfnNtWow64WriteVirtualMemory64 = pfnNtWow64WriteVirtualMemory64;
+ }
+ if (pfnNtWow64WriteVirtualMemory64)
+ {
+ struct
+ {
+ ULONGLONG volatile ullBefore;
+ ULONGLONG cbWritten64;
+ ULONGLONG volatile ullAfter;
+ } Wtf = { ~(ULONGLONG)0, 0, ~(ULONGLONG)0 };
+ NTSTATUS rcNt = pfnNtWow64WriteVirtualMemory64(hProcess, ullDst, pvSrc, cbToWrite, &Wtf.cbWritten64);
+ *pcbWritten = (SIZE_T)Wtf.cbWritten64;
+ SetLastError(rcNt); /* lazy bird */
+ return NT_SUCCESS(rcNt);
+ }
+ }
+#endif
+ return WriteProcessMemory(hProcess, (void *)(uintptr_t)ullDst, pvSrc, cbToWrite, pcbWritten);
+}
+
+
+/**
+ * Injects standard handles into a child process (created suspended).
+ *
+ * @returns 0 on success. On failure a non-zero windows error or NT status,
+ * with details in @a pszErr.
+ * @param hProcess The child process (created suspended).
+ * @param pafReplace Selects which handles to actually replace (TRUE) and
+ * which to leave as-is (FALSE). The first entry is
+ * starndard input, second is standard output, and the
+ * final is standard error.
+ * @param pahHandles The handle in the current process to inject into the
+ * child process. This runs parallel to pafReplace. The
+ * values NULL and INVALID_HANDLE_VALUE will be written
+ * directly to the child without duplication.
+ * @param pszErr Pointer to error message buffer.
+ * @param cbErr Size of error message buffer.
+ */
+int nt_child_inject_standard_handles(HANDLE hProcess, BOOL pafReplace[3], HANDLE pahHandles[3], char *pszErr, size_t cbErr)
+{
+ typedef NTSTATUS (NTAPI *PFN_NtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
+ static PFN_NtQueryInformationProcess volatile s_pfnNtQueryInformationProcess = NULL;
+ PFN_NtQueryInformationProcess pfnNtQueryInformationProcess;
+#if K_ARCH_BITS != 64
+ static PFN_NtQueryInformationProcess volatile s_pfnNtWow64QueryInformationProcess64 = NULL;
+ PFN_NtQueryInformationProcess pfnNtWow64QueryInformationProcess64;
+
+ static BOOL s_fHostIs64Bit = K_ARCH_BITS == 64;
+ static BOOL volatile s_fCheckedHost = FALSE;
+#endif
+
+ static const unsigned s_offProcessParametersInPeb32 = 0x10;
+ static const unsigned s_offProcessParametersInPeb64 = 0x20;
+ static const unsigned s_offStandardInputInProcParams32 = 0x18;
+ static const unsigned s_offStandardInputInProcParams64 = 0x20;
+ static const char * const s_apszNames[3] = { "standard input", "standard output", "standard error" };
+
+
+ ULONG cbActual1 = 0;
+ union
+ {
+ PROCESS_BASIC_INFORMATION Natural;
+ struct
+ {
+ NTSTATUS ExitStatus;
+ ULONGLONG PebBaseAddress;
+ ULONGLONG AffinityMask;
+ ULONG BasePriority;
+ ULONGLONG UniqueProcessId;
+ ULONGLONG InheritedFromUniqueProcessId;
+ } Wow64;
+ } BasicInfo = { { 0, 0, } };
+ ULONGLONG ullBasicInfoPeb;
+ NTSTATUS rcNt;
+ ULONGLONG ullPeb32 = 0;
+ ULONGLONG ullPeb64 = 0;
+ ULONGLONG ullProcParams32 = 0;
+ ULONGLONG ullProcParams64 = 0;
+ DWORD au32Handles[3] = { 0, 0, 0 };
+ ULONGLONG au64Handles[3] = { 0, 0, 0 };
+ unsigned iFirstToInject;
+ unsigned cHandlesToInject;
+ unsigned i;
+
+ /*
+ * Analyze the input to figure out exactly what we need to do.
+ */
+ iFirstToInject = 0;
+ while (iFirstToInject < 3 && !pafReplace[iFirstToInject])
+ iFirstToInject++;
+ if (iFirstToInject >= 3)
+ return 0;
+
+ cHandlesToInject = 3 - iFirstToInject;
+ while ( cHandlesToInject > 1
+ && !pafReplace[iFirstToInject + cHandlesToInject - 1])
+ cHandlesToInject--;
+
+#if K_ARCH_BITS != 64
+ /*
+ * Determine host bit count first time through.
+ */
+ if (!s_fCheckedHost)
+ {
+ BOOL fAmIWow64 = FALSE;
+ if ( IsWow64Process(GetCurrentProcess(), &fAmIWow64)
+ && fAmIWow64)
+ s_fHostIs64Bit = TRUE;
+ else
+ s_fHostIs64Bit = FALSE;
+ s_fCheckedHost = TRUE;
+ }
+#endif
+
+ /*
+ * Resolve NT API first time through.
+ */
+ pfnNtQueryInformationProcess = s_pfnNtQueryInformationProcess;
+#if K_ARCH_BITS != 64
+ pfnNtWow64QueryInformationProcess64 = s_pfnNtWow64QueryInformationProcess64;
+#endif
+ if (!pfnNtQueryInformationProcess)
+ {
+ HMODULE hmodNtDll = GetModuleHandleA("NTDLL.DLL");
+#if K_ARCH_BITS != 64
+ *(FARPROC *)&pfnNtWow64QueryInformationProcess64 = GetProcAddress(hmodNtDll, "NtWow64QueryInformationProcess64");
+ s_pfnNtWow64QueryInformationProcess64 = pfnNtWow64QueryInformationProcess64;
+#endif
+ *(FARPROC *)&pfnNtQueryInformationProcess = GetProcAddress(hmodNtDll, "NtQueryInformationProcess");
+ if (!pfnNtQueryInformationProcess)
+ {
+ _snprintf(pszErr, cbErr, "The NtQueryInformationProcess API was not found in NTDLL");
+ return ERROR_PROC_NOT_FOUND;
+ }
+ s_pfnNtQueryInformationProcess = pfnNtQueryInformationProcess;
+ }
+
+ /*
+ * Get the PEB address.
+ *
+ * If we're a WOW64 process, we must use NtWow64QueryInformationProcess64
+ * here or the PEB address will be set to zero for 64-bit children.
+ */
+#if K_ARCH_BITS != 64
+ if (s_fHostIs64Bit && pfnNtWow64QueryInformationProcess64)
+ {
+ rcNt = pfnNtWow64QueryInformationProcess64(hProcess, ProcessBasicInformation, &BasicInfo.Wow64,
+ sizeof(BasicInfo.Wow64), &cbActual1);
+ if (!NT_SUCCESS(rcNt))
+ {
+ _snprintf(pszErr, cbErr, "NtWow64QueryInformationProcess64 failed: %#x", rcNt);
+ return rcNt;
+ }
+ if ((ULONGLONG)BasicInfo.Wow64.PebBaseAddress < 0x1000)
+ {
+ _snprintf(pszErr, cbErr, "NtWow64QueryInformationProcess64 returned bad PebBaseAddress: %#llx",
+ BasicInfo.Wow64.PebBaseAddress);
+ return ERROR_INVALID_ADDRESS;
+ }
+ ullBasicInfoPeb = BasicInfo.Wow64.PebBaseAddress;
+ }
+ else
+#endif
+ {
+ rcNt = pfnNtQueryInformationProcess(hProcess, ProcessBasicInformation, &BasicInfo.Natural,
+ sizeof(BasicInfo.Natural), &cbActual1);
+ if (!NT_SUCCESS(rcNt))
+ {
+ _snprintf(pszErr, cbErr, "NtQueryInformationProcess failed: %#x", rcNt);
+ return rcNt;
+ }
+ if ((uintptr_t)BasicInfo.Natural.PebBaseAddress < 0x1000)
+ {
+ _snprintf(pszErr, cbErr, "NtQueryInformationProcess returned bad PebBaseAddress: %#llx",
+ (ULONGLONG)BasicInfo.Natural.PebBaseAddress);
+ return ERROR_INVALID_ADDRESS;
+ }
+ ullBasicInfoPeb = (uintptr_t)BasicInfo.Natural.PebBaseAddress;
+ }
+
+ /*
+ * Get the 32-bit PEB if it's a WOW64 process.
+ * This query should return 0 for non-WOW64 processes, but we quietly
+ * ignore failures and assume non-WOW64 child.
+ */
+#if K_ARCH_BITS != 64
+ if (!s_fHostIs64Bit)
+ ullPeb32 = ullBasicInfoPeb;
+ else
+#endif
+ {
+ ULONG_PTR uPeb32Ptr = 0;
+ cbActual1 = 0;
+ rcNt = pfnNtQueryInformationProcess(hProcess, ProcessWow64Information, &uPeb32Ptr, sizeof(uPeb32Ptr), &cbActual1);
+ if (NT_SUCCESS(rcNt) && uPeb32Ptr != 0)
+ {
+ ullPeb32 = uPeb32Ptr;
+ ullPeb64 = ullBasicInfoPeb;
+#if K_ARCH_BITS != 64
+ assert(ullPeb64 != ullPeb32);
+ if (ullPeb64 == ullPeb32)
+ ullPeb64 = 0;
+#endif
+ }
+ else
+ {
+ assert(NT_SUCCESS(rcNt));
+ ullPeb64 = ullBasicInfoPeb;
+ }
+ }
+
+ /*
+ * Read the process parameter pointers.
+ */
+ if (ullPeb32)
+ {
+ DWORD uProcParamPtr = 0;
+ SIZE_T cbRead = 0;
+ if ( MyReadProcessMemory(hProcess, ullPeb32 + s_offProcessParametersInPeb32,
+ &uProcParamPtr, sizeof(uProcParamPtr), &cbRead)
+ && cbRead == sizeof(uProcParamPtr))
+ ullProcParams32 = uProcParamPtr;
+ else
+ {
+ DWORD dwErr = GetLastError();
+ _snprintf(pszErr, cbErr, "Failed to read PEB32!ProcessParameter at %#llx: %u/%#x (%u read)",
+ ullPeb32 + s_offProcessParametersInPeb32, dwErr, dwErr, cbRead);
+ return dwErr ? dwErr : -1;
+ }
+ if (uProcParamPtr < 0x1000)
+ {
+ _snprintf(pszErr, cbErr, "Bad PEB32!ProcessParameter value: %#llx", ullProcParams32);
+ return ERROR_INVALID_ADDRESS;
+ }
+ }
+
+ if (ullPeb64)
+ {
+ ULONGLONG uProcParamPtr = 0;
+ SIZE_T cbRead = 0;
+ if ( MyReadProcessMemory(hProcess, ullPeb64 + s_offProcessParametersInPeb64,
+ &uProcParamPtr, sizeof(uProcParamPtr), &cbRead)
+ && cbRead == sizeof(uProcParamPtr))
+ ullProcParams64 = uProcParamPtr;
+ else
+ {
+ DWORD dwErr = GetLastError();
+ _snprintf(pszErr, cbErr, "Failed to read PEB64!ProcessParameter at %p: %u/%#x (%u read)",
+ ullPeb64 + s_offProcessParametersInPeb64, dwErr, dwErr, cbRead);
+ return dwErr ? dwErr : -1;
+ }
+ if (uProcParamPtr < 0x1000)
+ {
+ _snprintf(pszErr, cbErr, "Bad PEB64!ProcessParameter value: %#llx", uProcParamPtr);
+ return ERROR_INVALID_ADDRESS;
+ }
+ }
+
+ /*
+ * If we're replacing standard input and standard error but not standard
+ * output, we must read the standard output handle. We ASSUME that in
+ * WOW64 processes the two PEBs have the same value, saving a read.
+ */
+ if (iFirstToInject == 0 && cHandlesToInject == 3 && !pafReplace[1])
+ {
+ if (ullProcParams64)
+ {
+ SIZE_T cbRead = 0;
+ if ( MyReadProcessMemory(hProcess, ullProcParams64 + s_offStandardInputInProcParams64 + sizeof(au64Handles[0]),
+ &au64Handles[1], sizeof(au64Handles[1]), &cbRead)
+ && cbRead == sizeof(au64Handles[1]))
+ au32Handles[1] = (DWORD)au64Handles[1];
+ else
+ {
+ DWORD dwErr = GetLastError();
+ _snprintf(pszErr, cbErr, "Failed to read ProcessParameter64!StandardOutput at %#llx: %u/%#x (%u read)",
+ ullProcParams64 + s_offStandardInputInProcParams64 + sizeof(au64Handles[0]), dwErr, dwErr, cbRead);
+ return dwErr ? dwErr : -1;
+ }
+ }
+ else if (ullProcParams32)
+ {
+ SIZE_T cbRead = 0;
+ if ( !MyReadProcessMemory(hProcess, ullProcParams32 + s_offStandardInputInProcParams32 + sizeof(au32Handles[0]),
+ &au32Handles[1], sizeof(au32Handles[1]), &cbRead)
+ || cbRead != sizeof(au32Handles[1]))
+ {
+ DWORD dwErr = GetLastError();
+ _snprintf(pszErr, cbErr, "Failed to read ProcessParameter32!StandardOutput at %#llx: %u/%#x (%u read)",
+ ullProcParams32 + s_offStandardInputInProcParams32 + sizeof(au32Handles[0]), dwErr, dwErr, cbRead);
+ return dwErr ? dwErr : -1;
+ }
+ }
+ }
+
+ /*
+ * Duplicate the handles into process, preparing the two handle arrays
+ * that we'll write to the guest afterwards.
+ */
+ for (i = iFirstToInject; i < 3; i++)
+ if (pafReplace[i])
+ {
+ HANDLE hInChild = pahHandles[i];
+ if ( hInChild == NULL
+ || hInChild == INVALID_HANDLE_VALUE
+ || DuplicateHandle(GetCurrentProcess(), pahHandles[i], hProcess, &hInChild,
+ 0 /*fDesiredAccess*/, TRUE /*fInheritable*/, DUPLICATE_SAME_ACCESS))
+ {
+ au32Handles[i] = (DWORD)(uintptr_t)hInChild;
+ au64Handles[i] = (uintptr_t)hInChild;
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ _snprintf(pszErr, cbErr, "Failed to duplicate handle %p into the child as %s: %u",
+ pahHandles[i], s_apszNames[i], dwErr);
+ return dwErr ? dwErr : -1;
+ }
+ }
+
+ /*
+ * Write the handle arrays to the child.
+ *
+ * If we're a WOW64 we need to use NtWow64WriteVirtualMemory64 instead of
+ * WriteProcessMemory because the latter fails with ERROR_NOACCESS (998).
+ * So, we use a wrapper for doing the writing.
+ */
+ if (ullProcParams32)
+ {
+ ULONGLONG ullDst = ullProcParams32 + s_offStandardInputInProcParams32 + iFirstToInject * sizeof(au32Handles[0]);
+ SIZE_T cbToWrite = cHandlesToInject * sizeof(au32Handles[0]);
+ SIZE_T cbWritten = 0;
+ if ( !MyWriteProcessMemory(hProcess, ullDst, &au32Handles[iFirstToInject], cbToWrite, &cbWritten)
+ || cbWritten != cbToWrite)
+ {
+ DWORD dwErr = GetLastError();
+ _snprintf(pszErr, cbErr, "Failed to write handles to ProcessParameter32 (%#llx LB %u): %u/%#x (%u written)",
+ ullDst, cbToWrite, dwErr, dwErr, cbWritten);
+ return dwErr ? dwErr : ERROR_MORE_DATA;
+ }
+ }
+
+ if (ullProcParams64)
+ {
+ ULONGLONG ullDst = ullProcParams64 + s_offStandardInputInProcParams64 + iFirstToInject * sizeof(au64Handles[0]);
+ SIZE_T cbToWrite = cHandlesToInject * sizeof(au64Handles[0]);
+ SIZE_T cbWritten = 0;
+ if ( !MyWriteProcessMemory(hProcess, ullDst, &au64Handles[iFirstToInject], cbToWrite, &cbWritten)
+ || cbWritten != cbToWrite)
+ {
+ DWORD dwErr = GetLastError();
+ _snprintf(pszErr, cbErr, "Failed to write handles to ProcessParameter64 (%#llx LB %u): %u/%#x (%u written)",
+ ullDst, cbToWrite, dwErr, dwErr, cbWritten);
+ return dwErr ? dwErr : ERROR_MORE_DATA;
+ }
+ }
+
+ /* Done successfully! */
+ return 0;
+}
+