🔐

再起動を跨いで自律完走するセキュアブート(CVE-2023-24932)自動更新スクリプトの実装

に公開

更新履歴


Windowsのセキュアブート要件の変更(CVE-2023-24932対応)において、インターネットに接続されていない閉域網端末へのパッチ適用は自動化の難易度が高い。
本記事では、端末が自身の物理状態(UEFI変数)をスキャンし、複数回の再起動を跨いで処理を継続しつつ、ログを出力して自己消去する完全自律型のPowerShellスクリプトを公開する。

1. はじめに

セキュアブートの更新あるってよ

BlackLotusブートキット等に対抗するため、Microsoftは段階的にセキュアブートの証明書(失効リスト等)の更新を強制している。インターネットに繋がる端末はバックグラウンドでよしなに処理されるが、WSUS運用かつインターネットから切り離された環境では、システム管理者による意図的な介入(レジストリ操作と再起動のオーケストレーション)が必須となる。

セキュアブート証明書の有効期限についてはこちら
https://support.microsoft.com/ja-jp/topic/windows-セキュア-ブート証明書の有効期限と-ca-更新プログラム-7ff40d33-95dc-4c3c-8725-a9b95457578e

そもそもセキュアブートとは?

PCの電源を入れると、OS(Windows)が立ち上がる前に、ブートローダーなどの様々なプログラムが実行しようとしてくる。もしここに悪意のあるマルウェア(ブートキット等)が紛れ込んでしまうと、アンチウイルスソフトが起動する前に乗っ取られてしまう。
そこで、セキュアブートが、プログラムの提示する「デジタル署名」をチェックし、身元が確かなものしか実行させない仕組みになっている。(「DBX(ブラックリスト)」にないか、「DB(ホワイトリスト)」にあるかを読み取り)

4種類のKey(鍵)

端末が持っているKeyには4種類ある。

  • PK (Platform Key):
    オーナー(通常はPCメーカー)。最も強い権限を持ち、後述するKEKの任命や解任を行うことができる。
  • KEK (Key Exchange Key):
    オーナーから権限を委譲された責任者(通常はMicrosoftとPCメーカー)。実際に使われる「有効リスト(DB)」や「拒否リスト(DBX)」のルールを更新する権限を持つ。
    • 今回追加(Microsoft Corporation KEK 2K CA 2023)
  • DB (Signature Database):
    照合するホワイトリスト。「この署名を持つプログラムは安全なので実行を許可する」というリスト。
    • 今回追加(Windows UEFI CA 2023)
  • DBX (Revoked Signature Database):
    実行させてはならないブラックリスト。
    • 今回追加(Microsoft Windows Production PCA 2011)

2. 検証した前提条件

行政機関や厳格なエンタープライズでよくある「インターネットから完全に隔離された環境」を想定している。

  • OS: Windows 11 25H2
  • パッチ状況: WSUS経由で2026年2月の累積更新プログラムを適用済み。
    (※前提要件である2025年7月8日リリースの月次パッチが含まれているため条件クリア)
  • ネットワーク: キッティング直後からインターネット接続なし(完全オフライン)。
  • ハードウェア: 2023年納品モデル。UEFIのバージョンは出荷状態から未更新。同一メーカーで統一。

3. 実機の状態確認

  1. セキュアブート有効状態の確認
    セキュアブートはWindows11の最小システム要件になっているが、念のため確認
  • 「msinfo32.exe」を実行し、「セキュアブートの状態」を確認
  • PowerShellを管理者で起動し、「Confirm-SecureBootUEFI」コマンドを実行してTrue が返るか。
    のどちらかで確認できる。
    セキュアブートの状態を確認するmsinfo32.exeの画面
    msinfo32.exeでセキュアブートの状態を確認した画面
  1. 証明書が未更新かどうかの確認(PowerShellを管理者で起動して実行)
  • 新証明書がDBに入っているかを確認(True=ある False=ない)
[System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI -Name db).bytes) -match 'Windows UEFI CA 2023'
  • 2026年3月累積更新プログラム適用後であればこちら
    • 「Windows UEFI CA 2023」がリストにあれば入っている
(Get-SecureBootUEFI -Name DB -Decoded).Subject -match 'Windows UEFI CA 2023'
  • 新KEKが入っているか確認(True=ある False=ない)
