Blinding Sysmon: How to disable Windows monitoring in a covert way

Date: 27/02/2025

Immediately after getting access to the target system, the attacker tries to blind its audit tools to remain undetected as long as possible. In this article, I will explain how to blind Sysmon in a covert way making it possible to fool the regular Windows audit. The technique is pretty simple and involves manipulations with handles and security descriptors.

warning

This article is intended for security specialists operating under a contract; all information provided in it is for educational purposes only. Neither the author nor the Editorial Board can be held liable for any damages caused by improper usage of this publication. Distribution of malware, disruption of systems, and violation of secrecy of correspondence are prosecuted by law.

Sysmon internals

Important: to perform tricks described in this article, the attacker must have administrative privileges. Someone might ask: Why delve into the Windows internals if admin rights allow you to stop the Sysmon service or even delete it? The answer is simple. First, if you use traditional techniques (e.g. stop a service, terminate a process, or completely delete Sysmon), the standard Windows audit would generate plenty of events (from EventID 4689 in the Security log to EventID 1 in the System log from the FilterManager provider). Second, this is a useful exercise for aspiring hackers pentesters: you have to comprehend the security subsystem of your favorite OS to successfully disrupt tricks used by hackers.

As you are likely aware, System Monitor (Sysmon) is a Windows system service and device driver that performs in-depth monitoring of activities in the system, which significantly expands the standard Windows audit.

From the inside, its operation looks as follows.

The first command (search <ServicePattern>) searches for services and drivers registered in the system using the EnumServicesStatus() Win32 function. As you can see, two entities operate in the system: Sysmon64 user mode service and SysmonDrv driver. Both entities are started automatically at system startup (the StartType field). You can get this information using the show all <ServiceName> command that sequentially calls QueryServiceStatus() (to get the current service status: running or stopped) and QueryServiceConfig() (to get the rest of information).

It’s logical to assume that events (or some of the events) recorded by the Sysmon64 service to the Microsoft-Windows-Sysmon\Operational log are generated by the SysmonDrv kernel module, and a communication device is required for the interaction between the service and the driver. To check this assumption, let’s examine the list of handles opened by the Sysmon64 service (Sysmon64.exe process).

First, you have to find out the Sysmon64.exe process ID (search name <ProcessPattern>).

The search name <ProcessPattern> command calls the CreateToolhelp32Snapshot() function, which, in turn, calls the Process32First() and Process32Next() functions.

After getting the process ID, you can examine the list of its handles.

The output indicates that the Sysmon64.exe process has several open handles to devices. Note the \Device\SysmonDrv device whose name is the same as the driver name. Apparently, the Sysmon64 user mode service receives information from the driver through this device and subsequently generates audit events.

The show handles <PID> <Type> command calls NtQueryInformationProcess() with the ProcessHandleInformation parameter.

__kernel_entry NTSTATUS NtQueryInformationProcess(
[in] HANDLE ProcessHandle,
[in] PROCESSINFOCLASS ProcessInformationClass,
[out] PVOID ProcessInformation,
[in] ULONG ProcessInformationLength,
[out, optional] PULONG ReturnLength
);

The first parameter (ProcessHandle) is the handle to the process whose handles you want to receive. It can be obtained using the OpenProcess() function with the PROCESS_QUERY_INFORMATION parameter.

ProcessInformationClass is the type of information requested. In this particular case, the value of this parameter is ProcessHandleInformation (i.e. you need information about process handles).

The next parameter is ProcessInformation, a pointer to the buffer supplied for result: PPROCESS_HANDLE_SNAPSHOT_INFORMATION (i.e. a pointer to the structure containing the list of all handles and their number).

The fourth and fifth parameters indicate the size of the buffer and the size of the requested information returned by the function.

Duplicating a handle and then closing the original one.

It seems that an attacker can force the Sysmon64.exe process to close the handle to the \Device\SysmonDrv device to make Sysmon stop generating audit events.

This can be done in several ways, but let’s try the simplest one by calling the DuplicateHandle() function:

