Verificare la vulnerabilità WinRE CVE-2022-41099 con Intune

Recentemente mi sono imbattuto di nuovo in questa vulnerabilità di Windows che ai tempi avevo deliberatamente deciso di ignorare a causa della scarsa documentazione sulla questione.

A distanza di qualche mese, la situazione sembra essere più chiara ed è possibile verificare in modo efficiente se il proprio sistema è vulnerabile.

Vediamo poi come utilizzare una Custom Compliance Policy di Microsoft Intune per verificare la presenza della vulnerabilità nel nostro ambiente.

INFO: L'articolo è stato modificato dalla sua pubblicazione originale.

13/09/2023:
- Corretto un bug nello script che non comparava correttamente la versione richiesta con la versione rilevata
- Corretto il link alla patch per Windows 10
- Corretto l'output dello script di patching

Indice

Cos'è la vulnerabilità CVE-2022-41099

La vulnerabilità CVE-2022-41099 di Windows permette ad un attore malevolo in possesso del computer fisico di bypassare la protezione BitLocker e accedere alle informazioni salvate nel disco. La vulnerabilità è sfruttabile solo se il BitLocker è attivo senza un PIN ma solo tramite il TPM, che è la situazione nella stragrande maggioranza dei casi.

Per poter correggere questa vulnerabilità è necessario un intervento manuale, ovvero l'installazione di una patch nell'ambiente WinRE, la partizione di Recovery.

Non mi dilungherò molto, ma vi consiglio caldamente la lettura di questo articolo che spiega molto bene il problema:
https://blog.scrt.ch/2023/08/14/cve-2022-41099-analysis-of-a-bitlocker-drive-encryption-bypass/

Verificare manualmente la vulnerabilità

Prendendo sempre spunto dall'articolo indicato sopra, è sufficiente aprire una PowerShell come amministratore ed eseguire questo script:

$BitLocker = Get-WmiObject -Namespace "Root\cimv2\Security\MicrosoftVolumeEncryption" -Class "Win32_EncryptableVolume" -Filter "DriveLetter = '$($env:SystemDrive)'"
if ($BitLocker) {
    if ($BitLocker.GetProtectionStatus().protectionStatus -eq 1) {
        Write-Host "[CVE-2022-41099] BitLocker is enabled."
        $TpmProtectors = $BitLocker.GetKeyProtectors("0").volumekeyprotectorID | Where-Object {
            $KeyProtectorType = $BitLocker.GetKeyProtectorType($_).KeyProtectorType
            ($KeyProtectorType -eq 1) -or ($KeyProtectorType -ge 4 -and $KeyProtectorType -le 6)
        }
        if ($TpmProtectors) {
            Write-Host "[CVE-2022-41099] A TPM-based protector was found."
            $MountDir = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath "$([Guid]::NewGuid().Guid)_winre"
            Write-Host "[CVE-2022-41099] Create mount directory: $MountDir"
            $null = New-Item -Path $MountDir -ItemType Directory
            Write-Host "[CVE-2022-41099] Mount RE..."
            reagentc.exe /mountre /path $MountDir
            if ($LASTEXITCODE -eq 0) {
                $TargetFile = Join-Path -Path $MountDir -ChildPath "\Windows\System32\bootmenuux.dll"
                Write-Host "[CVE-2022-41099] File to check: $TargetFile"
                $RealNtVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($TargetFile).ProductVersion
                Write-Host "[CVE-2022-41099] File version: $RealNtVersion"
                $VersionString = "$($RealNtVersion.Split('.')[0]).$($RealNtVersion.Split('.')[1])"
                $FileVersion = $($RealNtVersion.Split('.')[2])
                $FileRevision = $($RealNtVersion.Split('.')[3])
                if ($VersionString -eq "10.0") {
                    $ExpectedRevision = 0
                    switch ($FileVersion) {
                        "10240" { Write-Host "[CVE-2022-41099] Windows 10, version 1507, file revision should be >= 19567."; $ExpectedRevision = 19567 }
                        "14393" { Write-Host "[CVE-2022-41099] Windows 10, version 1607, file revision should be >= 5499."; $ExpectedRevision = 5499 }
                        "17763" { Write-Host "[CVE-2022-41099] Windows 10, version 1809, file revision should be >= 3646."; $ExpectedRevision = 3646 }
                        "19041" { Write-Host "[CVE-2022-41099] Windows 10, version 2004, file revision should be >= 2247."; $ExpectedRevision = 2247 }
                        "22000" { Write-Host "[CVE-2022-41099] Windows 11, version 21H2, file revision should be >= 1215."; $ExpectedRevision = 1215 }
                        "22621" { Write-Host "[CVE-2022-41099] Windows 11, version 22H2, file revision should be >= 815."; $ExpectedRevision = 815 }
                        default { Write-Host "[CVE-2022-41099] Unsupported OS."}
                    }
                    if ($ExpectedRevision -ne 0) {
                        if ($FileRevision -lt $ExpectedRevision) { Write-Host "[CVE-2022-41099] WinRE is vulnerable." -ForegroundColor Red }
                        else { Write-Host "[CVE-2022-41099] WinRE is not vulnerable." -ForegroundColor Green }
                    }
                }
                else { Write-Host "[CVE-2022-41099] Unsupported version: $VersionString" }
                Write-Host "[CVE-2022-41099] Unmount RE..."
                dism.exe /unmount-image /mountDir:$MountDir /discard
            }
            else { Write-Host "[CVE-2022-41099] Failed to mount WinRE." }
            Write-Host "[CVE-2022-41099] Remove mount directory."
            Remove-Item -Path $MountDir
        }
        else { Write-Host "[CVE-2022-41099] No TPM-based protector was found." }
    }
    else { Write-Host "[CVE-2022-41099] BitLocker is not enabled." }
}

