A universal EDR bypass built in Windows 10

While studying internals of a mechanism used by all EDR software to get information about processes activities on Windows, we came across a way for malicious processes to disable the generation of some security events related to process interactions. This technique could be used to evade EDR software while performing malicious operations such as process memory dumping, code injection or process hollowing.

A primer on EDR’s monitoring capacities

Usermode vs. kernelmode methods

On Windows, EDR software mainly use two categories of techniques to monitor the actions performed by the processes: user-space methods, like function hooking, which are targeting each individual process, and kernel-space features, which are relying on OS-provided functions to collect system-wide telemetry about processes activity.

The first category can often technically be evaded by a malicious process, as long as it knows the exact techniques used by the EDR. Indeed, the monitoring code and the monitored code often run in the same “space”, the process’ memory, so it boils down to a game of cat-and-mouse between the malware and the EDR, given that each can interact or alter the code of the “opposing party”.

For the second category, the monitoring code runs in the Windows kernel space, not directly accessible from any process, regardless of its privilege level. However, these monitoring capacities are provided by Windows itself to the installed security products, and all EDR software are forced to use them nearly identically to get telemetry about processes activity (how to detect malicious activity from said telemetry is obviously up to each EDR software).

For more in-depth information about the subject, both types of mechanisms were notably described in our article in the 116th edition of MISC magazine ( FR (original) or EN (translated) ). Also, to better understand the stakes of what follows in the present article, we recommend the readers to look at our article about EDR monitoring bypasses in the 118th edition of MISC magazine ( FR (original) or EN (translated) ), as well as the README of our tool, EDRSandblast

Event Tracing for Windows – Threat Intelligence

Among the aforementioned mechanisms, Event Tracing for Windows – Threat Intelligence (ETW-Ti for short in this article) allows the generation of events upon security-critical kernel operations, such as process creation, memory read/write between processes, executable memory creation, etc. (see our article in MISC 116 for more details).

The event feed produced by the mechanism is normally only “consumable” by security products, which need to be protected processes (PROTECTED_ANTIMALWARE_LIGHT), cryptographically signed as such by Microsoft.

These security events’ creation is handled by the Windows kernel, and is implemented by simple calls to dedicated EtwTi* functions, embedded inside each kernel function of interest. The following image shows the call to EtwTiLogReadWriteVm inside the MiReadWriteVirtualMemory function, the latter being responsible for memory reads and writes between processes.

 

A call to EtwTiLogReadWriteVm highlighted in a control-flow graph
EtwTiLogReadWriteVm call inside MiReadWriteVirtualMemory

 

Our findings

A convenient exception

Looking at the whole control flow graph of the function above, we see that the call to the ETW-Ti logging function is always performed in a successful call to MiReadWriteVirtualMemory, unless PsIsProcessLoggingEnabled returns FALSE

This latter function, mentioned nowhere we could find in the Windows reverse-engineering literature, does the following (comments, variable names and types were reverse-engineered and/or inferred from public debugging symbols):

decompiled source code of PsIsProcessLoggingEnabled
Reverse-engineered source code of PsIsProcessLoggingEnabled

As we can see, the function checks the state of a flag among EnableReadVmLogging, EnableWriteVmLogging, EnableThreadSuspendResumeLogging and EnableProcessSuspendResumeLogging, indicating whether the currently performed action (among an inter-process memory read, memory write, thread suspension/resuming or a process suspension/resuming, respectively) should be effectively logged by ETW-Ti. These flags are located in various fields of the _EPROCESS structure of the targeted process.

Accessing logging flags

By cross-referencing the use of the aforementioned flags in the kernel, we found that NtQueryInformationProcess and NtSetInformationProcess were used to get or set the specific bits corresponding to these logging flags.

While mostly undocumented, these functions have been scrutinized by Windows Internals reverse engineers (and malware developers) for a long time, since they handle the eponym system calls reachable from user space. The System Informer project (formerly known as Process Hacker) harbors an impressive database of function prototypes, structures and enums related to Windows Internals, gathered through the years thanks to “a lot of reverse engineering and guessing”

The prototype of the NtSetInformationProcess function is the following:

Prototype of NtSetInformationProcess
Prototype of NtSetInformationProcess

The function can be used for more than a hundred use cases, depending on the value of ProcessInformationClass. The function is implemented using a huge switch-case statement, and the specific code touching the logging flags is located under the ProcessEnableReadWriteVmLogging and ProcessEnableLogging cases (undocumented constants named by System Informer’s developers).

Reverse-engineered source code of NtSetInformationProcess
Reverse-engineered source code of NtSetInformationProcess

The behavior of the code above can be reduced to the following points:

  • The ProcessInformationLength argument’s consistency is checked against the expected ProcessInformation structure (i.e. flags are stored in a BYTE or in a DWORD, see the expected structures for both ProcessEnableReadWriteVmLogging and ProcessEnableLogging);
  • Process privileges are checked: the call is only accepted if at least one of SeDebugPrivilege or SeTcbPrivilege is held by the calling process;
  • The kernel object (_EPROCESS) of the target process is recovered, while checking its handle does have the PROCESS_SET_LIMITED_INFORMATION access right;
  • Different flags (from the Flags2 and Flags3 unions fields of the _EPROCESS structure) are updated, based on provided ProcessInformation structure.

