/* $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 * * 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 * */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #include #include #include #include #include #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; }