Questo script è il metodo più affidabile e "idiot proof" per verificare se il sistema è vulnerabile o meno, con un risultato parlante senza lasciare dubbi.

WinRE is vulnerable
WinRE is vulnerable
WinRE is not vulnerable
WinRE is not vulnerable

Nonostante la sua semplicità e chiarezza, questo codice è poco indicato per un check automatico in background, perché i comandi usati sono abbastanza gravosi sulle risorse di sistema e poco indicati per un "health check", che a seconda della configurazione può essere eseguito anche più volte al giorno.

Cercando un po' in giro ho scoperto che è possibile eseguire lo stesso controllo senza dover fare il mount della partizione di Recovery, grazie a questo script:
https://p0w3rsh3ll.wordpress.com/2023/04/06/patching-cve-2022-41099/

Inizialmente non funzionava sui miei sistemi, ma semplicemente perché lo script è tarato per i sistemi in inglese, mentre l'output di ReAgentC.exe nei sistemi in italiano o in altre lingue è differente. (Thanks, Micro💩...)

Ho quindi modificato lo script per identificare la partizione di recovery a prescindere dalla lingua del sistema:

$null,$RELoc = ((& (Get-Command "$($env:systemroot)\system32\ReAgentC.exe") @('/info')) | Where-Object { $_ -match '\\Recovery\\WindowsRE' } ) -split ':'
$RELoc = $RELoc.Trim()
Get-WindowsImageContent -ImagePath "$($RELoc)\winre.wim" -Index 1 |
Where-Object { $_ -match 'Windows\\WinSxS\\amd64_microsoft-windows-bootmenuux_.+_(?<Version>(\d{1,5}\.)+\d{1,5})_.+\\BootMenuUX\.dll'} |
ForEach-Object { ([regex]'Windows\\WinSxS\\amd64_microsoft-windows-bootmenuux_.+_(?<Version>(\d{1,5}\.)+\d{1,5})_.+\\BootMenuUX\.dll').Matches($_) |
Select-Object -Expand Groups | Where-Object Name -eq 'Version' | Select-Object -ExpandProperty Value}| ForEach-Object { try {[version]$_}catch{ 'Failed'}} |
Sort-Object -Descending | Select-Object -First 1 | ForEach-Object ToString

Questo codice però non è così versatile come il precedente, mostra semplicemente la versione del file BootMenuUX.dll, sta poi all'utente finale capire se la patch è necessaria o meno.

Sfruttando questo script veloce ho integrato la logica di verifica automatica implementata nello script precedente, ottenendo quindi uno script veloce ma allo stesso tempo "parlante":

$null,$RELoc = ((& (Get-Command "$($env:systemroot)\system32\ReAgentC.exe") @('/info')) | Where-Object { $_ -match '\\Recovery\\WindowsRE' } ) -split ':'
$RELoc = $RELoc.Trim()
$RealNtVersion = (Get-WindowsImageContent -ImagePath "$($RELoc)\winre.wim" -Index 1 |
Where-Object { $_ -match 'Windows\\WinSxS\\amd64_microsoft-windows-bootmenuux_.+_(?<Version>(\d{1,5}\.)+\d{1,5})_.+\\BootMenuUX\.dll'} |
ForEach-Object { ([regex]'Windows\\WinSxS\\amd64_microsoft-windows-bootmenuux_.+_(?<Version>(\d{1,5}\.)+\d{1,5})_.+\\BootMenuUX\.dll').Matches($_) |
Select-Object -Expand Groups | Where-Object Name -eq 'Version' | Select-Object -ExpandProperty Value}| ForEach-Object { try {[version]$_}catch{ 'Failed'}} |
Sort-Object -Descending | Select-Object -First 1 | ForEach-Object ToString)

