Kernel Mitigations

In this section, we shall take a brief look at some of the Windows kernel mitigations that might hinder or completely block our exploits. Additional details and how to bypass/overcome each of these obstacles(if possible) will be discussed later as per the exploitation scenario.

Kernel Patch Protection(KPP)

Also known as Patch Guard(PG), this is one of the more prominent and effective anti-tampering kernel mitigations aimed at stopping rootkits(and shady software including PSPs) from ravaging around the kernel to accomplish their objectives such as AVs hooking System Service Descriptor Table(SSDT) to gain visibility into system calls etc. It was first introduced in Windows XP x64.
Remember that x86 systems do not have KPP enabled not even on the latest versions.
KPP is designed to detect any changes to critical Windows kernel structures/registers and bugcheck(Code: 0x109/CRITICAL_STRUCTURE_CORRUPTION) or crash the system with a much dreaded Blue/Green Screen Of Death(BSOD/GSOD).
However, it is polled and the beauty of KPP lies in its randomness(apart from its obfuscation) of these checks which means it is quite possible that the changes are not caught instantly but after some time has passed by. This way it takes away the reliability from hooking random stuff in the kernel even if it cannot stop it right away.
As a general rule of thumb, keep in mind that PG checks run every 5-10 minutes.
Bypasses do exist against KPP but since it is a moving target, vulnerabilities may be fixed as happened with InfinityHook. Also, note that KPP may be disabled by using a bootkit or hooks can simply be hidden using EPT/NPT ghost hooks by loading a rogue hypervisor.
Astute readers may observe that there's nothing stopping us from tampering whatever we want in the kernel for an extremely short duration of time to accomplish our goals and then reverting the changes immediately before KPP even gets a chance to trigger on it and none will be the wiser.
Well, this sounds nice in theory, however, since we have no way of knowing when the value was last checked, doing this has a very real(albeit small) chance of BSODing a target system. In our job, no matter how small the chances are, this is not recommended at all and should only be considered as a last resort option.
Remember that KPP is disabled when remote kernel debugging(not lkd) is enabled.


Driver Signature Enforcement(DSE)

Also known as Kernel Mode Code Signing(KMCS), it is another prominent and quite effective mitigations to ensure that all kernel drivers are properly signed with a valid digital signature. It was first introduced in Windows Vista x64.
Remember that x86 systems do not have DSE enabled not even on the latest versions.
Starting from Windows 10 RS1/1607, DSE allows third-party drivers to be loaded iff:
1. Driver is Attestation Signed(signed using Extended Validation(EV) code signing certificate + countersigned by Microsoft Windows Hardware Compatibility Publisher)
2. Driver is Windows Hardware Quality Labs/WHQL-testing Signed
However, there are three notable exceptions to this rule under which it will still allow a driver to load:
1. SecureBoot(SB) is turned off from UEFI settings OR
2. The system was upgraded from an earlier version or a build earlier than Windows 10 RS1/1607 OR
3. Driver was Cross Signed using a valid certificate issued prior to 29th October, 2015
There's a driver blacklist in the form of a Shim Database File(SDB) for use by Kernel Shim Engine(KSE)that blocks some known bad vendor drivers from loading. It is located at: C:\Windows\AppPatch\drvmain.sdb and can be examined by SDBExplorer.
Note that WHQL-testing is mostly automatic and it is not unusual for bad drivers to slip past testing and get certified.
Some bypasses include temporary patching of a global variable that controls DSE - ci!g_CiOptions(KPP-protected) by exploiting a vulnerable signed driver, using kernel shellcode/PIC as the payload instead of a driver or acquiring valid certificates from third-parties.
DSE may be turned off forcefully by enabling Test Signing mode from an admin prompt and rebooting:
bcdedit /set testsigning on


Kernel Address Space Layout Randomization(KASLR)

