mirror of
https://github.com/yardenshafir/cet-research
synced 2026-06-21 14:14:07 +00:00
Update functions to match build 20226
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user