├── CISpotter.sln ├── CISpotter ├── CISpotter.cpp ├── CISpotter.h ├── CISpotter.vcxproj └── CISpotter.vcxproj.filters ├── LICENSE.txt └── README.md /CISpotter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31727.386 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CISpotter", "CISpotter\CISpotter.vcxproj", "{25F1BE0A-6335-40B8-9ADD-F49AC2976104}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Release|x64 = Release|x64 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {25F1BE0A-6335-40B8-9ADD-F49AC2976104}.Debug|x64.ActiveCfg = Debug|x64 15 | {25F1BE0A-6335-40B8-9ADD-F49AC2976104}.Debug|x64.Build.0 = Debug|x64 16 | {25F1BE0A-6335-40B8-9ADD-F49AC2976104}.Debug|x64.Deploy.0 = Debug|x64 17 | {25F1BE0A-6335-40B8-9ADD-F49AC2976104}.Release|x64.ActiveCfg = Release|x64 18 | {25F1BE0A-6335-40B8-9ADD-F49AC2976104}.Release|x64.Build.0 = Release|x64 19 | {25F1BE0A-6335-40B8-9ADD-F49AC2976104}.Release|x64.Deploy.0 = Release|x64 20 | EndGlobalSection 21 | GlobalSection(SolutionProperties) = preSolution 22 | HideSolutionNode = FALSE 23 | EndGlobalSection 24 | GlobalSection(ExtensibilityGlobals) = postSolution 25 | SolutionGuid = {171B0B4C-8E6D-431C-83F0-26986D96A8CC} 26 | EndGlobalSection 27 | EndGlobal 28 | -------------------------------------------------------------------------------- /CISpotter/CISpotter.cpp: -------------------------------------------------------------------------------- 1 | #include "CISpotter.h" 2 | 3 | static BOOLEAN bCallbackRegistered = FALSE; 4 | static PVOID gNtdllBaseAddress = NULL; 5 | 6 | DECLARE_CONST_UNICODE_STRING(gzuZwQueryInformationProcess, L"ZwQueryInformationProcess"); 7 | static ZwQueryInformationProcess_t gZwQueryInformationProcess = NULL; 8 | 9 | DECLARE_CONST_UNICODE_STRING(gzuZwQuerySection, L"ZwQuerySection"); 10 | static ZwQuerySection_t gZwQuerySection = NULL; 11 | 12 | void MyLoadImageNotifyRoutine( 13 | PUNICODE_STRING FullImageName, 14 | HANDLE ProcessId, 15 | PIMAGE_INFO ImageInfo 16 | ) 17 | { 18 | NTSTATUS ntStatus = STATUS_SUCCESS; 19 | OBJECT_ATTRIBUTES objAttr = { 0, }; 20 | CLIENT_ID cid = { ProcessId, 0 }; 21 | PS_PROTECTION protection = { 0 }; 22 | HANDLE hProcess = NULL; 23 | ULONG returnLength = 0; 24 | PEPROCESS pProcess = NULL; 25 | 26 | UNREFERENCED_PARAMETER(FullImageName); 27 | UNREFERENCED_PARAMETER(ProcessId); 28 | 29 | // Sanity check 30 | if (!ProcessId || 31 | !ImageInfo || 32 | !ImageInfo->ImageBase || 33 | !ImageInfo->ExtendedInfoPresent) 34 | { 35 | goto Cleanup; 36 | } 37 | 38 | // Exclude NTDLL, which is missing signer information 39 | if (gNtdllBaseAddress == ImageInfo->ImageBase) 40 | { 41 | goto Cleanup; 42 | } 43 | 44 | // PID => HANDLE 45 | InitializeObjectAttributes(&objAttr, NULL, OBJ_KERNEL_HANDLE, 0, 0); 46 | ntStatus = ZwOpenProcess(&hProcess, 0, &objAttr, &cid); 47 | if (!NT_SUCCESS(ntStatus)) 48 | { 49 | goto Cleanup; 50 | } 51 | 52 | // HANDLE => PEPROCESS 53 | ntStatus = ObReferenceObjectByHandle(hProcess, 0, *PsProcessType, KernelMode, (PVOID*)&pProcess, NULL); 54 | if (!NT_SUCCESS(ntStatus)) 55 | { 56 | goto Cleanup; 57 | } 58 | 59 | // Exclude the main EXE, which is missing signer information 60 | if (PsGetProcessSectionBaseAddress(pProcess) == ImageInfo->ImageBase) 61 | { 62 | goto Cleanup; 63 | } 64 | 65 | // Get protection level 66 | ntStatus = gZwQueryInformationProcess(hProcess, ProcessProtectionInformation, &protection, sizeof(protection), &returnLength); 67 | if (!NT_SUCCESS(ntStatus)) 68 | { 69 | goto Cleanup; 70 | } 71 | 72 | // CI only applies to PPL 73 | if (PsProtectedTypeProtectedLight != protection.Type) 74 | { 75 | goto Cleanup; 76 | } 77 | 78 | // Only enforce CI for PPL >= AntiMalware 79 | switch (protection.Signer) 80 | { 81 | case PsProtectedSignerAntimalware: 82 | case PsProtectedSignerLsa: 83 | case PsProtectedSignerWindows: 84 | case PsProtectedSignerWinTcb: 85 | case PsProtectedSignerWinSystem: 86 | break; 87 | default: 88 | goto Cleanup; 89 | } 90 | 91 | // Is the image unsigned or is the signature level too low? 92 | if ((0 == ImageInfo->ImageSignatureType) || 93 | (ImageInfo->ImageSignatureLevel < SE_SIGNING_LEVEL_ANTIMALWARE)) 94 | { 95 | // This callback can sometimes execute with special APCs disabled 96 | // ZwTerminateProcess uses a special APC. Calling it can deadlock in such cases. 97 | if (!KeAreAllApcsDisabled()) 98 | { 99 | // Prove we stopped the CI violation by termination 100 | // This can leak memory per MSDN, but this is a POC 101 | (void)ZwTerminateProcess(hProcess, STATUS_INVALID_SIGNATURE); 102 | } 103 | } 104 | 105 | Cleanup: 106 | if (hProcess) 107 | { 108 | ZwClose(hProcess); 109 | } 110 | if (pProcess) 111 | { 112 | ObDereferenceObject(pProcess); 113 | } 114 | return; 115 | } 116 | 117 | NTSTATUS GetNtdllBaseAddress() 118 | { 119 | NTSTATUS ntStatus = STATUS_SUCCESS; 120 | OBJECT_ATTRIBUTES objAttr = { 0, }; 121 | HANDLE hSection = NULL; 122 | SECTION_IMAGE_INFORMATION sii = { 0, }; 123 | 124 | DECLARE_CONST_UNICODE_STRING(knownDllsNtdll, L"\\KnownDlls\\ntdll.dll"); 125 | InitializeObjectAttributes(&objAttr, (PUNICODE_STRING)&knownDllsNtdll, OBJ_KERNEL_HANDLE, 0, 0); 126 | 127 | ntStatus = ZwOpenSection(&hSection, SECTION_QUERY, &objAttr); 128 | if (!NT_SUCCESS(ntStatus)) 129 | { 130 | goto Cleanup; 131 | } 132 | 133 | ntStatus = gZwQuerySection(hSection, SectionImageInformation, &sii, sizeof(sii), 0); 134 | if (!NT_SUCCESS(ntStatus)) 135 | { 136 | goto Cleanup; 137 | } 138 | 139 | gNtdllBaseAddress = sii.TransferAddress; 140 | 141 | Cleanup: 142 | if (hSection) 143 | { 144 | ZwClose(hSection); 145 | } 146 | 147 | return ntStatus; 148 | } 149 | 150 | void DriverUnload(_DRIVER_OBJECT* DriverObject) 151 | { 152 | UNREFERENCED_PARAMETER(DriverObject); 153 | 154 | if (bCallbackRegistered) 155 | { 156 | (void)PsRemoveLoadImageNotifyRoutine(MyLoadImageNotifyRoutine); 157 | bCallbackRegistered = FALSE; 158 | } 159 | } 160 | 161 | EXTERN_C NTSTATUS DriverEntry( 162 | _In_ PDRIVER_OBJECT DriverObject, 163 | _In_ PUNICODE_STRING RegistryPath 164 | ) 165 | { 166 | NTSTATUS ntStatus = STATUS_SUCCESS; 167 | 168 | UNREFERENCED_PARAMETER(RegistryPath); 169 | 170 | DriverObject->DriverUnload = DriverUnload; 171 | 172 | // Resolve imports 173 | { 174 | gZwQueryInformationProcess = (ZwQueryInformationProcess_t)MmGetSystemRoutineAddress((PUNICODE_STRING)&gzuZwQueryInformationProcess); 175 | if (!gZwQueryInformationProcess) 176 | { 177 | ntStatus = STATUS_PROCEDURE_NOT_FOUND; 178 | goto Cleanup; 179 | } 180 | 181 | gZwQuerySection = (ZwQuerySection_t)MmGetSystemRoutineAddress((PUNICODE_STRING)&gzuZwQuerySection); 182 | if (!gZwQuerySection) 183 | { 184 | ntStatus = STATUS_PROCEDURE_NOT_FOUND; 185 | goto Cleanup; 186 | } 187 | } 188 | 189 | ntStatus = GetNtdllBaseAddress(); 190 | if (!NT_SUCCESS(ntStatus)) 191 | { 192 | goto Cleanup; 193 | } 194 | 195 | ntStatus = PsSetLoadImageNotifyRoutineEx(MyLoadImageNotifyRoutine, 0); 196 | if (!NT_SUCCESS(ntStatus)) 197 | { 198 | goto Cleanup; 199 | } 200 | bCallbackRegistered = TRUE; 201 | 202 | Cleanup: 203 | 204 | if (!NT_SUCCESS(ntStatus)) 205 | { 206 | DriverUnload(DriverObject); 207 | } 208 | 209 | return ntStatus; 210 | } -------------------------------------------------------------------------------- /CISpotter/CISpotter.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | typedef NTSTATUS (NTAPI *ZwQueryInformationProcess_t)( 6 | _In_ HANDLE ProcessHandle, 7 | _In_ PROCESSINFOCLASS ProcessInformationClass, 8 | _Out_ PVOID ProcessInformation, 9 | _In_ ULONG ProcessInformationLength, 10 | _Out_opt_ PULONG ReturnLength 11 | ); 12 | 13 | typedef enum _SECTION_INFORMATION_CLASS { 14 | SectionBasicInformation, 15 | SectionImageInformation 16 | } SECTION_INFORMATION_CLASS, * PSECTION_INFORMATION_CLASS; 17 | 18 | typedef NTSTATUS (NTAPI * ZwQuerySection_t)( 19 | IN HANDLE SectionHandle, 20 | IN SECTION_INFORMATION_CLASS InformationClass, 21 | OUT PVOID InformationBuffer, 22 | IN ULONG InformationBufferSize, 23 | OUT PULONG ResultLength OPTIONAL); 24 | 25 | EXTERN_C 26 | _Must_inspect_result_ 27 | NTSYSAPI 28 | PVOID 29 | PsGetProcessSectionBaseAddress( 30 | __in PEPROCESS Process 31 | ); 32 | 33 | #pragma warning(push) 34 | #pragma warning(disable: 4201) // warning C4201: nonstandard extension used: nameless struct/union 35 | #pragma warning(disable: 4214) // warning C4214: nonstandard extension used: bit field types other than int 36 | 37 | // From https://docs.microsoft.com/en-us/windows/win32/procthread/zwqueryinformationprocess 38 | typedef enum _PS_PROTECTED_TYPE { 39 | PsProtectedTypeNone = 0, 40 | PsProtectedTypeProtectedLight = 1, 41 | PsProtectedTypeProtected = 2 42 | } PS_PROTECTED_TYPE, * PPS_PROTECTED_TYPE; 43 | 44 | typedef enum _PS_PROTECTED_SIGNER { 45 | PsProtectedSignerNone = 0, 46 | PsProtectedSignerAuthenticode, 47 | PsProtectedSignerCodeGen, 48 | PsProtectedSignerAntimalware, 49 | PsProtectedSignerLsa, 50 | PsProtectedSignerWindows, 51 | PsProtectedSignerWinTcb, 52 | PsProtectedSignerWinSystem, 53 | PsProtectedSignerApp, 54 | PsProtectedSignerMax 55 | } PS_PROTECTED_SIGNER, * PPS_PROTECTED_SIGNER; 56 | 57 | typedef struct _PS_PROTECTION { 58 | union { 59 | UCHAR Level; 60 | struct { 61 | UCHAR Type : 3; 62 | UCHAR Audit : 1; // Reserved 63 | UCHAR Signer : 4; 64 | }; 65 | }; 66 | } PS_PROTECTION, * PPS_PROTECTION; 67 | 68 | typedef struct _SECTION_IMAGE_INFORMATION { 69 | PVOID TransferAddress; 70 | char Reserved[256]; 71 | } SECTION_IMAGE_INFORMATION, * PSECTION_IMAGE_INFORMATION; 72 | 73 | #pragma warning(pop) 74 | -------------------------------------------------------------------------------- /CISpotter/CISpotter.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 | Debug 22 | ARM 23 | 24 | 25 | Release 26 | ARM 27 | 28 | 29 | Debug 30 | ARM64 31 | 32 | 33 | Release 34 | ARM64 35 | 36 | 37 | 38 | {25F1BE0A-6335-40B8-9ADD-F49AC2976104} 39 | {dd38f7fc-d7bd-488b-9242-7d8754cde80d} 40 | v4.5 41 | 12.0 42 | Debug 43 | Win32 44 | CISpotter 45 | 46 | 47 | 48 | Windows10 49 | true 50 | WindowsKernelModeDriver10.0 51 | Driver 52 | WDM 53 | 54 | 55 | Windows10 56 | false 57 | WindowsKernelModeDriver10.0 58 | Driver 59 | WDM 60 | 61 | 62 | Windows10 63 | true 64 | WindowsKernelModeDriver10.0 65 | Driver 66 | WDM 67 | 68 | 69 | Windows10 70 | false 71 | WindowsKernelModeDriver10.0 72 | Driver 73 | WDM 74 | 75 | 76 | Windows10 77 | true 78 | WindowsKernelModeDriver10.0 79 | Driver 80 | WDM 81 | 82 | 83 | Windows10 84 | false 85 | WindowsKernelModeDriver10.0 86 | Driver 87 | WDM 88 | 89 | 90 | Windows10 91 | true 92 | WindowsKernelModeDriver10.0 93 | Driver 94 | WDM 95 | 96 | 97 | Windows10 98 | false 99 | WindowsKernelModeDriver10.0 100 | Driver 101 | WDM 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | DbgengKernelDebugger 113 | false 114 | 115 | 116 | DbgengKernelDebugger 117 | 118 | 119 | DbgengKernelDebugger 120 | 121 | 122 | DbgengKernelDebugger 123 | 124 | 125 | DbgengKernelDebugger 126 | 127 | 128 | DbgengKernelDebugger 129 | 130 | 131 | DbgengKernelDebugger 132 | 133 | 134 | DbgengKernelDebugger 135 | 136 | 137 | 138 | sha256 139 | 140 | 141 | 142 | 143 | sha256 144 | 145 | 146 | 147 | 148 | sha256 149 | 150 | 151 | 152 | 153 | sha256 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /CISpotter/CISpotter.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;hpp;hxx;hm;inl;inc;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 | {8E41214B-6785-4CFE-B992-037D68949A14} 18 | inf;inv;inx;mof;mc; 19 | 20 | 21 | 22 | 23 | Source Files 24 | 25 | 26 | 27 | 28 | Header Files 29 | 30 | 31 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Elastic 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code Integrity Violation Spotter 2 | 3 | See [this blog post](https://www.elastic.co/blog/detect-block-unknown-knowndlls-windows-acl-hardening-attacks-cache-poisoning-escalation) for more information. 4 | 5 | Windows normally performs Protected Process Light code integrity checks during `NtCreateSection(SEC_IMAGE)`. 6 | CI Spotter adds similar checks during `NtMapViewOfSection`, preventing CI bypasses through mechanisms such as [KnownDlls cache poisoning](https://www.elastic.co/blog/protecting-windows-protected-processes). 7 | 8 | **This is a proof of concept. Use it at your own risk.** 9 | 10 | ## Building and running it 11 | 12 | 1. Compile CISpotter.sln with Visual Studio 2019. 13 | 2. Enable [Test Signing](https://docs.microsoft.com/en-us/windows-hardware/drivers/install/the-testsigning-boot-configuration-option). 14 | 3. Register and start the service: 15 | ``` 16 | sc create CISpotter type= kernel start= demand binpath= %CD%\CISpotter.sys 17 | sc start CISpotter 18 | ``` 19 | --------------------------------------------------------------------------------