diff --git a/Xpolicy/Xpolicy.vcxproj b/Xpolicy/Xpolicy.vcxproj
index 9dbfa2d..55fd186 100644
--- a/Xpolicy/Xpolicy.vcxproj
+++ b/Xpolicy/Xpolicy.vcxproj
@@ -77,6 +77,9 @@
truetrue
+
+ "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.23.28105\lib\x86"
+
diff --git a/Xpolicy/bin/Xpolicy32.exe b/Xpolicy/bin/Xpolicy32.exe
new file mode 100644
index 0000000..732ee60
Binary files /dev/null and b/Xpolicy/bin/Xpolicy32.exe differ
diff --git a/Xpolicy/bin/Xpolicy32.pdb b/Xpolicy/bin/Xpolicy32.pdb
new file mode 100644
index 0000000..bde0702
Binary files /dev/null and b/Xpolicy/bin/Xpolicy32.pdb differ
diff --git a/Xpolicy/bin/Xpolicy64.exe b/Xpolicy/bin/Xpolicy64.exe
new file mode 100644
index 0000000..42a6bf8
Binary files /dev/null and b/Xpolicy/bin/Xpolicy64.exe differ
diff --git a/Xpolicy/bin/Xpolicy64.pdb b/Xpolicy/bin/Xpolicy64.pdb
new file mode 100644
index 0000000..ee2a68b
Binary files /dev/null and b/Xpolicy/bin/Xpolicy64.pdb differ
diff --git a/src/KeVerifyContextIpForUserCet.c b/src/KeVerifyContextIpForUserCet.c
new file mode 100644
index 0000000..ee3f4bd
--- /dev/null
+++ b/src/KeVerifyContextIpForUserCet.c
@@ -0,0 +1,46 @@
+NTSTATUS
+KeVerifyContextIpForUserCet (
+ _In_ PETHREAD Thread,
+ _In_ PCONTEXT Context,
+ _In_ PKCONTINUE_TYPE ContinueType,
+ _Inout_ PULONG_PTR ShadowStack
+ )
+{
+ PEPROCESS process;
+ NTSTATUS status;
+
+ //
+ // No need to do anything if shadow stack is not enabled
+ //
+ if (!Thread->Tcb.CetShadowStack)
+ {
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // No need to do anything if UserCetSetContextIpValidation is not
+ // set in this process or if Rip is not being modified
+ //
+ process = Thread->Tcb.ApcState.Process;
+ if (!(process->MitigationFlags2Values.UserCetSetContextIpValidation) ||
+ !(BooleanFlagOn(Context->ContextFlags, CONTEXT_CONTROL)))
+ {
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // Verify the new Rip target
+ //
+ status = KiVerifyContextIpForUserCet(Thread, Context, ContinueType, ShadowStack);
+
+ //
+ // Audit failure if requested and fake success
+ //
+ if ((status == STATUS_SET_CONTEXT_DENIED) &&
+ (process->MitigationFlags2Values.AuditUserCetSetContextIpValidation))
+ {
+ KiLogUserCetSetContextIpValidationAudit(*ContinueType);
+ status = STATUS_SUCCESS;
+ }
+ return status;
+}
\ No newline at end of file
diff --git a/src/KeVerifyContextRecord.c b/src/KeVerifyContextRecord.c
new file mode 100644
index 0000000..df27a6c
--- /dev/null
+++ b/src/KeVerifyContextRecord.c
@@ -0,0 +1,69 @@
+NTSTATUS
+KeVerifyContextRecord (
+ _In_ PKTHREAD TargetThread,
+ _In_ PCONTEXT ContextFrame
+ _In_ PKCONTINUE_ARGUMENT ContinueArgument,
+ _Outptr_ PULONG_PTR ShadowStack
+ )
+{
+ PKPROCESS targetProcess;
+ ULONG_PTR userStack;
+ PTEB userTeb;
+ PEWOW64PROCESS wow64Process;
+ USHORT wowMachine;
+
+ targetProcess = TargetThread->Process;
+ if (targetProcess->CheckStackExtents != FALSE)
+ {
+ if (BooleanFlagOn(ContextFrame->ContextFlags, CONTEXT_CONTROL))
+ {
+ userStack = ContextFrame->Rsp;
+ userTeb = TargetThread->Teb;
+
+ //
+ // Get the stack limits from the process' TEB and
+ // check if the new stack pointer is inside the native stack
+ //
+ if (!RtlGuardIsValidStackPointer(userStack, userTeb))
+ {
+ //
+ // New stack pointer is not inside the native stack.
+ // Check if this is a wow64 process, and if it is
+ // check if the new stack pointer is inside the wow64 stack.
+ //
+ wowMachine = PsWow64GetProcessMachine(targetProcess);
+ if ((wowMachine != IMAGE_FILE_MACHINE_I386) &&
+ (wowMachine != IMAGE_FILE_MACHINE_ARMNT))
+ {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if ((userStack >= (_4GB - 1)) ||
+ !(RtlGuardIsValidWow64StackPointer(userStack, userTeb)))
+ {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Call KiVerifyContextRecord to validate the new values of CS and RIP
+ //
+ status = KiVerifyContextRecord(TargetThread,
+ ContextFrame,
+ ContinueArgument,
+ ShadowStack);
+ }
+ }
+ }
+
+ //
+ // If this is a non-wow64 process trying to set its CS to something
+ // other than KGDT64_R3_CODE, force it to be KGDT64_R3_CODE.
+ //
+ if ((BooleanFlagOn(ContextFrame->ContextFlags, CONTEXT_CONTROL)) &&
+ (PsWow64GetProcessMachine(targetProcess) != IMAGE_FILE_MACHINE_I386))
+ {
+ ContextFrame->SegCs = KGDT64_R3_CODE | RPL_MASK;
+ }
+
+ return STATUS_SUCCESS;
+}
\ No newline at end of file
diff --git a/src/KeVerifyContextXStateCetU.c b/src/KeVerifyContextXStateCetU.c
new file mode 100644
index 0000000..fbffc85
--- /dev/null
+++ b/src/KeVerifyContextXStateCetU.c
@@ -0,0 +1,78 @@
+NTSTATUS
+KeVerifyContextXStateCetU (
+ _In_ PKTHREAD Thread,
+ _In_ PCONTEXT ContextRecord,
+ _Outptr_ PULONG_PTR ShadowStack
+ )
+{
+ PXSAVE_CET_U_FORMAT cetData;
+ PXSAVE_AREA_HEADER xsaveData;
+ NTSTATUS status;
+
+ if (!BooleanFlagOn(Context->Context.ContextFlags, CONTEXT_XSTATE))
+ {
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // Get the address of the CET state from the supplied context
+ //
+ cetData = (PXSAVE_CET_U_FORMAT)RtlLocateExtendedFeature2((PCONTEXT_EX)(Context + 1),
+ XSTATE_CET_U,
+ &SharedUserData.XState,
+ NULL);
+ if (cetData == NULL)
+ {
+ return STATUS_SUCCESS;
+ }
+
+ *ShadowStack = __readmsr(MSR_IA32_PL3_SSP);
+
+ //
+ // Check if the context contains values for CET registers.
+ // If it doesn't, it means CET registers will not be set, and
+ // will disable CET if it was previously enabled.
+ //
+ xsaveData = (PXSAVE_AREA_HEADER)RTL_CONTEXT_CHUNK(Context, XState);
+
+ if (Thread->CetUserShadowStack != FALSE)
+ {
+ if (!BooleanFlagOn(xsaveData->Mask, XSTATE_MASK_CET_U))
+ {
+ //
+ // If the thread has CET enabled but the new context doesn't have
+ // CET registers in it, set the CET registers in the context to
+ // the current CET values.
+ //
+ SetFlag(xsaveData->Mask, XSTATE_MASK_CET_U);
+ cetData->Ia32CetUMsr = MSR_IA32_CET_SHSTK_EN;
+ cetData->Ia32Pl3SspMsr = *ShadowStack;
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // Verify that the new Ssp value is inside the shadow stack
+ //
+ status = KiVerifyContextXStateCetUEnabled(cetData, *ShadowStack);
+ if (NT_SUCCESS(status))
+ {
+ return STATUS_SUCCESS;
+ }
+
+ return status;
+ }
+
+ //
+ // If the thread doesn't have CET enabled and the new context doesn't
+ // have CET registers, or the CET mask is set but the CET registers
+ // don't hold any value, allow because the CET state will not change.
+ //
+ if (!(BooleanFlagOn(xsaveData->Mask, XSTATE_MASK_CET_U)) ||
+ ((cetData->Ia32CetUMsr == 0) &&
+ (cetData->Ia32Pl3SspMsr == NULL)))
+ {
+ return STATUS_SUCCESS;
+ }
+
+ return STATUS_SET_CONTEXT_DENIED;
+}
\ No newline at end of file
diff --git a/src/KiVerifyContextIpForUserCet.c b/src/KiVerifyContextIpForUserCet.c
new file mode 100644
index 0000000..d5a5431
--- /dev/null
+++ b/src/KiVerifyContextIpForUserCet.c
@@ -0,0 +1,100 @@
+NTSTATUS
+KiVerifyContextIpForUserCet (
+ _In_ PETHREAD Thread,
+ _In_ PCONTEXT Context,
+ _In_ PKCONTINUE_TYPE ContinueType,
+ _Inout_ PULONG_PTR ShadowStack
+ )
+{
+ ULONG64 userRip;
+ PKSTACK_CONTROL stackControl;
+ ULONG_PTR shadowStack;
+ KCONTINUE_TYPE continueType;
+ PKTRAP_FRAME trapFrame;
+
+ //
+ // Deny if the target Rip a kernel address or below 0x10000
+ //
+ userRip = Context->Rip;
+ if ((userRip >= MM_USER_PROBE_ADDRESS) ||
+ (userRip < MM_ALLOCATION_GRANULARITY))
+ {
+ return STATUS_SET_CONTEXT_DENIED;
+ }
+
+ //
+ // Ignore if target Rip is the previous address in user space
+ // (such as the initial thread start address)
+ //
+ trapFrame = PspGetBaseTrapFrame(Thread);
+ if (userRip == trapFrame->Rip)
+ {
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // Handle Rip validation for each KCONTINUE_TYPE
+ //
+ shadowStack = *ShadowStack;
+ continueType = *ContinueType;
+ switch (continueType)
+ {
+ case KCONTINUE_UNWIND:
+ case KCONTINUE_RESUME:
+ case KCONTINUE_SET:
+
+ //
+ // Get address of shadow stack if one was not provided by caller.
+ // If no shadow stack exists, allow any Rip.
+ //
+ if (shadowStack == NULL)
+ {
+ shadowStack = __readmsr(MSR_IA32_PL3_SSP);
+ if (shadowStack == NULL)
+ {
+ return STATUS_SUCCESS;
+ }
+ }
+
+ //
+ // Iterate over shadow stack and check if target Rip is in it.
+ // If thread is terminating, only try to find the target Rip
+ // in the current page of the shadow stack.
+ //
+ __try
+ {
+ do
+ {
+ shadowStack += sizeof(userRip);
+ if (*shadowStack == userRip)
+ {
+ *ShadowStack = shadowStack + sizeof(userRip);
+ return STATUS_SUCCESS;
+ }
+ } while (!(PAGE_ALIGNED(shadowStack)) || !(Thread->Terminated));
+
+ return STATUS_THREAD_IS_TERMINATING;
+ }
+ //
+ // If target Rip was not found and this is an unwind, try to verify
+ // Rip in the exception table unwind.
+ //
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ if (continueType == KCONTINUE_UNWIND)
+ {
+ return RtlVerifyUserUnwindTarget(userRip, KCONTINUE_UNWIND);
+ }
+
+ return STATUS_SET_CONTEXT_DENIED;
+ }
+ //
+ // If this is a long jump, try to verify Rip in the longjmp table.
+ //
+ case KCONTINUE_LONGJUMP:
+ return RtlVerifyUserUnwindTarget(userRip, KCONTINUE_LONGJUMP);
+
+ default:
+ return STATUS_INVALID_PARAMETER;
+ }
+}
\ No newline at end of file
diff --git a/src/KiVerifyContextRecord.c b/src/KiVerifyContextRecord.c
new file mode 100644
index 0000000..f95a347
--- /dev/null
+++ b/src/KiVerifyContextRecord.c
@@ -0,0 +1,51 @@
+NTSTATUS
+KiVerifyContextRecord (
+ _In_ PKTHREAD TargetThread,
+ _In_ PCONTEXT ContextFrame
+ _In_ PKCONTINUE_ARGUMENT ContinueArgument,
+ _Outptr_ PULONG_PTR ShadowStack
+ )
+{
+ PKPROCESS process;
+
+ process = Thread->Tcb.Process;
+
+ if (!BooleanFlagOn(ContextFrame->ContextFlags, CONTEXT_CONTROL))
+ {
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // If this is a non-wow64 process trying to set CS to a value other than KGDT64_R3_CODE,
+ // Or this is a pico process trying to set CS to a value other than KGDT64_R3_CODE or
+ // KGDT64_R3_CMCODE, Force CS to be KGDT64_R3_CODE.
+ //
+ if ((PsWow64GetProcessMachine(process) != IMAGE_FILE_MACHINE_I386) &&
+ ((process->PicoContext == NULL) ||
+ (ContextFrame->SegCs != (KGDT64_R3_CMCODE | RPL_MASK))))
+ {
+ ContextFrame->SegCs = KGDT64_R3_CODE | RPL_MASK;
+ }
+
+ //
+ // New context structure is not supported
+ //
+ if (!ARGUMENT_PRESENT(ContinueArgument))
+ {
+ return STATUS_SUCCESS;
+ }
+
+ //
+ // Verify new RIP value in the shadow stack
+ //
+ status = KeVerifyContextIpForUserCet(TargetThread,
+ ContextFrame,
+ ContinueArgument,
+ ShadowStack);
+ if (NT_SUCCESS(status))
+ {
+ return STATUS_SUCCESS;
+ }
+
+ return status;
+}
\ No newline at end of file
diff --git a/src/KiVerifyContextXStateCetUEnabled.c b/src/KiVerifyContextXStateCetUEnabled.c
new file mode 100644
index 0000000..35ebc27
--- /dev/null
+++ b/src/KiVerifyContextXStateCetUEnabled.c
@@ -0,0 +1,60 @@
+NTSTATUS
+KiVerifyContextXStateCetUEnabled (
+ _In_ PXSAVE_CET_U_FORMAT CetData,
+ _In_ ULONG_PTR ShadowStack
+ )
+{
+ MEMORY_REGION_INFORMATION regionInfo;
+ ULONG_PTR shadowStackEnd;
+ ULONG_PTR newShadowStack;
+
+ //
+ // If the value for the MSR mask is not 1 (CET enabled), deny the new context
+ //
+ if (CetData->Ia32CetUMsr != MSR_IA32_CET_SHSTK_EN)
+ {
+ return STATUS_SET_CONTEXT_DENIED;
+ }
+
+ //
+ // Deny the context if the new Ssp value is not 8-byte aligned
+ //
+ newShadowStack = CetData->Ia32Pl3SspMsr;
+ if ((newShadowStack & 7) != 0)
+ {
+ return STATUS_SET_CONTEXT_DENIED;
+ }
+
+ //
+ // Check if the new Ssp is lower than the current Ssp,
+ // so it will point to uninitialized memory
+ //
+ if (newShadowStack < ShadowStack)
+ {
+ return STATUS_SET_CONTEXT_DENIED;
+ }
+
+ //
+ // Get the end address of the shadow stack
+ //
+ ZwQueryVirtualMemory(NtCurrentProcess(),
+ ShadowStack,
+ MemoryRegionInformation,
+ ®ionInfo,
+ sizeof(regionInfo),
+ NULL);
+ shadowStackEnd = MemoryInformation.AllocationBase +
+ MemoryInformation.RegionSize -
+ PAGE_SIZE;
+
+ //
+ // Check if the new Ssp is higher than the end address of
+ // the shadiw stack, so outside the stack bounds
+ //
+ if (newShadowStack >= shadowStackEnd)
+ {
+ return STATUS_SET_CONTEXT_DENIED;
+ }
+
+ return STATUS_SUCCESS;
+}
\ No newline at end of file
diff --git a/src/NtSetInformationProcess.c b/src/NtSetInformationProcess.c
new file mode 100644
index 0000000..1e32dab
--- /dev/null
+++ b/src/NtSetInformationProcess.c
@@ -0,0 +1,191 @@
+NTSTATUS
+NTAPI
+NtSetInformationProcess (
+ _In_ HANDLE ProcessHandle,
+ _In_ PROCESSINFOCLASS ProcessInformationClass,
+ _In_ PVOID ProcessInformation,
+ _In_ ULONG ProcessInformationLength
+ )
+{
+ PROCESS_DYNAMIC_EH_CONTINUATION_TARGETS_INFORMATION targetInfo;
+ ULONG targetsSize;
+ PPROCESS_DYNAMIC_EH_CONTINUATION_TARGET targetsArray;
+ PPROCESS_DYNAMIC_EH_CONTINUATION_TARGET ehTargets;
+ PEPROCESS targetProcess;
+ NTSTATUS status;
+ KPROCESSOR_MODE previousMode = ExGetPreviousMode();
+ ULONG i;
+ ULONG targetsProcessed;
+
+ //
+ // Handle the dynamic exception handlers information class
+ //
+ if (ProcessInformationClass == ProcessDynamicEHContinuationTargets)
+ {
+ //
+ // Validate the data is the right size
+ //
+ if (ProcessInformationLength != sizeof(targetInfo))
+ {
+ return STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ //
+ // Make a local copy of the data to avoid races
+ //
+ __try
+ {
+ targetInfo = *(PPROCESS_DYNAMIC_EH_CONTINUATION_TARGETS_INFORMATION)ProcessInformation;
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ return GetExceptionCode();
+ }
+
+ //
+ // Check how many targets there are
+ //
+ targetsSize = sizeof(PROCESS_DYNAMIC_EH_CONTINUATION_TARGET) *
+ targetInfo.NumberOfTargets;
+ if (targetsSize == 0)
+ {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Make sure there are targets
+ //
+ targetsArray = targetInfo.Targets;
+ if (targetsArray == NULL)
+ {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Probe that the targets are all in writeable UM memory
+ //
+ __try
+ {
+ ProbeForWrite(targetsArray, targetsSize, 8);
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ return GetExceptionCode();
+ }
+
+ //
+ // These fields aren't used yet
+ //
+ if ((targetInfo.Reserved != 0) || (targetInfo.Reserved2 != 0))
+ {
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ //
+ // Only user-mode code should be setting dynamic EH targets
+ //
+ if (previousMode != UserMode)
+ {
+ return STATUS_ACCESS_DENIED;
+ }
+
+ //
+ // Make sure the caller has a full process write handle to the target
+ //
+ targetProcess = NULL;
+ status = ObReferenceObjectByHandle(ProcessHandle,
+ GENERIC_WRITE & ~SYNCHRONIZE,
+ (POBJECT_TYPE)PsProcessType,
+ UserMode,
+ (PVOID*)&targetProcess,
+ NULL);
+ if (!NT_SUCCESS(status))
+ {
+ goto Cleanup;
+ }
+
+ //
+ // Don't allow the current process to add targets to itself
+ //
+ if (targetProcess == PsGetCurrentProcess())
+ {
+ status = STATUS_ACCESS_DENIED;
+ goto Cleanup;
+ }
+
+ //
+ // Don't allow setting EH handlers if the target process doesn't have CET
+ //
+ if (targetProcess->MitigationFlags2Values.CetUserShadowStacks == FALSE)
+ {
+ status = STATUS_NOT_SUPPORTED;
+ goto Cleanup;
+ }
+
+ //
+ // Allocate a kernel copy of the targets
+ //
+ ehTargets = ExAllocatePoolWithQuotaTag(PagedPool |
+ POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,
+ targetsSize,
+ 'NHED');
+ if (ehTargets == NULL)
+ {
+ status = STATUS_NO_MEMORY;
+ goto Cleanup;
+ }
+
+ //
+ // Copy them in the array
+ //
+ RtlCopyMemory(ehTargets, targetsArray, targetsSize);
+
+ //
+ // Process each target in the array
+ //
+ targetsProcessed = 0;
+ status = PspProcessDynamicEHContinuationTargets(targetProcess,
+ ehTargets,
+ targetInfo.NumberOfTargets,
+ &targetsProcessed);
+
+ //
+ // Write out the flags back in the original user buffer, which will
+ // basically fill set DYNAMIC_EH_CONTINUATION_TARGET_PROCESSED so the
+ // caller knows what wasn't processed
+ //
+ __try
+ {
+ for (i = 0; i < targetsProcessed; i++)
+ {
+ targetsArray[i].Flags = ehTargets[i].Flags;
+ }
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ status = GetExceptionCode();
+ }
+
+ Cleanup:
+ //
+ // Dereference the target process if needed
+ //
+ if (targetProcess != NULL)
+ {
+ ObDereferenceObject(targetProcess);
+ }
+
+ //
+ // Free the EH target array if needed
+ //
+ if (ehTargets != NULL)
+ {
+ ExFreePoolWithTag(ehTargets, 'NHED');
+ }
+ }
+
+ //
+ // Return back to caller
+ //
+ return status;
+}
\ No newline at end of file
diff --git a/src/RtlVerifyUserUnwindTarget.c b/src/RtlVerifyUserUnwindTarget.c
new file mode 100644
index 0000000..ee16cba
--- /dev/null
+++ b/src/RtlVerifyUserUnwindTarget.c
@@ -0,0 +1,184 @@
+NTSTATUS
+RtlVerifyUserUnwindTarget (
+ _In_ PVOID TargetRip,
+ _In_ KCONTINUE_TYPE ContinueType
+ )
+{
+ PIMAGE_LOAD_CONFIG_DIRECTORY64 loadConfig;
+ INVERTED_FUNCTION_TABLE_ENTRY userFunctionTable;
+ ULONGLONG imageSize;
+ NTSTATUS status;
+ ULONG guardFlags;
+ SIZE_T configSize;
+ PVOID table;
+ ULONGLONG count;
+ ULONG rva;
+ SIZE_T metaSize;
+ BOOLEAN found;
+ PVOID entry;
+ //
+ // First, do a quick lookup in the user function table, which should almost always work
+ //
+ found = RtlpLookupUserFunctionTableInverted(TargetRip, &userFunctionTable);
+ if (found == FALSE)
+ {
+ //
+ // This module might not have any exception/unwind data, so do a slow VAD lookup instead
+ //
+ status = MmGetImageBase(TargetRip, &userFunctionTable.ImageBase, &imageSize);
+ if (!NT_SUCCESS(status))
+ {
+ //
+ // There does not appear to be a valid module loaded at this address.
+ // The only other possibility is that this is JIT, which we'll handle at the end.
+ //
+ userFunctionTable.ImageBase = NULL;
+ }
+ else
+ {
+ //
+ // The VAD lookup can theoretically return a >= 4GB-sized module. This is not expected
+ // and not supported for actual PE images.
+ //
+ if (imageSize >= MAXULONG)
+ {
+ return STATUS_INTEGER_OVERFLOW;
+ }
+ //
+ // To simplify the code, capture the size in the same structure that the user function
+ // table lookup would've returned.
+ //
+ userFunctionTable.SizeOfImage = (ULONG)imageSize;
+ }
+ }
+ //
+ // Did we find a loaded module at this address?
+ //
+ if (userFunctionTable.ImageBase != NULL)
+ {
+ //
+ // We're going to touch user-mode data, so enter an exception handler context
+ //
+ __try
+ {
+ //
+ // Kind of an arbitrary probe of 64 bytes, since the call below will call
+ // RtlImageNtHeaderEx which does a proper probe of the whole header already.
+ //
+ ProbeForRead(userFunctionTable.ImageBase, 64, 1);
+ //
+ // Get the Image Load Config Directory. Note that this is a user-mode pointer
+ //
+ loadConfig = LdrImageDirectoryEntryToLoadConfig(userFunctionTable.ImageBase);
+ //
+ // For longjmp, use the longjump table, otherwise, for unwind, use the dynamic
+ // exception handler continuation table.
+ //
+ if (ContinueType == KCONTINUE_LONGJUMP)
+ {
+ guardFlags = IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT;
+ configSize = FIELD_OFFSET(IMAGE_LOAD_CONFIG_DIRECTORY64, DynamicValueRelocTable);
+ }
+ else
+ {
+ guardFlags = IMAGE_GUARD_EH_CONTINUATION_TABLE_PRESENT;
+ configSize = sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64);
+ }
+ //
+ // Probe the configuration directory, as LdrImageDirectoryEntryToLoadConfig only
+ // probes the first 4 bytes to account for the "Size" field.
+ //
+ // This probe will also raise if loadConfig is NULL (unless this is NTVDM on 32-bit).
+ //
+ ProbeForRead(loadConfig, configSize, 1);
+ //
+ // Make sure there's a load configuration directory, that it's large enough to have
+ // one of the two tables we care about, and that the guard flags indicate that the
+ // table we care about is actually present.
+ //
+ if ((loadConfig == NULL) ||
+ (loadConfig->Size < configSize) ||
+ !(guardFlags & loadConfig->GuardFlags))
+ {
+ //
+ // We return success here, because this means that the binary is not compatible
+ // with CET. As such, for compatibility, allow this jump target.
+ //
+ return STATUS_SUCCESS;
+ }
+ }
+ __except (EXCEPTION_EXECUTE_HANDLER)
+ {
+ //
+ // Something's wrong with the user address space, bail out
+ //
+ return GetExceptionCode();
+ }
+ //
+ // Use the correct table and count (longjmp vs. unwind)
+ //
+ if (ContinueType == KCONTINUE_LONGJUMP)
+ {
+ table = (PVOID)loadConfig->GuardLongJumpTargetTable;
+ count = loadConfig->GuardLongJumpTargetCount;
+ }
+ else
+ {
+ table = (PVOID)loadConfig->GuardEHContinuationTable;
+ count = loadConfig->GuardEHContinuationCount;
+ }
+ //
+ // More than 4 billion entries are not allowed
+ //
+ if (count >= MAXULONG)
+ {
+ return STATUS_INTEGER_OVERFLOW;
+ }
+ //
+ // If the table is empty, then there can't be any valid targets in this image...
+ //
+ if (count != 0)
+ {
+ //
+ // PE Images are always <= 4GB, so compute the 32-bit RVA
+ //
+ rva = (ULONG)((ULONG_PTR)TargetRip - (ULONG_PTR)userFunctionTable.ImageBase);
+ //
+ // The guard tables can have n-bytes of metadata, indicated by the upper nibble
+ //
+ metaSize = loadConfig->GuardFlags >> IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT;
+ //
+ // Search through the guard table for this RVA
+ //
+ entry = bsearch_s(&rva, table, count, metaSize + sizeof(rva), RtlpTargetCompare, NULL);
+ if (entry != NULL)
+ {
+ //
+ // The entry was found, so this is a valid target
+ //
+ return STATUS_SUCCESS;
+ }
+ }
+ }
+ //
+ // Either there's no valid image mapped at this address, or there is, but its relevant guard
+ // table does not contain the target RIP requested (as a reminder, if there's no table, then
+ // the target _is_ allowed, for compatibility reasons).
+ //
+ // In this case, for exception unwinding (and obviously not longjmp), check if there is a
+ // JIT-ted (dynamic) exception handler continuation target registered at this target.
+ //
+ if (ContinueType == KCONTINUE_UNWIND)
+ {
+ found = RtlpFindDynamicEHContinuationTarget(TargetRip);
+ if (found != FALSE)
+ {
+ return STATUS_SUCCESS;
+ }
+ }
+ //
+ // Otherwise, we either didn't find a dynamic handler, or this wasn't an unwind to begin with,
+ // so fail the request.
+ //
+ return STATUS_SET_CONTEXT_DENIED;
+}
\ No newline at end of file
diff --git a/src/RtlpTargetCompare.c b/src/RtlpTargetCompare.c
new file mode 100644
index 0000000..e11a611
--- /dev/null
+++ b/src/RtlpTargetCompare.c
@@ -0,0 +1,14 @@
+INT
+RtlpTargetCompare (
+ void* Context,
+ const void* Key,
+ const void* Datum
+ )
+{
+ ULONG_PTR rva1;
+ ULONG_PTR rva2;
+ UNREFERENCED_PARAMETER(Context);
+ rva1 = *(PULONG_PTR)Key;
+ rva2 = *(PULONG_PTR)Datum;
+ return (INT)(rva1 - rva2);
+}
\ No newline at end of file