diff --git a/src/KeVerifyContextIpForUserCet.c b/src/KeVerifyContextIpForUserCet.c index ee3f4bd..1a789bc 100644 --- a/src/KeVerifyContextIpForUserCet.c +++ b/src/KeVerifyContextIpForUserCet.c @@ -8,6 +8,10 @@ KeVerifyContextIpForUserCet ( { PEPROCESS process; NTSTATUS status; + BOOLEAN cetRelaxedMode; + ULONG64 userRip; + BOOLEAN notRelaxed; + KCONTINUE_TYPE continueType; // // No need to do anything if shadow stack is not enabled @@ -31,16 +35,23 @@ KeVerifyContextIpForUserCet ( // // Verify the new Rip target // - status = KiVerifyContextIpForUserCet(Thread, Context, ContinueType, ShadowStack); + cetRelaxedMode = process->MitigationFlags2Values.UserCetSetContextIpValidationRelaxedMode & 1 != 0; + status = KiVerifyContextIpForUserCet(Thread, Context, ContinueType, cetRelaxedMode, ShadowStack); // - // Audit failure if requested and fake success + // Log failure if needed and fake success // - if ((status == STATUS_SET_CONTEXT_DENIED) && - (process->MitigationFlags2Values.AuditUserCetSetContextIpValidation)) + if ( status == STATUS_SET_CONTEXT_DENIED ) { - KiLogUserCetSetContextIpValidationAudit(*ContinueType); - status = STATUS_SUCCESS; + userRip = Context->Rip; + continueType = *ContinueType; + notRelaxed = CetRelaxedMode ^ 1; + if (!(process->MitigationFlags2Values.AuditUserCetSetContextIpValidation)) + { + KiLogUserCetSetContextIpValidationFailure(2, continueType, userRip, notRelaxed); + return status; + } + KiLogUserCetSetContextIpValidationFailure(1, continueType, userRip, notRelaxed); } return status; } \ No newline at end of file diff --git a/src/KiVerifyContextIpForUserCet.c b/src/KiVerifyContextIpForUserCet.c index d5a5431..b33f470 100644 --- a/src/KiVerifyContextIpForUserCet.c +++ b/src/KiVerifyContextIpForUserCet.c @@ -1,16 +1,26 @@ +struct _UNWIND_STATE +{ + PVOID ImageBase; + PIMAGE_LOAD_CONFIG_DIRECTORY64 LoadConfig; + BOOLEAN CheckedLoadConfig; +} UNWIND_STATE, *PUNWIND_STATE; + + NTSTATUS KiVerifyContextIpForUserCet ( - _In_ PETHREAD Thread, - _In_ PCONTEXT Context, - _In_ PKCONTINUE_TYPE ContinueType, - _Inout_ PULONG_PTR ShadowStack + _In_ PETHREAD Thread, + _In_ PCONTEXT Context, + _In_ PKCONTINUE_TYPE ContinueType, + _In_ BOOLEAN RelaxedMode, + _Inout_ ULONG_PTR ShadowStack ) { ULONG64 userRip; - PKSTACK_CONTROL stackControl; ULONG_PTR shadowStack; - KCONTINUE_TYPE continueType; - PKTRAP_FRAME trapFrame; + UKCONTINUE_TYPE continueType; + NTSTATUS status; + BOOLEAN loadConfigChecked; + UNWIND_STATE unwindState; // // Deny if the target Rip a kernel address or below 0x10000 @@ -21,7 +31,6 @@ KiVerifyContextIpForUserCet ( { return STATUS_SET_CONTEXT_DENIED; } - // // Ignore if target Rip is the previous address in user space // (such as the initial thread start address) @@ -32,69 +41,121 @@ KiVerifyContextIpForUserCet ( return STATUS_SUCCESS; } - // - // Handle Rip validation for each KCONTINUE_TYPE - // shadowStack = *ShadowStack; continueType = *ContinueType; - switch (continueType) + if (continueType == KCONTINUE_LONGJUMP) + { + return RtlVerifyUserUnwindTarget(userRip, KCONTINUE_LONGJUMP, 0); + } + else if ((continueType != KCONTINUE_UNWIND) && + (continueType != KCONTINUE_RESUME) && + (continueType != KCONTINUE_SET)) { - 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; } + // + // 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; + } + } + + if ((continueType == KCONTINUE_UNWIND) && + (userRip == PsNtdllExports.RtlUserThreadStart)) + { + *ContinueType = KCONTINUE_RESUME; + continueType = KCONTINUE_RESUME; + } + RtlZeroMemory(&unwindState, sizeof(unwindState)); + if (continueType == KCONTINUE_UNWIND) + { + status = RtlVerifyUserUnwindTarget(userRip, KCONTINUE_UNWIND, &unwindState); + if (NT_SUCCESS(status)) + { + return status; + } + } + // + // This code will run when RelaxedMode is enabled and continueType is + // either KCONTINUE_SET or KCONTINUE_UNWIND if RtlVerifyUserUnwindTarget failed + // + if ((RelaxedMode) && (*ContinueType != KCONTINUE_RESUME)) + { + if (!unwindState.CheckedLoadConfig) + { + status = RtlGetImageBaseAndLoadConfig(userRip, &unwindState.ImageBase, &unwindState.LoadConfig); + loadConfigChecked = NT_SUCCESS(status) ? 1: unwindState.CheckedLoadConfig; + unwindState.CheckedLoadConfig = loadConfigChecked; + } + if ( loadConfigChecked ) + { + if ( unwindState.ImageBase ) + { + // + // Check if there is a EhContinuationTable in the LoadConfig. + // If it exists it would be after the XFG data + // This code is actually just a "probe" to see if there is a point in checking the EhCont flag, + // and will throw STATUS_ACCESS_VIOLATION if it fails. + // + __try + { + ProbeForRead(unwindState.LoadConfig, + offsetof(IMAGE_LOAD_CONFIG_DIRECTORY64, GuardEHContinuationCount), + RTL_SIZEOF_THROUGH_FIELD(IMAGE_LOAD_CONFIG_DIRECTORY64, GuardEHContinuationCount)); + + // + // So this code is meant as a "whitelist" for older binaries that don't fully support CET. + // There are some FPs with processes using NtSetContextThread to targets that CET does not expect. + // For newer processes that were compiled recently with the correct flags, + // this will create an EX_CONTINUATION_TABLE that will contain those targets. + // But for older processes Windows supports "relaxed mode" CET. + // If "relaxed mode" is set for the process, any module that does not have an EX_CONTINUATION_TABLE + // will be allowed to set the context to any address. + // + if ((unwindState.LoadConfig) && + (unwindState.LoadConfig->Size >= RTL_SIZEOF_THROUGH_FIELD(IMAGE_LOAD_CONFIG_DIRECTORY64, GuardEHContinuationCount)) && + ((unwindState.LoadConfig->GuardFlags & IMAGE_GUARD_EH_CONTINUATION_TABLE_PRESENT) != 0)) + { + goto CheckAddressInShadowStack; + } + } + __except + { + goto CheckAddressInShadowStack; + } + return STATUS_SUCCESS; + } + return STATUS_SUCCESS; + } + } +CheckAddressInShadowStack: + // + // 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 + { + if (*shadowStack == userRip) + { + *ShadowStack = shadowStack + sizeof(userRip); + return STATUS_SUCCESS; + } + shadowStack += sizeof(userRip); + } while (!(PAGE_ALIGNED(shadowStack)) || !(Thread->Terminated)); + + return STATUS_THREAD_IS_TERMINATING; + } + __except + { + return STATUS_SET_CONTEXT_DENIED; + } } \ No newline at end of file