// Pseudocode and structs for nt!PsSyscallProviderDispatch. // For ntosknrl win11 24H2 10.0.26100.1742. // Restored by Cyra, adjusted by @sixtyvividtails. // // See actual research: // by @gal_kristal: https://gist.github.com/Kristal-g/eec050b3fcea2a77715ef0cff4acf841 // by @0xfluxsec: https://fluxsec.red/alt-syscalls-for-windows-11 // name's mine // @gal_kristal: _PS_SYSCALL_PROVIDER_SERVICE_DESCRIPTOR_GROUP struct $SVC_DESCRIPTOR { /*00*/ void* HandlersBase; // all syscall handlers are at HandlersBase + offset /*08*/ $SVC_TABLE* LowTable; // for syscall numbers < 0x1000 /*10*/ $SVC_TABLE* HighTable; // for syscall numbers >= 0x1000 /*18*/ // size }; // name's mine // @gal_kristal: _PS_SYSCALL_PROVIDER_SERVICE_DESCRIPTOR struct $SVC_TABLE { /*00*/ uint SyscallEntriesCount; // number of entries in table, up to 0x1000 /*04*/ $SYSCALL_ENTRY SyscallEntry[]; // indexed from 0 to SyscallEntriesCount-1 /*4004*/ // maximum size }; // name's mine // @gal_kristal: _PS_SYSCALL_PROVIDER_SERVICE_ENTRY struct $SYSCALL_ENTRY { uint StackArgsCount: 4; // number of non-register arguments uint NeedsGenericDispatch: 1; // PspSyscallProviderServiceDispatch[Generic] uint Reserved: 3; uint PackedOffset: 24; // real offset = PackedOffset << 4 operator uint&() { return *(uint*)this; } }; // Up to 0x20 distinct descriptors allowed system-wide. // Each process bound to one via EPROCESS.SyscallProviderDispatchContext.Slot. $SVC_DESCRIPTOR PspServiceDescriptorGroupTable[0x20]; // Invoked from nt!KiSystemCall64 when // (ETHREAD.Tcb.Header.Minimal or ETHREAD.Tcb.Header.AltSyscall) // // Return values: // <1: handled, trapFrame->Rax contains return status // =1: needs regular syscall dispatch in KiSystemCall64 // >1: raise STATUS_INVALID_SYSTEM_SERVICE schar PsSyscallProviderDispatch(_Inout_ _KTRAP_FRAME *trapFrame): ETHREAD* currentThread = PsGetCurrentThread() if currentThread->Tcb.Header.Minimal: PsPicoSystemCallDispatch(trapFrame) // invokes PspPicoProviderRoutines[1] return 0 // notice potential TOCTOU? Hint: NtSetContextThread (most likely unusable) uint syscallNumber = trapFrame->Rax & ~0x6000 uint syscallIndex = syscallNumber & 0xFFF bool isHighSyscall = (syscallNumber & 0x1000) != 0 EPROCESS* currentProcess = IoThreadToProcess(currentThread) uint slot = currentProcess->SyscallProviderDispatchContext.Slot if slot >= 0x20: KeBugCheckEx(0x1E0, 5, slot, currentProcess->SyscallProvider, 0) $SVC_DESCRIPTOR& svcDescriptor = PspServiceDescriptorGroupTable[slot] $SVC_TABLE* svcTable = isHighSyscall? svcDescriptor.HighTable: svcDescriptor.LowTable if not svcTable: return 1 // needs regular dispatch if syscallIndex >= svcTable->SyscallEntriesCount: trapFrame->Rax = STATUS_INVALID_SYSTEM_SERVICE // 0xC000001C return 0 $SYSCALL_ENTRY syscallEntry = svcTable->SyscallEntry[syscallIndex] if syscallEntry == 0: return 1 // needs regular dispatch if syscallEntry == 1: return 2 // raise STATUS_INVALID_SYSTEM_SERVICE if isHighSyscall: NTSTATUS st = PspEnsureGuiThreadAndBatchFlush(currentThread) if st == STATUS_UNSUCCESSFUL: // transform status if needed using extra table of bytes beyond packed shadow table schar* shadowSyscallInfo = (schar*)KeServiceDescriptorTableShadow[1].Base + sizeof(uint) * KeServiceDescriptorTableShadow[1].Limit if shadowSyscallInfo[syscallIndex] == 1: st = STATUS_INVALID_SYSTEM_SERVICE // was not intended to be called if FAILED(st): trapFrame->Rax = st return 0 // unlike regular syscall packed tables, offset here is unsigned; so one can specify // driver image base as svcDescriptor.HandlersBase, then use RVAs for handlers uint offset = syscallEntry.PackedOffset << 4 void* syscallHandler = (char*)svcDescriptor.HandlersBase + offset // function to invoke NTSTATUS st = STATUS_SUCCESS if syscallEntry.NeedsGenericDispatch: int rez = PspSyscallProviderServiceDispatchGeneric(trapFrame, syscallHandler, syscallEntry.StackArgsCount, syscallNumber, &st) if rez: return (schar)rez else: st = PspSyscallProviderServiceDispatch(trapFrame, syscallHandler, syscallEntry.StackArgsCount) trapFrame->Rax = st return 0