KASLR is designed to prevent predicting addresses of some desired kernel memory by randomizing the base address of the kernel image, kernel modules and device drivers on a per-boot/load basis. It was first introduced in Windows Vista.
Over the years, it has undergone some significant changes like High Entropy ASLR(22 bits of entropy) and Force ASLR(randomize even for non-ASLR compiled modules) along with the information disclosure leaks that have been fixed by Microsoft make this a truly effective mitigation.
Remember that KASLR is meant to protect against REMOTE kernel exploits and not LOCAL exploits. This is because it is trivial to retrieve the base address of a kernel module from a Medium-IL process as compared to a Low-IL/sandboxed process.
Some of the interesting changes made to KASLR over the years are:
1. The offset of Self-Reference PML4E has been randomized from the static value of 0x1ed. This consequently leads to VA randomization of the base of paging structures(PxE) and its entries
2. Kernel address leak from GDI objects have been fixed
3. Kernel address leak from DesktopHeap(TEB) and GdiSharedHandleTable(PEB) has been fixed
4. Kernel address leak from SGDT/SIDT instruction is fixed when VBS is enabled
5. Low-IL processes cannot use NtQuerySystemInformation() or EnumDeviceDrivers() to leak kernel addresses
6. HAL Heap VA is now randomized
There exists bypasses against KASLR but they usually rely on additional vulnerabilities in the form of information leaks.


Data Execution Prevention(DEP)

DEP is designed to prevent code execution in non-executable portions of memory/data segment of memory. In other words, DEP ensures that no memory page is both writable and executable(W^X) simultaneously. It was first introduced(fully) in Windows 8.
Remember that DEP is enforced both via software and hardware support.
Since Windows 8, drivers now have a pool type known as NonPagedPoolNx which essentially marks it as non-executable non-paged memory pool.
DEP is enforced on a per-page basis via a Page Table Entry(PTE) control bit known as NoExecute(NX) bit.
Some bypasses include using Return Oriented Programming(ROP) payload in the kernel/re-using existing kernel code to execute our code, flipping the NX bit in the PTE or redirecting execution flow to a User-Mode(UM) allocated page.
DEP may be turned off forcefully by running the following command from an admin prompt and rebooting:
bcdedit.exe /set nx AlwaysOff


Supervisor Mode Execution Prevention(SMEP)

