Reorganize and add useful functions.

This commit is contained in:
Yarden Shafir
2020-01-13 00:44:53 +02:00
parent 3e9b810bd0
commit 837d202a5c
14 changed files with 796 additions and 0 deletions
+3
View File
@@ -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.
+46
View File
@@ -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;
}
+69
View File
@@ -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;
}
+78
View File
@@ -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;
}
+100
View File
@@ -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;
}
}
+51
View File
@@ -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;
}
+60
View File
@@ -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,
&regionInfo,
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;
}
+191
View File
@@ -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;
}
+184
View File
@@ -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;
}
+14
View File
@@ -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);
}