$VersionString = "$($RealNtVersion.Split('.')[0]).$($RealNtVersion.Split('.')[1])"
                $FileVersion = $($RealNtVersion.Split('.')[2])
                $FileRevision = $($RealNtVersion.Split('.')[3])
                if ($VersionString -eq "10.0") {
                    $ExpectedRevision = 0
                    switch ($FileVersion) {
                        "10240" { Write-Host "[CVE-2022-41099] Windows 10, version 1507, file revision should be >= 19567."; $ExpectedRevision = 19567 }
                        "14393" { Write-Host "[CVE-2022-41099] Windows 10, version 1607, file revision should be >= 5499."; $ExpectedRevision = 5499 }
                        "17763" { Write-Host "[CVE-2022-41099] Windows 10, version 1809, file revision should be >= 3646."; $ExpectedRevision = 3646 }
                        "19041" { Write-Host "[CVE-2022-41099] Windows 10, version 2004, file revision should be >= 2247."; $ExpectedRevision = 2247 }
                        "22000" { Write-Host "[CVE-2022-41099] Windows 11, version 21H2, file revision should be >= 1215."; $ExpectedRevision = 1215 }
                        "22621" { Write-Host "[CVE-2022-41099] Windows 11, version 22H2, file revision should be >= 815."; $ExpectedRevision = 815 }
                        default { Write-Host "[CVE-2022-41099] Unsupported OS."}
                    }
                    if ($ExpectedRevision -ne 0) {
                        if ([int]$FileRevision -lt $ExpectedRevision) { Write-Host "[CVE-2022-41099] WinRE is vulnerable." -ForegroundColor Red }
                        else { Write-Host "[CVE-2022-41099] WinRE is not vulnerable." -ForegroundColor Green }
                    }
                }
WinRE is not vulnerable
WinRE is not vulnerable

Ora che abbiamo il nostro script di verifica veloce e parlante, possiamo utilizzarlo come base per costruire una policy di verifica compliance in Intune.

Introduzione alle Custom Compliance Policies con Intune

In Microsoft Intune è possibile creare delle Compliance Policies personalizzate. La policy è composta da due elementi: uno script PowerShell di "detection" che viene eseguito sulla macchina e un JSON per configurare i valori che desideriamo avere nella nostra "compliance".

Anche qui non mi dilungo sulla letteratura ma vi lascio la risorsa ufficiale di Microsoft:
https://learn.microsoft.com/en-us/mem/intune/protect/compliance-use-custom-settings

E vi lascio anche questo articolo molto chiaro che mi ha aiutato a capire meglio il funzionamento:
https://call4cloud.nl/2021/11/the-last-days-of-custom-compliance/

Creiamo la nostra Policy di Compliance

Script PowerShell di detection

La prima cosa da fare è costruire lo script PowerShell di detection. Prendendo come base lo script "veloce", lo adattiamo per ritornare un JSON come richiesto dalle specifiche di Intune.