Put simply, SMEP prevents execution of Kernel-Mode code residing in User-Mode page. In other words, if code residing in CPL-3 is executed in context of CPL-0, SMEP would bugcheck(Code: 0xfc/ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY or crash the system with a BSOD/GSOD. It was first introduced(fully) in Windows 8.
Remember that SMEP is a hardware-enforced mitigation.
This is a particularly effective mitigation for LOCAL kernel exploits where we somehow gain control over the RIP register in Ring 0, allocate memory in User-Mode and jump to get kernel shellcode execution in Ring 3 which is not possible anymore with SMEP enabled.
SMEP is controlled by the 20th bit of the CR4 register and enforced at page level by the Owner control bit/User/Supervisor(U/S) bit in the Page Table Entry(PTE).
Some bypasses include using Return Oriented Programming(ROP) chain in the kernel to flip the SMEP bit(KPP-protected) or using an arbitrary read/write primitive to flip the U/S bit in PTE.


Kernel Virtual Address Shadow(KVAS)

Also known as Software SMEP, this software mitigation is designed to protect against leaking kernel memory via Speculative Execution Variant 3 or Meltdown(CVE-2017-5754). It was first introduced in Windows 10 RS4/1803.
Remember that KVAS is only applicable for old(er) CPUs that are vulnerable. With newer processors(or processors that were not affected), KVAS will not be enabled at boot time.
This mitigation is the Windows equivalent of Linux's Kernel Page Table Isolation(KPTI) based off of KAISER and involves the separation of Kernel-Mode page tables from User-Mode page tables for each process. This means that most of the System Space is not mapped in the User-Mode page tables while the Kernel-Mode page tables both of the mappings. The Kernel-Mode page table base is stored in nt!_KPRCB.KernelDirectoryTableBase while the User-Mode page table base is stored in nt!_KPROCESS.UserDirectoryTableBase. KVAS is enforced on a per-process basis.
We may call NtQuerySystemInformation() with SystemKernelVaShadowInformation class to check whether KVAS is enabled.
Remember that High-IL processes do not use KVAS.
Some bypasses for KVAS include leaking the base VA of the PML4 table and then using ROP gadgets to turn off the NX bit in the PML4E of the shellcode VA.
KVAS may be turned off forcefully by running the following commands from an admin prompt and rebooting:
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" /v FeatureSettingsOverride /t REG_DWORD /d 3 /f
reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" /v FeatureSettingsOverrideMask /t REG_DWORD /d 3 /f


Windows Defender Application Control(WDAC) [Optional]

Also known as Device Guard(DG), it is an incredibly powerful and flexible second-generation application control/whitelisting solution for both User-Mode and Kernel-Mode code. It was first introduced in Windows 10 RS1/1607.
Although WDAC is meant for whitelisting, it can also be used for blacklisting known bad code.
WDAC allows creation and enforcement of configurable Code Integrity(CI) policies that can either be for Ring 3 or Ring 0 code. However, we are concerned with the Ring 0 portion only i.e. auditing/blocking conflicting device drivers via Kernel Mode Code Integrity(KMCI) or Hypervisor-enforced Code Integrity(HVCI). It supports both Audit mode(only log event with Event ID = 3076, do not actually block loading) and Enforce/Block mode(log and block it for real with Event ID = 3077). Note that there's also an additional event with Event ID = 3089 that gives extended signer information of the device driver at fault.
WDAC allows granular control over what Kernel-Mode code should be allowed to load and as a result may effectively mitigate the threat from signed and vulnerable drivers. CI policies start out as XML files and they are converted to a binary format before being deployed. It allows enforcement of multiple policies and they can even be signed to protect against possible tampering from a High-IL context.
Here is a Powershell one-liner to deploy an example Audit policy from Microsoft that only allows WHQL-test Signed and Attestation Signed third-party device drivers to load:
ConvertFrom-CIPolicy -XmlFilePath C:\Windows\schemas\CodeIntegrity\ExamplePolicies\DefaultWindows_Audit.xml -BinaryFilePath C:\Windows\System32\CodeIntegrity\SIPolicy.p7b
Import-Module .\WDACTools.psd1
$Events = Get-WDACCodeIntegrityEvent -Kernel -SignerAndWhqlChecks
Do not forget to remove UMCI from the CI policy before deploying it since we do not want to be flooded by events from User-Mode code.
Bypasses would heavily depend on the specific scenario at hand.
Note that WDAC is meant to be used alongside HVCI for enhanced security (SKM/VTL-1 Ring 0) when Hyper-V support is available in hardware. However, it can fallback to KMCI in absence and function independently too.
Remember that this is an optional mitigation and it is not enforced by default. However, there is a built-in WDAC driver blacklist policy called DriverSiPolicy that is activated when HVCI is enabled and it blocks the loading of some known bad device drivers such as Sandra.sys etc.


Virtualization Based Security(VBS) [Optional]

Also known as Virtual Secure Mode(VSM), this is not a mitigation per se but rather a set of powerful features leveraging Microsoft's hypervisor called Hyper-V using hardware virtualization capabilities of modern processors to enforce additional security boundaries. It was first introduced in Windows 10 TS1/1507.
The minimum requirements of VBS are:
1. 64-bit CPU
2. Second Level Address Translation(SLAT) support - Intel EPT or AMD NPT
3. Virtualization Extensions support - Intel VT-x or AMD-V preferably with Mode Based Execution Control(MBEC) or Guest Mode Execute Trap(GMET) respectively
However, to fully secure the hypervisor from all kinds of attacks including physical and enforce VBS security, the following additional requirements should be satisfied:
1. SecureBoot
2. Input Output Memory Management Unit(IOMMU) support - Intel VT-d or AMD-Vi
3. Trusted Platform Module(TPM)
On capable hardware Hyper-V and subsequently VBS(only base features like HyperGuard) may be enabled by executing the following command from an admin prompt and rebooting:
bcdedit /set hypervisorlaunchtype auto
Note that Hyper-V may also be enabled via Windows Features.
After enabling VBS features we can use msinfo32.exe to check its status.
VBS splits the trust into two separate partitions/VMs(also called Virtual Trust Level(VTL)):
1. VTL-0 Ring 0(ntoskrnl.exe) - The assume-compromised VM which runs the normal kernel
2. VTL-1 Ring 0(securekernel.exe) - The protected VM also called Secure Kernel Mode(SKM) which runs a minimalized and secure kernel, cng.sys and skci.dll
VTL-1 has full control over VTL-0 and is responsible for maintaining its security. Furthermore, access to VTL-1 pages by a lower VTL i.e. VTL-0 can be controlled via SLAT and is thus protected from compromise even if the normal kernel is compromised.
There also's an Isolated User Mode(IUM) or VTL-1 Ring 3 where only special Microsoft-signed processes called Trustlets(Ex: LsaIso.exe) can run and the normal Ring 3 where all other User-Mode code is run i.e. in VTL-0 Ring 3.
Remember that this is an OptIn feature that is not enabled by default. However, Secured Core PCs including Microsoft's Surface Pro series and Windows 10 in S Mode has VBS turned on by default. Furthermore, it should be noted that most of the following features are not turnkey solutions as of yet i.e. turning on a switch and hoping everything would work fine in every scenario.

Hyper Guard(HG)

Also known as Secure Kernel Patch Guard(SKPG), HG is designed to detect any changes to critical kernel structures/CPU registers and bugcheck(Code: 0x18C/HYPERGUARD_VIOLATION) if any inconsistency is detected much like its predecessor PG.
HG does not need to be enabled separately. It is enabled when VBS is enabled.
However, it does not share the same weaknesses of PG in the sense that HG can detect the changes in real-time and crash the system as soon as any tampering occurs unlike the former which is polled and adversaries may get away with a burst tampering tactic. HG operates in VTL-1 Ring 0 unlike PG and sets up Secure Intercepts that trigger when certain events occur in the kernel. As a result of this design, it is also better resilient to attacks.
Among the major things(in our context) that HG protects are:
1. Control registers, Model Specific Register(MSR)s, GDTR, IDTR etc. This implies that we can no longer disable SMEP via flipping 20th bit of CR4 register nor can we use exposed __writemsr in vulnerable drivers to achieve function pointer overwrite primitive via MSR_LSTAR and get code execution.
2. Global Descriptor Table(GDT)
3. Interrupt Descriptor Table(IDT)
4. KCFG bitmap
Look out for Skpg* functions in securekernel.exe. Example: sk!SkpgxInstallIntercepts which is used to install the Secure Register Intercepts.
Some workarounds may include direct PTE manipulation to bypass SMEP instead of tampering with CR4 register.
Remember that HG is disabled when the hypervisor boots in debug mode with a remote debugger attached.

Hypervisor-enforced Code Integrity(HVCI) [Optional]

Also known as Memory Integrity, this is one of the core components of VBS that is designed to enhance CI via hypervisor and enforce W ^ X in the kernel to keep in check what a malicious device driver can do even after it is allowed to load. It is also utilized by Custom Code Integrity(CCI) when hardware support is available for strong code guarantees.
After enabling VBS, HVCI may be turned on by executing the following command from an admin prompt and rebooting:
reg add "HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity" /v "Enabled" /t REG_DWORD /d 1 /f
Note that HVCI may also be enabled by turning on Memory Integrity from Device Security -> Core Isolation.
HVCI provides the following security guarantees:
1. Only properly signed kernel pages can become executable. CI is actually enforced by the hypervisor(skci.dll in Secure Kernel/VTL-1 Ring 0). This implies that it wouldn't be possible to load unsigned drivers even if we have already compromised the kernel(VTL-0 Ring 0) i.e. patching CI.dll in kernel.
2. Kernel pages can never be +W and +X at the same time. This implies that Ring 0 code once loaded cannot be modified and dynamically loaded code(Ring 0 shellcode) is not allowed either since kernel cannot allocate +RWX code permissions
3. Additionally, it mitigates the ability to directly modify PTE control bits in the kernel to bypass DEP/SMEP etc.
This is achieved with the help of kernel's EPT along with MBEC/GMET(or it's software emulation called Restricted User Mode(RUM) if hardware support is not available) which are additional "secured" control bits maintained by VTL-1 Ring 0.
Remember there's a WDAC driver blacklist in C:\Windows\System32\CodeIntegrity\driversipolicy.p7b that gets enforced when HVCI is enabled.
Some workarounds for HVCI include using code-reuse techniques such as ROP/COP/JOP payloads in the kernel, resorting to data corruption attacks, finding leftover +RWX pages from the early boot phase or compromising either Ring -1/Hypervisor directly or via Ring -2/SMM.