BOOL DuplicateHandle(
[in] HANDLE hSourceProcessHandle,
[in] HANDLE hSourceHandle,
[in] HANDLE hTargetProcessHandle,
[out] LPHANDLE lpTargetHandle,
[in] DWORD dwDesiredAccess,
[in] BOOL bInheritHandle,
[in] DWORD dwOptions
);

This function can be used to copy handles between processes, and it has an interesting option making it possible to close the original handle after duplicating it. Let’s exploit this feature! The code looks as follows:

DuplicateHandle(
hProcess,
hObject,
GetCurrentProcess(),
&hDupObject,
0,
FALSE,
DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE
);

The first parameter (hProcess) is the handle to the process whose handle has to be duplicated closed. Important: the hProcess object must be opened with the access mask PROCESS_DUP_HANDLE.

The next parameter (hObject) is the handle that must be closed.

The third (GetCurrentProcess()) and the fourth (&hDupObject) parameters point to the process that is to receive the duplicated handle (current process) and the variable that receives the duplicate handle, respectively.

The last parameter contains a combination of two options: the first one (DUPLICATE_SAME_ACCESS) indicates that the new handle must have the same access mask as the original; the second one (DUPLICATE_CLOSE_SOURCE), that the original handle must be closed after the duplication. Great, that’s exactly what I need!

As said earlier, the Sysmon64.exe process has an open handle to the \Device\SysmonDrv object. Let’s try to close it.

Alas! I closed the handle with an identifier of 0x310, but immediately after that, the Sysmon64.exe process successfully reopened it (0x4с4) and continued its operation as if nothing had happened. So let’s break out the big guns (the above-described technique will also be used, but a bit later).

Driver objects

The first attempt to manipulate the handle failed… Let’s try to manipulate the security descriptor that defines access rules for the \Device\SysmonDrv device. But first, let’s remember what a driver object and a device object are, and how the Windows access control subsystem operates. If you are familiar with such things as driver object, device object, security descriptor, and access token, you can skip the next two sections.

When a driver is loaded to the kernel address space, the system creates a _DRIVER_OBJECT structure (i.e. a special object of the driver type). Let’s examine the contents of this structure using the WinDbg debugger.

At offset 0x008 from the beginning of the structure, there is the DeviceObject element: a pointer to the _DEVICE_OBJECT structure (another kernel object required for communication between the user mode process and the driver). In other words, the communication device is responsible for data transfer between the user process and the driver. For this purpose, drivers create device objects; while user processes interact with them.

The most interesting element of the structure is SecurityDescriptor. It stores information about subjects allowed to interact with the device and their access methods. Let’s try to manipulate its contents.

Windows security system

The Windows access control subsystem consists of three participants (roles):

  • Subject;
  • Object; and 
  • Independent arbitrator.

Subject is a thread (or process; for the purposes of this study, these terms are considered synonymous) that attempts to access a resource (e.g. file or registry key) – object. Threads are executed in a specific user context. This means that a process can only get as much access as is granted to the account on whose behalf the process is running.

A special kernel structure, _Token (access token), determines the specific user context the thread belongs to. This is a kind of ‘subject passport’, and the system checks it every time a process requests to perform an operation with an object (read a registry key, write to a file, read from a communication device, etc.). The _Token structure contains the following information:

  • account on whose behalf the process is running (account ID);
  • security groups the account belongs to (group IDs); and 
  • privileges granted to the account.

Let’s extract the Token address from the _EPROCESS structure (a kernel structure that describes a process object).

In fact, Token points to the _EX_FAST_REF structure, not to access token. _EX_FAST_REF is a union type (in C terminology) and includes three elements. Importantly, the union type assumes that all elements of this type share the memory allocated to them.

The Object and Value elements are 8 bytes in size each; while RefCnt is 4 bits. The RefCnt value is used by internal Windows kernel mechanisms to count the number of references. Accordingly, to get the address of the pointer to the access token, you have to zero the 4 low bits of the Object element.

To view the Access Token contents in a legible way, use the !token extension.

As you can see, Sysmon64.exe is running on behalf of the LocalSystem account (S-1-5-18).

