Windows Updateの更新履歴から入手した.cabをexpand.exeで展開しても、更新後のC:\Windows\System32\control.exeとハッシュが一致しない――その現象は手順ミスではなく、近年の累積更新がPSFX(差分配布)へ移行したことが原因です。この記事では仕組みと、ホワイトリスト運用で使える現実的な検証方法を整理します。
起きていること:cabを展開しても「更新後のcontrol.exe」が出てこない
Windows 10(例:x64 / 2004)で累積更新(例:KB4570334、KB4566782 など)を適用した直後、C:\Windows\System32\control.exe が置き換わります。ところが、更新履歴や Microsoft Update Catalog から入手したパッチの .cab を expand.exe で展開し、展開先に見つかった control.exe をハッシュ計算しても、実機上の更新後ファイルのハッシュと一致しないことがあります。
このとき多くの人が次のように疑います。
- 展開手順(expand のオプションや展開対象)が間違っている?
- OSビルドやエディションが違うと、展開結果も変わる?
- cabの別の場所(深い階層)に“本体”がある?
- そもそも更新時にファイルが合成されて、cab内に完成品が無い?
結論から言うと、最後の「更新時に合成される」が核心です。近年の Windows 10 の品質更新は、従来のように“完成したフルバイナリ”を配るよりも、差分中心で配り、適用時に OS 側で最終ファイルを生成する設計になっています。そのため、expand の結果とディスク上の最終ファイルが一致しないケースが出ます。
前提:System32の実体はコンポーネントストア(WinSxS)にある
まず押さえておきたいのが、System32 の多くのファイルは「コンポーネントストア(WinSxS)にある実体へのハードリンク」である、という Windows の基本設計です。つまり、C:\Windows\System32\control.exe を見ているつもりでも、内部的には C:\Windows\WinSxS\... 配下の実体(同一内容)を参照していることが多いです。
| 観点 | System32 | WinSxS(コンポーネントストア) |
|---|---|---|
| 役割 | アプリやユーザーが参照する“表の場所” | 更新や修復の基準となる“部品の保管庫” |
| 更新時の動き | 多くの場合、実体の更新に合わせてリンク先が切り替わる | 新しいバージョンの部品が追加・有効化される |
| ハッシュの考え方 | 最終的に動作しているファイルのハッシュを取りたいならここを計算する | System32 と同一実体ならハッシュは同じ。複数バージョンが混在する点に注意 |
「別の場所に本体があるのか?」という疑問は、半分はYesです。System32 の“見えているファイル”は、WinSxS を基準に整合が取られており、更新の適用は WinSxS を中心に進みます。とはいえ、今回の論点は「WinSxSを見れば cab 展開と一致する」という話ではなく、そもそも cab の中身が完成品ではない点です。
原因:PSFX(差分配布)では、更新適用時にファイルが生成される
Windows 10 1809 以降の累積更新(LCU: Latest Cumulative Update)では、PSFX(PSF を使った差分配布方式)と呼ばれる新しい更新パッケージ形態が使われるようになりました。大きな特徴は、配布物が「完成バイナリ一式」ではなく、「ベース(既存のファイル)+差分(デルタ)」を前提にしている点です。
| 項目 | 従来(フルバイナリ中心) | PSFX(差分中心) |
|---|---|---|
| 配布物 | 更新後の完成ファイル(またはそれに近いもの)が含まれやすい | 差分・圧縮・メタデータ中心。完成品が含まれない/一部だけのことがある |
| 適用時の処理 | パッケージ内のファイルを置換するイメージ | ベース版+差分から最終ファイルを生成(hydration)して反映 |
| cabをexpandした結果 | そのまま更新後ファイルと一致しやすい | 一致しないことがある(むしろ想定内) |
| 配布サイズ | 大きくなりがち | 小さくしやすい(特に“express”配布で顕著) |
この「生成(hydration)」があるため、expand で取り出した control.exe が“更新後の完成ファイル”とは限りません。取り出せたとしても、それが差分適用前の中間物だったり、別条件(別ビルド・別言語・別コンポーネント)向けの断片だったりします。更新時には Servicing(CBS/TrustedInstaller)側が、現在の OS の状態を見ながら正しい差分を選び、最終的な 1 本のバイナリとして組み立てます。
cabの中身は何が入っているのか(ざっくり)
更新パッケージを展開すると、次のようなファイルが見つかることが多いです(名称はパッケージにより異なります)。
| 拡張子 / 種別 | 主な役割 | ポイント |
|---|---|---|
| .mum | パッケージ定義(どのコンポーネントをどう更新するか) | 更新の“骨格”。依存関係や適用条件の判断材料になる |
| .cat | カタログ(署名・整合性) | 改ざん検知の根拠。高セキュリティ環境では重要 |
| .manifest | コンポーネントのマニフェスト(ファイル一覧やハッシュ等) | 最終ファイルのハッシュ(SHA-256)が記載されることがある |
| .psf など | 差分(デルタ)や圧縮データの本体 | これ単体では完成品にならない。OS側の処理で“展開・合成”される |
「cabを展開して一致するバイナリを探す」という発想は、フルバイナリ中心の時代にはうまくいきました。しかし PSFX では、探しても一致しない(または見つからない)ことが起こり得ます。ここが“ハマりポイント”です。
結論:expandで“更新後の完成ファイル”が取り出せないのは正常
整理すると、ハッシュが一致しない主な理由は次のとおりです。
- 差分配布のため:cabに入っているのはデルタで、最終ファイルは更新適用時に生成される。
- 条件分岐があるため:OSのビルド番号、搭載機能、言語、追加コンポーネントの有無で“当たる差分”が変わり得る。
- 中間生成物が見えるため:展開できても、それは更新処理の入力や途中経過であり、完成品のcontrol.exeとは一致しない。
したがって、「展開方法が間違っているから一致しない」というより、一致しないことがある設計と捉えるのが正解です。
ハッシュで検証したい現場の最適解:manifestに載る最終SHA-256を見る
高セキュリティ環境でのハッシュ・ホワイトリスト運用では、「更新後に実際に配置されるファイルのハッシュ」が必要です。PSFX の世界でこれを“パッケージから直接”取りたいなら、実務的には manifest(マニフェスト)に記載された最終ハッシュを根拠にするのが一番筋が良い方法になります。
ポイントは次の2つです。
- manifest に、最終的に生成されるファイルの SHA-256 が記載されていることがある。
- その表現が base64 のことが多い(運用でよく使う16進表記と変換が必要)。
手順:cabからmanifestを見つけてcontrol.exeのハッシュを探す
パッケージの入手経路(更新履歴、WSUS、Microsoft Update Catalog)によりファイル構成は多少変わりますが、考え方は同じです。
- 更新パッケージを展開する 例:cab を展開(展開先フォルダは任意)
mkdir C:\Temp\KB expand -F:* C:\Temp\Windows10.0-KBxxxxxxx-x64.cab C:\Temp\KB例:msu の場合は、まず msu を展開して中の cab を取り出します。mkdir C:\Temp\MSU expand -F:* C:\Temp\Windows10.0-KBxxxxxxx-x64.msu C:\Temp\MSU - manifest を検索する 展開先で
.manifestを探し、そこからcontrol.exeに紐づく記述を見つけます。まずは文字列検索が早いです。cd C:\Temp\KB powershell -NoProfile -Command "Get-ChildItem -Recurse -Filter *.manifest | Select-String -SimpleMatch 'control.exe' | Select-Object -First 20" - 該当manifestのハッシュを取り出す manifest は XML なので、PowerShell で XML として読み取り、該当ノードの属性や要素を確認します。実際のタグ名はパッケージにより差がありますが、まずは「control.exe を含む行の近く」を目視で当たり、どの属性にハッシュが入っているかを確認すると迷いません。
base64のSHA-256を16進表記へ変換して照合する
manifest に載っている SHA-256 が base64 の場合、ホワイトリスト台帳で一般的な16進表記へ変換しておくと運用が楽です。変換と照合の例を示します。
| データ | よくある表現 | 備考 |
|---|---|---|
| manifest 記載の SHA-256 | base64 | XML 属性や要素に入っていることが多い |
| Get-FileHash の結果 | 16進(hex) | ホワイトリストの管理ではこちらが扱いやすい |
PowerShell 例(base64 → hex 変換):
# 例:manifestから取り出した base64 の SHA-256
$sha256_b64 = "(ここにbase64文字列)"
# base64 → byte[] → hex文字列へ
$bytes = [Convert]::FromBase64String($sha256_b64)
$sha256_hex = ($bytes | ForEach-Object { $_.ToString("x2") }) -join ""
$sha256_hex
実機上の control.exe の SHA-256(hex)を取得して照合:
Get-FileHash C:\Windows\System32\control.exe -Algorithm SHA256
ここで一致すれば、「cabを展開して一致するバイナリを探す」必要はありません。manifest に記載された“最終ハッシュ”を正としてホワイトリストに登録する運用ができます。
PSFXの差分を“自前で合成”して完成品を作るのが難しい理由
「差分が入っているなら、自分でベースと合成して更新後のcontrol.exeを再現できないか?」と考えがちですが、ここは現実的にハードルが高い領域です。PSFX の差分は、単純なバイナリ差分の足し算ではなく、更新サービス(CBS/TrustedInstaller)が複数の条件や依存関係を解決しながら適用する前提で設計されています。
- 適用条件が多い:OSビルド、コンポーネントの有効/無効、言語、既存の更新状態により、どの差分が当たるかが変わり得ます。
- 生成処理がブラックボックス寄り:更新の“展開”というより、内部の更新エンジンが最終ファイルを生成します。一般用途のコマンドで完成品だけを取り出す、というユースケースは想定されていません。
- 整合性・署名の世界とつながっている:コンポーネントストア、カタログ署名、マニフェスト整合性などが一体で動くため、手作業の合成は検証コストが跳ね上がります。
そのため、ホワイトリスト目的であれば「完成バイナリを探す/生成する」よりも、manifest が示す最終ハッシュ(または検証環境で生成した実体)を根拠にするほうが、再現性と監査性の両面で強いです。
manifestにハッシュが見つからない/揺れるときの考え方
環境やパッケージにより、manifest に欲しい情報が見つけにくいケースもあります。その場合は、次の観点で切り分けると前に進みやすいです。
| 状況 | 起こりがちな原因 | 次の一手 |
|---|---|---|
| control.exe の記述が見つからない | 別コンポーネント名で管理されている/対象外の更新を見ている | まずは control.exe のバージョン情報(ファイルのプロパティ)を確認し、同じバージョンを含むコンポーネントを探す |
| ハッシュらしき値があるが形式が違う | base64以外(例:別アルゴリズム、別表現) | 値の長さや形式を見て、SHA-256かどうかを判定。必要なら変換ロジックを合わせる |
| 同名ファイルが複数ありどれが最終か不明 | 差分・条件分岐・コンポーネント分割 | 対象OSと同じビルド条件で、該当コンポーネントの manifest を優先。最終的には参照環境での検証に寄せる |
更新適用後に確認する場合:WinSxS\Manifests からも追える
すでに更新を適用済みで「このマシン上で最終的に有効になったcontrol.exeの根拠が欲しい」だけなら、更新パッケージ側ではなく、OS側(コンポーネントストア)に保存されている manifest を参照する手もあります。C:\Windows\WinSxS\Manifests 配下には多数の .manifest があり、そこにファイル名やハッシュが含まれることがあります。
powershell -NoProfile -Command "Get-ChildItem C:\Windows\WinSxS\Manifests -Filter *.manifest | Select-String -SimpleMatch 'control.exe' | Select-Object -First 20"
ただし、WinSxS は世代管理されるため、同名ファイルが複数バージョン存在することがあります。最終的に有効なバージョンと結び付けるには、ファイルのバージョン情報や、実体へのリンク関係(fsutil hardlink list など)と合わせて確認すると安全です。
特に「OSが違うと展開結果が変わるのか?」については、expand自体の出力は同じでも、適用される差分や生成される最終ファイルがOSの状態に依存するという意味で影響があります。ビルド番号、言語、オプション機能、適用済み更新の組み合わせが変われば、最終生成物が変わり得ます。
どうしても“生成後の実体(完成バイナリ)”が必要な場合の代替案
ハッシュ台帳の作り方として「manifestのハッシュを根拠にする」方法は非常に強力ですが、監査要件や社内規定で「生成後の実体ファイルも保管しなければならない」ケースもあります。その場合の現実的な代替案をまとめます。
同一ビルドの検証用マシンに更新を適用して採取する
最も確実で、実装難易度が低い方法です。ポイントは「同一ビルド条件」を揃えることです。
- Windows 10 のバージョン/ビルド(例:2004 19041.x)
- x64 / x86
- 言語パック(日本語/英語など)
- オプション機能(例:.NET、RSAT、特定の Windows 機能)
- 適用順序(SSU/LCU の順など)
更新適用後に、対象ファイルを採取してハッシュ化します。ハッシュだけでなく、ファイルバージョンや署名情報も一緒に保存すると、後からのトラブルシュートが楽になります。
オフラインイメージに更新を適用して取り出す(DISM活用)
「実機に入れずに生成後ファイルを得たい」場合、WIM(install.wim)等のオフラインイメージをマウントし、そこに更新を適用してからファイルを取り出す方法があります。更新適用が成功すれば、イメージ内の System32 に最終生成物が反映されます。
mkdir C:\Mount
dism /Mount-Wim /WimFile:D:\sources\install.wim /Index:1 /MountDir:C:\Mount
# LCU(および必要に応じてSSU)を適用
dism /Image:C:\Mount /Add-Package /PackagePath:C:\Temp\KBxxxxxxx\
# 取り出し(例)
copy C:\Mount\Windows\System32\control.exe C:\Temp\Extracted\
dism /Unmount-Wim /MountDir:C:\Mount /Commit
注意点として、オフライン適用は更新の依存関係(Servicing Stack の要件など)に左右されます。また、PSFX の性質上「単純に cab を展開しただけ」では得られなかった完成品を、ここで初めて得られることがあります。
現場でよくある“勘違い”チェックリスト
同じ症状でも、原因が PSFX 以外の単純ミスであることもあります。最低限のチェックリストを置いておきます。
| チェック | 理由 | 確認方法 |
|---|---|---|
| 対象の更新が本当に該当OS向けか | KB番号が同じでも対象製品が違う/別アーキテクチャ | 更新ファイル名、パッケージの識別子、OSのビルドを突合 |
| msu の中のどの cab を展開したか | 複数cabが入っていることがある | expand で msu を展開後、目的の cab を選別 |
| 実機側の control.exe が本当に更新されたか | 適用失敗/保留/再起動待ちで置換が完了していない | ファイルバージョン、更新履歴、再起動要否を確認 |
| ハッシュの計算条件が一致しているか | SHA-1 と SHA-256 の取り違え、改行混入など | Get-FileHash -Algorithm SHA256 で統一 |
ホワイトリスト運用を壊さないための設計ヒント
ハッシュ・ホワイトリスト運用は「強い」反面、Windows Update の配布方式変更に引きずられやすい弱点があります。PSFX を前提にするなら、運用設計も少し寄せると安定します。
おすすめの運用フロー例(manifest根拠)
| 工程 | 作業 | 成果物 |
|---|---|---|
| 更新入手 | KB単位で msu/cab を確保(社内リポジトリへ保存) | 更新パッケージ原本 |
| メタデータ抽出 | 展開して manifest を検索、対象ファイルの SHA-256(base64)を収集 | KB → 対象ファイル → SHA-256(base64)一覧 |
| 運用向け整形 | base64 を hex に変換し、台帳フォーマットへ整形 | ホワイトリスト台帳(hex) |
| 検証 | 検証環境に更新適用し、実ファイルのハッシュと突合 | 突合結果ログ(監査用) |
| 本番適用 | 更新適用と同時に台帳を更新、ポリシーへ反映 | 適用記録、ロールバック手順 |
この流れにしておくと、PSFX で「cabを展開しても一致するcontrol.exeが無い」問題に引きずられません。さらに、監査対応としても「更新パッケージ(署名付き)→ manifest(整合性情報)→ 実機ファイル」というトレーサビリティが作りやすくなります。
まとめ:探す対象を“バイナリ”から“manifestの最終ハッシュ”へ切り替える
- PSFX(差分配布)では、
expandで取り出せるものが“更新後の完成ファイル”とは限らない。 - System32 のファイルは WinSxS の実体へのリンクであり、更新はコンポーネントストアを軸に進む。
- ホワイトリスト運用で必要なハッシュは、manifest に記載される最終 SHA-256(base64 のことが多い)を根拠にするのが現実的。
- どうしても完成バイナリが必要なら、同一ビルドの検証環境で更新を適用して採取するか、オフラインイメージに適用して取り出す。
PSFX 化によって「cabを展開してハッシュを列挙する」方式は破綻しやすくなりました。発想を切り替え、manifest を一次情報として扱うことで、更新の追跡とハッシュ管理を安定させられます。

コメント