├── README.md ├── Xpolicy.sln ├── Xpolicy ├── Xpolicy.vcxproj ├── Xpolicy.vcxproj.filters ├── Xpolicy.vcxproj.user ├── bin │ ├── Xpolicy32.exe │ ├── Xpolicy32.pdb │ ├── Xpolicy64.exe │ └── Xpolicy64.pdb └── xpolicy.c └── src ├── KeVerifyContextIpForUserCet.c ├── KeVerifyContextRecord.c ├── KeVerifyContextXStateCetU.c ├── KiVerifyContextIpForUserCet.c ├── KiVerifyContextRecord.c ├── KiVerifyContextXStateCetUEnabled.c ├── NtSetInformationProcess.c └── RtlVerifyUserUnwindTarget.c /README.md: -------------------------------------------------------------------------------- 1 | # Windows Control Flow Enforcement Technology (CET) Research Repository 2 | 3 | A collection of tools, source code, and papers researching Windows' implementation of CET. 4 | 5 | Associated blog post: https://www.windows-internals.com/cet-on-windows 6 | 7 | (c) By Yarden Shafir (@yarden_shafir) & Alex Ionescu (@aionescu) 8 | -------------------------------------------------------------------------------- /Xpolicy.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.852 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Xpolicy", "Xpolicy\Xpolicy.vcxproj", "{A82A784A-5EFB-4E1C-BE43-83169D135076}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {A82A784A-5EFB-4E1C-BE43-83169D135076}.Debug|x64.ActiveCfg = Debug|x64 17 | {A82A784A-5EFB-4E1C-BE43-83169D135076}.Debug|x64.Build.0 = Debug|x64 18 | {A82A784A-5EFB-4E1C-BE43-83169D135076}.Debug|x86.ActiveCfg = Debug|Win32 19 | {A82A784A-5EFB-4E1C-BE43-83169D135076}.Debug|x86.Build.0 = Debug|Win32 20 | {A82A784A-5EFB-4E1C-BE43-83169D135076}.Release|x64.ActiveCfg = Release|x64 21 | {A82A784A-5EFB-4E1C-BE43-83169D135076}.Release|x64.Build.0 = Release|x64 22 | {A82A784A-5EFB-4E1C-BE43-83169D135076}.Release|x86.ActiveCfg = Release|Win32 23 | {A82A784A-5EFB-4E1C-BE43-83169D135076}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {29038274-320C-46F5-8173-5B13D1F41515} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /Xpolicy/Xpolicy.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 15.0 23 | {A82A784A-5EFB-4E1C-BE43-83169D135076} 24 | Xpolicy 25 | 10.0.19525.0 26 | 27 | 28 | 29 | Application 30 | true 31 | v141 32 | MultiByte 33 | 34 | 35 | Application 36 | false 37 | v141 38 | true 39 | MultiByte 40 | 41 | 42 | Application 43 | true 44 | v141 45 | MultiByte 46 | 47 | 48 | Application 49 | false 50 | v141 51 | true 52 | MultiByte 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | Level3 76 | Disabled 77 | true 78 | true 79 | 80 | 81 | "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.23.28105\lib\x86" 82 | 83 | 84 | 85 | 86 | Level3 87 | Disabled 88 | true 89 | true 90 | 91 | 92 | "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.23.28105\lib\x64" 93 | 94 | 95 | 96 | 97 | Level3 98 | MaxSpeed 99 | true 100 | true 101 | true 102 | true 103 | 104 | 105 | true 106 | true 107 | 108 | 109 | 110 | 111 | Level3 112 | MaxSpeed 113 | true 114 | true 115 | true 116 | true 117 | 118 | 119 | true 120 | true 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /Xpolicy/Xpolicy.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /Xpolicy/Xpolicy.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /Xpolicy/bin/Xpolicy32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yardenshafir/cet-research/f97cfb131165cb524671dc9ff0dbd8dcedfbf2d1/Xpolicy/bin/Xpolicy32.exe -------------------------------------------------------------------------------- /Xpolicy/bin/Xpolicy32.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yardenshafir/cet-research/f97cfb131165cb524671dc9ff0dbd8dcedfbf2d1/Xpolicy/bin/Xpolicy32.pdb -------------------------------------------------------------------------------- /Xpolicy/bin/Xpolicy64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yardenshafir/cet-research/f97cfb131165cb524671dc9ff0dbd8dcedfbf2d1/Xpolicy/bin/Xpolicy64.exe -------------------------------------------------------------------------------- /Xpolicy/bin/Xpolicy64.pdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yardenshafir/cet-research/f97cfb131165cb524671dc9ff0dbd8dcedfbf2d1/Xpolicy/bin/Xpolicy64.pdb -------------------------------------------------------------------------------- /Xpolicy/xpolicy.c: -------------------------------------------------------------------------------- 1 | #define UNICODE 2 | #include 3 | #include 4 | #include 5 | 6 | typedef struct _XSAVE_CPU_INFO 7 | { 8 | /* 0x0000 */ unsigned char Processor; 9 | /* 0x0002 */ unsigned short Family; 10 | /* 0x0004 */ unsigned short Model; 11 | /* 0x0006 */ unsigned short Stepping; 12 | /* 0x0008 */ unsigned short ExtendedModel; 13 | /* 0x000c */ unsigned long ExtendedFamily; 14 | /* 0x0010 */ unsigned __int64 MicrocodeVersion; 15 | /* 0x0018 */ unsigned long Reserved; 16 | /* 0x001c */ long __PADDING__[1]; 17 | } XSAVE_CPU_INFO, *PXSAVE_CPU_INFO; /* size: 0x0020 */ 18 | 19 | typedef struct _XSAVE_CPU_ERRATA 20 | { 21 | /* 0x0000 */ unsigned long NumberOfErrata; 22 | /* 0x0008 */ struct _XSAVE_CPU_INFO Errata[1]; 23 | } XSAVE_CPU_ERRATA, *PXSAVE_CPU_ERRATA; /* size: 0x0028 */ 24 | 25 | typedef struct _XSAVE_SUPPORTED_CPU 26 | { 27 | /* 0x0000 */ struct _XSAVE_CPU_INFO CpuInfo; 28 | union 29 | { 30 | /* 0x0020 */ struct XSAVE_CPU_ERRATA* CpuErrata; 31 | /* 0x0020 */ unsigned __int64 Unused; 32 | }; /* size: 0x0008 */ 33 | } XSAVE_SUPPORTED_CPU, *PXSAVE_SUPPORTED_CPU; /* size: 0x0028 */ 34 | 35 | typedef struct _XSAVE_VENDOR 36 | { 37 | /* 0x0000 */ unsigned long VendorId[3]; 38 | /* 0x0010 */ struct _XSAVE_SUPPORTED_CPU SupportedCpu; 39 | } XSAVE_VENDOR, *PXSAVE_VENDOR; /* size: 0x0038 */ 40 | 41 | typedef struct _XSAVE_VENDORS 42 | { 43 | /* 0x0000 */ unsigned long NumberOfVendors; 44 | /* 0x0008 */ struct _XSAVE_VENDOR Vendor[1]; 45 | } XSAVE_VENDORS, *PXSAVE_VENDORS; /* size: 0x0040 */ 46 | 47 | typedef struct _XSAVE_FEATURE 48 | { 49 | /* 0x0000 */ unsigned long FeatureId; 50 | union 51 | { 52 | /* 0x0008 */ struct _XSAVE_VENDORS* Vendors; 53 | /* 0x0008 */ unsigned __int64 Unused; 54 | }; /* size: 0x0008 */ 55 | } XSAVE_FEATURE, *PXSAVE_FEATURE; /* size: 0x0010 */ 56 | 57 | typedef struct _XSAVE_POLICY 58 | { 59 | /* 0x0000 */ unsigned long Version; 60 | /* 0x0004 */ unsigned long Size; 61 | /* 0x0008 */ unsigned long Flags; 62 | /* 0x000c */ unsigned long MaxSaveAreaLength; 63 | /* 0x0010 */ unsigned __int64 FeatureBitmask; 64 | /* 0x0018 */ unsigned long NumberOfFeatures; 65 | /* 0x0020 */ struct _XSAVE_FEATURE Features[1]; 66 | } XSAVE_POLICY, *PXSAVE_POLICY; /* size: 0x0030 */ 67 | 68 | PCHAR featureName[] = 69 | { 70 | "XSTATE_LEGACY_FLOATING_POINT", 71 | "XSTATE_LEGACY_SSE", 72 | "XSTATE_AVX", 73 | "XSTATE_MPX_BNDREGS", 74 | "XSTATE_MPX_BNDCSR", 75 | "XSTATE_AVX512_KMASK", 76 | "XSTATE_AVX512_ZMM_H", 77 | "XSTATE_AVX512_ZMM", 78 | "XSTATE_IPT", 79 | "XSTATE_PKRU", 80 | "XSTATE_UNKNOWN", 81 | "XSTATE_CET_U", 82 | }; 83 | 84 | int main() 85 | { 86 | UINT res; 87 | WCHAR filePath[MAX_PATH]; 88 | HMODULE handle; 89 | HRSRC resource; 90 | HGLOBAL hResource; 91 | PXSAVE_POLICY policy; 92 | PXSAVE_VENDORS xsaveVendors; 93 | PXSAVE_CPU_INFO cpuInfo; 94 | PXSAVE_CPU_ERRATA cpuErrata; 95 | 96 | // 97 | // Build the path and load hwpolicy.sys 98 | // 99 | res = GetSystemDirectory(filePath, sizeof(filePath)); 100 | if (res == 0) 101 | { 102 | printf("Failed to get system directory. Error: %d\n", GetLastError()); 103 | return 1; 104 | } 105 | wcscat_s(filePath, _countof(filePath), L"\\drivers\\hwpolicy.sys"); 106 | handle = LoadLibraryEx(filePath, NULL, LOAD_LIBRARY_AS_IMAGE_RESOURCE); 107 | if (handle == NULL) 108 | { 109 | printf("Failed loading hwpolicy.sys. Error: %d\n", GetLastError()); 110 | return 1; 111 | } 112 | 113 | // 114 | // Load the resource that contains the errata information 115 | // 116 | resource = FindResource(handle, MAKEINTRESOURCE(1), MAKEINTRESOURCE(101)); 117 | if (resource == NULL) 118 | { 119 | printf("Failed finding resource. Error: %d\n", GetLastError()); 120 | return 1; 121 | } 122 | hResource = LoadResource(handle, resource); 123 | if (hResource == NULL) 124 | { 125 | printf("Failed loading resource. Error: %d\n", GetLastError()); 126 | return 1; 127 | } 128 | 129 | policy = (PXSAVE_POLICY)LockResource(hResource); 130 | 131 | printf("Xsave policy: %p\n", policy); 132 | printf("Version: 0x%x\n", policy->Version); 133 | printf("Size: 0x%x\n", policy->Size); 134 | printf("Flags: 0x%x\n", policy->Flags); 135 | printf("MaxSaveAreaLength: 0x%x\n", policy->MaxSaveAreaLength); 136 | printf("FeatureBitmask: 0x%llx\n", policy->FeatureBitmask); 137 | printf("NumberOfFeatures: %d\n", policy->NumberOfFeatures); 138 | 139 | printf("\nFeatures:\n"); 140 | for (ULONG i = 0; i < policy->NumberOfFeatures; i++) 141 | { 142 | printf("\tFeatureId: %d (%s)\n", 143 | policy->Features[i].FeatureId, 144 | policy->Features[i].FeatureId < _countof(featureName) ? 145 | featureName[policy->Features[i].FeatureId] : 146 | "UNKNOWN"); 147 | 148 | if (policy->Features[i].Unused == 0) 149 | { 150 | continue; 151 | } 152 | 153 | printf("\tVendors:\n"); 154 | xsaveVendors = (PXSAVE_VENDORS)(policy->Features[i].Unused + (ULONG_PTR)policy); 155 | printf("\tNumber of vendors: %d\n", xsaveVendors->NumberOfVendors); 156 | for (ULONG j = 0; j < xsaveVendors->NumberOfVendors; j++) 157 | { 158 | printf("\t\tVendor Id: %s\n", (PCHAR)xsaveVendors->Vendor[j].VendorId); 159 | printf("\t\tCpu Info:\n"); 160 | cpuInfo = &xsaveVendors->Vendor[j].SupportedCpu.CpuInfo; 161 | printf("\t\t\tProcessor: %x\n", cpuInfo->Processor); 162 | printf("\t\t\tFamily: %x\n", cpuInfo->Family); 163 | printf("\t\t\tModel: %x\n", cpuInfo->Model); 164 | printf("\t\t\tStepping: %x\n", cpuInfo->Stepping); 165 | printf("\t\t\tExtended model: %x\n", cpuInfo->ExtendedModel); 166 | printf("\t\t\tExtended family: %x\n", cpuInfo->ExtendedFamily); 167 | printf("\t\t\tMicrocode version: %llx\n", cpuInfo->MicrocodeVersion); 168 | printf("\n"); 169 | 170 | if (xsaveVendors->Vendor[j].SupportedCpu.Unused == 0) 171 | { 172 | continue; 173 | } 174 | 175 | printf("\t\tCpu Errata:\n"); 176 | cpuErrata = (PXSAVE_CPU_ERRATA)(xsaveVendors->Vendor[j].SupportedCpu.Unused + (ULONG_PTR)policy); 177 | printf("\t\tNumber of errata: %d\n", cpuErrata->NumberOfErrata); 178 | 179 | for (ULONG n = 0; n NumberOfErrata; n++) 180 | { 181 | cpuInfo = &cpuErrata->Errata[n]; 182 | printf("\t\t\tProcessor: %x\n", cpuInfo->Processor); 183 | printf("\t\t\tFamily: %x\n", cpuInfo->Family); 184 | printf("\t\t\tModel: %x\n", cpuInfo->Model); 185 | printf("\t\t\tStepping: %x\n", cpuInfo->Stepping); 186 | printf("\t\t\tExtended model: %x\n", cpuInfo->ExtendedModel); 187 | printf("\t\t\tExtended family: %x\n", cpuInfo->ExtendedFamily); 188 | printf("\t\t\tMicrocode version: %llx\n", cpuInfo->MicrocodeVersion); 189 | printf("\n"); 190 | } 191 | 192 | printf("\n"); 193 | } 194 | } 195 | 196 | UnlockResource(hResource); 197 | FreeLibrary(handle); 198 | 199 | return 0; 200 | } -------------------------------------------------------------------------------- /src/KeVerifyContextIpForUserCet.c: -------------------------------------------------------------------------------- 1 | NTSTATUS 2 | KeVerifyContextIpForUserCet ( 3 | _In_ PETHREAD Thread, 4 | _In_ PCONTEXT Context, 5 | _In_ PKCONTINUE_TYPE ContinueType, 6 | _Inout_ PULONG_PTR ShadowStack 7 | ) 8 | { 9 | PEPROCESS process; 10 | NTSTATUS status; 11 | BOOLEAN cetRelaxedMode; 12 | ULONG64 userRip; 13 | BOOLEAN notRelaxed; 14 | KCONTINUE_TYPE continueType; 15 | 16 | // 17 | // No need to do anything if shadow stack is not enabled 18 | // 19 | if (!Thread->Tcb.CetShadowStack) 20 | { 21 | return STATUS_SUCCESS; 22 | } 23 | 24 | // 25 | // No need to do anything if UserCetSetContextIpValidation is not 26 | // set in this process or if Rip is not being modified 27 | // 28 | process = Thread->Tcb.ApcState.Process; 29 | if (!(process->MitigationFlags2Values.UserCetSetContextIpValidation) || 30 | !(BooleanFlagOn(Context->ContextFlags, CONTEXT_CONTROL))) 31 | { 32 | return STATUS_SUCCESS; 33 | } 34 | 35 | // 36 | // Verify the new Rip target 37 | // 38 | cetRelaxedMode = process->MitigationFlags2Values.UserCetSetContextIpValidationRelaxedMode & 1 != 0; 39 | status = KiVerifyContextIpForUserCet(Thread, Context, ContinueType, cetRelaxedMode, ShadowStack); 40 | 41 | // 42 | // Log failure if needed and fake success 43 | // 44 | if ( status == STATUS_SET_CONTEXT_DENIED ) 45 | { 46 | userRip = Context->Rip; 47 | continueType = *ContinueType; 48 | notRelaxed = CetRelaxedMode ^ 1; 49 | if (!(process->MitigationFlags2Values.AuditUserCetSetContextIpValidation)) 50 | { 51 | KiLogUserCetSetContextIpValidationFailure(2, continueType, userRip, notRelaxed); 52 | return status; 53 | } 54 | KiLogUserCetSetContextIpValidationFailure(1, continueType, userRip, notRelaxed); 55 | } 56 | return status; 57 | } -------------------------------------------------------------------------------- /src/KeVerifyContextRecord.c: -------------------------------------------------------------------------------- 1 | NTSTATUS 2 | KeVerifyContextRecord ( 3 | _In_ PKTHREAD TargetThread, 4 | _In_ PCONTEXT ContextFrame 5 | _In_ PKCONTINUE_ARGUMENT ContinueArgument, 6 | _Outptr_ PULONG_PTR ShadowStack 7 | ) 8 | { 9 | PKPROCESS targetProcess; 10 | ULONG_PTR userStack; 11 | PTEB userTeb; 12 | PEWOW64PROCESS wow64Process; 13 | USHORT wowMachine; 14 | 15 | targetProcess = TargetThread->Process; 16 | if (targetProcess->CheckStackExtents != FALSE) 17 | { 18 | if (BooleanFlagOn(ContextFrame->ContextFlags, CONTEXT_CONTROL)) 19 | { 20 | userStack = ContextFrame->Rsp; 21 | userTeb = TargetThread->Teb; 22 | 23 | // 24 | // Get the stack limits from the process' TEB and 25 | // check if the new stack pointer is inside the native stack 26 | // 27 | if (!RtlGuardIsValidStackPointer(userStack, userTeb)) 28 | { 29 | // 30 | // New stack pointer is not inside the native stack. 31 | // Check if this is a wow64 process, and if it is 32 | // check if the new stack pointer is inside the wow64 stack. 33 | // 34 | wowMachine = PsWow64GetProcessMachine(targetProcess); 35 | if ((wowMachine != IMAGE_FILE_MACHINE_I386) && 36 | (wowMachine != IMAGE_FILE_MACHINE_ARMNT)) 37 | { 38 | return STATUS_INVALID_PARAMETER; 39 | } 40 | 41 | if ((userStack >= (_4GB - 1)) || 42 | !(RtlGuardIsValidWow64StackPointer(userStack, userTeb))) 43 | { 44 | return STATUS_INVALID_PARAMETER; 45 | } 46 | 47 | // 48 | // Call KiVerifyContextRecord to validate the new values of CS and RIP 49 | // 50 | status = KiVerifyContextRecord(TargetThread, 51 | ContextFrame, 52 | ContinueArgument, 53 | ShadowStack); 54 | } 55 | } 56 | } 57 | 58 | // 59 | // If this is a non-wow64 process trying to set its CS to something 60 | // other than KGDT64_R3_CODE, force it to be KGDT64_R3_CODE. 61 | // 62 | if ((BooleanFlagOn(ContextFrame->ContextFlags, CONTEXT_CONTROL)) && 63 | (PsWow64GetProcessMachine(targetProcess) != IMAGE_FILE_MACHINE_I386)) 64 | { 65 | ContextFrame->SegCs = KGDT64_R3_CODE | RPL_MASK; 66 | } 67 | 68 | return STATUS_SUCCESS; 69 | } -------------------------------------------------------------------------------- /src/KeVerifyContextXStateCetU.c: -------------------------------------------------------------------------------- 1 | NTSTATUS 2 | KeVerifyContextXStateCetU ( 3 | _In_ PKTHREAD Thread, 4 | _In_ PCONTEXT ContextRecord, 5 | _Outptr_ PULONG_PTR ShadowStack 6 | ) 7 | { 8 | PXSAVE_CET_U_FORMAT cetData; 9 | PXSAVE_AREA_HEADER xsaveData; 10 | NTSTATUS status; 11 | 12 | if (!BooleanFlagOn(Context->Context.ContextFlags, CONTEXT_XSTATE)) 13 | { 14 | return STATUS_SUCCESS; 15 | } 16 | 17 | // 18 | // Get the address of the CET state from the supplied context 19 | // 20 | cetData = (PXSAVE_CET_U_FORMAT)RtlLocateExtendedFeature2((PCONTEXT_EX)(Context + 1), 21 | XSTATE_CET_U, 22 | &SharedUserData.XState, 23 | NULL); 24 | if (cetData == NULL) 25 | { 26 | return STATUS_SUCCESS; 27 | } 28 | 29 | *ShadowStack = __readmsr(MSR_IA32_PL3_SSP); 30 | 31 | // 32 | // Check if the context contains values for CET registers. 33 | // If it doesn't, it means CET registers will not be set, and 34 | // will disable CET if it was previously enabled. 35 | // 36 | xsaveData = (PXSAVE_AREA_HEADER)RTL_CONTEXT_CHUNK(Context, XState); 37 | 38 | if (Thread->CetUserShadowStack != FALSE) 39 | { 40 | if (!BooleanFlagOn(xsaveData->Mask, XSTATE_MASK_CET_U)) 41 | { 42 | // 43 | // If the thread has CET enabled but the new context doesn't have 44 | // CET registers in it, set the CET registers in the context to 45 | // the current CET values. 46 | // 47 | SetFlag(xsaveData->Mask, XSTATE_MASK_CET_U); 48 | cetData->Ia32CetUMsr = MSR_IA32_CET_SHSTK_EN; 49 | cetData->Ia32Pl3SspMsr = *ShadowStack; 50 | return STATUS_SUCCESS; 51 | } 52 | 53 | // 54 | // Verify that the new Ssp value is inside the shadow stack 55 | // 56 | status = KiVerifyContextXStateCetUEnabled(cetData, *ShadowStack); 57 | if (NT_SUCCESS(status)) 58 | { 59 | return STATUS_SUCCESS; 60 | } 61 | 62 | return status; 63 | } 64 | 65 | // 66 | // If the thread doesn't have CET enabled and the new context doesn't 67 | // have CET registers, or the CET mask is set but the CET registers 68 | // don't hold any value, allow because the CET state will not change. 69 | // 70 | if (!(BooleanFlagOn(xsaveData->Mask, XSTATE_MASK_CET_U)) || 71 | ((cetData->Ia32CetUMsr == 0) && 72 | (cetData->Ia32Pl3SspMsr == NULL))) 73 | { 74 | return STATUS_SUCCESS; 75 | } 76 | 77 | return STATUS_SET_CONTEXT_DENIED; 78 | } -------------------------------------------------------------------------------- /src/KiVerifyContextIpForUserCet.c: -------------------------------------------------------------------------------- 1 | struct _UNWIND_STATE 2 | { 3 | PVOID ImageBase; 4 | PIMAGE_LOAD_CONFIG_DIRECTORY64 LoadConfig; 5 | BOOLEAN CheckedLoadConfig; 6 | } UNWIND_STATE, *PUNWIND_STATE; 7 | 8 | 9 | NTSTATUS 10 | KiVerifyContextIpForUserCet ( 11 | _In_ PETHREAD Thread, 12 | _In_ PCONTEXT Context, 13 | _In_ PKCONTINUE_TYPE ContinueType, 14 | _In_ BOOLEAN RelaxedMode, 15 | _Inout_ ULONG_PTR ShadowStack 16 | ) 17 | { 18 | ULONG64 userRip; 19 | ULONG_PTR shadowStack; 20 | UKCONTINUE_TYPE continueType; 21 | NTSTATUS status; 22 | BOOLEAN loadConfigChecked; 23 | UNWIND_STATE unwindState; 24 | 25 | // 26 | // Deny if the target Rip a kernel address or below 0x10000 27 | // 28 | userRip = Context->Rip; 29 | if ((userRip >= MM_USER_PROBE_ADDRESS) || 30 | (userRip < MM_ALLOCATION_GRANULARITY)) 31 | { 32 | return STATUS_SET_CONTEXT_DENIED; 33 | } 34 | // 35 | // Ignore if target Rip is the previous address in user space 36 | // (such as the initial thread start address) 37 | // 38 | trapFrame = PspGetBaseTrapFrame(Thread); 39 | if (userRip == trapFrame->Rip) 40 | { 41 | return STATUS_SUCCESS; 42 | } 43 | 44 | shadowStack = *ShadowStack; 45 | continueType = *ContinueType; 46 | if (continueType == KCONTINUE_LONGJUMP) 47 | { 48 | return RtlVerifyUserUnwindTarget(userRip, KCONTINUE_LONGJUMP, 0); 49 | } 50 | else if ((continueType != KCONTINUE_UNWIND) && 51 | (continueType != KCONTINUE_RESUME) && 52 | (continueType != KCONTINUE_SET)) 53 | { 54 | return STATUS_INVALID_PARAMETER; 55 | } 56 | // 57 | // Get address of shadow stack if one was not provided by caller. 58 | // If no shadow stack exists, allow any Rip. 59 | // 60 | if (shadowStack == NULL) 61 | { 62 | shadowStack = __readmsr(MSR_IA32_PL3_SSP); 63 | if (shadowStack == NULL) 64 | { 65 | return STATUS_SUCCESS; 66 | } 67 | } 68 | 69 | if ((continueType == KCONTINUE_UNWIND) && 70 | (userRip == PsNtdllExports.RtlUserThreadStart)) 71 | { 72 | *ContinueType = KCONTINUE_RESUME; 73 | continueType = KCONTINUE_RESUME; 74 | } 75 | RtlZeroMemory(&unwindState, sizeof(unwindState)); 76 | if (continueType == KCONTINUE_UNWIND) 77 | { 78 | status = RtlVerifyUserUnwindTarget(userRip, KCONTINUE_UNWIND, &unwindState); 79 | if (NT_SUCCESS(status)) 80 | { 81 | return status; 82 | } 83 | } 84 | // 85 | // This code will run when RelaxedMode is enabled and continueType is 86 | // either KCONTINUE_SET or KCONTINUE_UNWIND if RtlVerifyUserUnwindTarget failed 87 | // 88 | if ((RelaxedMode) && (*ContinueType != KCONTINUE_RESUME)) 89 | { 90 | if (!unwindState.CheckedLoadConfig) 91 | { 92 | status = RtlGetImageBaseAndLoadConfig(userRip, &unwindState.ImageBase, &unwindState.LoadConfig); 93 | loadConfigChecked = NT_SUCCESS(status) ? 1: unwindState.CheckedLoadConfig; 94 | unwindState.CheckedLoadConfig = loadConfigChecked; 95 | } 96 | if ( loadConfigChecked ) 97 | { 98 | if ( unwindState.ImageBase ) 99 | { 100 | // 101 | // Check if there is a EhContinuationTable in the LoadConfig. 102 | // If it exists it would be after the XFG data 103 | // This code is actually just a "probe" to see if there is a point in checking the EhCont flag, 104 | // and will throw STATUS_ACCESS_VIOLATION if it fails. 105 | // 106 | __try 107 | { 108 | ProbeForRead(unwindState.LoadConfig, 109 | offsetof(IMAGE_LOAD_CONFIG_DIRECTORY64, GuardEHContinuationCount), 110 | RTL_SIZEOF_THROUGH_FIELD(IMAGE_LOAD_CONFIG_DIRECTORY64, GuardEHContinuationCount)); 111 | 112 | // 113 | // So this code is meant as a "whitelist" for older binaries that don't fully support CET. 114 | // There are some FPs with processes using NtSetContextThread to targets that CET does not expect. 115 | // For newer processes that were compiled recently with the correct flags, 116 | // this will create an EX_CONTINUATION_TABLE that will contain those targets. 117 | // But for older processes Windows supports "relaxed mode" CET. 118 | // If "relaxed mode" is set for the process, any module that does not have an EX_CONTINUATION_TABLE 119 | // will be allowed to set the context to any address. 120 | // 121 | if ((unwindState.LoadConfig) && 122 | (unwindState.LoadConfig->Size >= RTL_SIZEOF_THROUGH_FIELD(IMAGE_LOAD_CONFIG_DIRECTORY64, GuardEHContinuationCount)) && 123 | ((unwindState.LoadConfig->GuardFlags & IMAGE_GUARD_EH_CONTINUATION_TABLE_PRESENT) != 0)) 124 | { 125 | goto CheckAddressInShadowStack; 126 | } 127 | } 128 | __except 129 | { 130 | goto CheckAddressInShadowStack; 131 | } 132 | return STATUS_SUCCESS; 133 | } 134 | return STATUS_SUCCESS; 135 | } 136 | } 137 | CheckAddressInShadowStack: 138 | // 139 | // Iterate over shadow stack and check if target Rip is in it. 140 | // If thread is terminating, only try to find the target Rip 141 | // in the current page of the shadow stack. 142 | // 143 | __try 144 | { 145 | do 146 | { 147 | if (*shadowStack == userRip) 148 | { 149 | *ShadowStack = shadowStack + sizeof(userRip); 150 | return STATUS_SUCCESS; 151 | } 152 | shadowStack += sizeof(userRip); 153 | } while (!(PAGE_ALIGNED(shadowStack)) || !(Thread->Terminated)); 154 | 155 | return STATUS_THREAD_IS_TERMINATING; 156 | } 157 | __except 158 | { 159 | return STATUS_SET_CONTEXT_DENIED; 160 | } 161 | } -------------------------------------------------------------------------------- /src/KiVerifyContextRecord.c: -------------------------------------------------------------------------------- 1 | NTSTATUS 2 | KiVerifyContextRecord ( 3 | _In_ PKTHREAD TargetThread, 4 | _In_ PCONTEXT ContextFrame 5 | _In_ PKCONTINUE_ARGUMENT ContinueArgument, 6 | _Outptr_ PULONG_PTR ShadowStack 7 | ) 8 | { 9 | PKPROCESS process; 10 | 11 | process = Thread->Tcb.Process; 12 | 13 | // 14 | // If control registers (RIP/RSP) aren't beind modified, no checks to do 15 | // 16 | if (!BooleanFlagOn(ContextFrame->ContextFlags, CONTEXT_CONTROL)) 17 | { 18 | return STATUS_SUCCESS; 19 | } 20 | 21 | // 22 | // If this is a non-wow64 process trying to set CS to a value other than KGDT64_R3_CODE, 23 | // Or this is a pico process trying to set CS to a value other than KGDT64_R3_CODE or 24 | // KGDT64_R3_CMCODE, Force CS to be KGDT64_R3_CODE. 25 | // 26 | if ((PsWow64GetProcessMachine(process) != IMAGE_FILE_MACHINE_I386) && 27 | ((process->PicoContext == NULL) || 28 | (ContextFrame->SegCs != (KGDT64_R3_CMCODE | RPL_MASK)))) 29 | { 30 | ContextFrame->SegCs = KGDT64_R3_CODE | RPL_MASK; 31 | } 32 | 33 | // 34 | // New context structure is not supported 35 | // 36 | if (!ARGUMENT_PRESENT(ContinueArgument)) 37 | { 38 | return STATUS_SUCCESS; 39 | } 40 | 41 | // 42 | // Verify new RIP value in the shadow stack 43 | // 44 | status = KeVerifyContextIpForUserCet(TargetThread, 45 | ContextFrame, 46 | ContinueArgument, 47 | ShadowStack); 48 | if (NT_SUCCESS(status)) 49 | { 50 | return STATUS_SUCCESS; 51 | } 52 | 53 | return status; 54 | } 55 | -------------------------------------------------------------------------------- /src/KiVerifyContextXStateCetUEnabled.c: -------------------------------------------------------------------------------- 1 | NTSTATUS 2 | KiVerifyContextXStateCetUEnabled ( 3 | _In_ PXSAVE_CET_U_FORMAT CetData, 4 | _In_ ULONG_PTR ShadowStack 5 | ) 6 | { 7 | MEMORY_REGION_INFORMATION regionInfo; 8 | ULONG_PTR shadowStackEnd; 9 | ULONG_PTR newShadowStack; 10 | 11 | // 12 | // If the value for the MSR mask is not 1 (CET enabled), deny the new context 13 | // 14 | if (CetData->Ia32CetUMsr != MSR_IA32_CET_SHSTK_EN) 15 | { 16 | return STATUS_SET_CONTEXT_DENIED; 17 | } 18 | 19 | // 20 | // Deny the context if the new Ssp value is not 8-byte aligned 21 | // 22 | newShadowStack = CetData->Ia32Pl3SspMsr; 23 | if ((newShadowStack & 7) != 0) 24 | { 25 | return STATUS_SET_CONTEXT_DENIED; 26 | } 27 | 28 | // 29 | // Check if the new Ssp is lower than the current Ssp, 30 | // so it will point to uninitialized memory 31 | // 32 | if (newShadowStack < ShadowStack) 33 | { 34 | return STATUS_SET_CONTEXT_DENIED; 35 | } 36 | 37 | // 38 | // Get the end address of the shadow stack 39 | // 40 | ZwQueryVirtualMemory(NtCurrentProcess(), 41 | ShadowStack, 42 | MemoryRegionInformation, 43 | ®ionInfo, 44 | sizeof(regionInfo), 45 | NULL); 46 | shadowStackEnd = MemoryInformation.AllocationBase + 47 | MemoryInformation.RegionSize - 48 | PAGE_SIZE; 49 | 50 | // 51 | // Check if the new Ssp is higher than the end address of 52 | // the shadiw stack, so outside the stack bounds 53 | // 54 | if (newShadowStack >= shadowStackEnd) 55 | { 56 | return STATUS_SET_CONTEXT_DENIED; 57 | } 58 | 59 | return STATUS_SUCCESS; 60 | } -------------------------------------------------------------------------------- /src/NtSetInformationProcess.c: -------------------------------------------------------------------------------- 1 | NTSTATUS 2 | NTAPI 3 | NtSetInformationProcess ( 4 | _In_ HANDLE ProcessHandle, 5 | _In_ PROCESSINFOCLASS ProcessInformationClass, 6 | _In_ PVOID ProcessInformation, 7 | _In_ ULONG ProcessInformationLength 8 | ) 9 | { 10 | PROCESS_DYNAMIC_EH_CONTINUATION_TARGETS_INFORMATION targetInfo; 11 | ULONG targetsSize; 12 | PPROCESS_DYNAMIC_EH_CONTINUATION_TARGET targetsArray; 13 | PPROCESS_DYNAMIC_EH_CONTINUATION_TARGET ehTargets; 14 | PEPROCESS targetProcess; 15 | NTSTATUS status; 16 | KPROCESSOR_MODE previousMode = ExGetPreviousMode(); 17 | ULONG i; 18 | ULONG targetsProcessed; 19 | 20 | // 21 | // Handle the dynamic exception handlers information class 22 | // 23 | if (ProcessInformationClass == ProcessDynamicEHContinuationTargets) 24 | { 25 | // 26 | // Validate the data is the right size 27 | // 28 | if (ProcessInformationLength != sizeof(targetInfo)) 29 | { 30 | return STATUS_INFO_LENGTH_MISMATCH; 31 | } 32 | 33 | // 34 | // Make a local copy of the data to avoid races 35 | // 36 | __try 37 | { 38 | targetInfo = *(PPROCESS_DYNAMIC_EH_CONTINUATION_TARGETS_INFORMATION)ProcessInformation; 39 | } 40 | __except (EXCEPTION_EXECUTE_HANDLER) 41 | { 42 | return GetExceptionCode(); 43 | } 44 | 45 | // 46 | // Check how many targets there are 47 | // 48 | targetsSize = sizeof(PROCESS_DYNAMIC_EH_CONTINUATION_TARGET) * 49 | targetInfo.NumberOfTargets; 50 | if (targetsSize == 0) 51 | { 52 | return STATUS_INVALID_PARAMETER; 53 | } 54 | 55 | // 56 | // Make sure there are targets 57 | // 58 | targetsArray = targetInfo.Targets; 59 | if (targetsArray == NULL) 60 | { 61 | return STATUS_INVALID_PARAMETER; 62 | } 63 | 64 | // 65 | // Probe that the targets are all in writeable UM memory 66 | // 67 | __try 68 | { 69 | ProbeForWrite(targetsArray, targetsSize, 8); 70 | } 71 | __except (EXCEPTION_EXECUTE_HANDLER) 72 | { 73 | return GetExceptionCode(); 74 | } 75 | 76 | // 77 | // These fields aren't used yet 78 | // 79 | if ((targetInfo.Reserved != 0) || (targetInfo.Reserved2 != 0)) 80 | { 81 | return STATUS_INVALID_PARAMETER; 82 | } 83 | 84 | // 85 | // Only user-mode code should be setting dynamic EH targets 86 | // 87 | if (previousMode != UserMode) 88 | { 89 | return STATUS_ACCESS_DENIED; 90 | } 91 | 92 | // 93 | // Make sure the caller has a full process write handle to the target 94 | // 95 | targetProcess = NULL; 96 | status = ObReferenceObjectByHandle(ProcessHandle, 97 | GENERIC_WRITE & ~SYNCHRONIZE, 98 | (POBJECT_TYPE)PsProcessType, 99 | UserMode, 100 | (PVOID*)&targetProcess, 101 | NULL); 102 | if (!NT_SUCCESS(status)) 103 | { 104 | goto Cleanup; 105 | } 106 | 107 | // 108 | // Don't allow the current process to add targets to itself 109 | // 110 | if (targetProcess == PsGetCurrentProcess()) 111 | { 112 | status = STATUS_ACCESS_DENIED; 113 | goto Cleanup; 114 | } 115 | 116 | // 117 | // Don't allow setting EH handlers if the target process doesn't have CET 118 | // 119 | if (targetProcess->MitigationFlags2Values.CetUserShadowStacks == FALSE) 120 | { 121 | status = STATUS_NOT_SUPPORTED; 122 | goto Cleanup; 123 | } 124 | 125 | // 126 | // Allocate a kernel copy of the targets 127 | // 128 | ehTargets = ExAllocatePoolWithQuotaTag(PagedPool | 129 | POOL_QUOTA_FAIL_INSTEAD_OF_RAISE, 130 | targetsSize, 131 | 'NHED'); 132 | if (ehTargets == NULL) 133 | { 134 | status = STATUS_NO_MEMORY; 135 | goto Cleanup; 136 | } 137 | 138 | // 139 | // Copy them in the array 140 | // 141 | RtlCopyMemory(ehTargets, targetsArray, targetsSize); 142 | 143 | // 144 | // Process each target in the array 145 | // 146 | targetsProcessed = 0; 147 | status = PspProcessDynamicEHContinuationTargets(targetProcess, 148 | ehTargets, 149 | targetInfo.NumberOfTargets, 150 | &targetsProcessed); 151 | 152 | // 153 | // Write out the flags back in the original user buffer, which will 154 | // basically fill set DYNAMIC_EH_CONTINUATION_TARGET_PROCESSED so the 155 | // caller knows what wasn't processed 156 | // 157 | __try 158 | { 159 | for (i = 0; i < targetsProcessed; i++) 160 | { 161 | targetsArray[i].Flags = ehTargets[i].Flags; 162 | } 163 | } 164 | __except (EXCEPTION_EXECUTE_HANDLER) 165 | { 166 | status = GetExceptionCode(); 167 | } 168 | 169 | Cleanup: 170 | // 171 | // Dereference the target process if needed 172 | // 173 | if (targetProcess != NULL) 174 | { 175 | ObDereferenceObject(targetProcess); 176 | } 177 | 178 | // 179 | // Free the EH target array if needed 180 | // 181 | if (ehTargets != NULL) 182 | { 183 | ExFreePoolWithTag(ehTargets, 'NHED'); 184 | } 185 | } 186 | 187 | // 188 | // Return back to caller 189 | // 190 | return status; 191 | } -------------------------------------------------------------------------------- /src/RtlVerifyUserUnwindTarget.c: -------------------------------------------------------------------------------- 1 | INT 2 | RtlpTargetCompare ( 3 | _In_opt_ PVOID Context, 4 | _In_ PCVOID Key, 5 | _In_ PCVOID Datum 6 | ) 7 | { 8 | ULONG_PTR rva1; 9 | ULONG_PTR rva2; 10 | UNREFERENCED_PARAMETER(Context); 11 | 12 | // 13 | // Return if the compared RVA comes before (-1), after (+1), or identical (0) 14 | // 15 | rva1 = *(PULONG_PTR)Key; 16 | rva2 = *(PULONG_PTR)Datum; 17 | return (INT)(rva1 - rva2); 18 | } 19 | 20 | NTSTATUS 21 | RtlVerifyUserUnwindTarget ( 22 | _In_ PVOID TargetRip, 23 | _In_ KCONTINUE_TYPE ContinueType 24 | ) 25 | { 26 | PIMAGE_LOAD_CONFIG_DIRECTORY64 loadConfig; 27 | INVERTED_FUNCTION_TABLE_ENTRY userFunctionTable; 28 | ULONGLONG imageSize; 29 | NTSTATUS status; 30 | ULONG guardFlags; 31 | SIZE_T configSize; 32 | PVOID table; 33 | ULONGLONG count; 34 | ULONG rva; 35 | SIZE_T metaSize; 36 | BOOLEAN found; 37 | PVOID entry; 38 | 39 | // 40 | // First, do a quick lookup in the user function table, which should almost always work 41 | // 42 | found = RtlpLookupUserFunctionTableInverted(TargetRip, &userFunctionTable); 43 | if (found == FALSE) 44 | { 45 | // 46 | // This module might not have any exception/unwind data, so do a slow VAD lookup instead 47 | // 48 | status = MmGetImageBase(TargetRip, &userFunctionTable.ImageBase, &imageSize); 49 | if (!NT_SUCCESS(status)) 50 | { 51 | // 52 | // There does not appear to be a valid module loaded at this address. 53 | // The only other possibility is that this is JIT, which we'll handle at the end. 54 | // 55 | userFunctionTable.ImageBase = NULL; 56 | } 57 | else 58 | { 59 | // 60 | // The VAD lookup can theoretically return a >= 4GB-sized module. This is not expected 61 | // and not supported for actual PE images. 62 | // 63 | if (imageSize >= MAXULONG) 64 | { 65 | return STATUS_INTEGER_OVERFLOW; 66 | } 67 | // 68 | // To simplify the code, capture the size in the same structure that the user function 69 | // table lookup would've returned. 70 | // 71 | userFunctionTable.SizeOfImage = (ULONG)imageSize; 72 | } 73 | } 74 | 75 | // 76 | // Did we find a loaded module at this address? 77 | // 78 | if (userFunctionTable.ImageBase != NULL) 79 | { 80 | // 81 | // We're going to touch user-mode data, so enter an exception handler context 82 | // 83 | __try 84 | { 85 | // 86 | // Kind of an arbitrary probe of 64 bytes, since the call below will call 87 | // RtlImageNtHeaderEx which does a proper probe of the whole header already. 88 | // 89 | ProbeForRead(userFunctionTable.ImageBase, 64, 1); 90 | 91 | // 92 | // Get the Image Load Config Directory. Note that this is a user-mode pointer 93 | // 94 | loadConfig = LdrImageDirectoryEntryToLoadConfig(userFunctionTable.ImageBase); 95 | 96 | // 97 | // For longjmp, use the longjump table, otherwise, for unwind, use the dynamic 98 | // exception handler continuation table. 99 | // 100 | if (ContinueType == KCONTINUE_LONGJUMP) 101 | { 102 | guardFlags = IMAGE_GUARD_CF_LONGJUMP_TABLE_PRESENT; 103 | configSize = RTL_SIZEOF_THROUGH_FIELD(IMAGE_LOAD_CONFIG_DIRECTORY64, 104 | GuardLongJumpTargetTable); 105 | } 106 | else 107 | { 108 | guardFlags = IMAGE_GUARD_EH_CONTINUATION_TABLE_PRESENT; 109 | configSize = RTL_SIZEOF_THROUGH_FIELD(IMAGE_LOAD_CONFIG_DIRECTORY64, 110 | GuardEHContinuationTable); 111 | } 112 | // 113 | // Probe the configuration directory, as LdrImageDirectoryEntryToLoadConfig only 114 | // probes the first 4 bytes to account for the "Size" field. 115 | // 116 | // This probe will also raise if loadConfig is NULL (unless this is NTVDM on 32-bit). 117 | // 118 | ProbeForRead(loadConfig, configSize, 1); 119 | 120 | // 121 | // Make sure there's a load configuration directory, that it's large enough to have 122 | // one of the two tables we care about, and that the guard flags indicate that the 123 | // table we care about is actually present. 124 | // 125 | if ((loadConfig == NULL) || 126 | (loadConfig->Size < configSize) || 127 | !(guardFlags & loadConfig->GuardFlags)) 128 | { 129 | // 130 | // We return success here, because this means that the binary is not compatible 131 | // with CET. As such, for compatibility, allow this jump target. 132 | // 133 | return STATUS_SUCCESS; 134 | } 135 | } 136 | __except (EXCEPTION_EXECUTE_HANDLER) 137 | { 138 | // 139 | // Something's wrong with the user address space, bail out 140 | // 141 | return GetExceptionCode(); 142 | } 143 | 144 | // 145 | // Use the correct table and count (longjmp vs. unwind) 146 | // 147 | if (ContinueType == KCONTINUE_LONGJUMP) 148 | { 149 | table = (PVOID)loadConfig->GuardLongJumpTargetTable; 150 | count = loadConfig->GuardLongJumpTargetCount; 151 | } 152 | else 153 | { 154 | table = (PVOID)loadConfig->GuardEHContinuationTable; 155 | count = loadConfig->GuardEHContinuationCount; 156 | } 157 | 158 | // 159 | // More than 4 billion entries are not allowed 160 | // 161 | if (count >= MAXULONG) 162 | { 163 | return STATUS_INTEGER_OVERFLOW; 164 | } 165 | 166 | // 167 | // If the table is empty, then there can't be any valid targets in this image... 168 | // 169 | if (count != 0) 170 | { 171 | // 172 | // PE Images are always <= 4GB, so compute the 32-bit RVA 173 | // 174 | rva = (ULONG)((ULONG_PTR)TargetRip - (ULONG_PTR)userFunctionTable.ImageBase); 175 | 176 | // 177 | // The guard tables can have n-bytes of metadata, indicated by the upper nibble 178 | // 179 | metaSize = loadConfig->GuardFlags >> IMAGE_GUARD_CF_FUNCTION_TABLE_SIZE_SHIFT; 180 | 181 | // 182 | // Search through the guard table for this RVA 183 | // 184 | entry = bsearch_s(&rva, table, count, metaSize + sizeof(rva), RtlpTargetCompare, NULL); 185 | if (entry != NULL) 186 | { 187 | // 188 | // The entry was found, so this is a valid target 189 | // 190 | return STATUS_SUCCESS; 191 | } 192 | } 193 | } 194 | 195 | // 196 | // Either there's no valid image mapped at this address, or there is, but its relevant guard 197 | // table does not contain the target RIP requested (as a reminder, if there's no table, then 198 | // the target _is_ allowed, for compatibility reasons). 199 | // 200 | // In this case, for exception unwinding (and obviously not longjmp), check if there is a 201 | // JIT-ted (dynamic) exception handler continuation target registered at this target. 202 | // 203 | if (ContinueType == KCONTINUE_UNWIND) 204 | { 205 | found = RtlpFindDynamicEHContinuationTarget(TargetRip); 206 | if (found != FALSE) 207 | { 208 | return STATUS_SUCCESS; 209 | } 210 | } 211 | 212 | // 213 | // Otherwise, we either didn't find a dynamic handler, or this wasn't an unwind to begin with, 214 | // so fail the request. 215 | // 216 | return STATUS_SET_CONTEXT_DENIED; 217 | } 218 | --------------------------------------------------------------------------------