// // This is the wrapper function implemented in kernelbase.dll to queue APCs. This function is documented. // This function has 3 arguments: // // pfnAPC - the pointer to the apc routine in the target process context. // Note that the signature of this function is different from the signature in NtQueueApcThread. // // hThread - the handle to the target thread. Requires THREAD_SET_CONTEXT. // // dwData - the context argument passed to pfnAPC - This is the only argument passed to pfnAPC. // DWORD QueueUserAPC( PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData );
// // This is the signature of the APC Routine if QueueUserAPC is used. // The only parameter here is the dwData argument from QueueUserAPC. // You may ask why the signature is different than the signature of PPS_APC_ROUTINE, we'll see below why. // typedef VOID (NTAPI *PAPCFUNC)( IN ULONG_PTR Parameter );
// // This is the reverse engineered implementation of QueueUserAPC in windows 10 // (In was changed a bit in the latest insider, you'll see below) // // This function captures the activation context of the current thread // and saves it, so it can be inherited by the APC routine. // // Activation Contexts are data structures that save configuration for DLL redirection, SxS and COM. // To read more about activation context: https://docs.microsoft.com/en-us/windows/win32/sbscs/activation-contexts. // DWORD QueueUserAPC( PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData ) { ACTIVATION_CONTEXT_BASIC_INFORMATION Info; NTSTATUS Status;
if (!NT_SUCCESS(Status)) { DbgPrint("SXS: %s failing because RtlQueryInformationActivationContext() returned status %08lx", "QueueUserAPC", Status); BaseSetLastNTError(Status); return 0; }
// // Forward the call to the actual system call. // The ApcRoutine that is used is actually a wrapper function in ntdll, called "RtlDispatchApc" // The purpose of this wrapper function is to use the activation context passed as a parameter. // Status = NtQueueApcThread( hThread, // ThreadHandle RtlDispatchAPC, // ApcRoutine (PPS_APC_ROUTINE)pfnAPC, // SystemArgument1 (PVOID)dwData, // SystemArgument2 Info.hActCtx // SystemArgument3 );
if (!NT_SUCCESS(Status)) { BaseSetLastNTError(Status); return 0; }
return 1; }
// // This is used as SystemArgument3 if QueueUserAPC // was used to queue the APC. // typedef union _APC_ACTIVATION_CTX { ULONG_PTR Value; HANDLE hActCtx; } APC_ACTIVATION_CTX;
// // This is the actual APC routine. // It enables the activation context, calls the user provided routine, and deactivates the context. // VOID RtlDispatchAPC( // ntdll PAPCFUNC pfnAPC, ULONG_PTR dwData, APC_ACTIVATION_CTX ApcActivationContext ) { RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME_EXTENDED StackFrame;
// // Initialize the StackFrame data structure. // StackFrame.Size = sizeof(RTL_CALLER_ALLOCATED_ACTIVATION_CONTEXT_STACK_FRAME_EXTENDED); StackFrame.Format = 1; StackFrame.Extra1 = 0; StackFrame.Extra2 = 0; StackFrame.Extra3 = 0; StackFrame.Extra4 = 0; if (ApcActivationContext.Value == -1) { pfnAPC(dwData); return; } // // Use the activation context of the queuing thread. // RtlActivateActivationContextUnsafeFast(&StackFrame, ApcActivationContext.hActCtx); // // Call the user provided routine. // pfnAPC(dwData);
// // Pop the activation context from the "activation context stack" // RtlDeactivateActivationContextUnsafeFast(&StackFrame); // // Free the handle to the activation context. // RtlReleaseActivationContext(ApcActivationContext.hActCtx);
// 除了MemoryReserveHandle被替换为UserApcOption,其他和上面一样,这允许调用者使用UserApcOption中的MemoryReserveHandle或UserApcFlags NTSTATUS NtQueueApcThreadEx( IN HANDLE ThreadHandle, IN USER_APC_OPTION UserApcOption, IN PPS_APC_ROUTINE ApcRoutine, IN PVOID SystemArgument1 OPTIONAL, IN PVOID SystemArgument2 OPTIONAL, IN PVOID SystemArgument3 OPTIONAL );
int main( int argc, const char** argv ) { PNT_QUEUE_APC_THREAD_EX NtQueueApcThreadEx; USER_APC_OPTION UserApcOption; NTSTATUS Status;
NtQueueApcThreadEx = (PNT_QUEUE_APC_THREAD_EX)(GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueueApcThreadEx")); if (!NtQueueApcThreadEx) { printf("wtf, before win7\n"); return -1; }
// // This is a special flag that tells NtQueueApcThreadEx this APC is a special user APC. // UserApcOption.UserApcFlags = QueueUserApcFlagsSpecialUserApc;
while (TRUE) { // // This will force the current thread to execute the special user APC, // Although the current thread does not enter alertable state. // The APC will execute before the thread returns from kernel mode. // Status = NtQueueApcThreadEx( GetCurrentThread(), UserApcOption, ApcRoutine, NULL, NULL, NULL );
if (!NT_SUCCESS(Status)) { printf("NtQueueApcThreadEx Failed! 0x%08X\n", Status); return -1; }
// // This sleep does not enter alertable state. // Sleep(500); }
return 0; }
VOID ApcRoutine( PVOID SystemArgument1, PVOID SystemArgument2, PVOID SystemArgument3 ) { printf("yo wtf?? I was not alertable!\n"); }
NTSTATUS NtQueueApcThreadEx2( IN HANDLE ThreadHandle, IN HANDLE UserApcReserveHandle, IN QUEUE_USER_APC_FLAGS QueueUserApcFlags, IN PPS_APC_ROUTINE ApcRoutine, IN PVOID SystemArgument1 OPTIONAL, IN PVOID SystemArgument2 OPTIONAL, IN PVOID SystemArgument3 OPTIONAL );