Microsoft Has No Patch Yet for This BitLocker USB Attack. Do This Now

A security researcher publicly released a working proof-of-concept exploit for CVE-2026-45585, a vulnerability that lets attackers bypass BitLocker on Windows 11 using a USB drive. Microsoft confirmed the flaw, published interim mitigation guidance, and accused the researcher of violating responsible disclosure best practices. The researcher responded publicly, claiming Microsoft deleted their MSRC reporting account without explanation.

Microsoft Has No Patch Yet for This BitLocker USB Attack. Do This Now

Here is what the vulnerability does, who is at risk, how to apply the fix, and what happened between Microsoft and the researcher.

How CVE-2026-45585 Bypasses BitLocker Using WinRE

CVE-2026-45585 is a Security Feature Bypass vulnerability in the Windows Recovery Environment (WinRE). The researcher who discovered it, using the handle Nightmare-Eclipse, named the exploit “YellowKey.”

An attacker with physical access to a device can plug in a USB drive containing an FsTx folder and boot the machine into WinRE. Inside the recovery environment, a malicious executable called autofstx.exe runs through the BootExecute registry value very early in the boot process, before the operating system fully loads. Because this execution happens before BitLocker’s full verification runs, the attacker gains read and write access to encrypted data on the system storage device.

No password and no MFA bypass are needed. Physical access and the right USB drive are sufficient.

Microsoft classified the vulnerability as Important with a CVSS 3.1 score of 6.8. The attack vector is Physical, attack complexity is Low, and no privileges or user interaction are required. The underlying weakness is CWE-77 (Improper Neutralization of Special Elements used in a Command, or Command Injection).

Nightmare-Eclipse also disclosed a separate vulnerability called “MiniPlasma” around the same time. MiniPlasma lets threat actors plant malicious Registry modifications on a target system. That flaw is tracked and handled independently.

Which Windows Versions Are Affected

Microsoft released CVE-2026-45585 on May 19, 2026. The following versions are confirmed affected:

  • Windows 11 Version 24H2 (x64-based Systems)
  • Windows 11 Version 25H2 (x64-based Systems)
  • Windows 11 Version 26H1 (x64-based Systems)
  • Windows Server 2025
  • Windows Server 2025 (Server Core installation)

No security update patch is available yet. Microsoft is providing only an interim mitigation script while a full fix is in development.

Are You at Risk From the YellowKey BitLocker Attack

Your exposure depends on how BitLocker is configured on your device.

If you use TPM+PIN, you are not vulnerable. Microsoft confirmed in its official advisory that TPM+PIN configurations block this exploit entirely.

If you use TPM-only, which is the default on most consumer laptops and many enterprise devices, your device is at risk whenever an attacker can physically access it. The threat is highest for employees who take work laptops home or travel with them. Devices that stay in a locked, controlled environment face much lower practical risk, but the vulnerability is still present.

If you are unsure which BitLocker configuration you use, open PowerShell as Administrator and run:

manage-bde -protectors -get C:

If the output shows TPM And PIN, you are protected. If it shows only TPM, your device is affected.

How to Apply Microsoft’s Interim Fix

Microsoft published a PowerShell script as an official interim mitigation. The script removes autofstx.exe from the BootExecute registry value inside the WinRE image. This blocks the malicious executable from running during recovery boot.

Applying the mitigation does not impact service availability or management operations. When Microsoft releases a full security update, you do not need to revert the script’s changes. The patch will preserve the mitigation automatically.

Requirements: Run PowerShell as Administrator on a system where WinRE is enabled.

Save the script below as Remove-AutoFsTxFromWinRE.ps1. You can also copy the official version directly from the CVE-2026-45585 MSRC advisory.

<#
.SYNOPSIS
    Removes autofstx.exe from the WinRE BootExecute registry value.