Kernel Control Flow Guard(KCFG) [Optional]

This is Microsoft's Forward-Edge Control Flow Integrity(CFI) implementation in the kernel that protects against indirect function calls by validating against a bitmap of KCFG-valid call targets which are additionally protected from modification by the hypervisor/HyperGuard.
Note that KCFG is only enabled(fully) when HVCI is enabled. However, even without HVCI enabled if it detects a Ring 3 address in the call target then KCFG would bugcheck(Code: 0x139/KERNEL_SECURITY_CHECK_FAILURE) notwithstanding SMEP.
KCFG is another particularly effective mitigation along with HVCI that mitigates against overwriting function pointer and executing it to obtain code execution.
nt!MiInitializeKernelCfg is used to initialize the CFG bitmap for the kernel and HAL and nt!_guard_dispatch_icall is used to validate call targets via the bitmap.
Bypasses may include modifying an Import Address Table(IAT) entry with the address of shellcode(nt!_KUSER_SHARED_DATA) since KCFG does not take IAT indirect call targets into consideration.

Kernel Data Protection(KDP)

This is one of the newer VBS mitigations that is designed to enforce immutability of +R0 pages of kernel memory via SLAT and protect it from data corruption attacks.
There are two variants of this mitigation:
1. Static KDP
2. Dynamic KDP
Static KDP is aimed to prevent data corruption attacks commonly used to elevate privileges, kill defences, disable PPL by tampering with the Protection byte in nt!_EPROCESS structure etc. with a Ring 0 R/W primitive. With Static KDP, PSP drivers can protect a section of their image using MmProtectDriverSection which would make it Read Only(RO) using EPT entries thus enforcing immutability from VTL-0 Ring 0.
Dynamic KDP is aimed at letting device drivers allocate a secure +RO memory pool using ExAllocatePool3.
There are two more VBS features namely Credential Guard(CG) which is used to protect cryptographic secrets of lsass.exe and Windows Defender Application Guard(WDAG) which is a Hyper-V backed sandbox for Office/Edge. However, these are not important for this discussion.


Control-flow Enforcement Technology(CET)

This is a hardware-based mitigation created by Intel for enforcing both Forward-Edge CFI and Backward-Edge CFI. Support for CET has been added in Windows 10 20H1/2004.
Note that hardware support is required for enabling CET which is only available since Intel's Tiger Lake/11th gen CPUs.
CET uses Indirect Branch Tracking(IBT) to protect against Forward-Edge cases i.e. indirect CALL/JMP and Hardware-enforced Shadow Stack to protect against Backward-Edge cases i.e. RET.
Since CFG already protects against Forward-Edge cases, Microsoft has decided to rely on that(plus Extended Flow Guard(XFG)) and discard IBT. However, CFG does not provide any protection against Backward-Edge cases which is where CET comes in and mitigates corruption of return addresses on the stack.
When a mismatch is detected between a target return address on the stack and its preserved return address on the Hardware Shadow Stack, a Control Flow Protection Fault is generated. CET essentially takes away our ability to use ROP to get code execution.
3rd-party applications may use a linker flag called /CETCOMPAT to enforce hardware-based stack protection when appropriate support is available while native modules are built with this flag. CET is available in both Compatibility Mode and Strict Mode.