Flags that can be updated through this method are the following:

  • EnableProcessSuspendResumeLogging (resp. EnableThreadSuspendResumeLogging): controls if a ETW-Ti event is raised upon process (resp. thread) suspension or resuming. These operations are used in process hollowing techniques, for instance;
  • EnableReadVmLogging: controls if an ETW-Ti event is generated upon memory reads across different processes. These operations are typically used in LSASS dumping;
  • EnableWriteVmLogging: idem, for memory writes across processes. These operations are used in most process injection techniques.

From the attacker’s perspective

To sum it up, while the ETW-Ti mechanism cannot be disabled globally on the system from user-space (i.e., by a process), some of its features can be turned off by a process having the SeDebugPrivilege or SeTcbPrivilege privilege, which can be achieved by any elevated process.

As previously stated, the ETW-Ti event feed is normally only accessible to security products like EDR. However, in the above function, we see that any unprotected process can disable some logging features of another process without proving to the system it is a legitimate consumer of the ETW-Ti feed (e.g., an EDR).

It is important to note that EDR often correlate multiple events to construct alerts, in order not to generate false positive results. For instance, a LSASS dumping is often divided in multiple steps:

  • The opening of a handle to the lsass.exe process having PROCESS_VM_READ access;
  • The actual reads of all relevant memory ranges;
  • The creation of a minidump file.

If only the handle creation event exists, but the read events are not logged by ETW-Ti and the minidump file is encrypted or never written on disk, the EDR might not raise alerts regarding a LSASS process dumping, lacking evidence to do so.

Affected versions of Windows

Differences between Windows 10 and 11

The same analysis was performed on the NtSetInformationProcess code of Windows 11’s kernel.

Reverse-engineered source code of NtSetInformationProcess on Windows 11
Reverse-engineered source code of NtSetInformationProcess on Windows 11

The code shows two main differences. The first, and most important: the protection level of the process calling NtSetInformationProcess is checked to “dominate” the ANTIMALWARE_LIGHT level, using the call to EtwCheckSecurityLoggerAccess. A protection level is said to dominate another, if both following statements are true:

  • The protection type (Protected, Protected Light, or Unprotected) is identical or stronger than the other protection level (Protected is “stronger” than Protected Light, which is stronger than Unprotected, of course)
  • The Signer dominates that of the other protection level, according to rules that are hardcoded in the Windows Kernel (reversed from the RtlProtectedAccess structure). The following graph describes these rules :
Protected processes "domination" between different protection levels
Protected processes “domination” between different signers

This means that only a Protected or a Protected Light process with a signer being WinSystem, WinTcb, Windows or Antimalware (i.e. a system component or a security product cryptographically signed by Microsoft as such) is authorized to use the NtSetInformationProcess API to disable ETW-Ti logging features on Windows 11. This is an important improvement, as it sets a consistent boundary between security products and features on one side, and other processes on the other. 

The second difference between Windows 11 and Windows 10’s implementation of NtSetInformationProcess is that new logging feature bits seem to be writable with the API: EnableProcessLocalExecProtectVmLogging and EnableProcessRemoteExecProtectVmLogging, seemingly used to enable/disable the monitoring of operations making memory executable.

As a side note, this feature seems either bugged or not completely implemented yet, since in the code above, the bits are not reset by the bitwise AND operation (InterlockedAnd), the corresponding features thus cannot be turned off using this API.

Exact scope of affected versions

Analysis of various kernel builds across different Windows versions showed that the first available build of Windows 11 (21H2, version 10.0.22000.194) already implements the security check performed by EtwCheckSecurityLoggerAccess previously described.

On the other side, in the last available version of Windows 10 at the time of writing (22H2, version 10.0.19041.3393), the security check is still absent, while this build being 2 years more recent. This very likely indicates that Microsoft is well aware of the problem and does not patch the weakness voluntarily, likely for retro-compatibility reasons.

The different feature bits and their related handling by NtSetInformationProcess appeared progressively during Windows 10’s product life. The following table sums up the affected versions:

⚠ : ETWTi logging function does not exist yet
❌ : ETWTi logging can be disabled
✅ : ETWTi logging cannot be disabled
 

Win10

1507 -> 1703

Win10

1709 -> 1803

Win10

1809 -> 22H2

Win11

21H2 -> 22H2

Read virtual memory operation

Write virtual memory operation
Process suspension / resuming operations
Thread suspension / resuming operations

 

Final words

In conclusion, the mechanism described in this article actually allows an elevated malicious program wishing to perform nefarious actions (process injection, LSASS dumping, process hollowing, etc.), to carefully disable related telemetry before doing it, removing critical evidence from EDR monitoring, thus greatly improving its chances of not being detected.

Multiple pieces of evidence show that Microsoft is aware of the weakness, but is not changing the API behavior retroactively on Windows 10, likely due to retro-compatibility issues.

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to top