$Result = "ERROR"
$null,$RELoc = ((& (Get-Command "$($env:systemroot)\system32\ReAgentC.exe") @('/info')) | Where-Object { $_ -match '\\Recovery\\WindowsRE' } ) -split ':'
$RELoc = $RELoc.Trim()
$RealNtVersion = (Get-WindowsImageContent -ImagePath "$($RELoc)\winre.wim" -Index 1 |
Where-Object { $_ -match 'Windows\\WinSxS\\amd64_microsoft-windows-bootmenuux_.+_(?<Version>(\d{1,5}\.)+\d{1,5})_.+\\BootMenuUX\.dll'} |
ForEach-Object { ([regex]'Windows\\WinSxS\\amd64_microsoft-windows-bootmenuux_.+_(?<Version>(\d{1,5}\.)+\d{1,5})_.+\\BootMenuUX\.dll').Matches($_) |
Select-Object -Expand Groups | Where-Object Name -eq 'Version' | Select-Object -ExpandProperty Value}| ForEach-Object { try {[version]$_}catch{ 'Failed'}} |
Sort-Object -Descending | Select-Object -First 1 | ForEach-Object ToString)
$VersionString = "$($RealNtVersion.Split('.')[0]).$($RealNtVersion.Split('.')[1])"
                $FileVersion = $($RealNtVersion.Split('.')[2])
                $FileRevision = $($RealNtVersion.Split('.')[3])
                if ($VersionString -eq "10.0") {
                    $ExpectedRevision = 0
                    switch ($FileVersion) {
                        "10240" { $MatchVersion = "[CVE-2022-41099] Windows 10, version 1507, file revision should be 19567 or greater"; $ExpectedRevision = 19567 }
                        "14393" { $MatchVersion = "[CVE-2022-41099] Windows 10, version 1607, file revision should be 5499 or greater"; $ExpectedRevision = 5499 }
                        "17763" { $MatchVersion = "[CVE-2022-41099] Windows 10, version 1809, file revision should be 3646 or greater"; $ExpectedRevision = 3646 }
                        "19041" { $MatchVersion = "[CVE-2022-41099] Windows 10, version 2004, file revision should be 2247 or greater"; $ExpectedRevision = 2247 }
                        "22000" { $MatchVersion = "[CVE-2022-41099] Windows 11, version 21H2, file revision should be 1215 or greater"; $ExpectedRevision = 1215 }
                        "22621" { $MatchVersion = "[CVE-2022-41099] Windows 11, version 22H2, file revision should be 815 or greater"; $ExpectedRevision = 815 }
                        default { $MatchVersion = "[CVE-2022-41099] Unsupported OS."}
                    }
                    if ($ExpectedRevision -ne 0) {
                        if ($FileRevision -lt $ExpectedRevision) { $Result = "[CVE-2022-41099] WinRE is vulnerable" }
                        else { $Result = "[CVE-2022-41099] WinRE is not vulnerable" }
                    }
                }
                
$hash = @{Result = $Result; RealNtVersion = $RealNtVersion; MatchVersion = $MatchVersion}
return $hash | ConvertTo-Json -Compress

Nello script ho deciso di passare al JSON più informazioni di quante ne servono veramente. Purtroppo Intune non permette di mostrare i valori passati nella dashboard, quindi non avremo modo di vedere per esempio il valore di $RealNtVersion e $MatchVersion, ma ho deciso comunque di esporli nel JSON perché vengono registrati nei log in locale sulla macchina e potrebbero essere utili per fare diagnostica.

JSON di configurazione

A questo punto creiamo il JSON con la logica che ci interessa.

{
"Rules":[ 
    { 
        "SettingName":"Result",
        "Operator":"IsEquals",
        "DataType":"String",
        "Operand":"[CVE-2022-41099] WinRE is not vulnerable",
        "MoreInfoUrl":"https://example.com",
        "RemediationStrings":[ 
           { 
              "Language":"en_US",
              "Title":"[CVE-2022-41099] WinRE is vulnerable.",
              "Description": "Contattare il reparto IT per le verifiche."
           },
           { 
            "Language":"it_IT",
            "Title":"[CVE-2022-41099] WinRE is vulnerable.",
            "Description": "Contattare il reparto IT per le verifiche."
           }
        ]
    },
    { 
        "SettingName":"RealNtVersion",
        "Operator":"NotEquals",
        "DataType":"String",
        "Operand":"",
        "MoreInfoUrl":"https://example.com",
        "RemediationStrings":[ 
           { 
              "Language":"en_US",
              "Title":"[CVE-2022-41099] WinRE is vulnerable.",
              "Description": "Contattare il reparto IT per le verifiche."
           },
           { 
            "Language":"it_IT",
            "Title":"[CVE-2022-41099] WinRE is vulnerable.",
            "Description": "Contattare il reparto IT per le verifiche."
           }
        ]
    },
    { 
        "SettingName":"MatchVersion",
        "Operator":"NotEquals",
        "DataType":"String",
        "Operand":"",
        "MoreInfoUrl":"https://example.com",
        "RemediationStrings":[ 
           { 
              "Language":"en_US",
              "Title":"[CVE-2022-41099] WinRE is vulnerable.",
              "Description": "Contattare il reparto IT per le verifiche."
           },
           { 
            "Language":"it_IT",
            "Title":"[CVE-2022-41099] WinRE is vulnerable.",
            "Description": "Contattare il reparto IT per le verifiche."
           }
        ]
    }

 ]
}

I due valori $RealNtVersion e $MatchVersion li facciamo validare sempre a true poiché non ci interessano nella logica di compliance. Il valore importante è $Result.

Configuriamo il tutto nella Dashboard

