summaryrefslogtreecommitdiffstats
path: root/src/lib/nt/nt_child_inject_standard_handles.c
blob: 93b139d025afeba5c32bbb7fe554918b38e0063c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
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;
}