From 29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:21:29 +0200 Subject: Adding upstream version 1:0.1.9998svn3589+dfsg. Signed-off-by: Daniel Baumann --- src/lib/nt/nt_child_inject_standard_handles.c | 462 ++++++++++++++++++++++++++ 1 file changed, 462 insertions(+) create mode 100644 src/lib/nt/nt_child_inject_standard_handles.c (limited to 'src/lib/nt/nt_child_inject_standard_handles.c') 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 + * + * 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; +} + -- cgit v1.2.3