Entriamo nella dashboard di Intune e carichiamo il nostro PowerShell andando in
Devices > Compliance Policies > Scripts e poi Add > Windows 10 and later

Compliance policies | Scripts
Compliance policies | Scripts

Qui diamo un bel nome al nostro script, io l'ho chiamato "WinRE Vulnerability [CVE-2022-41099]", andiamo avanti e incolliamo lo script PowerShell lasciando le configurazioni come da screen. Al termine salviamo il tutto.

Create custom script
Create custom script

Una volta caricato lo script di detection, dobbiamo creare la Custom Compliance Policy e per farlo si va in Devices > Windows > Compliance Policies, qui selezioniamo Create Policy > Windows 10 and later.

Diamo un nome alla nostra Compliance Policy (es. "Windows - WinRE not vulnerable") e proseguiamo selezionando Custom Compliance, attiviamo Require, selezioniamo il nostro script di detection e carichiamo il JSON creato precedentemente.

Se tutto è stato impostato correttamente dovrebbe popolare i settings impostati nel JSON.

Windows | Compliance policies
Windows | Compliance policies

A questo punto possiamo proseguire come una classica Compliance Policy, assegnando le azioni in caso di non-compliance e gli assignments, che variano in base al tipo di ambiente. Personalmente ho configurato di segnalare immediatamente la non-compliance assegnando la policy a tutti i dispositivi del tenant.

Fatto questo... bisogna aspettare! 

Purtroppo ci vuole circa mezz'oretta prima che una Compliance Policy venga recepita dall'ambiente di Intune e distribuita. Maggiori dettagli li trovate nel link riportato sopra di call4cloud. 

Una volta che la policy si sarà propagata a dovere, sarà possibile vedere a colpo d'occhio quali sono i sistemi vulnerabili alla CVE-2022-41099 e pianificare l'installazione manuale della patch.

Windows - WinRE Not Vulnerable | Device Status
Windows - WinRE Not Vulnerable | Device Status

Come applicare la patch per CVE-2022-41099

A marzo 2023 Microsoft ha rilasciato uno script PowerShell per applicare la patch alla partizione WinRE. Qui trovate la pagina di riferimento:
https://support.microsoft.com/en-us/topic/kb5025175-updating-the-winre-partition-on-deployed-devices-to-address-security-vulnerabilities-in-cve-2022-41099-ba6621fa-5a9f-48f1-9ca3-e13eb56fb589

(In realtà due script, ma se avete una versione di Windows più vecchia della 2004 direi che WinRE è l'ultimo dei vostri problemi.)

Il problema di questo script è che bisogna prima scaricare il pacchetto di aggiornamento "Safe OS Dynamic Update" corretto e poi eseguire lo script, passando il percorso del pacchetto come parametro.

C'è un po' di letteratura in giro in merito all'applicazione della patch, ve li linko per conoscenza.

Qui c'è uno dei post più vecchi sulla questione, che poi è stato aggiornato rimandando allo script ufficiale Microsoft:
https://www.elevenforum.com/t/important-issue-to-be-aware-of-if-you-use-bitlocker-on-your-os-drive.11818/page-2#post-244851 

Qui trovate uno script che fa un po' di tutto (e un po' di troppo anche, imho) ma come lo script Microsoft bisogna predisporre i file nella giusta cartella:
https://manima.de/2023/01/modify-winre-patches-drivers-and-cve-2022-41099/

Qui trovate uno script per scaricare i file di aggiornamento per i vari sistemi, ma i link nello script non sono aggiornati alle ultime versioni di Safe OS rilasciate: 
https://homotechsual.dev/2023/01/17/Download-CVE-2022-41099-Patches/

Qui c'è tutto il mega-spiegone di Microsoft in merito all'aggiornamento di WinRE:
https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/add-update-to-winre

1-Click WinRE Download&Patch

Nessuno degli script trovati in giro fa una cosa molto semplice: scaricare l'ultima versione di SafeOS compatibile e installarla.

Ho quindi deciso di prendere lo script di Microsoft e "appiccicarci" sopra in modo molto grezzo e rozzo il download automatico della patch basandomi sulla logica dello script di detection usato in precedenza. Ho modificato la parte iniziale dello script di Microsoft per rimuovere la richiesta dei parametri in ingresso e prendere il file scaricato nella tmp.

ATTENZIONE: lo script non è stato testato a fondo in ogni casistica, è scritto male e di fretta, potrebbe andare in errore. La logica di patching è immutata dallo script di Microsoft originale ma non mi assumo comunque nessuna responsabilità in caso di fallimento critico.

