mirror of
https://github.com/yardenshafir/cet-research
synced 2026-06-21 14:14:07 +00:00
Reorganize and add useful functions.
This commit is contained in:
@@ -77,6 +77,9 @@
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.23.28105\lib\x86"</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user