[System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI kek).bytes) -match 'Microsoft Corporation KEK 2K CA 2023'
  • 2026年3月累積更新プログラム適用後であればこちら
    • 「Microsoft Corporation KEK 2K CA 2023」がリストにあれば入っている
(Get-SecureBootUEFI -Name KEK -Decoded).Subject -match 'Microsoft Corporation KEK 2K CA 2023'
  • DBxにPCA2011が追記されて失効されているか(True=ある False=ない)
[System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI dbx).bytes) -match 'Microsoft Windows Production PCA 2011'
  • 2026年3月累積更新プログラム適用後であればこちら
    • 「Microsoft Windows Production PCA 2011」がリストにあれば入っている
(Get-SecureBootUEFI -Name dbx -Decoded).Subject -match 'Microsoft Windows Production PCA 2011'
  • ブートローダーのデジタル署名が更新されているか
mountvol S: /S #ドライブレターは空いているものを指定
(Get-PfxCertificate -FilePath "S:\EFI\Microsoft\Boot\bootmgfw.efi").issuer -match 'Windows UEFI CA 2023'
mountvol S: /D #必ずアンマウントすること
  • 古いブートマネージャーのSVNを取り消すため処理も必要だが、イベントビューアーで確認するしかない。

MSの説明は↓の通りだけど、ローバックさせない仕組みらしい。

ブートマネージャーの実行が開始されると、ファームウェアに格納されているセキュリティで保護されたバージョン番号 (SVN) とブートマネージャーに組み込まれている SVN を比較することによって、自己チェックが実行されます。 ブートマネージャーの SVN がファームウェアに格納されている SVN よりも低い場合、ブートマネージャーは実行を拒否します。 この機能により、攻撃者はブートマネージャーを古い更新されていないバージョンにロールバックできなくなります。

4. 検証した内容

  • UEFIのアップデートを実施してから行った。UEFIのサイレントでのアップデート方法はメーカーによるので割愛

手動で処理していく場合の手順

  1. 更新された証明書定義を DB&KEK にインストール
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot" -Name "AvailableUpdates" -Value 0x5944 -Type DWord
Start-ScheduledTask -TaskName "\Microsoft\Windows\PI\Secure-Boot-Update"
  1. 再起動
  2. デバイスのブート マネージャーを更新
Start-ScheduledTask -TaskName "\Microsoft\Windows\PI\Secure-Boot-Update"
  1. 再起動
  2. 失効を有効
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot" -Name "AvailableUpdates" -Value 0x80 -Type DWord
Start-ScheduledTask -TaskName "\Microsoft\Windows\PI\Secure-Boot-Update"
  1. SVN 更新プログラムをファームウェアに適用
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot" -Name "AvailableUpdates" -Value 0x200 -Type DWord
Start-ScheduledTask -TaskName "\Microsoft\Windows\PI\Secure-Boot-Update"

イベントビューアーで実行状況を確認する方法

  1. 証明書が未更新かどうかの確認(PowerShellを管理者で起動して実行)

で記載したコマンドで更新されているかのチェックが可能だが、イベントビューアーでも確認は可能。
※イベントビューアーは古いイベントから削除されてしまうので、前に適用済みの端末ではイベントがない可能性あり。

Windowsログ → システム内でソースが「TPM-WMI」の以下のイベントIDを確認する

  • 1036
    セキュア ブート Db 更新プログラムが正常に適用されました
  • 1043
    セキュア ブート KEK 更新プログラムが正常に適用されました
  • 1799
    Windows UEFI CA 2023 で署名されたブート マネージャーが正常にインストール
  • 1808
    このデバイスはセキュア ブート CA/キーを更新しました。このデバイス署名情報はここに含まれています。
  • 1037
    Microsoft Windows Production PCA 2011 を取り消すためのセキュア ブート Dbx 更新プログラムが正常に適用
  • 1042
    古いブート マネージャーの SVN を取り消すためのセキュア ブート Dbx 更新プログラムが正常に適用されました

自動更新スクリプトの紹介