.DESCRIPTION
    This remediation script removes the "autofstx.exe" entry from the BootExecute
    REG_MULTI_SZ value inside the Windows Recovery Environment (WinRE) offline
    SYSTEM registry hive. This is a security fix to prevent autofstx.exe from
    executing during WinRE boot.

    The script performs the following steps:
      1. Verify administrator privileges
      2. Verify WinRE is enabled
      3. Mount the WinRE image via reagentc
      4. Load the offline SYSTEM registry hive
      5. Read BootExecute and remove autofstx.exe if present
      6. Unload the offline hive
      7. Unmount the WinRE image with commit
      8. Disable and re-enable WinRE to re-seal BitLocker trust chain

    Exit 0 = Success (entry removed or not present)
    Exit 1 = Failure (error during remediation)

.PARAMETER MountPath
    Directory to use as the WinRE mount point. Created if it does not exist.
    Default: C:\Mount

.EXAMPLE
    # Standard run
    .\Remove-AutoFsTxFromWinRE.ps1

.EXAMPLE
    # Custom mount path
    .\Remove-AutoFsTxFromWinRE.ps1 -MountPath D:\mount

.NOTES
    Requirements: Windows with WinRE and reagentc support, Administrator privileges.
    The WinRE disable/enable cycle at the end is required to re-seal the
    BitLocker measurement chain after modifying the WinRE image.

    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.
#>
param(
    [Parameter(Mandatory = $false)]
    [string]$MountPath = "C:\Mount"
)

# Target entry to remove from BootExecute
$EntryToRemove = "autofstx.exe"

# Internal constant for the offline hive key name
$HiveName = "WinREHive"

# State tracking for cleanup
$hiveLoaded   = $false
$imageMounted = $false
$mountCreated = $false
$changesMade  = $false

Write-Host "Remove autofstx.exe from WinRE BootExecute"

# 1. Verify Administrator Privileges
try {
    $identity  = [Security.Principal.WindowsIdentity]::GetCurrent()
    $principal = [Security.Principal.WindowsPrincipal]$identity
    $isAdmin   = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

    if (-not $isAdmin) {
        Write-Host "[1/8] Administrator check: FAILED" -ForegroundColor Red
        Write-Host "  This script must be run as Administrator."
        Write-Host "  Right-click PowerShell and select 'Run as administrator'."
        exit 1
    }

    Write-Host "[1/8] Administrator check: Passed" -ForegroundColor Green
} catch {
    Write-Warning "Error checking administrator privileges: $_"
    exit 1
}

# 2. Check WinRE Status
try {
    $winreOutput = & reagentc /info 2>&1
    if ($LASTEXITCODE -ne 0) {
        Write-Host "[2/8] WinRE status check: FAILED" -ForegroundColor Red
        Write-Warning "reagentc /info returned exit code $LASTEXITCODE"
        exit 1
    }
    $winreOutputStr = $winreOutput -join "`n"

    if ($winreOutputStr -match "[::]\s*Enabled\b") {
        Write-Host "[2/8] WinRE status: Enabled" -ForegroundColor Green
    } else {
        Write-Host "[2/8] WinRE status: NOT ENABLED. Exiting." -ForegroundColor Green
        exit 0
    }
} catch {
    Write-Warning "Error checking WinRE status: $_"
    exit 1
}

# 3. Mount WinRE Image
try {
    if (-not (Test-Path $MountPath)) {
        New-Item -ItemType Directory -Path $MountPath -Force | Out-Null
        $mountCreated = $true
        Write-Host "[3/8] Created mount directory: $MountPath"
    } else {
        $existing = Get-ChildItem -Path $MountPath -Force -ErrorAction SilentlyContinue
        if ($existing) {
            Write-Host "[3/8] Mount WinRE image: FAILED" -ForegroundColor Red
            Write-Host "  Mount directory $MountPath is not empty."
            Write-Host "  Clean it or specify a different -MountPath."
            exit 1
        }
    }

    $mountOutput = & reagentc /mountre /path $MountPath 2>&1
    if ($LASTEXITCODE -ne 0) {
        Write-Host "[3/8] Mount WinRE image: FAILED" -ForegroundColor Red
        Write-Warning "reagentc /mountre output: $mountOutput"
        exit 1
    }

    $imageMounted = $true
    Write-Host "[3/8] WinRE image mounted: $MountPath" -ForegroundColor Green
} catch {
    Write-Warning "Error mounting WinRE image: $_"
    exit 1
}