Object is any Windows element access to which can be arbitrarily restricted. In fact, Windows is based on the object model (i.e. all its resources are objects). Files, registry keys, processes, and input/output devices are objects, and OS mechanisms control access to them. Information on access rights (i.e. who can access the object and who cannot) is contained in the security descriptor (SD, _SECURITY_DESCRIPTOR kernel structure). Let’s examine the security descriptor contents for the \Device\SysmonDrv device. To do this, the following steps are required:

  1. Using the !drvobj extension, get the address of the _DRIVER_OBJECT structure for the SysmonDrv driver;
  2. From where, get the DeviceObject value that points to the object of the \Device\SysmonDrv communication device (_DEVICE_OBJECT); and 
  3. From _DEVICE_OBJECT, get a pointer to _SECURITY_DESCRIPTOR.

Let’s examine the contents of the object’s security descriptor.

The structure contains:

  • object owner information – LocalSystem (S-1-5-18);
  • object primary group information – LocalSystem; and 
  • DACL and SACL lists.

DACL identifies accounts (SIDs) that are allowed (or denied) to access a given object. The access methods (masks and access operations) are specified as well.

So, there is a subject willing to access a protected resource (the subject has its own access token containing information about the user account, its group memberships, and privileges), and an object (i.e. the protected resource) that has a security descriptor (security information: who is allowed to access it and how). Now you need an ‘independent arbitrator’ who can decide, based on the above information, whether to grant access or not.

A component of the executive subsystem called Security Reference Monitor (SRM) acts as such an independent arbitrator. Based on its internal algorithms, this component checks whether the subject can access the object using the requested access method.

To sum-up:

  • the subject is the Sysmon64.exe process running on behalf of the LocalSystem account (S-1-5-18);
  • Sysmon64.exe receives data generated by the SysmonDrv driver using a communication device: \Device\SysmonDrv (object); and 
  • the \Device\SysmonDrv device has its own security descriptor, and the attacker wants to modify it so that the subject (Sysmon64.exe process) is denied access.

Blinding Sysmon

Now that you’ve refreshed your knowledge of the access control subsystem, let’s proceed to the main goal: blind Sysmon. In this section, I will show how to modify the security descriptor so that the Sysmon64.exe process is denied access to \Device\SysmonDrv.

To examine the security descriptor of the communication device, a handle to this device is required. You can get it by calling the CreateFile() function and then the GetSecurityInfo() function. Let’s take a closer look at these functions. CreateFile() can get a handle to a file or other objects, including communication devices:

HANDLE CreateFileW(
[in] LPCWSTR lpFileName,
[in] DWORD dwDesiredAccess,
[in] DWORD dwShareMode,
[in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
[in] DWORD dwCreationDisposition,
[in] DWORD dwFlagsAndAttributes,
[in, optional] HANDLE hTemplateFile
);

Let’s examine each of the seven parameters in more detail.

  1. lpFileName takes the object name (in this case it’s \\.\SysmonDrv, a symbolic link to \Device\SysmonDrv);
  2. dwDesiredAccess is a list of flags describing the requested access (access mask). In Windows, you must specify the list of required accesses prior to performing an operation with an object. To view the Owner, Group, and DACL values, you must specify READ_CONTROL; to view or change SACL, specify ACCESS_SYSTEM_SECURITY (the latter flag requires the calling process to have SE_SECURITY_NAME privileges enabled; otherwise ERROR_ACCESS_DENIED would occur);
  3. dwShareMode defines the sharing mode. In this particular case, it’s set to 0;
  4. lpSecurityAttributes takes a pointer to the security descriptor that will be associated with the object. This parameter is meaningful only when a new object is created;
  5. dwCreationDisposition defines the action for existing or nonexistent files or devices; in this particular case, its value should be OPEN_EXISTING;
  6. dwFlagsAndAttributes defines the list of flags and attributes required to request access to the object. It has to be set to FILE_FLAG_BACKUP_SEMANTICS: this will inform the security subsystem that the existing DACL should be bypassed (this option requires the calling process to have the SE_BACKUP_NAME and SE_RESTORE_NAME privileges enabled); and 
  7. hTemplateFile is used only when new objects are created.

Overall, the CreateFile()function call should look as follows:

hDevice = CreateFile(
lpDeviceName,
READ_CONTROL | ACCESS_SYSTEM_SECURITY,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL
);

After getting a handle to the \\.\SysmonDrv device, let’s use the GetSecurityInfo() function to extract information from this security descriptor:

DWORD GetSecurityInfo(
[in] HANDLE handle,
[in] SE_OBJECT_TYPE ObjectType,
[in] SECURITY_INFORMATION SecurityInfo,
[out, optional] PSID *ppsidOwner,
[out, optional] PSID *ppsidGroup,
[out, optional] PACL *ppDacl,
[out, optional] PACL *ppSacl,
[out, optional] PSECURITY_DESCRIPTOR *ppSecurityDescriptor
);

The first parameter (handle) takes the handle of the object obtained by calling CreateFile().

Next, you have to specify the object type (ObjectType) whose security descriptor you want to view. Let’s set it to SE_UNKNOWN_OBJECT_TYPE.

In the next argument (SecurityInfo), you have to specify security descriptor elements you want to get. In this particular case, the following set of options is required:

OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION

Overall, the GetSecurityInfo()function call should look as follows:

GetSecurityInfo(
hObject,
SE_UNKNOWN_OBJECT_TYPE,
OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION,
&pOwner,
&pGroup,
&pDacl,
&pSacl,
&pSecurityDescriptor
);

Now let’s examine the security descriptor for \Device\SysmonDrv. The show owner <Device> and show group <Device> commands display object’s owner and group; while the show sd <Device> command displays the full SD contents in SDDL format.

As you can see, the security descriptor looks as follows:

O:SYG:SYD:(A;;0x1201bf;;;WD)(A;;FA;;;SY)(A;;FA;;;BA)(A;;0x1200a9;;;RC)S:AI

Detailed decoding of this format is beyond the scope of this article. Instead, let’s focus on just one of its sections: discretionary access control list (DACL):

D:(A;;0x1201bf;;;WD)(A;;FA;;;SY)(A;;FA;;;BA)(A;;0x1200a9;;;RC)

DACL consists of access control entries (ACE). Each ACE is written in separate parentheses. DACL stores information about access rights: what subjects are allowed what access and subjects are denied access). Let’s examine it in more detail.

The entry (A;;FA;;;SY) immediately catches the eye: it indicates that the LocalSystem account (SY) can be granted (A) full access (FA). Let’s try to explicitly deny any access for this account by replacing the A character with D: (D;;FA;;;SY). The security descriptor can be overwritten using the set sddldacl <DACL> command. It calls the SetSecurityInfo() function that takes parameters similar to those taken by GetSecurityInfo().

Let’s see what happens if you request the system to close the handle to the device using the above-described technique involving its duplication.

As you can see, nothing has changed, and the Sysmon64.exe process still has the open \Device\SysmonDrv device, although with a different handle. Apparently, something is missing… Let’s get back to the security descriptor.

Note the first entry in the DACL: (A;;0x1201bf;;;WD). It indicates that the Everyone (WD) account is granted (A) extensive access: 0x1201bf (at least FILE_READ_DATA and FILE_WRITE_DATA). Let’s remove this entry from the DACL and repeat the operation (i.e. duplicate the handle to the device and close it).

“Voila! It worked!” – a hacker would say.

Let’s check the Microsoft-Windows-Sysmon\Operational log: too bad, Sysmon stopped generating events.

Last error: Invalid handle
Last error: Invalid handle

But don’t despair: as a security specialists, you’ve got an additional detection rule for your SIEM:

Provider Name='Microsoft-Windows-Sysmon' and EventID='255' and Data Name.Description='Failed to access the driver - Last error: Invalid handle'

From the pentesting perspective, there is still plenty of interesting stuff there, and I intend to address it in future articles.

See you soon!


Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>