【処理ステップの解説】

  1. ステップ0(スキャンとジャンプ):
    Get-SecureBootUEFI と イベントログ 1042 を直接スキャン。
    過去の検証で「DBxだけ更新されてSVNが未了」という中途半端な端末を見つけた場合、自動的に 0x200 を単発で打つリカバリモードも対応

  2. フェーズ0 (0x5944):
    DB/KEKの更新準備。タスクを仕込んで再起動を待つ。

  3. フェーズ1 (0x4000 待機):
    再起動後、OSがパッチを受け入れる準備ができるまで待機。

  4. フェーズ2 (0x280 並列適用):
    DBxとSVNを一括更新。
    (もともとは、0x80してから0x200と処理を分けていたが、0x280で一括処理OKとMSドキュメントに記載あったので変更した)

  5. 最終処理
    レジストリが0に戻ったのを確認し、最終的な監査ログ(CSV)をファイルサーバーに吐き出し、タスクと自身を完全に消去(クリーンアップ)して終了する。

SecureBootUpdate.ps1
<#
.SYNOPSIS
    SecureBoot証明書(DB/Bootloader/DBX/SVN) 統合更新
#>

# ---------------------------------------------------------
# 1. 基本設定と変数定義
# ---------------------------------------------------------
$SharedFolder = "\\FileServer\Log\SecureBootUpdate" # ★本番環境のパスに変更してください
$HostName     = $env:COMPUTERNAME
$LogFilePath  = "$SharedFolder\SecureBootResult_$($HostName).csv"

$MyTask = "Resume_SecureBoot_Update"
$MyPath = $MyInvocation.MyCommand.Path

$StateRegPath = "HKLM:\SOFTWARE\ITAdmin\SecureBootUpdate"
$StateRegName = "UpdatePhase"
$ControlPath  = "HKLM:\SYSTEM\CurrentControlSet\Control\SecureBoot"
$PiTaskName   = "\Microsoft\Windows\PI\Secure-Boot-Update"

# ---------------------------------------------------------
# 2. 補助関数群
# ---------------------------------------------------------
# 再起動継続用タスクの登録(遅延・電源オプション最適化版)
function Register-ResumeTask {
    $action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"$MyPath`""
    
    # 1. トリガーの生成と「10分の遅延」の設定
    $trigger = New-ScheduledTaskTrigger -AtStartup
    $trigger.Delay = "PT10M"
    
    # 2. 条件の設定:「AC電源のみ」のチェックを外し、バッテリー駆動への切り替え時も停止しない
    $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
    $principal = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\SYSTEM" -LogonType ServiceAccount -RunLevel Highest
    
    # Settingsパラメータを追加してタスクを登録
    Register-ScheduledTask -TaskName $MyTask -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Force | Out-Null
}
# ---------------------------------------------------------
# 全ての痕跡を完全に消去する機能
function Invoke-Cleanup {
    Unregister-ScheduledTask -TaskName $MyTask -Confirm:$false -ErrorAction SilentlyContinue
    if (Test-Path $StateRegPath) { Remove-Item -Path $StateRegPath -Force -Recurse -ErrorAction SilentlyContinue }
    if (Test-Path $MyPath) { Remove-Item -Path $MyPath -Force -ErrorAction SilentlyContinue }
}
# ---------------------------------------------------------
# 指定のレジストリ値になるまで待機する機能
function Wait-ForRegistryValue ($ExpectedValue, $WaitMinutes = 10) {
    $timeout = (Get-Date).AddMinutes($WaitMinutes)
    while ((Get-Date) -lt $timeout) {
        $val = Get-ItemPropertyValue -Path $ControlPath -Name "AvailableUpdates" -ErrorAction SilentlyContinue
        if ($null -ne $val -and $val -eq $ExpectedValue) { return $true }
        Start-Sleep -Seconds 30
    }
    return $false
}
# ---------------------------------------------------------
# 最終監査ログを出力して自己消滅する機能(共通処理)
function Write-AuditLogAndExit {
    $chkDb   = "False"
    $chkKek  = "False"
    $chkBoot = "Error"
    $chkDbx  = "False"
    $chkSvn  = "False"

    try { $chkDb  = [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI db).bytes) -match 'Windows UEFI CA 2023' } catch {}
    try { $chkKek = [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI KEK).bytes) -match 'Microsoft Corporation KEK 2K CA 2023' } catch {}
    try { $chkDbx = [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI dbx).bytes) -match 'Microsoft Windows Production PCA 2011' } catch {}

    $event1042 = Get-WinEvent -FilterHashtable @{LogName="System"; ProviderName="Microsoft-Windows-TPM-WMI"; Id=1042} -MaxEvents 1 -ErrorAction SilentlyContinue
    if ($event1042) { $chkSvn = "True" }

    $mountDrive = $null
    try {
        $freeDrive = (90..67 | ForEach-Object { [char]$_ + ':' } | Where-Object { !(Test-Path $_) })[0]
        if ($freeDrive) {
            $mountDrive = $freeDrive
            mountvol $mountDrive /S
            $chkBoot = (Get-PfxCertificate -FilePath "$mountDrive\EFI\Microsoft\Boot\bootmgfw.efi").issuer -match 'Windows UEFI CA 2023'
        }
    } finally {
        if ($mountDrive -and (Test-Path $mountDrive)) { mountvol $mountDrive /D }
    }

    $finalRecord = [PSCustomObject]@{
        HostName = $HostName
        DB       = $chkDb.ToString()
        KEK      = $chkKek.ToString()
        BootMgr  = $chkBoot.ToString()
        DBx      = $chkDbx.ToString()
        SVN_1042 = $chkSvn.ToString()
    }
    $finalRecord | Export-Csv $LogFilePath -NoTypeInformation -Encoding UTF8 -Append:$false

    Invoke-Cleanup
    exit
}