# 4. Load Offline SYSTEM Registry Hive
try {
    $hivePath = $null
    $hiveCandidates = @(
        "$MountPath\Windows\System32\config\SYSTEM",
        "$MountPath\windows\system32\config\SYSTEM"
    )

    foreach ($candidate in $hiveCandidates) {
        if (Test-Path $candidate) {
            $hivePath = $candidate
            break
        }
    }

    if (-not $hivePath) {
        $found = Get-ChildItem -Path $MountPath -Recurse -Filter "SYSTEM" -ErrorAction SilentlyContinue |
                 Where-Object { $_.FullName -match "config\\SYSTEM$" } |
                 Select-Object -First 1

        if ($found) {
            $hivePath = $found.FullName
        }
    }

    if (-not $hivePath) {
        Write-Host "[4/8] Load offline hive: FAILED" -ForegroundColor Red
        Write-Warning "Cannot locate SYSTEM hive in mounted WinRE image."
        $null = & reagentc /unmountre /path $MountPath /discard 2>&1
        $imageMounted = $false
        exit 1
    }

    Write-Host "[4/8] Found SYSTEM hive: $hivePath"

    $loadOutput = & reg load "HKLM\$HiveName" $hivePath 2>&1
    if ($LASTEXITCODE -ne 0) {
        Write-Host "[4/8] Load offline hive: FAILED" -ForegroundColor Red
        Write-Warning "reg load output: $loadOutput"
        $null = & reagentc /unmountre /path $MountPath /discard 2>&1
        $imageMounted = $false
        exit 1
    }

    $hiveLoaded = $true
    Write-Host "[4/8] Hive loaded as HKLM\$HiveName" -ForegroundColor Green
} catch {
    Write-Warning "Error loading offline hive: $_"
    if ($imageMounted) {
        $null = & reagentc /unmountre /path $MountPath /discard 2>&1
        $imageMounted = $false
    }
    exit 1
}

