├── README.md
├── LICENSE.txt
├── CISpotter.sln
└── CISpotter
├── CISpotter.vcxproj.filters
├── CISpotter.h
├── CISpotter.cpp
└── CISpotter.vcxproj
/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 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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.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 |
--------------------------------------------------------------------------------
/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.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.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 |
--------------------------------------------------------------------------------