# ---------------------------------------------------------
# 3. ステップ0:ガード節・物理状態スキャン・ジャンプ判定
# ---------------------------------------------------------
# ① セキュアブート有効確認
$secureBootEnabled = $false
try { $secureBootEnabled = Confirm-SecureBootUEFI } catch { }
if (-not $secureBootEnabled) {
    [PSCustomObject]@{ HostName=$HostName; DB="Skipped_Disabled"; KEK="Skipped"; BootMgr="Skipped"; DBx="Skipped"; SVN_1042="Skipped" } | Export-Csv $LogFilePath -NoTypeInformation -Encoding UTF8 -Append:$false
    Invoke-Cleanup
    exit
}

# ② 高速スタートアップの無効化(次回以降の確実なコールドブート保証)
$powerPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power"
$hiberboot = Get-ItemPropertyValue -Path $powerPath -Name "HiberbootEnabled" -ErrorAction SilentlyContinue
if ($hiberboot -eq 1) {
    Set-ItemProperty -Path $powerPath -Name "HiberbootEnabled" -Value 0 -Force
}

# ③ 物理到達地点のスキャン
$chkDbx = $false
try { $chkDbx = [System.Text.Encoding]::ASCII.GetString((Get-SecureBootUEFI dbx).bytes) -match 'Microsoft Windows Production PCA 2011' } catch {}

$chkSvn1042 = $false
$event1042 = Get-WinEvent -FilterHashtable @{LogName="System"; ProviderName="Microsoft-Windows-TPM-WMI"; Id=1042} -MaxEvents 1 -ErrorAction SilentlyContinue
if ($event1042) { $chkSvn1042 = $true }

# レジストリ状態の読み取り
$currentPhase = 0
if (Test-Path $StateRegPath) {
    $currentPhase = Get-ItemPropertyValue -Path $StateRegPath -Name $StateRegName -ErrorAction SilentlyContinue
}

$recoveryMode = $false

# 物理スキャンに基づく強制ジャンプ判定(KISSの原則準拠)
if ($chkDbx) {
    if ($chkSvn1042) {
        # 【全完了】
        Write-AuditLogAndExit
    } else {
        # 【リカバリ】DBxは完了だがSVNが未了。フェーズ2へ強制ジャンプし、0x200を打つ
        $currentPhase = 2
        $recoveryMode = $true
    }
}

