diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..22c7b48
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+.vs/
+bin/
+obj/
+asset/pki-architecture.xml
+
diff --git a/CustomKernelSignersPersistent/CustomKernelSignersPersistent.vcxproj b/CustomKernelSignersPersistent/CustomKernelSignersPersistent.vcxproj
new file mode 100644
index 0000000..d11bb41
--- /dev/null
+++ b/CustomKernelSignersPersistent/CustomKernelSignersPersistent.vcxproj
@@ -0,0 +1,139 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}
+ {dd38f7fc-d7bd-488b-9242-7d8754cde80d}
+ v4.5
+ 12.0
+ Debug
+ Win32
+ CustomKernelSignersPersistent
+ $(LatestTargetPlatformVersion)
+
+
+
+ Windows10
+ true
+ WindowsKernelModeDriver10.0
+ Driver
+ WDM
+ Desktop
+
+
+ Windows10
+ false
+ WindowsKernelModeDriver10.0
+ Driver
+ WDM
+ Desktop
+
+
+ Windows10
+ true
+ WindowsKernelModeDriver10.0
+ Driver
+ WDM
+ Desktop
+
+
+ Windows10
+ false
+ WindowsKernelModeDriver10.0
+ Driver
+ WDM
+ Desktop
+
+
+
+
+
+
+
+
+
+
+ DbgengKernelDebugger
+ ckspdrv
+ $(SolutionDir)bin\$(Platform)-$(ConfigurationName)\
+ $(SolutionDir)obj\$(Platform)-$(ConfigurationName)\$(ProjectName)\
+
+
+
+ DbgengKernelDebugger
+ ckspdrv
+ $(SolutionDir)bin\$(Platform)-$(ConfigurationName)\
+ $(SolutionDir)obj\$(Platform)-$(ConfigurationName)\$(ProjectName)\
+
+
+
+ DbgengKernelDebugger
+
+ ckspdrv
+ $(SolutionDir)bin\$(Platform)-$(ConfigurationName)\
+ $(SolutionDir)obj\$(Platform)-$(ConfigurationName)\$(ProjectName)\
+
+
+ DbgengKernelDebugger
+
+ ckspdrv
+ $(SolutionDir)bin\$(Platform)-$(ConfigurationName)\
+ $(SolutionDir)obj\$(Platform)-$(ConfigurationName)\$(ProjectName)\
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CustomKernelSignersPersistent/CustomKernelSignersPersistent.vcxproj.filters b/CustomKernelSignersPersistent/CustomKernelSignersPersistent.vcxproj.filters
new file mode 100644
index 0000000..2a8a3be
--- /dev/null
+++ b/CustomKernelSignersPersistent/CustomKernelSignersPersistent.vcxproj.filters
@@ -0,0 +1,40 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hpp;hxx;hm;inl;inc;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+ {8E41214B-6785-4CFE-B992-037D68949A14}
+ inf;inv;inx;mof;mc;
+
+
+
+
+ Header Files
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/CustomKernelSignersPersistent/CustomKernelSignersPersistent.vcxproj.user b/CustomKernelSignersPersistent/CustomKernelSignersPersistent.vcxproj.user
new file mode 100644
index 0000000..e71ec7f
--- /dev/null
+++ b/CustomKernelSignersPersistent/CustomKernelSignersPersistent.vcxproj.user
@@ -0,0 +1,34 @@
+
+
+
+ true
+
+
+ Off
+
+
+
+
+
+
+ Off
+
+
+
+
+
+
+ Off
+
+
+
+
+
+
+ Off
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CustomKernelSignersPersistent/cksp_defs.h b/CustomKernelSignersPersistent/cksp_defs.h
new file mode 100644
index 0000000..6e0bf98
--- /dev/null
+++ b/CustomKernelSignersPersistent/cksp_defs.h
@@ -0,0 +1,72 @@
+#pragma once
+#include
+
+NTSTATUS NTAPI ZwQueryLicenseValue(
+ _In_ PUNICODE_STRING ValueName,
+ _Out_opt_ PULONG Type,
+ _Out_writes_bytes_to_opt_(DataSize, *ResultDataSize) PVOID Data,
+ _In_ ULONG DataSize,
+ _Out_ PULONG ResultDataSize
+);
+
+NTSTATUS NTAPI ExUpdateLicenseData(
+ _In_ ULONG cbBytes,
+ _In_reads_bytes_(cbBytes) PVOID lpBytes
+);
+
+typedef enum _CKSP_WORKER_ACTION {
+ CkspWorkerActionNone = 0,
+ CkspWorkerActionStop = 1
+} CKSP_WORKER_ACTION;
+
+typedef struct _CKSP_WORKER_CONTEXT {
+ UNICODE_STRING LaunchDriverServiceName;
+ HANDLE ProductOptionsKeyChangeEventHandle;
+ PKEVENT ProductOptionsKeyChangeEventObject;
+ HANDLE ProductOptionsKey;
+ PKEY_VALUE_PARTIAL_INFORMATION ProductPolicyValueInfo;
+ ULONG ProductPolicyValueInfoSize;
+ CKSP_WORKER_ACTION Action;
+} CKSP_WORKER_CONTEXT, *PCKSP_WORKER_CONTEXT;
+
+extern PCKSP_WORKER_CONTEXT g_CkspWorkerContext;
+extern HANDLE g_CkspWorkerThreadHandle;
+extern PVOID g_CkspWorkerThreadObject;
+
+NTSTATUS NTAPI DriverEntry(
+ _In_ PDRIVER_OBJECT DriverObject,
+ _In_ PUNICODE_STRING RegistryPath
+);
+
+VOID NTAPI DriverUnload(
+ _In_ PDRIVER_OBJECT DriverObject
+);
+
+NTSTATUS NTAPI IrpNullHandler(
+ _In_ PDEVICE_OBJECT DeviceObject,
+ _In_ PIRP Irp
+);
+
+//
+// CKSP routines
+//
+
+NTSTATUS CkspInitContext(
+ _In_ PCKSP_WORKER_CONTEXT Context,
+ _In_ PDRIVER_OBJECT DriverObject,
+ _In_ PUNICODE_STRING RegistryPath
+);
+
+VOID CkspClearContext(
+ _In_ PCKSP_WORKER_CONTEXT Context
+);
+
+VOID NTAPI CkspWorker(
+ _In_ PVOID StartContext
+);
+
+VOID CkspDeferUnloadAsPossible(
+ _In_ PCKSP_WORKER_CONTEXT Context
+);
+
+
diff --git a/CustomKernelSignersPersistent/cksp_entry.c b/CustomKernelSignersPersistent/cksp_entry.c
new file mode 100644
index 0000000..2cbac77
--- /dev/null
+++ b/CustomKernelSignersPersistent/cksp_entry.c
@@ -0,0 +1,70 @@
+#include "cksp_defs.h"
+
+PCKSP_WORKER_CONTEXT g_CkspWorkerContext;
+HANDLE g_CkspWorkerThreadHandle;
+PVOID g_CkspWorkerThreadObject;
+
+NTSTATUS NTAPI DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
+ NTSTATUS Status;
+ OBJECT_ATTRIBUTES ThreadAttribute;
+
+ //
+ // We don't handle any IRQs
+ //
+ for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; ++i)
+ DriverObject->MajorFunction[i] = IrpNullHandler;
+
+ DriverObject->DriverUnload = DriverUnload;
+
+ //
+ // Allocate CKSP context
+ //
+ g_CkspWorkerContext = (PCKSP_WORKER_CONTEXT)ExAllocatePoolWithTag(NonPagedPool, sizeof(CKSP_WORKER_CONTEXT), 'cksp');
+ if (g_CkspWorkerContext == NULL) {
+ Status = STATUS_NO_MEMORY;
+ goto ON_DriverEntry_ERROR;
+ }
+
+ //
+ // Initialize CKSP context
+ //
+ Status = CkspInitContext(g_CkspWorkerContext, DriverObject, RegistryPath);
+ if (!NT_SUCCESS(Status))
+ goto ON_DriverEntry_ERROR;
+
+ //
+ // Launch CkspWorker thread
+ //
+ InitializeObjectAttributes(&ThreadAttribute, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);
+ Status = PsCreateSystemThread(&g_CkspWorkerThreadHandle,
+ THREAD_ALL_ACCESS,
+ &ThreadAttribute,
+ NULL,
+ NULL,
+ CkspWorker,
+ g_CkspWorkerContext);
+ if (!NT_SUCCESS(Status)) {
+ goto ON_DriverEntry_ERROR;
+ } else {
+ //
+ // never fail here
+ //
+ ObReferenceObjectByHandle(g_CkspWorkerThreadHandle,
+ THREAD_ALL_ACCESS,
+ *PsThreadType,
+ KernelMode,
+ &g_CkspWorkerThreadObject,
+ NULL);
+ }
+
+ return Status;
+
+ON_DriverEntry_ERROR:
+ if (g_CkspWorkerContext) {
+ CkspClearContext(g_CkspWorkerContext);
+ ExFreePoolWithTag(g_CkspWorkerContext, 'cksp');
+ g_CkspWorkerContext = NULL;
+ }
+ return Status;
+}
+
diff --git a/CustomKernelSignersPersistent/cksp_irp_null.c b/CustomKernelSignersPersistent/cksp_irp_null.c
new file mode 100644
index 0000000..694dfc2
--- /dev/null
+++ b/CustomKernelSignersPersistent/cksp_irp_null.c
@@ -0,0 +1,12 @@
+#include "cksp_defs.h"
+
+NTSTATUS NTAPI IrpNullHandler(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {
+ UNREFERENCED_PARAMETER(DeviceObject);
+
+ Irp->IoStatus.Information = 0;
+ Irp->IoStatus.Status = STATUS_NOT_SUPPORTED;
+
+ IoCompleteRequest(Irp, IO_NO_INCREMENT);
+ return STATUS_NOT_SUPPORTED;
+}
+
diff --git a/CustomKernelSignersPersistent/cksp_main.c b/CustomKernelSignersPersistent/cksp_main.c
new file mode 100644
index 0000000..1744aad
--- /dev/null
+++ b/CustomKernelSignersPersistent/cksp_main.c
@@ -0,0 +1,302 @@
+#include "cksp_defs.h"
+
+typedef struct _PPBinaryHeader {
+ ULONG TotalSize;
+ ULONG DataSize;
+ ULONG EndMarkerSize;
+ ULONG Reserved;
+ ULONG Revision;
+} PPBinaryHeader, *PPPBinaryHeader;
+
+typedef struct _PPBinaryValue {
+ USHORT TotalSize;
+ USHORT NameSize;
+ USHORT DataType;
+ USHORT DataSize;
+ ULONG Flags;
+ ULONG Reserved;
+} PPBinaryValue, *PPPBinaryValue;
+
+UNICODE_STRING g_ProductOptionsKeyName =
+ RTL_CONSTANT_STRING(L"\\Registry\\Machine\\SYSTEM\\CurrentControlSet\\Control\\ProductOptions");
+
+UNICODE_STRING g_ProductPolicyValueName =
+ RTL_CONSTANT_STRING(L"ProductPolicy");
+
+UNICODE_STRING g_CiAcpName =
+ RTL_CONSTANT_STRING(L"CodeIntegrity-AllowConfigurablePolicy");
+
+UNICODE_STRING g_CiAcpCksName =
+ RTL_CONSTANT_STRING(L"CodeIntegrity-AllowConfigurablePolicy-CustomKernelSigners");
+
+static NTSTATUS CkspModifyPolicyBinary(_In_ PUCHAR lpBytes, _In_ ULONG cbBytes) {
+ BOOLEAN AllowConfigurablePolicySet = FALSE;
+ BOOLEAN AllowConfigurablePolicyCustomKernelSignerSet = FALSE;
+ PPPBinaryHeader pHeader = (PPPBinaryHeader)lpBytes;
+ PUCHAR EndPtr = lpBytes + cbBytes;
+ PPPBinaryValue pVal;
+
+ if (cbBytes < sizeof(PPBinaryHeader) ||
+ cbBytes != pHeader->TotalSize ||
+ cbBytes != sizeof(PPBinaryHeader) + sizeof(ULONG) + pHeader->DataSize)
+ {
+ return STATUS_INFO_LENGTH_MISMATCH;
+ }
+
+ EndPtr -= sizeof(ULONG);
+ if (*(PULONG)EndPtr != 0x45) // Product policy end-mark
+ return STATUS_INVALID_PARAMETER;
+
+ for (pVal = (PPPBinaryValue)(pHeader + 1); (PUCHAR)pVal + sizeof(PPBinaryValue) < EndPtr; pVal = (PPPBinaryValue)((PUCHAR)pVal + pVal->TotalSize)) {
+ PWSTR pValName;
+ PVOID pValData;
+
+ if (pVal->NameSize % 2 != 0)
+ return STATUS_INVALID_PARAMETER;
+
+ pValName = (PWSTR)(pVal + 1);
+ pValData = (PUCHAR)pValName + pVal->NameSize;
+
+ if ((PUCHAR)pValData + pVal->DataSize > EndPtr)
+ return STATUS_INVALID_PARAMETER;
+
+ if (AllowConfigurablePolicySet == FALSE && _wcsnicmp(pValName, L"CodeIntegrity-AllowConfigurablePolicy", pVal->NameSize / 2) == 0) {
+ if (pVal->DataType == REG_DWORD && pVal->DataSize == 4) {
+ *(PULONG)pValData = 1;
+ AllowConfigurablePolicySet = TRUE;
+ } else {
+ return STATUS_INVALID_PARAMETER;
+ }
+ } else if (AllowConfigurablePolicyCustomKernelSignerSet == FALSE && _wcsnicmp(pValName, L"CodeIntegrity-AllowConfigurablePolicy-CustomKernelSigners", pVal->NameSize / 2) == 0) {
+ if (pVal->DataType == REG_DWORD && pVal->DataSize == 4) {
+ *(PULONG)pValData = 1;
+ AllowConfigurablePolicyCustomKernelSignerSet = TRUE;
+ } else {
+ return STATUS_INVALID_PARAMETER;
+ }
+ }
+
+ if (AllowConfigurablePolicySet && AllowConfigurablePolicyCustomKernelSignerSet)
+ break;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+static NTSTATUS CkspEnableCustomKernelSigners(_In_ PCKSP_WORKER_CONTEXT Context) {
+ NTSTATUS Status;
+ ULONG ResultLength;
+
+ while (1) {
+ Status = ZwQueryValueKey(Context->ProductOptionsKey,
+ &g_ProductPolicyValueName,
+ KeyValuePartialInformation,
+ Context->ProductPolicyValueInfo,
+ Context->ProductPolicyValueInfoSize,
+ &ResultLength);
+
+ if (NT_SUCCESS(Status)) {
+ break;
+ } else if (Status == STATUS_BUFFER_OVERFLOW || Status == STATUS_BUFFER_TOO_SMALL) {
+ ExFreePoolWithTag(Context->ProductPolicyValueInfo, 'cksp');
+ Context->ProductPolicyValueInfo =
+ (PKEY_VALUE_PARTIAL_INFORMATION)ExAllocatePoolWithTag(PagedPool, ResultLength, 'cksp');
+ if (Context->ProductPolicyValueInfo) {
+ Context->ProductPolicyValueInfoSize = ResultLength;
+ } else {
+ Context->ProductPolicyValueInfoSize = 0;
+ Status = STATUS_NO_MEMORY;
+ goto ON_CkspEnableCustomKernelSigners_Error;
+ }
+ } else {
+ goto ON_CkspEnableCustomKernelSigners_Error;
+ }
+ }
+
+ Status = CkspModifyPolicyBinary(Context->ProductPolicyValueInfo->Data,
+ Context->ProductPolicyValueInfo->DataLength);
+ if (!NT_SUCCESS(Status))
+ goto ON_CkspEnableCustomKernelSigners_Error;
+
+ Status = ExUpdateLicenseData(Context->ProductPolicyValueInfo->DataLength,
+ Context->ProductPolicyValueInfo->Data);
+
+ON_CkspEnableCustomKernelSigners_Error:
+ return Status;
+}
+
+NTSTATUS CkspInitContext(_In_ PCKSP_WORKER_CONTEXT Context, _In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
+ NTSTATUS Status;
+
+ UNREFERENCED_PARAMETER(DriverObject);
+
+ RtlZeroMemory(Context, sizeof(CKSP_WORKER_CONTEXT));
+
+ {
+ Status = RtlDuplicateUnicodeString(RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE,
+ RegistryPath,
+ &Context->LaunchDriverServiceName);
+ if (!NT_SUCCESS(Status))
+ goto ON_CkspInitContext_ERROR;
+ }
+
+ {
+ Status = ZwCreateEvent(&Context->ProductOptionsKeyChangeEventHandle,
+ EVENT_ALL_ACCESS,
+ NULL,
+ SynchronizationEvent,
+ FALSE);
+ if (!NT_SUCCESS(Status)) {
+ goto ON_CkspInitContext_ERROR;
+ } else {
+ ObReferenceObjectByHandle(Context->ProductOptionsKeyChangeEventHandle,
+ EVENT_ALL_ACCESS,
+ *ExEventObjectType,
+ KernelMode,
+ (PVOID*)&Context->ProductOptionsKeyChangeEventObject,
+ NULL);
+ }
+ }
+
+ {
+ OBJECT_ATTRIBUTES KeyAttribute;
+ InitializeObjectAttributes(&KeyAttribute,
+ &g_ProductOptionsKeyName,
+ OBJ_CASE_INSENSITIVE,
+ NULL,
+ NULL);
+ Status = ZwOpenKey(&Context->ProductOptionsKey, KEY_READ, &KeyAttribute);
+ if (!NT_SUCCESS(Status))
+ goto ON_CkspInitContext_ERROR;
+ }
+
+
+ {
+ ULONG ResultLength = 0;
+ KEY_VALUE_PARTIAL_INFORMATION KeyInfo;
+ Status = ZwQueryValueKey(Context->ProductOptionsKey,
+ &g_ProductPolicyValueName,
+ KeyValuePartialInformation,
+ &KeyInfo,
+ sizeof(KeyInfo),
+ &ResultLength);
+ if (Status != STATUS_BUFFER_OVERFLOW && Status != STATUS_BUFFER_TOO_SMALL && Status != STATUS_SUCCESS)
+ goto ON_CkspInitContext_ERROR;
+
+ Context->ProductPolicyValueInfo = (PKEY_VALUE_PARTIAL_INFORMATION)ExAllocatePoolWithTag(PagedPool, ResultLength, 'cksp');
+ if (Context->ProductPolicyValueInfo == NULL) {
+ Status = STATUS_NO_MEMORY;
+ goto ON_CkspInitContext_ERROR;
+ } else {
+ Context->ProductPolicyValueInfoSize = ResultLength;
+ Status = STATUS_SUCCESS;
+ }
+ }
+
+ON_CkspInitContext_ERROR:
+ if (!NT_SUCCESS(Status))
+ CkspClearContext(Context);
+ return Status;
+}
+
+VOID CkspClearContext(_In_ PCKSP_WORKER_CONTEXT Context) {
+ if (Context->ProductPolicyValueInfo) {
+ ExFreePoolWithTag(Context->ProductPolicyValueInfo, 'cksp');
+ Context->ProductPolicyValueInfo = NULL;
+ Context->ProductPolicyValueInfoSize = 0;
+ }
+
+ if (Context->ProductOptionsKey) {
+ ZwClose(Context->ProductOptionsKey);
+ Context->ProductOptionsKey = NULL;
+ }
+
+ if (Context->ProductOptionsKeyChangeEventHandle) {
+ ObDereferenceObject(Context->ProductOptionsKeyChangeEventObject);
+ Context->ProductOptionsKeyChangeEventObject = NULL;
+
+ ZwClose(Context->ProductOptionsKeyChangeEventHandle);
+ Context->ProductOptionsKeyChangeEventHandle = NULL;
+ }
+
+ if (Context->LaunchDriverServiceName.Buffer) {
+ RtlFreeUnicodeString(&Context->LaunchDriverServiceName);
+ RtlZeroMemory(&Context->LaunchDriverServiceName, sizeof(UNICODE_STRING));
+ }
+}
+
+static NTSTATUS CkspMain(_In_ PCKSP_WORKER_CONTEXT Context) {
+ NTSTATUS Status;
+ ULONG PolicyValueType;
+ ULONG ReturnLength;
+ ULONG CiAcp;
+ ULONG CiAcpCks;
+ IO_STATUS_BLOCK IoStatusBlock;
+
+ while (1) {
+ //
+ // Get status of CodeIntegrity-AllowConfigurablePolicy
+ //
+ Status = ZwQueryLicenseValue(&g_CiAcpName, &PolicyValueType, &CiAcp, sizeof(CiAcp), &ReturnLength);
+ if (!NT_SUCCESS(Status)) {
+ goto ON_CpskMain_ERROR;
+ }
+ if (PolicyValueType != REG_DWORD || ReturnLength != sizeof(ULONG)) {
+ Status = STATUS_OBJECT_TYPE_MISMATCH;
+ goto ON_CpskMain_ERROR;
+ }
+
+ //
+ // Get status of CodeIntegrity-AllowConfigurablePolicy-CustomKernelSigners
+ //
+ Status = ZwQueryLicenseValue(&g_CiAcpCksName, &PolicyValueType, &CiAcpCks, sizeof(CiAcpCks), &ReturnLength);
+ if (!NT_SUCCESS(Status)) {
+ goto ON_CpskMain_ERROR;
+ }
+ if (PolicyValueType != REG_DWORD || ReturnLength != sizeof(ULONG)) {
+ Status = STATUS_OBJECT_TYPE_MISMATCH;
+ goto ON_CpskMain_ERROR;
+ }
+
+ //
+ // When either CodeIntegrity-AllowConfigurablePolicy or CodeIntegrity-AllowConfigurablePolicy-CustomKernelSigners
+ // is not enable, we need update now;
+ //
+ if (CiAcp == 0 || CiAcpCks == 0) {
+ Status = CkspEnableCustomKernelSigners(Context);
+ if (!NT_SUCCESS(Status))
+ goto ON_CpskMain_ERROR;
+ }
+
+ Status = ZwNotifyChangeKey(Context->ProductOptionsKey,
+ Context->ProductOptionsKeyChangeEventHandle,
+ NULL,
+ NULL,
+ &IoStatusBlock,
+ REG_NOTIFY_CHANGE_LAST_SET,
+ FALSE,
+ NULL,
+ 0,
+ TRUE);
+ if (!NT_SUCCESS(Status))
+ goto ON_CpskMain_ERROR;
+
+ KeWaitForSingleObject(Context->ProductOptionsKeyChangeEventObject, Executive, KernelMode, FALSE, NULL);
+
+ if (Context->Action == CkspWorkerActionStop)
+ break;
+ }
+
+ CkspClearContext(Context);
+ return Status;
+
+ON_CpskMain_ERROR:
+ CkspDeferUnloadAsPossible(Context);
+ CkspClearContext(Context);
+ return Status;
+}
+
+VOID NTAPI CkspWorker(_In_ PVOID StartContext) {
+ PsTerminateSystemThread(CkspMain((PCKSP_WORKER_CONTEXT)StartContext));
+}
+
diff --git a/CustomKernelSignersPersistent/cksp_unload.c b/CustomKernelSignersPersistent/cksp_unload.c
new file mode 100644
index 0000000..8472522
--- /dev/null
+++ b/CustomKernelSignersPersistent/cksp_unload.c
@@ -0,0 +1,58 @@
+#include "cksp_defs.h"
+
+VOID NTAPI DriverUnload(_In_ PDRIVER_OBJECT DriverObject) {
+ UNREFERENCED_PARAMETER(DriverObject);
+
+ g_CkspWorkerContext->Action = CkspWorkerActionStop;
+ KeSetEvent(g_CkspWorkerContext->ProductOptionsKeyChangeEventObject, IO_NO_INCREMENT, TRUE);
+ KeWaitForSingleObject(g_CkspWorkerThreadObject, Executive, KernelMode, FALSE, NULL);
+
+ ObDereferenceObject(g_CkspWorkerThreadObject);
+ g_CkspWorkerThreadObject = NULL;
+
+ ZwClose(g_CkspWorkerThreadHandle);
+ g_CkspWorkerThreadHandle = NULL;
+
+ ExFreePoolWithTag(g_CkspWorkerContext, 'cksp');
+ g_CkspWorkerContext = NULL;
+}
+
+VOID NTAPI CkspDeferUnloadWorker(PVOID StartContext) {
+ NTSTATUS Status;
+ PUNICODE_STRING DriverServiceName;
+
+ DriverServiceName = (PUNICODE_STRING)StartContext;
+ Status = ZwUnloadDriver(DriverServiceName);
+
+ RtlFreeUnicodeString(DriverServiceName);
+ ExFreePoolWithTag(StartContext, 'cksp');
+ PsTerminateSystemThread(Status);
+}
+
+VOID CkspDeferUnloadAsPossible(_In_ PCKSP_WORKER_CONTEXT Context) {
+ PUNICODE_STRING DriverServiceName;
+
+ DriverServiceName = (PUNICODE_STRING)ExAllocatePoolWithTag(PagedPool, sizeof(UNICODE_STRING), 'cksp');
+ if (DriverServiceName) {
+ NTSTATUS Status;
+ HANDLE ThreadHandle;
+
+ RtlCopyMemory(DriverServiceName, &Context->LaunchDriverServiceName, sizeof(UNICODE_STRING));
+ RtlZeroMemory(&Context->LaunchDriverServiceName, sizeof(UNICODE_STRING));
+
+ Status = PsCreateSystemThread(&ThreadHandle,
+ THREAD_ALL_ACCESS,
+ NULL,
+ NULL,
+ NULL,
+ CkspDeferUnloadWorker,
+ DriverServiceName);
+ if (!NT_SUCCESS(Status)) {
+ RtlFreeUnicodeString(DriverServiceName);
+ ExFreePoolWithTag(DriverServiceName, 'cksp');
+ DriverServiceName = NULL;
+ } else {
+ ZwClose(ThreadHandle);
+ }
+ }
+}
diff --git a/EnableCustomKernelSigners/EnableCustomKernelSigners.vcxproj b/EnableCustomKernelSigners/EnableCustomKernelSigners.vcxproj
new file mode 100644
index 0000000..eb78d4c
--- /dev/null
+++ b/EnableCustomKernelSigners/EnableCustomKernelSigners.vcxproj
@@ -0,0 +1,186 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 15.0
+ {E999DB5C-1C05-4F3E-A9F9-E668C47B3546}
+ Win32Proj
+ EnableCustomKernelSigners
+ 10.0.17763.0
+
+
+
+ Application
+ true
+ v141
+ Unicode
+ false
+
+
+ Application
+ false
+ v141
+ true
+ Unicode
+ false
+
+
+ Application
+ true
+ v141
+ Unicode
+ false
+
+
+ Application
+ false
+ v141
+ true
+ Unicode
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+ $(SolutionDir)bin\$(Platform)-$(Configuration)\
+ $(SolutionDir)obj\$(Platform)-$(Configuration)\$(ProjectName)\
+ EnableCKS
+
+
+ true
+ $(SolutionDir)bin\$(Platform)-$(Configuration)\
+ $(SolutionDir)obj\$(Platform)-$(Configuration)\$(ProjectName)\
+ EnableCKS
+
+
+ false
+ $(SolutionDir)bin\$(Platform)-$(Configuration)\
+ $(SolutionDir)obj\$(Platform)-$(Configuration)\$(ProjectName)\
+ EnableCKS
+
+
+ false
+ $(SolutionDir)bin\$(Platform)-$(Configuration)\
+ $(SolutionDir)obj\$(Platform)-$(Configuration)\$(ProjectName)\
+ EnableCKS
+
+
+
+ Level3
+ Disabled
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ MultiThreadedDebug
+ stdcpp17
+
+
+ true
+ Console
+ RequireAdministrator
+
+
+
+
+ Level3
+ Disabled
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ MultiThreadedDebug
+ stdcpp17
+
+
+ true
+ Console
+ RequireAdministrator
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ MultiThreaded
+ stdcpp17
+
+
+ true
+ true
+ true
+ Console
+ RequireAdministrator
+
+
+
+
+ Level3
+ MaxSpeed
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ MultiThreaded
+ stdcpp17
+
+
+ true
+ true
+ true
+ Console
+ RequireAdministrator
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EnableCustomKernelSigners/EnableCustomKernelSigners.vcxproj.filters b/EnableCustomKernelSigners/EnableCustomKernelSigners.vcxproj.filters
new file mode 100644
index 0000000..f487506
--- /dev/null
+++ b/EnableCustomKernelSigners/EnableCustomKernelSigners.vcxproj.filters
@@ -0,0 +1,39 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ 源文件
+
+
+ 源文件
+
+
+ 源文件
+
+
+
+
+ 头文件
+
+
+ 头文件
+
+
+ 头文件
+
+
+
\ No newline at end of file
diff --git a/EnableCustomKernelSigners/EnableCustomKernelSigners.vcxproj.user b/EnableCustomKernelSigners/EnableCustomKernelSigners.vcxproj.user
new file mode 100644
index 0000000..0b0f24d
--- /dev/null
+++ b/EnableCustomKernelSigners/EnableCustomKernelSigners.vcxproj.user
@@ -0,0 +1,6 @@
+
+
+
+ true
+
+
\ No newline at end of file
diff --git a/EnableCustomKernelSigners/OwnedResource.hpp b/EnableCustomKernelSigners/OwnedResource.hpp
new file mode 100644
index 0000000..4608fcc
--- /dev/null
+++ b/EnableCustomKernelSigners/OwnedResource.hpp
@@ -0,0 +1,129 @@
+#pragma once
+#include
+
+template
+class OwnedResource {
+public:
+ using HandleType = typename __ResourceTraits::HandleType;
+private:
+ HandleType _HandleValue;
+public:
+
+ OwnedResource() noexcept :
+ _HandleValue(__ResourceTraits::InvalidValue) {}
+
+ explicit OwnedResource(const HandleType& Handle) noexcept :
+ _HandleValue(Handle) {}
+
+ //
+ // Copy constructor is not allowed
+ //
+ OwnedResource(const OwnedResource<__ResourceTraits>& Other) = delete;
+
+ OwnedResource(OwnedResource<__ResourceTraits>&& Other) noexcept :
+ _HandleValue(Other._HandleValue)
+ {
+ Other._HandleValue = __ResourceTraits::InvalidValue;
+ }
+
+ //
+ // Copy assignment is not allowed
+ //
+ OwnedResource<__ResourceTraits>& operator=(const OwnedResource<__ResourceTraits>& Other) = delete;
+
+ OwnedResource<__ResourceTraits>& operator=(OwnedResource<__ResourceTraits>&& Other) noexcept {
+ _HandleValue = Other._HandleValue;
+ Other._HandleValue = __ResourceTraits::InvalidValue;
+ return *this;
+ }
+
+ template::value>::type>
+ HandleType operator->() const noexcept {
+ return _HandleValue;
+ }
+
+ operator HandleType() const noexcept {
+ return _HandleValue;
+ }
+
+ //
+ // Check if handle is a valid handle
+ //
+ bool IsValid() const noexcept {
+ return _HandleValue != __ResourceTraits::InvalidValue;
+ }
+
+ //
+ // Retrieve handle
+ //
+ HandleType Get() const noexcept {
+ return _HandleValue;
+ }
+
+ //
+ // Get address of _HandleValue
+ //
+ HandleType* GetAddress() noexcept {
+ return &_HandleValue;
+ }
+
+ //
+ // A const version for GetAddress
+ //
+ const HandleType* GetAddress() const noexcept {
+ return &_HandleValue;
+ }
+
+ template
+ void TakeOver(const HandleType& Handle) noexcept {
+ if constexpr (__NoRelease == false) {
+ if (_HandleValue != __ResourceTraits::InvalidValue)
+ __ResourceTraits::Releasor(_HandleValue);
+ }
+ _HandleValue = Handle;
+ }
+
+ template
+ void Abandon() noexcept {
+ if constexpr (__NoRelease == false) {
+ if (_HandleValue != __ResourceTraits::InvalidValue)
+ __ResourceTraits::Releasor(_HandleValue);
+ }
+ _HandleValue = __ResourceTraits::InvalidValue;
+ }
+
+ //
+ // Force release
+ //
+ void Release() {
+ if (_HandleValue != __ResourceTraits::InvalidValue) {
+ __ResourceTraits::Releasor(_HandleValue);
+ _HandleValue = __ResourceTraits::InvalidValue;
+ }
+ }
+
+ ~OwnedResource() {
+ if (_HandleValue != __ResourceTraits::InvalidValue) {
+ __ResourceTraits::Releasor(_HandleValue);
+ _HandleValue = __ResourceTraits::InvalidValue;
+ }
+ }
+};
+
+template
+struct CppObjectTraits {
+ using HandleType = __ClassType * ;
+ static inline const HandleType InvalidValue = nullptr;
+ static inline void Releasor(HandleType pObject) {
+ delete pObject;
+ }
+};
+
+template
+struct CppDynamicArrayTraits {
+ using HandleType = __ClassType * ;
+ static inline const HandleType InvalidValue = nullptr;
+ static inline void Releasor(HandleType ArrayPtr) {
+ delete[] ArrayPtr;
+ }
+};
diff --git a/EnableCustomKernelSigners/ProductPolicy.cpp b/EnableCustomKernelSigners/ProductPolicy.cpp
new file mode 100644
index 0000000..bee9173
--- /dev/null
+++ b/EnableCustomKernelSigners/ProductPolicy.cpp
@@ -0,0 +1,69 @@
+#include "ProductPolicy.hpp"
+#include
+
+size_t ProductPolicy::NumberOfPolicies() const noexcept {
+ return _Policies.size();
+}
+
+size_t ProductPolicy::FindPolicy(const std::wstring& Regex, size_t StartPos) {
+ for (size_t i = StartPos; i < _Policies.size(); ++i) {
+ if (regex_match(_Policies[i].GetName(), std::wregex(Regex)))
+ return i;
+ }
+ return InvalidPos;
+}
+
+PolicyValue& ProductPolicy::operator[](size_t Index) {
+ return _Policies[Index];
+}
+
+PolicyValue& ProductPolicy::operator[](const std::wstring& Name) {
+ size_t i = 0, j = _Policies.size();
+ while (true) {
+ size_t mid = i + (j - i) / 2;
+
+ if (i == mid)
+ break;
+
+ switch (_Policies[mid].GetName().compare(Name)) {
+ case 1:
+ j = mid;
+ break;
+ case -1:
+ i = mid;
+ break;
+ default:
+ return _Policies[mid];
+ }
+ }
+
+ throw std::out_of_range("Cannot find target policy.");
+}
+
+const PolicyValue& ProductPolicy::operator[](size_t Index) const {
+ return _Policies[Index];
+}
+
+const PolicyValue& ProductPolicy::operator[](const std::wstring& Name) const {
+ size_t i = 0, j = _Policies.size();
+ while (true) {
+ size_t mid = i + (j - i) / 2;
+
+ if (i == mid)
+ break;
+
+ switch (_Policies[mid].GetName().compare(Name)) {
+ case 1:
+ j = mid;
+ break;
+ case -1:
+ i = mid;
+ break;
+ default:
+ return _Policies[mid];
+ }
+ }
+
+ throw std::out_of_range("Cannot find target policy.");
+}
+
diff --git a/EnableCustomKernelSigners/ProductPolicy.hpp b/EnableCustomKernelSigners/ProductPolicy.hpp
new file mode 100644
index 0000000..73d770d
--- /dev/null
+++ b/EnableCustomKernelSigners/ProductPolicy.hpp
@@ -0,0 +1,91 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+class PolicyValue {
+ friend class ProductPolicyParser;
+public:
+ enum class TypeLabel {
+ String = REG_SZ,
+ Binary = REG_BINARY,
+ UInt32 = REG_DWORD
+ };
+
+ using TypeOfString = std::wstring;
+ using TypeOfBinary = std::vector;
+ using TypeOfUInt32 = uint32_t;
+private:
+ TypeLabel Type;
+ std::wstring Name;
+ std::variant Data;
+public:
+ uint32_t Flags;
+ uint32_t Reserved;
+
+ template
+ using ConstructAs = std::in_place_type_t<__Type>;
+
+ template
+ PolicyValue(ConstructAs<__DataType> Hint);
+
+ template<>
+ PolicyValue(ConstructAs Hint) :
+ Type(TypeLabel::String),
+ Data(Hint) {}
+
+ template<>
+ PolicyValue(ConstructAs Hint) :
+ Type(TypeLabel::Binary),
+ Data(Hint) {}
+
+ template<>
+ PolicyValue(ConstructAs Hint) :
+ Type(TypeLabel::UInt32),
+ Data(Hint) {}
+
+ TypeLabel GetType() const noexcept {
+ return Type;
+ }
+
+ const std::wstring& GetName() const noexcept {
+ return Name;
+ }
+
+ template
+ __Type& GetData() {
+ return std::get<__Type>(Data);
+ }
+
+ template
+ const __Type& GetData() const {
+ return std::get<__Type>(Data);
+ }
+};
+
+class ProductPolicy {
+ friend class ProductPolicyParser;
+private:
+ std::vector _Policies;
+public:
+ static constexpr size_t InvalidPos = -1;
+
+ size_t NumberOfPolicies() const noexcept;
+
+ //void Add(const PolicyValue& Policy);
+ size_t FindPolicy(const std::wstring& Regex, size_t StartPos = 0);
+
+ //void Remove(size_t PolicyIndex);
+ //void Remove(const std::wstring& PolicyName);
+
+ PolicyValue& operator[](size_t Index);
+ PolicyValue& operator[](const std::wstring& Name);
+
+ const PolicyValue& operator[](size_t Index) const;
+ const PolicyValue& operator[](const std::wstring& Name) const;
+};
+
diff --git a/EnableCustomKernelSigners/ProductPolicyParser.cpp b/EnableCustomKernelSigners/ProductPolicyParser.cpp
new file mode 100644
index 0000000..c717ffa
--- /dev/null
+++ b/EnableCustomKernelSigners/ProductPolicyParser.cpp
@@ -0,0 +1,163 @@
+#include "ProductPolicyParser.hpp"
+
+ProductPolicy ProductPolicyParser::FromBinary(const std::vector& Binary) {
+ ProductPolicy Result;
+ const uint8_t* pb = Binary.data();
+ const uint8_t* EndOfpb = Binary.data() + Binary.size();
+
+ if (pb + sizeof(PPBinaryHeader) < EndOfpb) {
+ auto HeaderPtr = AsPtrOf(pb);
+
+ if (HeaderPtr->TotalSize != Binary.size())
+ throw std::invalid_argument("HeaderPtr->TotalSize is incorrect.");
+
+ if (HeaderPtr->EndMarkerSize != sizeof(uint32_t))
+ throw std::invalid_argument("HeaderPtr->EndMarkerSize is incorrect.");
+
+ if (HeaderPtr->DataSize + HeaderPtr->EndMarkerSize + sizeof(PPBinaryHeader) != HeaderPtr->TotalSize)
+ throw std::invalid_argument("HeaderPtr->DataSize is incorrect.");
+
+ if (HeaderPtr->Revision != 1)
+ throw std::invalid_argument("HeaderPtr->Revision is incorrect.");
+
+ pb += sizeof(PPBinaryHeader);
+ EndOfpb -= sizeof(uint32_t);
+ } else {
+ throw std::invalid_argument("Invalid binary data.");
+ }
+
+ if (*AsPtrOf(EndOfpb) != EndMarker)
+ throw std::invalid_argument("EndMarker is incorrect.");
+
+ while (pb < EndOfpb) {
+ auto pVal = AsPtrOf(pb);
+ auto pValName = AsPtrOf(pb + sizeof(PPBinaryValue));
+ auto pValData = pb + sizeof(PPBinaryValue) + pVal->NameSize;
+
+ if (pb + pVal->TotalSize > EndOfpb)
+ throw std::invalid_argument("pBinaryVal->TotalSize is incorrect.");
+
+ switch (pVal->DataType) {
+ case REG_SZ: {
+ PolicyValue PolicyVal(std::in_place_type_t{});
+
+ PolicyVal.Name.assign(pValName, pVal->NameSize / 2);
+ PolicyVal.GetData().assign(AsPtrOf(pValData), pVal->DataSize / sizeof(wchar_t));
+ PolicyVal.Flags = pVal->Flags;
+ PolicyVal.Reserved = pVal->Reserved;
+
+ if (Result._Policies.size() == 0 || Result._Policies.back().GetName() <= PolicyVal.GetName()) {
+ Result._Policies.emplace_back(PolicyVal);
+ } else {
+ throw std::invalid_argument("Unsorted policy binary data.");
+ }
+
+ break;
+ }
+ case REG_BINARY: {
+ PolicyValue PolicyVal(std::in_place_type_t{});
+
+ PolicyVal.Name.assign(pValName, pVal->NameSize / 2);
+ PolicyVal.GetData().assign(AsPtrOf(pValData), AsPtrOf(pValData) + pVal->DataSize);
+ PolicyVal.Flags = pVal->Flags;
+ PolicyVal.Reserved = pVal->Reserved;
+
+ if (Result._Policies.size() == 0 || Result._Policies.back().GetName() <= PolicyVal.GetName()) {
+ Result._Policies.emplace_back(PolicyVal);
+ } else {
+ throw std::invalid_argument("Unsorted policy binary data.");
+ }
+
+ break;
+ }
+ case REG_DWORD: {
+ PolicyValue PolicyVal(std::in_place_type_t{});
+
+ PolicyVal.Name.assign(pValName, pVal->NameSize / 2);
+ PolicyVal.GetData() = *AsPtrOf(pValData);
+ PolicyVal.Flags = pVal->Flags;
+ PolicyVal.Reserved = pVal->Reserved;
+
+ if (Result._Policies.size() == 0 || Result._Policies.back().GetName() <= PolicyVal.GetName()) {
+ Result._Policies.emplace_back(PolicyVal);
+ } else {
+ throw std::invalid_argument("Unsorted binary policy data.");
+ }
+
+ break;
+ }
+ default:
+ throw std::invalid_argument("Unexpected value type.");
+ }
+
+ pb += pVal->TotalSize;
+ }
+
+ return Result;
+}
+
+std::vector ProductPolicyParser::ToBinary(const ProductPolicy& PolicyObject) {
+ std::vector Result;
+ PPBinaryHeader Header;
+
+ for (const PolicyValue& Policy : PolicyObject._Policies) {
+ PPBinaryValue BinaryValue;
+ size_t PaddingSize;
+
+ BinaryValue.NameSize =
+ static_cast(Policy.GetName().length() * sizeof(wchar_t));
+ BinaryValue.DataType =
+ static_cast(Policy.GetType());
+ switch (Policy.GetType()) {
+ case PolicyValue::TypeLabel::String:
+ BinaryValue.DataSize =
+ static_cast(Policy.GetData().length() * sizeof(wchar_t));
+ break;
+ case PolicyValue::TypeLabel::Binary:
+ BinaryValue.DataSize =
+ static_cast(Policy.GetData().size());
+ break;
+ case PolicyValue::TypeLabel::UInt32:
+ BinaryValue.DataSize =
+ sizeof(Policy.GetData());
+ break;
+ default:
+ throw std::invalid_argument("Unexpected value type.");
+ }
+ BinaryValue.Flags = Policy.Flags;
+ BinaryValue.Reserved = Policy.Reserved;
+ BinaryValue.TotalSize = (sizeof(PPBinaryValue) + BinaryValue.NameSize + BinaryValue.DataSize + 2 + 3) / 4 * 4;
+ PaddingSize = BinaryValue.TotalSize - (sizeof(PPBinaryValue) + BinaryValue.NameSize + BinaryValue.DataSize);
+
+ WriteIn<'END'>(Result, &BinaryValue, sizeof(PPBinaryValue));
+ WriteIn<'END'>(Result, Policy.GetName().data(), BinaryValue.NameSize);
+ switch (Policy.GetType()) {
+ case PolicyValue::TypeLabel::String:
+ WriteIn<'END'>(Result, Policy.GetData().data(), BinaryValue.DataSize);
+ break;
+ case PolicyValue::TypeLabel::Binary:
+ WriteIn<'END'>(Result, Policy.GetData().data(), BinaryValue.DataSize);
+ break;
+ case PolicyValue::TypeLabel::UInt32:
+ WriteIn<'END'>(Result, &Policy.GetData(), BinaryValue.DataSize);
+ break;
+ default:
+ throw std::invalid_argument("Unexpected value type.");
+ }
+
+ if (PaddingSize)
+ WriteNullBytesIn<'END'>(Result, PaddingSize);
+ }
+
+ Header.DataSize = static_cast(Result.size());
+ Header.EndMarkerSize = sizeof(uint32_t);
+ Header.Reserved = 0;
+ Header.Revision = 1;
+ Header.TotalSize = sizeof(PPBinaryHeader) + Header.DataSize + Header.EndMarkerSize;
+
+ WriteIn<'BEGN'>(Result, &Header, sizeof(PPBinaryHeader));
+ WriteIn<'END'>(Result, &EndMarker, sizeof(uint32_t));
+
+ return Result;
+}
+
diff --git a/EnableCustomKernelSigners/ProductPolicyParser.hpp b/EnableCustomKernelSigners/ProductPolicyParser.hpp
new file mode 100644
index 0000000..070affb
--- /dev/null
+++ b/EnableCustomKernelSigners/ProductPolicyParser.hpp
@@ -0,0 +1,71 @@
+#pragma once
+#include "ProductPolicy.hpp"
+
+//
+// Reference:
+// 1. https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/slmem/productpolicy.htm
+//
+
+class ProductPolicyParser {
+ friend class ProductPolicy;
+private:
+ static inline const uint32_t EndMarker = 0x45;
+
+ ProductPolicyParser() = delete;
+ ProductPolicyParser(const ProductPolicyParser&) = delete;
+ ProductPolicyParser(ProductPolicyParser&&) = delete;
+ ProductPolicyParser& operator=(const ProductPolicyParser&) = delete;
+ ProductPolicyParser& operator=(ProductPolicyParser&&) = delete;
+
+ template
+ static const __Type* AsPtrOf(const void* p) {
+ return reinterpret_cast(p);
+ }
+
+ template
+ static void WriteIn(std::vector& buf, const void* p, size_t s) {
+ if constexpr (__Pos == 'BEGN') {
+ auto pb = reinterpret_cast(p);
+ buf.insert(buf.begin(), pb, pb + s);
+ } else if constexpr (__Pos == 'END') {
+ auto pb = reinterpret_cast(p);
+ buf.insert(buf.end(), pb, pb + s);
+ } else {
+ static_assert(__Pos == 'BEGN' || __Pos == 'END');
+ }
+ }
+
+ template
+ static void WriteNullBytesIn(std::vector& buf, size_t s) {
+ if constexpr (__Pos == 'BEGN') {
+ buf.insert(buf.begin(), s, 0);
+ } else if constexpr (__Pos == 'END') {
+ buf.insert(buf.end(), s, 0);
+ } else {
+ static_assert(__Pos == 'BEGN' || __Pos == 'END');
+ }
+ }
+
+ struct PPBinaryHeader {
+ uint32_t TotalSize;
+ uint32_t DataSize;
+ uint32_t EndMarkerSize;
+ uint32_t Reserved;
+ uint32_t Revision;
+ };
+
+ struct PPBinaryValue {
+ uint16_t TotalSize;
+ uint16_t NameSize;
+ uint16_t DataType;
+ uint16_t DataSize;
+ uint32_t Flags;
+ uint32_t Reserved;
+ };
+
+ static_assert(sizeof(PPBinaryHeader) == 20);
+ static_assert(sizeof(PPBinaryValue) == 16);
+public:
+ static ProductPolicy FromBinary(const std::vector& Binary);
+ static std::vector ToBinary(const ProductPolicy& PolicyObject);
+};
diff --git a/EnableCustomKernelSigners/_tmain.cpp b/EnableCustomKernelSigners/_tmain.cpp
new file mode 100644
index 0000000..81f447f
--- /dev/null
+++ b/EnableCustomKernelSigners/_tmain.cpp
@@ -0,0 +1,298 @@
+#include
+#include
+#include
+#include
+#include "OwnedResource.hpp"
+#include "ProductPolicy.hpp"
+#include "ProductPolicyParser.hpp"
+
+#pragma comment(lib, "ntdll.lib")
+
+struct GenericHandleTraits {
+ using HandleType = HANDLE;
+ static inline const HandleType InvalidValue = NULL;
+ static constexpr auto& Releasor = CloseHandle;
+};
+
+struct RegistryKeyTraits {
+ using HandleType = HKEY;
+ static inline const HandleType InvalidValue = NULL;
+ static constexpr auto& Releasor = RegCloseKey;
+};
+
+DWORD EnableCksCheckPrivilege(_In_ HANDLE hToken, _In_ PCTSTR lpszPrivilegeName, _Out_ PBOOL lpEnable) {
+ PRIVILEGE_SET ps = { 1, PRIVILEGE_SET_ALL_NECESSARY };
+
+ if (!LookupPrivilegeValue(NULL, lpszPrivilegeName, &ps.Privilege[0].Luid))
+ return GetLastError();
+
+ if (!PrivilegeCheck(hToken, &ps, lpEnable))
+ return GetLastError();
+
+ return ERROR_SUCCESS;
+}
+
+DWORD EnableCksSetPrivilege(_In_ HANDLE hToken, _In_ PCTSTR lpszPrivilegeName, _In_ BOOL bEnable) {
+ TOKEN_PRIVILEGES tp = { 1 };
+
+ if (!LookupPrivilegeValue(NULL, lpszPrivilegeName, &tp.Privileges[0].Luid))
+ return GetLastError();
+
+ tp.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
+
+ if (!AdjustTokenPrivileges(hToken, FALSE, &tp, NULL, 0, NULL))
+ return GetLastError();
+
+ return ERROR_SUCCESS;
+}
+
+int EnableCksNonSetupMode() {
+ LSTATUS Status;
+ PROCESS_BASIC_INFORMATION ProcessBasicInfo = {};
+ OwnedResource hKey;
+ DWORD SetupType;
+ DWORD CmdLineSize;
+ OwnedResource> lpszCmdLine;
+
+ //
+ // This call never fails
+ //
+ NtQueryInformationProcess(GetCurrentProcess(),
+ ProcessBasicInformation,
+ &ProcessBasicInfo,
+ sizeof(PROCESS_BASIC_INFORMATION),
+ NULL);
+
+ CmdLineSize = _sctprintf(TEXT("\"%ls\" \"--setupmode\""),
+ ProcessBasicInfo.PebBaseAddress->ProcessParameters->ImagePathName.Buffer) + 1;
+
+ lpszCmdLine.TakeOver(new TCHAR[CmdLineSize]());
+
+ _stprintf_s(lpszCmdLine, CmdLineSize, TEXT("\"%ls\" \"--setupmode\""),
+ ProcessBasicInfo.PebBaseAddress->ProcessParameters->ImagePathName.Buffer);
+
+ Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\Setup"), NULL, KEY_WRITE, hKey.GetAddress());
+ if (Status != ERROR_SUCCESS) {
+ _tprintf_s(TEXT("[-] Failed to open \"HKLM\\SYSTEM\\Setup\". CODE: 0x%.8X\n"), Status);
+ return -1;
+ } else {
+ _tprintf_s(TEXT("[+] Succeeded to open \"HKLM\\SYSTEM\\Setup\".\n"));
+ }
+
+ Status = RegSetValueEx(hKey, TEXT("CmdLine"), NULL, REG_SZ, reinterpret_cast(lpszCmdLine.Get()), CmdLineSize * sizeof(TCHAR));
+ if (Status != ERROR_SUCCESS) {
+ _tprintf_s(TEXT("[-] Failed to set \"CmdLine\" value. CODE: 0x%.8X\n"), Status);
+ return -1;
+ } else {
+ _tprintf_s(TEXT("[+] Succeeded to set \"CmdLine\" value.\n"));
+ }
+
+ SetupType = 1;
+ Status = RegSetValueEx(hKey, TEXT("SetupType"), NULL, REG_DWORD, reinterpret_cast(&SetupType), sizeof(DWORD));
+ if (Status != ERROR_SUCCESS) {
+ _tprintf_s(TEXT("[-] Failed to set \"SetupType\" value. CODE: 0x%.8X\n"), Status);
+ return -1;
+ } else {
+ _tprintf_s(TEXT("[+] Succeeded to set \"SetupType\" value.\n"));
+ }
+
+ return 0;
+}
+
+int EnableCksSetupMode() {
+ LSTATUS Status;
+ OwnedResource hKey;
+ DWORD ValueType;
+ DWORD ValueSize;
+ ProductPolicy Policy;
+
+ Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
+ TEXT("SYSTEM\\CurrentControlSet\\Control\\ProductOptions"),
+ NULL,
+ KEY_READ | KEY_WRITE,
+ hKey.GetAddress());
+ if (Status != ERROR_SUCCESS) {
+ _tprintf_s(TEXT("[-] Failed to open \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\ProductOptions\". CODE: 0x%.8X\n"), Status);
+ return -1;
+ } else {
+ _tprintf_s(TEXT("[+] Succeeded to open \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\ProductOptions\".\n"));
+ }
+
+ Status = RegQueryValueEx(hKey, TEXT("ProductPolicy"), NULL, &ValueType, NULL, &ValueSize);
+ if (Status != ERROR_SUCCESS) {
+ _tprintf_s(TEXT("[-] Failed to get size of \"ProductPolicy\" value. CODE: 0x%.8X\n"), Status);
+ return -1;
+ } else {
+ _tprintf_s(TEXT("[+] Succeeded to get size of \"ProductPolicy\" value. SIZE: %d(0x%.8X)\n"), ValueSize, ValueSize);
+ }
+
+ if (ValueType != REG_BINARY) {
+ _tprintf_s(TEXT("[-] The type of \"ProductPolicy\" value mismatches. Abort!\n"));
+ return -1;
+ }
+
+ std::vector Value(ValueSize);
+
+ Status = RegQueryValueEx(hKey, TEXT("ProductPolicy"), NULL, &ValueType, Value.data(), &ValueSize);
+ if (Status != ERROR_SUCCESS) {
+ _tprintf_s(TEXT("[-] Failed to get \"ProductPolicy\" value data. CODE: 0x%.8x\n"), Status);
+ return -1;
+ } else {
+ _tprintf_s(TEXT("[+] Succeeded to get \"ProductPolicy\" value data.\n"));
+ }
+
+ try {
+ Policy = ProductPolicyParser::FromBinary(Value);
+ } catch (std::exception& ex) {
+ _tprintf_s(TEXT("[-] Failed to parse \"ProductPolicy\" value.\n"
+ " REASON: %hs\n"),
+ ex.what());
+ return -1;
+ }
+
+ Policy[L"CodeIntegrity-AllowConfigurablePolicy"].GetData() = 1;
+ _tprintf_s(TEXT("[*] Enable CodeIntegrity-AllowConfigurablePolicy\n"));
+
+ Policy[L"CodeIntegrity-AllowConfigurablePolicy-CustomKernelSigners"].GetData() = 1;
+ _tprintf_s(TEXT("[*] Enable CodeIntegrity-AllowConfigurablePolicy-CustomKernelSigners\n"));
+
+ try {
+ Value = ProductPolicyParser::ToBinary(Policy);
+ } catch (std::exception& ex) {
+ _tprintf_s(TEXT("[-] Failed to parse Policy to binary.\n"
+ " REASON: %hs\n"),
+ ex.what());
+ return -1;
+ }
+
+ Status = RegSetValueEx(hKey, TEXT("ProductPolicy"), NULL, REG_BINARY, Value.data(), static_cast(Value.size()));
+ if (Status != ERROR_SUCCESS) {
+ _tprintf_s(TEXT("[-] Failed to set \"ProductPolicy\" value, CODE: 0x%.8x\n"), Status);
+ return -1;
+ } else {
+ _tprintf_s(TEXT("[+] Succeeded to set \"ProductPolicy\" value.\n"));
+ }
+
+ _tprintf_s(TEXT("[*] Checking......\n"));
+ SleepEx(1000, FALSE);
+
+ Status = RegQueryValueEx(hKey, TEXT("ProductPolicy"), NULL, &ValueType, NULL, &ValueSize);
+ if (Status != ERROR_SUCCESS) {
+ _tprintf_s(TEXT("[-] Failed to get size of \"ProductPolicy\" value. CODE: 0x%.8X\n"), Status);
+ return -1;
+ }
+
+ std::vector Value2(ValueSize);
+
+ Status = RegQueryValueEx(hKey, TEXT("ProductPolicy"), NULL, &ValueType, Value2.data(), &ValueSize);
+ if (Status != ERROR_SUCCESS) {
+ _tprintf_s(TEXT("[-] Failed to get \"ProductPolicy\" value data. CODE: 0x%.8x\n"), Status);
+ return -1;
+ }
+
+ if (Value.size() == Value2.size() && memcmp(Value.data(), Value2.data(), Value2.size()) == 0) {
+ _tprintf_s(TEXT("[+] Checking...... Pass!\n"));
+ } else {
+ _tprintf_s(TEXT("[-] Checking...... Fail! Are you sure you are in Setup Mode?\n"));
+ return -1;
+ }
+
+ hKey.Release();
+
+ _tprintf_s(TEXT("[*] Clearing \"CmdLine\" in \"HKLM\\SYSTEM\\Setup\".\n"));
+
+ Status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SYSTEM\\Setup"), NULL, KEY_WRITE, hKey.GetAddress());
+ if (Status != ERROR_SUCCESS) {
+ _tprintf_s(TEXT("[-] Failed to open \"HKLM\\SYSTEM\\Setup\". CODE: 0x%.8X\n"), Status);
+ return -1;
+ }
+
+ Status = RegSetValueEx(hKey, TEXT("CmdLine"), NULL, REG_SZ, reinterpret_cast(TEXT("")), sizeof(TEXT("")));
+ if (Status != ERROR_SUCCESS) {
+ _tprintf_s(TEXT("[-] Failed to set \"CmdLine\" value. CODE: 0x%.8X\n"), Status);
+ return -1;
+ }
+
+ return 0;
+}
+
+int EnableCksFinal(int Status, bool SetupMode) {
+ if (Status != 0) {
+ _tprintf_s(TEXT("\n"
+ "Press Enter to contiue......"));
+ _gettchar();
+ } else {
+ if (SetupMode == false) {
+ bool bReboot;
+
+ _tprintf_s(TEXT("\n"));
+
+ while (true) {
+ _tprintf_s(TEXT("Reboot is required. Are you ready to reboot? [y/N] "));
+
+ auto c = _gettchar();
+ while (c != '\n' && _gettchar() != '\n') {}
+
+ if (c == 'y' || c == 'Y') {
+ bReboot = true;
+ break;
+ }
+
+ if (c == '\n' || c == 'n' || c == 'N' || c == _TEOF) {
+ bReboot = false;
+ break;
+ }
+
+ _tprintf_s(TEXT("Invalid char.\n"));
+ }
+
+ if (bReboot) {
+ _tprintf_s(TEXT("Rebooting......\n"));
+ if (!ExitWindowsEx(EWX_REBOOT, 0)) {
+ _tprintf_s(TEXT("Failed to reboot. CODE: 0x%.8X\n"), GetLastError());
+ _tprintf_s(TEXT("Please reboot by yourself.\n"));
+ Status = -1;
+ } else {
+ SleepEx(INFINITE, FALSE);
+ }
+ } else {
+ _tprintf_s(TEXT("Reboot will not take place. Please reboot by yourself.\n"));
+ }
+ } else { // when in setup mode
+ _tprintf_s(TEXT("Rebooting......\n"));
+ InitiateSystemShutdownEx(NULL, NULL, 0, TRUE, TRUE, 0);
+ SleepEx(INFINITE, FALSE);
+ }
+ }
+
+ return Status;
+}
+
+int _tmain(int argc, PTSTR argv[]) {
+ LSTATUS Status;
+ OwnedResource hToken;
+ BOOL bShutdownPrivilegeEnable;
+
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, hToken.GetAddress())) {
+ _tprintf_s(TEXT("[-] Failed to open current process's token. CODE: 0x%.8X\n"), GetLastError());
+ return -1;
+ }
+
+ Status = EnableCksCheckPrivilege(hToken, SE_SHUTDOWN_NAME, &bShutdownPrivilegeEnable);
+ if (Status != ERROR_SUCCESS) {
+ _tprintf_s(TEXT("[-] Failed to check " SE_SHUTDOWN_NAME ". CODE: 0x%.8X\n"), Status);
+ return -1;
+ }
+
+ if (bShutdownPrivilegeEnable == FALSE && (Status = EnableCksSetPrivilege(hToken, SE_SHUTDOWN_NAME, TRUE)) != ERROR_SUCCESS) {
+ _tprintf_s(TEXT("[-] Failed to enable " SE_SHUTDOWN_NAME " . CODE: 0x%.8X\n"), Status);
+ return -1;
+ }
+
+ if (argc == 2 && _tcsicmp(argv[1], TEXT("--setupmode")) == 0) {
+ return EnableCksFinal(EnableCksSetupMode(), true);
+ } else {
+ return EnableCksFinal(EnableCksNonSetupMode(), false);
+ }
+}
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..129ed43
--- /dev/null
+++ b/README.md
@@ -0,0 +1,177 @@
+# Windows10 - Custom Kernel Signers
+
+[中文版README](README.zh-CN.md)
+
+## 1. What is Custom Kernel Signers?
+
+We know that Windows10 has strict requirements for kernel mode driver. One of the requirements is that drivers must be signed by a EV certificate that Microsoft trusts. What's more start from 1607, new drivers must be submitted to Windows Hardware Portal to get signed by Microsoft. For a driver signed by a self-signed certificate, without enabling TestSigning mode, Windows10 still refuses to load it even the self-signed certificate was installed into Windows Certificate Store(`certlm.msc` or `certmgr.msc`). That means Windows10 has a independent certificate store for kernel mode driver.
+
+__Custom Kernel Signers(CKS)__ is a product policy supported by Windows10(may be from 1703). The full product policy name is `CodeIntegrity-AllowConfigurablePolicy-CustomKernelSigners`. It allows users to decide what certificates is trusted or denied in kernel. By the way, this policy may require another policy, `CodeIntegrity-AllowConfigurablePolicy`, enable.
+
+Generally, __CKS__ is disabled by default on any edtions of Windows10 except __Windows10 China Government Edition__.
+
+If a Windows10 PC meets the following conditions:
+
+1. The product policy `CodeIntegrity-AllowConfigurablePolicy-CustomKernelSigners` is enabled.
+ (May be `CodeIntegrity-AllowConfigurablePolicy` is also required.)
+
+2. SecureBoot is enabled.
+
+one can add a certificate to kernel certificate store if he owns the PC's UEFI Platform Key so that he can lanuch any drivers signed by the certificate on that PC.
+
+If you are interested in looking for other product policies, you can see [this](https://www.geoffchappell.com/notes/windows/license/install.htm).
+
+## 2. How to enable this feature?
+
+### 2.1 Prerequisites
+
+1. You must have administrator privilege.
+
+2. You need a temporary environment whose OS is Windows10 Enterprise or Education.
+
+ Why? Because you need it to execute `ConvertFrom-CIPolicy` in Powershell which cannot be done in other editions of Windows10.
+
+3. You are able to set UEFI Platform Key.
+
+### 2.2 Create certificates and set Platform Key(PK)
+
+Please follow [this](asset/build-your-own-pki.md) to create certificates. After that you will get following files:
+
+```
+// self-signed root CA certificate
+localhost-root-ca.der
+localhost-root-ca.pfx
+
+// kernel mode certificate issued by self-signed root CA
+localhost-km.der
+localhost-km.pfx
+
+// UEFI Platform Key certificate issued by self-signed root CA
+localhost-pk.der
+localhost-pk.pfx
+```
+
+As for how to set PK in UEFI firmware, please do it yourself because different UEFI firmware has different methods. Here, I only tell you how to do it in VMware.
+
+#### 2.2.1 Set PK in VMware
+
+If your VMware virtual machine's name is `TestVM` and your vm has SecureBoot, there would be two files under your vm's folder: `TestVM.nvram` and `TestVM.vmx`. You can set PK by the following:
+
+1. Close your vm.
+
+2. Delete `TestVM.nvram`. This would reset your vm's UEFI settings next time your vm starts.
+
+3. Open `TestVM.vmx` by a text editor and append the following two lines:
+
+ ```
+ uefi.allowAuthBypass = "TRUE"
+ uefi.secureBoot.PKDefault.file0 = "localhost-pk.cer"
+ ```
+
+ The first line allows you manage SecureBoot keys in UEFI firmware.
+
+ The second line will make `localhost-pk.cer` in vm's folder as default UEFI PK. If `localhost-pk.cer` is not in vm's folder, please specify a full path.
+
+Then start `TestVM` and your PK has been set.
+
+### 2.3 Build kernel code-sign certificate rules
+
+Run Powershell as administrator in Windows10 Enterprise/Education edition.
+
+1. Use `New-CIPolicy` to create new CI(Code Integerity) policy. Please make sure that the OS is not affected with any malware.
+
+ ```powershell
+ New-CIPolicy -FilePath SiPolicy.xml -Level RootCertificate -ScanPath C:\windows\System32\
+ ```
+
+ It will scan the entire `System32` folder and take some time. If you do not want to scan, you can use [SiPolicy.xml](asset/SiPolicy.xml) I prepared.
+
+2. Use `Add-SignerRule` to add our own kernel code-sign certificate to `SiPolicy.xml`.
+
+ ```powershell
+ Add-SignerRule -FilePath .\SiPolicy.xml -CertificatePath .\localhost-km.cer -Kernel
+ ```
+
+3. Use `ConvertFrom-CIPolicy` to serialize `SiPolicy.xml` and get binary file `SiPolicy.bin`
+
+ ```powershell
+ ConvertFrom-CIPolicy -XmlFilePath .\SiPolicy.xml -BinaryFilePath .\SiPolicy.bin
+ ```
+
+Now our policy rules has been built. The newly-generated file can be applied to any editions of Windows10 once it is signed by PK certificate. From now on, we don't need Windows10 Enterprise/Education edition.
+
+### 2.4 Sign policy rules and apply policy rules
+
+1. For `SiPolicy.bin`, we should use PK certificate to sign it. If you have Windows SDK, you can sign it by `signtool`.
+
+ ```
+ signtool sign /fd sha256 /p7co 1.3.6.1.4.1.311.79.1 /p7 . /f .\localhost-pk.pfx /p SiPolicy.bin
+ ```
+
+ __Please fill `` with password of your `localhost-pk.pfx`.__
+
+ Then you will get `SiPolicy.bin.p7` at current directory.
+
+2. Rename `SiPolicy.bin.p7` to `SiPolicy.p7b` and copy `SiPolicy.p7b` to `EFI\Microsoft\Boot\`
+
+ ```powershell
+ # run powershell as administrator
+ mv .\SiPolicy.bin.p7 .\SiPolicy.p7b
+ mountvol x: /s
+ cp .\SiPolicy.p7b X:\EFI\Microsoft\Boot\
+ ```
+
+### 2.5 Enable CustomKernelSigners
+
+The variable that controls __CKS__ enable or not is stored in `ProductPolicy` value whose key path is `HKLM\SYSTEM\CurrentControlSet\Control\ProductOptions`.
+
+Although administrators can modify this value, the value will be reset immediately once modified. This is because this value is just a mapping of a varialbe in kernel once kernel is initialized. The only way to modify the variable is to call `ExUpdateLicenseData`. However, this API could only be called in kernel mode or indirectly called by calling `NtQuerySystemInformation` with `SystemPolicyInformation`. Unfortunately, the latter way succeeds only when caller is a protected process.
+
+So we could only modify it when kernel has not finished initialization. Do we have a chance? Yes, Windows Setup Mode can give us a chance.
+
+I've built a program to help us enable __CKS__. The code in under `EnableCustomKernelSigners` folder and the binary executable file `EnableCKS.exe` can be downloaded on [release](https://github.com/DoubleLabyrinth/Windows10-CustomKernelSigners/releases) page. Of course, you can build it with your own.
+
+Double click `EnableCKS.exe` and you can see
+
+```
+[+] Succeeded to open "HKLM\SYSTEM\Setup".
+[+] Succeeded to set "CmdLine" value.
+[+] Succeeded to set "SetupType" value.
+
+Reboot is required. Are you ready to reboot? [y/N]
+```
+
+Type `y` to reboot. Then system will enter Setup Mode. `EnableCKS.exe` will run automaticly and enable the following two policy
+
+```
+CodeIntegrity-AllowConfigurablePolicy
+CodeIntegrity-AllowConfigurablePolicy-CustomKernelSigners
+```
+
+Finally, system will reboot again and go back to normal mode.
+
+### 2.6 Persist CustomKernelSigners
+
+Now you should be able to load drivers signed by `localhost-km.pfx`. But wait for a minute. Within 10 minutes, __CKS__ will be reset to disable by `sppsvc` except when you have Windows10 China Government Edition. Don't worry, it takes effect only next time system starts up.
+
+So we have to load a driver to call `ExUpdateLicenseData` continuously to persist __CKS__. I've built a driver named `ckspdrv.sys` which can be downloaded on [release](https://github.com/DoubleLabyrinth/Windows10-CustomKernelSigners/releases) page. The code is in `CustomKernelSignersPersistent` folder.
+
+`ckspdrv.sys` is not signed. You must sign it with `localhost-km.pfx` so that it can be loaded into kernel.
+
+```
+signtool sign /fd sha256 /ac .\localhost-root-ca.cer /f .\localhost-km.pfx /p /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp ckspdrv.sys
+```
+
+__Please fill `` with password of your `localhost-km.pfx`.__
+
+Then move `ckspdrv.sys` to `c:\windows\system32\drivers` and run `cmd` as administrator:
+
+```
+sc create ckspdrv binpath=%windir%\system32\drivers\ckspdrv.sys type=kernel start=auto error=normal
+sc start ckspdrv
+```
+
+If nothing wrong, `ckspdrv.sys` will be loaded successfully, which also confirms that our policy rules have take effect.
+
+Now you can load any driver signed by `localhost-km.pfx`. Have fun and enjoy~
+
diff --git a/README.zh-CN.md b/README.zh-CN.md
new file mode 100644
index 0000000..a6f238b
--- /dev/null
+++ b/README.zh-CN.md
@@ -0,0 +1,179 @@
+# Windows10 - Custom Kernel Signers
+
+## 1. 什么是Custom Kernel Signers?
+
+我们知道Windows10对内核代码驱动有着严格的要求,其中一条就是驱动必须被微软认可的EV证书签名,并且自1607版本开始,新驱动还需要被提交到Windows Hardware Portal来获得微软的签名。对于自签名证书签署的驱动,在不开启TestSigning的情况下,即使自签名证书被安装到了Windows证书库(`certlm.msc`或`certmgr.msc`)的受信任区域,驱动仍然无法加载。这说明Windows10针对内核模式代码有独立的可信证书库。
+
+__Custom Kernel Signers(CKS)__ 是Windows10(可能从1703开始)支持的一种产品策略。这个产品策略的全名是`CodeIntegrity-AllowConfigurablePolicy-CustomKernelSigners`,它允许用户自定义内核代码证书,从而使得用户可以摆脱“驱动必须由微软签名”的强制性要求。值得注意的是,这个策略可能依赖另外一个产品策略`CodeIntegrity-AllowConfigurablePolicy`。
+
+在Windows10的所有版本中,__CKS__ 默认是关闭的,除了 __Windows10 中国政府特供版__。
+
+如果一个Windows10 PC满足下列条件:
+
+1. 产品策略`CodeIntegrity-AllowConfigurablePolicy-CustomKernelSigners`是开启的。
+ (也许`CodeIntegrity-AllowConfigurablePolicy`也要开启。)
+
+2. SecureBoot也是开启的。
+
+那么任何拥有该PC的UEFI Platform Key的人都可以自定义内核代码证书。这意味着,__在不开启调试模式、不开启TestSigning、不关闭DSE的情况下__,他可以使系统允许自签名驱动的加载。
+
+如果你对Windows的产品策略感兴趣,你可以看[这里](https://www.geoffchappell.com/notes/windows/license/install.htm)。
+
+## 2. 如何开启这个feature?
+
+开启这个feature确实非常麻烦。当然,如果你确实非常想要这个feature那就继续往下看吧。
+
+### 2.1 前提
+
+1. 你必须要有管理员权限。
+
+2. 你需要一个Windows10企业版或教育版的临时系统,这个系统用于执行`ConvertFrom-CIPolicy`命令。
+
+ 为什么?因为这个命令只能在Windows10企业版或教育版下运行。
+
+3. 你可以设置UEFI固件的Platform Key(PK)。
+
+### 2.2 创建证书与设置Platform Key(PK)
+
+请跟随[这里](asset/build-your-own-pki.zh-CN.md)来创建证书,之后你应该能得到如下文件:
+
+```
+// 自签名根CA证书
+localhost-root-ca.der
+localhost-root-ca.pfx
+
+// 自签名根CA颁发的内核代码证书
+localhost-km.der
+localhost-km.pfx
+
+// 自签名根CA颁发的UEFI Platform Key证书
+localhost-pk.der
+localhost-pk.pfx
+```
+
+关于如何设置UEFI固件的PK为自建的PK,不同的主板有不同的方法,请读者自行解决。这里只介绍VMware虚拟机设置UEFI PK的方法。
+
+#### 2.2.1 VMware设置UEFI PK
+
+假设你的虚拟机名为`TestVM`,并且你的虚拟机开启了SecureBoot,那么在你的虚拟机目录下应该有`TestVM.nvram`和`TestVM.vmx`这两个文件。你可以通过如下办法设置`TestVM`的PK:
+
+1. 关闭虚拟机。
+
+2. 删除`TestVM.nvram`。此举会使得虚拟机在下次启动时重置`TestVM`的UEFI设置。
+
+3. 用文本编辑器打开`TestVM.vmx`,在文件后面加上
+
+ ```
+ uefi.allowAuthBypass = "TRUE"
+ uefi.secureBoot.PKDefault.file0 = "localhost-pk.cer"
+ ```
+
+ 第一句允许你在虚拟机UEFI设置中管理SecureBoot keys。
+
+ 第二句指定虚拟机目录下`localhost-pk.cer`文件为UEFI的默认PK。如果该文件不在虚拟机目录下,请将文件名替换为全路径。
+
+完成后,启动`TestVM`即可导入PK。
+
+### 2.3 构建内核代码证书规则
+
+在Windows10企业版或教育版的临时系统中以管理员身份运行Powershell。
+
+1. 使用`New-CIPolicy`建立新的CI(Code Integerity) Policy。注意确保临时系统是干净的,不然新的Policy里可能会掺入恶意软件的自签名CA。
+
+ ```powershell
+ New-CIPolicy -FilePath SiPolicy.xml -Level RootCertificate -ScanPath C:\windows\System32\
+ ```
+
+ 此举会扫描整个System32目录,可能会耗费一段时间。如果你不想扫描,你可以用我准备好的[SiPolicy.xml](asset/SiPolicy.xml)文件。
+
+2. 针对新生成的`SiPolicy.xml`,使用`Add-SignerRule`添加我们自己的内核代码证书`localhost-km.cer`。
+
+ ```powershell
+ Add-SignerRule -FilePath .\SiPolicy.xml -CertificatePath .\localhost-km.cer -Kernel
+ ```
+
+3. 使用`ConvertFrom-CIPolicy`将`SiPolicy.xml`序列化,得到二进制的`SiPolicy.bin`
+
+ ```powershell
+ ConvertFrom-CIPolicy -XmlFilePath .\SiPolicy.xml -BinaryFilePath .\SiPolicy.bin
+ ```
+
+至此构建内核代码CA规则就算完成。规则文件在被Platform Key签名后可以被用于任何版本的Windows10系统。后面的话不必使用Windows10企业版或教育版的系统。
+
+### 2.4 签名规则并导入规则。
+
+1. 对于规则文件`SiPolicy.bin`,我们得用UEFI的Platform Key签名。如果你有Windows SDK,你可以用`signtool`签名。
+
+ ```
+ signtool sign /fd sha256 /p7co 1.3.6.1.4.1.311.79.1 /p7 . /f .\localhost-pk.pfx /p SiPolicy.bin
+ ```
+
+ __注意在`/p`后填写`localhost-pk.pfx`的密码。__
+
+ 之后你会在当前目录得到`SiPolicy.bin.p7`文件。
+
+2. 将`SiPolicy.bin.p7`文件重名为`SiPolicy.p7b`,并放到`\EFI\Microsoft\Boot\`文件夹下。
+
+ ```powershell
+ # 管理员权限开启Powershell
+ mv .\SiPolicy.bin.p7 .\SiPolicy.p7b
+ mountvol x: /s
+ cp .\SiPolicy.p7b X:\EFI\Microsoft\Boot\
+ ```
+
+### 2.5 开启CustomKernelSigners
+
+__CKS__ 的开关保存在`HKLM\SYSTEM\CurrentControlSet\Control\ProductOptions`键的`ProductPolicy`值里。
+
+尽管管理员可以修改这个值,但是这个值在被修改后会立即恢复原状。这是因为在内核初始化完后,这个值只是内核里一个变量的映射,只有通过`ExUpdateLicenseData`这个内核API才能修改。而这个API只能在内核里被调用,或者通过`NtQuerySystemInformation`的`SystemPolicyInformation`功能号间接调用。很遗憾的是后者只有Protected Process才能 __成功__ 调用。
+
+所以我们只能在内核还尚未初始化完的时候修改 __CKS__ 开关。有这个机会吗?有,Windows的Setup Mode可以给我们提供这个机会。
+
+我已经写了一个程序来帮助我们打开 __CKS__,代码在`EnableCustomKernelSigners`文件夹下。二进制程序可以在[release](https://github.com/DoubleLabyrinth/Windows10-CustomKernelSigners/releases)中得到,或者你也可以自己编译。
+
+二进制程序是`EnableCKS.exe`,直接双击打开即可。打开后你应该能看到
+
+```
+[+] Succeeded to open "HKLM\SYSTEM\Setup".
+[+] Succeeded to set "CmdLine" value.
+[+] Succeeded to set "SetupType" value.
+
+Reboot is required. Are you ready to reboot? [y/N]
+```
+
+输入`y`直接重启,然后系统会进入Setup Mode,`EnableCKS.exe`会自动启动并开启
+
+```
+CodeIntegrity-AllowConfigurablePolicy
+CodeIntegrity-AllowConfigurablePolicy-CustomKernelSigners
+```
+
+这两个策略。最后会自动重启并重新进入到正常模式下。
+
+### 2.6 CustomKernelSigners持久化
+
+重新进入正常模式后,你应该就可以加载由`localhost-km.pfx`签署的驱动了。但是别高兴得太早,大约在10分钟之内,__CKS__ 会被`sppsvc`服务重置为关闭,除非你的Windows10是中国政府特供版。但不用担心,关闭还得等重启后才会实际生效。
+
+所以我们得趁这个机会,加载自己编写的驱动,通过不断调用`ExUpdateLicenseData`来持久化 __CKS__。
+
+这个驱动也可以在[release](https://github.com/DoubleLabyrinth/Windows10-CustomKernelSigners/releases)中得到,二进制文件为`ckspdrv.sys`,相应代码在`CustomKernelSignersPersistent`文件夹下。
+
+这个驱动是没有签名的,你必须完成签名后才能加载。
+
+```
+signtool sign /fd sha256 /ac .\localhost-root-ca.cer /f .\localhost-km.pfx /p /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp ckspdrv.sys
+```
+
+__注意在`/p`后填写`localhost-km.pfx`的密码。__
+
+然后将`ckspdrv.sys`放入`c:\windows\system32\drivers`下,管理员启动`cmd`:
+
+```
+sc create ckspdrv binpath=%windir%\system32\drivers\ckspdrv.sys type=kernel start=auto error=normal
+sc start ckspdrv
+```
+
+不出意外的话`ckspdrv.sys`会成功加载,同时也证明我们构建的内核代码证书规则实际生效了。
+
+之后你可以加载其他由`localhost-km.pfx`签署的驱动。
+
diff --git a/Windows10-CustomKernelSigners.sln b/Windows10-CustomKernelSigners.sln
new file mode 100644
index 0000000..1d06040
--- /dev/null
+++ b/Windows10-CustomKernelSigners.sln
@@ -0,0 +1,51 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.28307.539
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CustomKernelSignersPersistent", "CustomKernelSignersPersistent\CustomKernelSignersPersistent.vcxproj", "{912F26A3-E2FA-4503-AF55-1980A05845E1}"
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EnableCustomKernelSigners", "EnableCustomKernelSigners\EnableCustomKernelSigners.vcxproj", "{E999DB5C-1C05-4F3E-A9F9-E668C47B3546}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Debug|Any CPU.ActiveCfg = Debug|Win32
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Debug|x64.ActiveCfg = Debug|x64
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Debug|x64.Build.0 = Debug|x64
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Debug|x64.Deploy.0 = Debug|x64
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Debug|x86.ActiveCfg = Debug|Win32
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Debug|x86.Build.0 = Debug|Win32
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Debug|x86.Deploy.0 = Debug|Win32
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Release|Any CPU.ActiveCfg = Release|Win32
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Release|x64.ActiveCfg = Release|x64
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Release|x64.Build.0 = Release|x64
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Release|x64.Deploy.0 = Release|x64
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Release|x86.ActiveCfg = Release|Win32
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Release|x86.Build.0 = Release|Win32
+ {912F26A3-E2FA-4503-AF55-1980A05845E1}.Release|x86.Deploy.0 = Release|Win32
+ {E999DB5C-1C05-4F3E-A9F9-E668C47B3546}.Debug|Any CPU.ActiveCfg = Debug|Win32
+ {E999DB5C-1C05-4F3E-A9F9-E668C47B3546}.Debug|x64.ActiveCfg = Debug|x64
+ {E999DB5C-1C05-4F3E-A9F9-E668C47B3546}.Debug|x64.Build.0 = Debug|x64
+ {E999DB5C-1C05-4F3E-A9F9-E668C47B3546}.Debug|x86.ActiveCfg = Debug|Win32
+ {E999DB5C-1C05-4F3E-A9F9-E668C47B3546}.Debug|x86.Build.0 = Debug|Win32
+ {E999DB5C-1C05-4F3E-A9F9-E668C47B3546}.Release|Any CPU.ActiveCfg = Release|Win32
+ {E999DB5C-1C05-4F3E-A9F9-E668C47B3546}.Release|x64.ActiveCfg = Release|x64
+ {E999DB5C-1C05-4F3E-A9F9-E668C47B3546}.Release|x64.Build.0 = Release|x64
+ {E999DB5C-1C05-4F3E-A9F9-E668C47B3546}.Release|x86.ActiveCfg = Release|Win32
+ {E999DB5C-1C05-4F3E-A9F9-E668C47B3546}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {9B73E5ED-4BC0-4144-BE32-71A737B1CB17}
+ EndGlobalSection
+EndGlobal
diff --git a/asset/SiPolicy.xml b/asset/SiPolicy.xml
new file mode 100644
index 0000000..1df2a28
--- /dev/null
+++ b/asset/SiPolicy.xml
@@ -0,0 +1,65 @@
+
+
+ 10.0.0.0
+ {A244370E-44C9-4C06-B551-F6016E563076}
+ {2E07F7E4-194C-4D20-B7C9-6F44A6C5A234}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
diff --git a/asset/build-your-own-pki.md b/asset/build-your-own-pki.md
new file mode 100644
index 0000000..df599fd
--- /dev/null
+++ b/asset/build-your-own-pki.md
@@ -0,0 +1,126 @@
+# Create certificates
+
+This section will create certificates whose relationship looks like the following picture. __Please DO NOT CLOSE Powershell while reading this section.__
+
+![](pki-architecture.png)
+
+## 1. Create A Root CA Certificate
+
+A root CA certificate is the root of trust. Once a root CA certificate is trusted, all certificates issued by the root CA certificate will be trusted as well, except those certificates in CA's CRL. But, so far, we don't need to care about CRL.
+
+Run Powershell as administrator:
+
+```powershell
+$cert_params = @{
+ Type = 'Custom'
+ Subject = 'CN=Localhost Root Certification Authority'
+ FriendlyName = 'Localhost Root Certification Authority'
+ TextExtension = '2.5.29.19={text}CA=1'
+ HashAlgorithm = 'sha512'
+ KeyLength = 4096
+ KeyAlgorithm = 'RSA'
+ KeyUsage = 'CertSign','CRLSign'
+ KeyExportPolicy = 'Exportable'
+ NotAfter = (Get-Date).AddYears(100)
+ CertStoreLocation = 'Cert:\LocalMachine\My'
+}
+
+$root_cert = New-SelfSignedCertificate @cert_params
+```
+
+where
+
+1. `TextExtension`
+
+ * `2.5.29.19` is the OID that represents `Basic Constraints`.
+
+ * `CA=1` indicates that new certificate is a CA certificate.
+
+ * Of cource, you can add `&pathlength=x` following `CA=1` where `x` represents the number of intermediate CA certificates that may follow in a valid certification path.
+
+ For example, if you add `&pathlength=2`, it means a valid certification path could only at most as long as
+
+ ```
+ [+] "Localhost Root Certification Authority"
+ |- [+] "Intermediate CA 1"
+ |- [+] "Intermediate CA 2"
+ ```
+
+ If there's `Intermediate CA 3` issued by `Intermediate CA 2`, it will not be trusted. Of cource, `Intermediate CA 2` can still issue non-CA certificates.
+
+ If `pathlength` is not specified, there's no length-limit for a valid certification path.
+
+After the two commands, you can open `certlm.msc` and see newly-generated certificates in `Personal\Certificates` with private key and in `Intermediate Certification Authority\Certificates` without private key.
+
+You can move the latter certificate to `Trusted Root Certification Authority\Certificates` area to trust it.
+
+## 2. Create Kernel Mode Code-Sign Certificate
+
+We use the newly-generated root CA certificate to issue a non-CA certificate that will be used to sign all kernel mode drivers.
+
+```powershell
+$cert_params = @{
+ Type = 'CodeSigningCert'
+ Subject = 'CN=Localhost Kernel Mode Driver Certificate'
+ FriendlyName = 'Localhost Kernel Mode Driver Certificate'
+ TextExtension = '2.5.29.19={text}CA=0'
+ Signer = $intermediate_cert
+ HashAlgorithm = 'sha256'
+ KeyLength = 2048
+ KeyAlgorithm = 'RSA'
+ KeyUsage = 'DigitalSignature'
+ KeyExportPolicy = 'Exportable'
+ NotAfter = (Get-Date).AddYears(10)
+ CertStoreLocation = 'Cert:\LocalMachine\My'
+}
+
+$km_cert = New-SelfSignedCertificate @cert_params
+```
+
+After the two commands, you can open `certlm.msc` and see newly-generated certificate in `Personal\Certificates` with private key.
+
+## 3. Create UEFI Platform Key Certificate
+
+We use the newly-generated root CA certificate to issue a non-CA certificate that will be used as UEFI Platform Key.
+
+```powershell
+$cert_params = @{
+ Type = 'Custom'
+ Subject = 'CN=Localhost UEFI Platform Key Certificate'
+ FriendlyName = 'Localhost UEFI Platform Key Certificate'
+ TextExtension = '2.5.29.19={text}CA=0'
+ Signer = $root_cert
+ HashAlgorithm = 'sha256'
+ KeyLength = 2048
+ KeyAlgorithm = 'RSA'
+ KeyUsage = 'DigitalSignature'
+ KeyExportPolicy = 'Exportable'
+ NotAfter = (Get-Date).AddYears(10)
+ CertStoreLocation = 'Cert:\LocalMachine\My'
+}
+
+$pk_cert = New-SelfSignedCertificate @cert_params
+```
+
+Again, you can open `certlm.msc` and see newly-generated certificate in `Personal\Certificates` with private key.
+
+## 4. Export certificates
+
+Export three certificates we just generated in `Personal\Certificates` of `certlm.msc` to following files
+
+```
+// self-signed root CA certificate
+localhost-root-ca.der
+localhost-root-ca.pfx
+
+// kernel mode certificate issued by self-signed root CA
+localhost-km.der
+localhost-km.pfx
+
+// UEFI Platform Key certificate issued by self-signed root CA
+localhost-pk.der
+localhost-pk.pfx
+```
+
+Note that `*.cer` are DER-encoded certificate files without private key. And `*.pfx` are certificate files with private key.
+
diff --git a/asset/build-your-own-pki.zh-CN.md b/asset/build-your-own-pki.zh-CN.md
new file mode 100644
index 0000000..23ec4fc
--- /dev/null
+++ b/asset/build-your-own-pki.zh-CN.md
@@ -0,0 +1,128 @@
+# 创建证书
+
+本节将会创建如下图的一个证书体系。__跟随教程的过程中请不要关闭Powershell。__
+
+![](pki-architecture.png)
+
+## 1. 建立一个根CA证书
+
+根CA是信任的根源,所有由根CA签发的下级证书都是被根CA承认的。
+
+所以你得保管好生成的根CA证书。
+
+管理员运行Powershell:
+
+```powershell
+$cert_params = @{
+ Type = 'Custom'
+ Subject = 'CN=Localhost Root Certification Authority'
+ FriendlyName = 'Localhost Root Certification Authority'
+ TextExtension = '2.5.29.19={text}CA=1'
+ HashAlgorithm = 'sha512'
+ KeyLength = 4096
+ KeyAlgorithm = 'RSA'
+ KeyUsage = 'CertSign','CRLSign'
+ KeyExportPolicy = 'Exportable'
+ NotAfter = (Get-Date).AddYears(100)
+ CertStoreLocation = 'Cert:\LocalMachine\My'
+}
+
+$root_cert = New-SelfSignedCertificate @cert_params
+```
+
+其中:
+
+1. `TextExtension`
+
+ * `2.5.29.19`是X509证书中`Basic Constraints`域的OID号。
+
+ * 后面跟了`CA=1`表明该证书是一个CA证书
+
+ * 在`CA=1`后面其实还可以加上`&pathlength=x`,这个是用来限制该CA的下级CA证书链长度。
+
+ 例如,如果是`&pathlength=2`,则意味着下级CA证书链最多只能到
+
+ ```
+ [+] "Localhost Root Certification Authority"
+ |- [+] "Intermediate CA 1"
+ |- [+] "Intermediate CA 2"
+ ```
+
+ 再下级的`Intermediate CA 3`是不被该CA证书承认的。当然`Intermediate CA 2`还是可以签发非CA证书的。
+
+ 如果`pathlength`未指定,则认为下级CA证书链可以无限长。
+
+这两条指令执行后,你可以在`certlm.msc`的`个人\证书`和`中间证书颁发机构\证书`中看到新生成的证书,其中前者包含私钥,后者不包含私钥。
+
+你可以通过将后者移动到`受信任的根证书颁发机构\证书`来信任该证书。
+
+## 2. 构建内核代码证书
+
+利用刚刚生成的根CA证书,我们生成一份内核驱动证书。
+
+```powershell
+$cert_params = @{
+ Type = 'CodeSigningCert'
+ Subject = 'CN=Localhost Kernel Mode Driver Certificate'
+ FriendlyName = 'Localhost Kernel Mode Driver Certificate'
+ TextExtension = '2.5.29.19={text}CA=0'
+ Signer = $root_cert
+ HashAlgorithm = 'sha256'
+ KeyLength = 2048
+ KeyAlgorithm = 'RSA'
+ KeyUsage = 'DigitalSignature'
+ KeyExportPolicy = 'Exportable'
+ NotAfter = (Get-Date).AddYears(10)
+ CertStoreLocation = 'Cert:\LocalMachine\My'
+}
+
+$km_cert = New-SelfSignedCertificate @cert_params
+```
+
+这两条命令执行后,你可以在`certlm.msc`的`个人\证书`中看到新生成的证书。这份证书你可以用来签署自己编写的内核驱动。
+
+## 3. 构建UEFI Plaform Key证书
+
+这份证书将会安装在UEFI中,作为Platform Key。通过这份证书,你可以控制UEFI的KEK,进而也可以控制DB、DBX。
+
+```powershell
+$cert_params = @{
+ Type = 'Custom'
+ Subject = 'CN=Localhost UEFI Platform Key Certificate'
+ FriendlyName = 'Localhost UEFI Platform Key Certificate'
+ TextExtension = '2.5.29.19={text}CA=0'
+ Signer = $root_cert
+ HashAlgorithm = 'sha256'
+ KeyLength = 2048
+ KeyAlgorithm = 'RSA'
+ KeyUsage = 'DigitalSignature'
+ KeyExportPolicy = 'Exportable'
+ NotAfter = (Get-Date).AddYears(10)
+ CertStoreLocation = 'Cert:\LocalMachine\My'
+}
+
+$pk_cert = New-SelfSignedCertificate @cert_params
+```
+
+关于如何设置Platform Key,这需要主板UEFI固件的支持,并且每台电脑有不同的方法。一般情况就是去UEFI设置界面、找到和SecureBoot相关的设置、Enroll PK即可。需要注意的是,不是所有的UEFI固件都支持修改Platform Key。例如Surface Book系列的,它的UEFI设置界面就没有Enroll PK相关的项。
+
+## 4. 导出证书
+
+在`certlm.msc`的`个人\证书`中导出生成的三份证书,分别为:
+
+```
+// 自签名根CA证书
+localhost-root-ca.der
+localhost-root-ca.pfx
+
+// 自签名根CA颁发的内核代码证书
+localhost-km.der
+localhost-km.pfx
+
+// 自签名根CA颁发的UEFI Platform Key证书
+localhost-pk.der
+localhost-pk.pfx
+```
+
+注意后缀`.cer`代表DER编码的不含私钥的证书,`.pfx`代表含私钥的证书文件。
+
diff --git a/asset/pki-architecture.png b/asset/pki-architecture.png
new file mode 100644
index 0000000..260bda4
Binary files /dev/null and b/asset/pki-architecture.png differ