INFO: Lo script scarica le patch attuali aggiornate al 22 agosto 2023 ed è compatibile con Windows 10 dalla versione 2004 alla 22h2 e Windows 11 dalla 21h2 alla 22h2.

Di seguito lo script:

$null,$RELoc = ((& (Get-Command "$($env:systemroot)\system32\ReAgentC.exe") @('/info')) | Where-Object { $_ -match '\\Recovery\\WindowsRE' } ) -split ':'
$RELoc = $RELoc.Trim()
$RealNtVersion = (Get-WindowsImageContent -ImagePath "$($RELoc)\winre.wim" -Index 1 |
Where-Object { $_ -match 'Windows\\WinSxS\\amd64_microsoft-windows-bootmenuux_.+_(?<Version>(\d{1,5}\.)+\d{1,5})_.+\\BootMenuUX\.dll'} |
ForEach-Object { ([regex]'Windows\\WinSxS\\amd64_microsoft-windows-bootmenuux_.+_(?<Version>(\d{1,5}\.)+\d{1,5})_.+\\BootMenuUX\.dll').Matches($_) |
Select-Object -Expand Groups | Where-Object Name -eq 'Version' | Select-Object -ExpandProperty Value}| ForEach-Object { try {[version]$_}catch{ 'Failed'}} |
Sort-Object -Descending | Select-Object -First 1 | ForEach-Object ToString)

$VersionString = "$($RealNtVersion.Split('.')[0]).$($RealNtVersion.Split('.')[1])"
                $FileVersion = $($RealNtVersion.Split('.')[2])
                if ($VersionString -eq "10.0") {
                    $WebLink = ""
                    switch ($FileVersion) {
                        "19041" { Write-Host "Windows 10, version 2004"; $WebLink = "https://catalog.s.download.windowsupdate.com/c/msdownload/update/software/crup/2023/06/windows10.0-kb5027389-x64_8d1880d95f920fc8a4c2d96ca8d3b44f3e044581.cab" }
                        "22000" { Write-Host "Windows 11, version 21H2"; $WebLink = "https://catalog.s.download.windowsupdate.com/c/msdownload/update/software/crup/2023/06/windows10.0-kb5027572-x64_2540b19f40241a5c7cae4e782e67c17383965125.cab" }
                        "22621" { Write-Host "Windows 11, version 22H2"; $WebLink = "https://catalog.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/fd6044df-9ac1-4633-a7f7-0964783d317c/public/windows11.0-kb5028495-x64_d16975c555b3b952605ecf2005c3f5cd75243eb6.cab" }
                        default { Write-Host "Unsupported OS."; exit 1}
                    }
                    if ($WebLink -ne "") {
                        # Get the temporary folder path
                        $tempFolder = [System.IO.Path]::GetTempPath()

                        # Full path where the file will be saved
                        $packagePath = Join-Path -Path $tempFolder -ChildPath "winre-patch.cab"

                        # Download the file
                        Invoke-WebRequest -Uri $WebLink -OutFile $packagePath

                        Write-Host "The file has been downloaded to: $packagePath"
                    }
                }


################################################################################################
#
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
#
# 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.
#
################################################################################################

$workDir=""

# ------------------------------------
# Help functions
# ------------------------------------
# Log message

function LogMessage([string]$message) {
    $message = "$([DateTime]::Now) - $message"
    Write-Host $message
}

function IsTPMBasedProtector {
    $DriveLetter = $env:SystemDrive
    LogMessage("Checking BitLocker status")
    $BitLocker = Get-WmiObject -Namespace "Root\cimv2\Security\MicrosoftVolumeEncryption" -Class "Win32_EncryptableVolume" -Filter "DriveLetter = '$DriveLetter'"

    if (-not $BitLocker) {
        LogMessage("No BitLocker object")
        return $False
    }

    $protectionEnabled = $False
    switch ($BitLocker.GetProtectionStatus().protectionStatus) {
        ("0") {
            LogMessage("Unprotected")
            break
        }
        ("1") {
            LogMessage("Protected")
            $protectionEnabled = $True
            break
        }
        ("2") {
            LogMessage("Uknown")
            break
        }
        default {
            LogMessage("NoReturn")
            break
        }
    }

    if (!$protectionEnabled) {
        LogMessage("Bitlocker isn’t enabled on the OS")
        return $False
    }

    $ProtectorIds = $BitLocker.GetKeyProtectors("0").volumekeyprotectorID
    $return = $False

    foreach ($ProtectorID in $ProtectorIds) {
        $KeyProtectorType = $BitLocker.GetKeyProtectorType($ProtectorID).KeyProtectorType
        switch($KeyProtectorType) {
            "1" {
                LogMessage("Trusted Platform Module (TPM)")
                $return = $True
                break
            }
            "4" {
                LogMessage("TPM And PIN")
                $return = $True
                break
            }
            "5" {
                LogMessage("TPM And Startup Key")
                $return = $True
                break
            }
            "6" {
                LogMessage("TPM And PIN And Startup Key")
                $return = $True
                break
            }
            default {break}
        } #endSwitch
    } #EndForeach

    if ($return) {
        LogMessage("Has TPM-based protector")
    }
    else {
        LogMessage("Doesn't have TPM-based protector")
    }

    return $return
}