# 5. Read BootExecute and Remove autofstx.exe
try {
    $selectPath = "Registry::HKEY_LOCAL_MACHINE\$HiveName\Select"
    $selectProps = Get-ItemProperty -Path $selectPath -ErrorAction SilentlyContinue

    if ($selectProps -and $selectProps.Current) {
        $csNumbers = @($selectProps.Current)
        if ($selectProps.Default -and $selectProps.Default -ne $selectProps.Current) {
            $csNumbers += $selectProps.Default
        }
        $controlSets = $csNumbers | ForEach-Object { "ControlSet{0:D3}" -f [int]$_ }
        Write-Host "[5/8] Active ControlSets (from Select key): $($controlSets -join ', ')"
    } else {
        $controlSets = @("ControlSet001")
        Write-Host "[5/8] Select key not readable, falling back to ControlSet001"
    }

    $foundEntry = $false

    foreach ($cs in $controlSets) {
        $regPath = "Registry::HKEY_LOCAL_MACHINE\$HiveName\$cs\Control\Session Manager"
        $testResult = Get-ItemProperty -Path $regPath -Name "BootExecute" -ErrorAction SilentlyContinue

        if (-not $testResult) {
            Write-Host "  $cs\Session Manager\BootExecute: not found, skipping"
            continue
        }

        $currentValue = $testResult.BootExecute

        if (-not $currentValue) {
            Write-Host "  $cs BootExecute: (empty)"
            continue
        }

        $updatedValue = @($currentValue | Where-Object {
            $_ -and
            ($_ -ne $EntryToRemove) -and
            ($_ -notmatch "^\s*$([regex]::Escape($EntryToRemove))\s*$")
        })

        if ($updatedValue.Count -eq @($currentValue).Count) {
            Write-Host "  $cs BootExecute: '$EntryToRemove' not present"
            continue
        }

        Set-ItemProperty -Path $regPath -Name "BootExecute" -Value $updatedValue
        $foundEntry = $true

        Write-Host "  $cs BootExecute: removed '$EntryToRemove'" -ForegroundColor Green

        $verifyValue = (Get-ItemProperty -Path $regPath -Name "BootExecute" -ErrorAction Stop).BootExecute
        Write-Host "  $cs verification: $($verifyValue -join '; ')"
    }

    if (-not $foundEntry) {
        Write-Host "[5/8] '$EntryToRemove' not found in any active ControlSet. No changes needed." -ForegroundColor Green

        [gc]::Collect()
        Start-Sleep -Seconds 1
        $null = & reg unload "HKLM\$HiveName" 2>&1
        $hiveLoaded = $false
        $null = & reagentc /unmountre /path $MountPath /discard 2>&1
        $imageMounted = $false
        if ($mountCreated) { Remove-Item -Path $MountPath -Recurse -Force -ErrorAction SilentlyContinue }
        exit 0
    }

    $changesMade = $true
    Write-Host "[5/8] Removed '$EntryToRemove' from BootExecute" -ForegroundColor Green
} catch {
    Write-Warning "Error modifying BootExecute: $_"

    [gc]::Collect()
    Start-Sleep -Seconds 2
    if ($hiveLoaded) {
        $null = & reg unload "HKLM\$HiveName" 2>&1
        $hiveLoaded = $false
    }
    if ($imageMounted) {
        $null = & reagentc /unmountre /path $MountPath /discard 2>&1
        $imageMounted = $false
    }
    exit 1
}

# 6. Unload Offline Registry Hive
try {
    [gc]::Collect()
    Start-Sleep -Seconds 2

    $unloadOutput = & reg unload "HKLM\$HiveName" 2>&1
    if ($LASTEXITCODE -ne 0) {
        Write-Warning "First unload attempt failed, retrying..."
        [gc]::Collect()
        Start-Sleep -Seconds 3
        $unloadOutput = & reg unload "HKLM\$HiveName" 2>&1
        if ($LASTEXITCODE -ne 0) {
            Write-Host "[6/8] Unload hive: FAILED" -ForegroundColor Red
            Write-Warning "reg unload output: $unloadOutput"
            Write-Host "  Close any Registry Editor windows and retry."
            $null = & reagentc /unmountre /path $MountPath /discard 2>&1
            $imageMounted = $false
            exit 1
        }
    }

    $hiveLoaded = $false
    Write-Host "[6/8] Offline hive unloaded" -ForegroundColor Green
} catch {
    Write-Warning "Error unloading hive: $_"
    $null = & reagentc /unmountre /path $MountPath /discard 2>&1
    $imageMounted = $false
    exit 1
}

# 7. Unmount WinRE Image with Commit
try {
    if ($changesMade) {
        $unmountOutput = & reagentc /unmountre /path $MountPath /commit 2>&1
    } else {
        $unmountOutput = & reagentc /unmountre /path $MountPath /discard 2>&1
    }

    if ($LASTEXITCODE -ne 0) {
        Write-Host "[7/8] Unmount WinRE: FAILED" -ForegroundColor Red
        Write-Warning "reagentc /unmountre output: $unmountOutput"

        if ($changesMade) {
            Write-Host "  Attempting discard to avoid leaving image in broken state..."
            $null = & reagentc /unmountre /path $MountPath /discard 2>&1
        }
        $imageMounted = $false
        exit 1
    }

    $imageMounted = $false

    if ($changesMade) {
        Write-Host "[7/8] WinRE image unmounted and committed" -ForegroundColor Green
    } else {
        Write-Host "[7/8] WinRE image unmounted (no changes)" -ForegroundColor Green
    }
} catch {
    Write-Warning "Error unmounting WinRE image: $_"
    exit 1
}

