The ApcExecutor
class schedules functions to be executed via user APC by using direct syscalls within an encrypted shellcode. Perfect for stealth operations in red-teams, anti-cheat, cheats, etc.
- Our class creates a 'sleeper' thread on the
WorkerRoutine
function which sleeps infinitely, essentially providing a means to queue APC into. - The
Queue
routine uses a function template with parameter pack to create theTaskThunk
class object using perfect forwarding. We then callCallQueueApc
using theThunk
routine and the newly madeTaskThunk
object - The
CallQueueApc
routine contains XOR-encrypted shellcode which mimicsNtQueueApcThreadEx2
. We allocate some memory, copy the decrypted shellcode to that memory, and call thesyscall
stub which maps toNtQueueApcThreadEx2
function, while passing our function template (the actual routine we want to execute code in, similar to a thread start address) and packed parameters as the arguments to the APC API (PAPCFUNC fn, ULONG_PTR param
) - The class
TaskThunk
uses astd::tuple
and a function pointer in order to pack any number of arguments to be used in our function pointerf
. These are the arguments which were passed to the APC API in the previous line - The
Thunk
routine is executed via APC, which callsCallWithArgs
with our function, its arguments, andstd::index_sequence_for
to properly unpack the parameters CallWithArgs
finally calls the function templatef
using parameter pack expansion (the original function we queued for work in the first place, along with its arguments).
The high-level explanation is that we are using a TaskThunk
object as the APC parameter, which holds our actual routine & parameters which we originally queued. We pass the Thunk
function as the APC routine, which then unpacks the arguments in the args
tuple and executes the func
member. This is what provides the illusion of allowing us to pass any number of arguments into the APC routine when usually only a maximum of 3 can be passed to it.
We use one APC sleeper thread which sleeps infinitely until woken up by a scheduled/queued routine, meaning work can be scheduled without any subsequent calls to CreateThread
, since queued routines are executed in the context of the sleeper thread. Because we are using encrypted shellcode with a direct syscall
, execution of our queued tasks cannot be tampered with easily at the usermode level (through API hooking or WINAPI patching). The downside is that routines are "queued" sequentially by the OS, and do not run parallel of eachother (which may imply slower execution times when compared to a pure multi-threaded application).
Multiple fallback methods are present incase we cannot somehow allocate memory for shellcodes: the file CallStub.asm
contains assembler routines which mimic NtQueueApcThreadEx
and NtQueueApcThreadEx2
, and we also have function pointer lookups which will directly call NtQueueApcThreadEx2
(although this is not resistant to patches over this routine, which is why shellcode exeuction is the best execution method).
Since different Windows builds might have different syscall dispatch numbers, you'll want to make sure that 0x166 and 0x167 are the correct ones for NtQueueApcThreadEx
and NtQueueApcThreadEx2
on your machi
5C16
ne. These can be found by viewing these routines in a disassembler and seeing what number it is in the second instruction of the routine, which usually looks like mov rax,00000166
. In the worst case when the NtQueueApcThreadEx2
cannot be located dynamically, the QueueUserAPC
is used as a fallback method. Some of my other projects have already explored that this routine can be easily blocked by patching over ntdll!Ordinal8
(on x64), so it's not ideal. The lower-level NtQueueApcThreadEx
routines can also be patched over, which is why we want to ideally execute our own syscall stub (which gets allocated into memory each time a queued routine is called).
APC-scheduled routine
Arguments: 26500, 6334, 18467, 41
APC-scheduled routine
Arguments: 29358, 11478, 15724, 19169
APC-scheduled routine
Arguments: 28145, 5705, 24464, 26962
APC-scheduled routine
Arguments: 491, 9961, 16827, 23281