function SetRegistrykeyForSuccess {
    reg add HKLM\SOFTWARE\Microsoft\PushButtonReset /v WinREPathScriptSucceed /d 1 /f
}

function TargetfileVersionExam([string]$mountDir) {
    # Exam target binary
    $targetBinary=$mountDir + "\Windows\System32\bootmenuux.dll"
    LogMessage("TargetFile: " + $targetBinary)
    $realNTVersion = [Diagnostics.FileVersionInfo]::GetVersionInfo($targetBinary).ProductVersion
    $versionString = "$($realNTVersion.Split('.')[0]).$($realNTVersion.Split('.')[1])"
    $fileVersion = $($realNTVersion.Split('.')[2])
    $fileRevision = $($realNTVersion.Split('.')[3])
    LogMessage("Target file version: " + $realNTVersion)
    if (!($versionString -eq "10.0")) {
        LogMessage("Not Windows 10 or later")
        return $False
    }

    $hasUpdated = $False

    #Windows 10, version 1507 10240.19567
    #Windows 10, version 1607 14393.5499
    #Windows 10, version 1809 17763.3646
    #Windows 10, version 2004 1904X.2247
    #Windows 11, version 21H2 22000.1215
    #Windows 11, version 22H2 22621.815
    switch ($fileVersion) {
        "10240" {
            LogMessage("Windows 10, version 1507")
            if ($fileRevision -ge 19567) {
                LogMessage("Windows 10, version 1507 with revision " + $fileRevision + " >= 19567, updates have been applied")
                $hasUpdated = $True
            }
            break
        }
        "14393" {
            LogMessage("Windows 10, version 1607")
            if ($fileRevision -ge 5499) {
                LogMessage("Windows 10, version 1607 with revision " + $fileRevision + " >= 5499, updates have been applied")
                $hasUpdated = $True
            }
            break
        }
        "17763" {
            LogMessage("Windows 10, version 1809")
            if ($fileRevision -ge 3646) {
                LogMessage("Windows 10, version 1809 with revision " + $fileRevision + " >= 3646, updates have been applied")
                $hasUpdated = $True
            }
            break
        }
        "19041" {
            LogMessage("Windows 10, version 2004")
            if ($fileRevision -ge 2247) {
                LogMessage("Windows 10, version 2004 with revision " + $fileRevision + " >= 2247, updates have been applied")
                $hasUpdated = $True
            }
            break
        }
        "22000" {
            LogMessage("Windows 11, version 21H2")
            if ($fileRevision -ge 1215) {
                LogMessage("Windows 11, version 21H2 with revision " + $fileRevision + " >= 1215, updates have been applied")
                $hasUpdated = $True
            }
            break
        }
        "22621" {
            LogMessage("Windows 11, version 22H2")
            if ($fileRevision -ge 815) {
                LogMessage("Windows 11, version 22H2 with revision " + $fileRevision + " >= 815, updates have been applied")
                $hasUpdated = $True
            }
            break
        }
        default {
            LogMessage("Warning: unsupported OS version")
        }
    }

    return $hasUpdated
}

function PatchPackage([string]$mountDir, [string]$packagePath) {
    # Exam target binary
    $hasUpdated =TargetfileVersionExam($mountDir)
    if ($hasUpdated) {
        LogMessage("The update has already been added to WinRE")
        SetRegistrykeyForSuccess
        return $False
    }

    # Add package
    LogMessage("Apply package:" + $packagePath)
    Dism /Add-Package /Image:$mountDir /PackagePath:$packagePath
    if ($LASTEXITCODE -eq 0) {
        LogMessage("Successfully applied the package")
    }
    else {
        LogMessage("Applying the package failed with exit code: " + $LASTEXITCODE)
        return $False
    }

    # Cleanup recovery image
    LogMessage("Cleanup image")
    Dism /image:$mountDir /cleanup-image /StartComponentCleanup /ResetBase
    if ($LASTEXITCODE -eq 0) {
        LogMessage("Cleanup image succeed")
    }
    else {
        LogMessage("Cleanup image failed: " + $LASTEXITCODE)
        return $False
    }

    return $True
}