# 8. Re-seal WinRE (Disable + Enable Cycle for BitLocker Trust Chain)
try {
    if (-not $changesMade) {
        Write-Host "[8/8] Re-seal WinRE: Skipped (no changes made)" -ForegroundColor Green
    } else {
        $disableOutput = & reagentc /disable 2>&1
        if ($LASTEXITCODE -ne 0) {
            Write-Warning "reagentc /disable returned non-zero: $disableOutput"
        } else {
            Write-Host "  WinRE disabled."
        }

        $enableOutput = & reagentc /enable 2>&1
        if ($LASTEXITCODE -ne 0) {
            Write-Host "[8/8] Re-seal WinRE: FAILED" -ForegroundColor Red
            Write-Warning "reagentc /enable output: $enableOutput"
            Write-Host "  WinRE may need manual recovery. Run: reagentc /enable"
            exit 1
        }

        Write-Host "[8/8] WinRE re-sealed (disable + enable cycle)" -ForegroundColor Green
    }
} catch {
    Write-Warning "Error during WinRE re-seal: $_"
    Write-Host "  Run manually: reagentc /enable"
    exit 1
}

Write-Host ""
Write-Host "  COMPLETE: '$EntryToRemove' removed from WinRE BootExecute" -ForegroundColor Green

# Cleanup: remove mount directory if we created it
if ($mountCreated -and (Test-Path $MountPath)) {
    Remove-Item -Path $MountPath -Recurse -Force -ErrorAction SilentlyContinue
}

exit 0

Open PowerShell as Administrator and run:

.\Remove-AutoFsTxFromWinRE.ps1

To use a custom mount path instead of the default C:\Mount:

.\Remove-AutoFsTxFromWinRE.ps1 -MountPath D:\mount

The script runs through eight steps automatically:

  1. Verifies that you are running as Administrator
  2. Confirms WinRE is enabled on the device using reagentc /info
  3. Creates the mount directory and mounts the WinRE image using reagentc /mountre
  4. Locates and loads the offline SYSTEM registry hive from the mounted WinRE image
  5. Reads the BootExecute value across all active ControlSets and removes autofstx.exe if present
  6. Unloads the offline registry hive cleanly
  7. Unmounts the WinRE image and commits changes using reagentc /unmountre /commit
  8. Runs a disable and re-enable cycle on WinRE to re-seal the BitLocker trust chain

If autofstx.exe is not present in your WinRE image, the script exits at step 5 without making any changes. A green COMPLETE message in the terminal confirms success.

Microsoft Blamed the Researcher. The Researcher Blamed Microsoft Back.

After Nightmare-Eclipse publicly released the YellowKey proof-of-concept, Microsoft included a pointed statement in its CVE advisory: the PoC publication violated coordinated vulnerability disclosure (CVD) best practices.

The researcher responded directly on their public blog, pushing responsibility back onto Microsoft. According to the researcher, Microsoft first revoked their access to the MSRC account they used to report vulnerabilities, then wiped the account completely when they asked for an explanation. Multiple follow-up requests to MSRC leadership went unanswered. The researcher described Microsoft’s public accusation as a defamation of their personal reputation and stated they are taking the statement personally.

Microsoft has not publicly addressed the researcher’s account of events.

This dispute highlights a recurring tension in security research. When a vendor cuts off a researcher’s disclosure channel without explanation, the researcher loses the normal path for coordinated disclosure. Public release of a PoC becomes the only remaining leverage. Whether that justifies publication is a genuinely contested question in the security community, but the sequence of events matters when assigning responsibility.

Related Guides

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

    Leave a Reply