Face à vulnérabilité affectant la fonction de sécurité BitLocker, Microsoft publie des scripts PowerShell pour les PCs sous Windows 10 et 11. L’objectif est d’automatiser la mise en oeuvre d’un solution de contournement.
Cette défaillance de la fonction de sécurité BitLocker est connue depuis plusieurs mois. Microsoft a reconnu son existence le 8 novembre 2022. Elle est identifiée sous la référence CVE-2022-41099. Il s’agit d’une faille sérieuse. Son exploitation permet de contourner la protection « BitLocker Device Encryption » sur un périphérique de stockage système. Cette gravité est cependant atténuée en raison d’un besoin « physique » à l’appareil pour l’exploiter et accéder aux données chiffrées.
Microsoft propose une solution à ce problème mais sa mise en œuvre demande aux administrateurs système une intervention manuelle dans l’environnement de récupération Windows. Les nouveaux scripts PowerShell proposés par Microsoft visent à automatiser ce processus de mise à jour. Les administrateurs système ont seulement besoin de les exécuter sur les appareils Windows 10 ou 11 pour corriger cette faille de sécurité.
Windows 11 & 10 et le faille BitLocker
Il y a deux scripts PowerShell disponibles. Dans les deux cas l’objectif est le même cependant ils ne visent pas les mêmes Windows. Ainsi
PatchWinREScript_2004plus.ps1 est le script recommandé. Il est compatible avec Windows 10 v2004 et supérieur. Cela inclut toutes les versions de Windows 11.
Le deuxième script, PatchWinREScript_General.ps1 est dit « moins robuste ». Il vise en priorité les appareils Windows 10 plus anciens, ceux exécutant Windows 10 v1909 ou antérieure. Il est également compatible avec des versions plus modernes de Windows 10 ou 11 mais il est conseillé dans ce cas d’utiliser le premier script.
Le site Web de Microsoft propose les codes sources. Il faut les recopier puis les coller dans un fichier texte et l’enregistrer avec l’extension « ps1 » et non « txt ». Les voici.
PatchWinREScript_2004plus.ps1
################################################################################################ # # 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. # ################################################################################################ Param ( [Parameter(HelpMessage="Work Directory for patch WinRE")][string]$workDir="", [Parameter(Mandatory=$true,HelpMessage="Path of target package")][string]$packagePath ) # ------------------------------------ # 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
PatchWinREScript_General.ps1
################################################################################################ # # 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. # ################################################################################################ Param ( [Parameter(HelpMessage="Work Directory for patch WinRE")][string]$workDir="", [Parameter(Mandatory=$true,HelpMessage="Path of target package")][string]$packagePath ) # ------------------------------------ # 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 } } } } # Get WinRE info $WinREInfo = Reagentc /info $findLocation = $False foreach ($line in $WinREInfo) { $params = $line.Split(':') if ($params.count -le 1) { continue } if ($params[1].Lenght -eq 0) { continue } $content = $params[1].Trim() if ($content.Lenght -eq 0) { continue } $index = $content.IndexOf("\\?\") if ($index -ge 0) { LogMessage("Find \\?\ at " + $index + " for [" + $content + "]") $WinRELocation = $content $findLocation = $True } } if (!$findLocation) { LogMessage("WinRE Disabled") exit 1 } LogMessage("WinRE Enabled. WinRE location:" + $WinRELocation) $WinREFile = $WinRELocation + "\winre.wim" 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:") Dism /mount-image /imagefile:$WinREFile /index:1 /mountdir:$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
Nous vous recommandons de faire une sauvegarde de votre système avant d’exécuter ces scripts. Cela permettra de faire rapidement machine arrière en cas de problème.
Je ne comprend pas pourquoi Microsoft ne déploie pas ces scripts via Windows Update, ça serait quand même plus efficace que de demander aux admins sys de faire des GPO pour lancer un truc fourni prêt à l’emploi par Microsoft.