# ------------------------------------
# Execution starts
# ------------------------------------
# Check breadcrumb

if (Test-Path HKLM:\Software\Microsoft\PushButtonReset) {
    $values = Get-ItemProperty -Path HKLM:\Software\Microsoft\PushButtonReset

    if (!(-not $values)) {
        if (Get-Member -InputObject $values -Name WinREPathScriptSucceed) {
            $value = Get-ItemProperty -Path HKLM:\Software\Microsoft\PushButtonReset -Name WinREPathScriptSucceed

            if ($value.WinREPathScriptSucceed -eq 1) {
                LogMessage("This script was previously run successfully")
                exit 1
            }
        }
    }
}

if ([string]::IsNullorEmpty($workDir)) {
    LogMessage("No input for mount directory")
    LogMessage("Use default path from temporary directory")
    $workDir = [System.IO.Path]::GetTempPath()
}

LogMessage("Working Dir: " + $workDir)
$name = "CA551926-299B-27A55276EC22_Mount"
$mountDir = Join-Path $workDir $name
LogMessage("MountDir: " + $mountdir)

# Delete existing mount directory
if (Test-Path $mountDir) {
    LogMessage("Mount directory: " + $mountDir + " already exists")
    LogMessage("Try to unmount it")
    Dism /unmount-image /mountDir:$mountDir /discard
    if (!($LASTEXITCODE -eq 0)) {
        LogMessage("Warning: unmount failed: " + $LASTEXITCODE)
    }
    LogMessage("Delete existing mount direcotry " + $mountDir)
    Remove-Item $mountDir -Recurse
}

# Create mount directory
LogMessage("Create mount directory " + $mountDir)
New-Item -Path $mountDir -ItemType Directory

# Set ACL for mount directory
LogMessage("Set ACL for mount directory")
icacls $mountDir /inheritance:r
icacls $mountDir /grant:r SYSTEM:"(OI)(CI)(F)"
icacls $mountDir /grant:r *S-1-5-32-544:"(OI)(CI)(F)"

# Mount WinRE
LogMessage("Mount WinRE:")
reagentc /mountre /path $mountdir
if ($LASTEXITCODE -eq 0) {
    # Patch WinRE
    if (PatchPackage -mountDir $mountDir -packagePath $packagePath) {
        $hasUpdated = TargetfileVersionExam($mountDir)
        if ($hasUpdated) {
            LogMessage("After patch, find expected version for target file")
        }
        else {
            LogMessage("Warning: After applying the patch, unexpected version found for the target file")
        }

        LogMessage("Patch succeed, unmount to commit change")
        Dism /unmount-image /mountDir:$mountDir /commit
        if (!($LASTEXITCODE -eq 0)) {
            LogMessage("Unmount failed: " + $LASTEXITCODE)
            exit 1
        }
        else {
            if ($hasUpdated) {
                if (IsTPMBasedProtector) {
                    # Disable WinRE and re-enable it to let new WinRE be trusted by BitLocker
                    LogMessage("Disable WinRE")
                    reagentc /disable
                    LogMessage("Re-enable WinRE")
                    reagentc /enable
                    reagentc /info
                }

                # Leave a breadcrumb indicates the script has succeed
                SetRegistrykeyForSuccess
            }
        }
    }
    else {
        LogMessage("Patch failed or is not applicable, discard unmount")
        Dism /unmount-image /mountDir:$mountDir /discard
        if (!($LASTEXITCODE -eq 0)) {
            LogMessage("Unmount failed: " + $LASTEXITCODE)
            exit 1
        }
    }

}
else {
    LogMessage("Mount failed: " + $LASTEXITCODE)
}

# Cleanup Mount directory in the end
LogMessage("Delete mount direcotry")
Remove-Item $mountDir -Recurse

Per concludere

Vorrei poter scrivere liberamente cosa ne penso di questa patch, dell'approccio che ha avuto Microsoft e della mirabolante procedura che bisogna seguire per garantire la sicurezza del dispositivo, ma vorrei evitare di scrivere imprecazioni su queste pagine, quindi mi limiterò a sperare che quanto scritto possa essere d'aiuto ad altre persone per mettere in sicurezza i propri sistemi senza dover impazzire troppo.

Microsoft, be better please. ❤️

This article was updated on 13 settembre 2023