# ---------------------------------------------------------
# 4. メイン・ステートマシン(進行管理)
# ---------------------------------------------------------
if ($currentPhase -eq 0) {
    # 【フェーズ0】DB/KEKの更新準備 (0x5944)、タスク登録、退勤を待つ
    if (!(Test-Path $ControlPath)) { New-Item -Path $ControlPath -Force | Out-Null }
    if (!(Test-Path $StateRegPath)) { New-Item -Path $StateRegPath -Force | Out-Null }
    
    Set-ItemProperty -Path $ControlPath -Name "AvailableUpdates" -Value 0x5944 -Type DWord
    Set-ItemProperty -Path $StateRegPath -Name $StateRegName -Value 1 -Type DWord
    
    Start-ScheduledTask -TaskName $PiTaskName
    Register-ResumeTask
    exit
}
# ---------------------------------------------------------
if ($currentPhase -eq 1) {
    # 【フェーズ1】DB/KEKの適用確認待機 (0x4000)
    Start-ScheduledTask -TaskName $PiTaskName
    $success = Wait-ForRegistryValue -ExpectedValue 0x4000 -WaitMinutes 10
if ($success) {
        Set-ItemProperty -Path $StateRegPath -Name $StateRegName -Value 2 -Type DWord
    } else {
        # ★追加:0x4000にならず失敗した場合の異常系離脱ルート
        Write-AuditLogAndExit
    }
    exit 
}
# ---------------------------------------------------------
if ($currentPhase -eq 2) {
    # 【フェーズ2】DBx/SVNの並列適用 (0x280 または 0x200) と最終完了処理
    
    $payload = if ($recoveryMode) { 0x200 } else { 0x280 }
    
    Set-ItemProperty -Path $ControlPath -Name "AvailableUpdates" -Value $payload -Type DWord
    Start-ScheduledTask -TaskName $PiTaskName
    
    $zeroReachable = Wait-ForRegistryValue -ExpectedValue 0 -WaitMinutes 10
# ---------------------------------------------------------
    if ($zeroReachable) {
        # 全ての処理が完了したため、最終ログ出力と自己消滅を実行
        Write-AuditLogAndExit
    } else {
        exit # フェーズ2内でタイムアウトした場合は、次回起動時に再度フェーズ3の頭から再開
    }
}

デプロイメントの工夫(TOCTOU脆弱性の排除)

今回の実装では、ファイルサーバーから直接スクリプトを実行したり、C:\Temp に一時保存したりせず、バッチファイルを使って C:\Program Files\ITAdmin のような管理者権限が必要なローカルフォルダに .ps1 ファイルをコピーしてからタスク登録を行っている。

Start-SecureBootUpdate.bat
@echo off
setlocal

:: ---------------------------------------------------------
:: 1. 変数定義
:: ---------------------------------------------------------
:: セキュアな配置先ディレクトリ
set "SECURE_DIR=C:\Program Files\SecureBootUpdate"
:: 実行するPowerShellスクリプト名
set "PS1_NAME=SecureBootUpdate.ps1"

:: batが実行されている現在のカレントディレクトリ(配信元の一時キャッシュ)からのパス
set "SOURCE_PS1=%~dp0%PS1_NAME%"
set "TARGET_PS1=%SECURE_DIR%\%PS1_NAME%"

:: ---------------------------------------------------------
:: 2. 保護領域のセットアップとファイル移送
:: ---------------------------------------------------------
:: セキュアディレクトリが存在しない場合は作成
if not exist "%SECURE_DIR%" (
    mkdir "%SECURE_DIR%"
)

:: ps1ファイルをセキュア領域へコピー(/Y で上書き)
copy /Y "%SOURCE_PS1%" "%TARGET_PS1%" >nul

:: ---------------------------------------------------------
:: 3. コピー成功確認とスクリプトの実行(キック)
:: ---------------------------------------------------------
if exist "%TARGET_PS1%" (
    :: ExecutionPolicyをBypassして非表示(Hidden)で実行
    start "" powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -File "%TARGET_PS1%"
) else (
    :: コピー失敗時はエラー終了
    exit /b 1
)

exit /b 0

4. おわりに

セキュアブートの仕組みについて勉強になったが、インターネットに繋がらない環境でも自動適用されるように本当はMSにして欲しいところ。(UEFI/BIOSの絡みがあるので難しいのだろうなと推測)
レジストリ値の詳しい推移はMS公式の参考サイトや別の方が解説されていたので、割愛した。

タスク登録することで、再起動後も継続して実行できるように自立型にしたが、権限昇格されないように実行する際は十分にお気を付けください。

参考サイト

https://support.microsoft.com/ja-jp/topic/cve-2023-24932-に関連付けられているセキュア-ブートの変更に対する-windows-ブート-マネージャー失効を管理する方法-41a975df-beb2-40c1-99a3-b3ff139f832d

https://support.microsoft.com/ja-jp/topic/セキュア-ブート証明書の更新-it-プロフェッショナルと組織向けのガイダンス-e2b43f9f-b424-42df-bc6a-8476db65ab2f

https://support.microsoft.com/ja-jp/topic/cve-2023-24932-のエンタープライズ展開ガイダンス-88b8f034-20b7-4a45-80cb-c6049b0f9967

Discussion