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 @@ true true + + "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