KASLR Leaks Restriction

In recent years, Microsoft has focused its efforts on mitigating bug classes and exploitation techniques. In latest Windows versions this includes another change that adds a significant challenge to attackers targeting the Windows kernel — restricting kernel address leaks to user mode. With almost any memory bugs, an attacker needs some kernel address leak to know which address will be read / written into / overflowed / corrupted. That address could be the address of ntoskrnl.exe or other kernel drivers, or the address of some object that the attacker targets. Until recently, getting those was very easy (for anyone running at medium integrity level or above). All you had to do was call one of several known windows APIs.

But starting Windows 11 / Windows Server 2022 24H2 edition, those APIs will no longer leak any kernel addresses, unless the requesting process has enabled SeDebugPrivilege, a powerful privilege which is only available to admin processes and not enabled by default. This check is implemented with a new flag passed to ExIsRestrictedCaller:

ExIsRestrictedCaller is called in various places in the kernel to check whether a process should receive access to a resource or be allowed to perform an operation. This is used to restrict processes running with an integrity level of Low or Untrusted from calling APIs that return kernel addresses, for example. Now, this API also checks if the process enables SeDebugPrivilege and uses the result to set the RestrictKernelAddressLeaks argument (name chosen by me, as the argument name is not public) and return it to the caller. This argument is then used by the caller to decide what kernel data can be returned to the user-mode caller.

For example, when NtQuerySystemInformation (which calls the internal ExpQuerySystemInformation) is called with the SystemModuleInformation class, ExIsRestrictedCaller is called to determine what data the caller can receive. The output argument then gets passed into ExpQueryModuleInformation:

Inside ExpQueryModuleInformation, the RestrictKernelAddressLeaks argument is used to decide whether the function will populate the DllBase field for every kernel module loaded in the system:

If the argument is set, which means the process does not enable SeDebugPrivilege, the process will still be able to receive information about the loaded kernel modules. But that information will not include the base address of those modules โ€“ that field will be set to 0.

This check is done in all other APIs known to leak kernel addresses to user-mode callers. In all cases, the query will succeed for callers running at Medium IL or above, but the fields that normally contain kernel addresses will be left empty. The full list of APIs which now restrict kernel address leaks are:

APIInformation ClassData Structure Returned by the APIRestricted Field
NtQuerySystemInformationSystemHandleInformation (16)SYSTEM_HANDLE_INFORMATIONSYSTEM_HANDLE_INFORMATION.Handles[N].Object
NtQuerySystemInformationSystemObjectInformation (17)SYSTEM_OBJECT_INFORMATIONSYSTEM_OBJECT_INFORMATION.Object
NtQuerySystemInformationSystemExtendedHandleInformation (64)SYSTEM_HANDLE_INFORMATION_EXSYSTEM_HANDLE_INFORMATION_EX.Handles[N].Object
NtQuerySystemInformationSystemBigPoolInformation (66)SYSTEM_BIGPOOL_INFORMATIONSYSTEM_BIGPOOL_INFORMATION.AllocationInfo[N].VirtualAddress

SYSTEM_EXTENDED_THREAD_INFORMATION.Win32StartAddress if the threadโ€™s Win32StartAddress is a kernel address.

NtQueryInformationProcessProcessHandleTracing (32)PROCESS_HANDLE_TRACING_QUERYPROCESS_HANDLE_TRACING_QUERY.HandleTrace[N].Stacks
NtQueryInformationProcessProcessWorkingSetWatchEx (42)PROCESS_WS_WATCH_INFORMATION_EXPROCESS_WS_WATCH_INFORMATION_EX.BasicInfo.FaultingPc โ€“ if FaultingPc is a kernel address.

PROCESS_WS_WATCH_INFORMATION_EX.BasicInfo.FaultingVa โ€“ if FaultingVa is a kernel address.

Are these APIs the only ways to leak kernel addresses? Are KASLR leaks finally dead? Of course not. But more on that in another blog post ๐Ÿ™‚