アンインストーラがExplorerを自滅させた真相
「Windowsが悪い」と思われていたExplorerのクラッシュの正体は、アンインストーラが自分のコードを自分で食い尽くす自滅劇だった。レイモンド・チェン(Raymond Chen)がダンプを一目見て犯人を言い当てた話がなかなか濃い。
「Windowsが悪い」と思われていたExplorerのクラッシュの正体は、アンインストーラが自分のコードを自分で食い尽くす自滅劇だった。レイモンド・チェン(Raymond Chen)がダンプを一目見て犯人を言い当てた話がなかなか濃い。
32bit Explorerが64bitシステムで動いている時点で不審
話の発端は、同僚が調査していたExplorerクラッシュの謎のスパイクだ。Windowsは何十億という異機種環境で動いているOSだから、クラッシュの原因は「Windows自身のバグ」と疑われがちになる。普通はそう考える。
ところがチェンはダンプを一目見て「アンインストーラのバグだ」と言い当てた。決め手は、64bit Windows上で32bit版Explorerがクラッシュしていた事実だ。
ここが最初の分岐点になる。64bit WindowsにはタスクバーやFile Explorerウィンドウを担当する64bit版のExplorerとは別に、32bit版のExplorer実行ファイルがC:\Windows\SysWOW64ディレクトリに存在する。32bitアプリとの後方互換のために残されているもので、通常のユーザー操作では起動しない。にもかかわらず32bit Explorerが動いていて、しかもクラッシュしている。これは「何か別のプログラムが、32bit Explorerを汚い仕事に使っている」ことを意味する。
32bit Explorerは通常、ユーザー操作から直接起動されることはない。動いているなら、それを動かしている何者かがいる。
チェンが「buggy uninstaller(バグだらけのアンインストーラ)」を即座に疑ったのは、過去に同種の事例を何度も見てきたからに他ならない。
呼び出し規約の取り違えで自分の足場を崩す
クラッシュの技術的な骨格はこうだ。問題のアンインストーラは注入コード(injected code)内にループを組んでいて、ファイル操作が失敗したら少し待って再試行する、という処理を回していた。ここまでは普通の話だ。
ところが、このコードの作者はWindows関数を呼び出す際の呼び出し規約を間違えていた。Windows APIは__stdcallで呼び出すのが正解だが、作者は__cdeclで呼び出すコードを書いてしまった。
この差は、一見些細に見えて致命的な帰結を生む。__stdcallでは呼び出された側(callee)がスタックから引数をポップする。__cdeclでは呼び出した側(caller)がポップする。規約が食い違うと、どうなるか。
コードがWindows関数を呼ぶたびに、引数をスタックに積む。Windows関数側が__stdcallの流儀で引数をポップしてスタックから取り去る。さらに__cdeclだと思い込んでいる呼び出し側が、もう一度ポップする。つまり、一回の関数呼び出しで「余分に一度ポップされる」分だけスタックポインタが進んでしまう。
ループの1周ごとに、コードは自分のスタックを少しずつ食い削っていく。
1回あたりの食い込みはわずかだが、ループは何度でも回る。
スタックポインタが実行中のコードに到達する瞬間
このループは相当な回数を反復したらしい。スタックを食い尽くし、やがてスタックポインタが注入コード自身の領域にまで到達した。
ここで注目すべきは、なぜスタックポインタが実行中コードに到達できたのか、という点だ。コメント欄でハリー・ジョンストン(Harry Johnston)が「原本コードをネットで調べたところ、このアンインストーラは意図的にシェルコード(shellcode)をスタック上に注入していた」と報告している。本来なら別途メモリを確保してそこに置くのが作法だが、なぜか作者はスタック上を選んだ。チェン本人の記事は注入コードの配置先までは踏み込んでいないが、ジョンストンの調査が正しければ、スタック食い潰しと実行中コード書き換えが一直線でつながる。
スタック上の注入コードを実行しながら、そのスタック自身を規約ミスで食い潰していく。ループ1周ごとに、実行中のコードの一部が、あとからスタックデータで塗り潰される。そして最後の瞬間、スタックポインタは実行中の命令そのものを書き換える位置にまで進み、無効な命令に遭遇して終わる。
コードが自分の足場を崩しながら走り続け、最後に自分自身を踏み潰して倒れる。ブラックコメディに近い。
Windowsが濡れ衣を着せられる構造
ここで重要な論点が浮かび上がる。クラッシュダンプを回収したMicrosoftの目線では、これは「32bit Explorerのクラッシュ」として積み上がる。何千件、何万件と集まれば「Windowsに重大な不具合がある」という信号にしか見えない。
チェンが過去に書いた「any sufficiently advanced uninstaller is indistinguishable from malware(十分に高度なアンインストーラは、マルウェアと区別がつかない)」という一文は、まさにこのパターンを突いている。アンインストーラはファイルの削除やレジストリの清掃のために、マルウェアと同じ低レベルの技術を使う。プロセス注入、デバッガAPI、シェルコードのメモリ配置といった、アンチウイルスが反応してもおかしくない手口を、合法的なソフトウェアが普通に使っている。
合法ソフトとマルウェアの境界は、技術ではなく「意図」と「品質」にしかない。そしてこのケースでは、意図は合法だが品質が悪かった。
結果、Explorerという無実のプロセスが巻き添えでクラッシュし、Microsoftはバグ報告の山を眺めることになる。犯人はアンインストーラのコードミスなのに、表向きはOSが壊れたように見える。
32bit互換層は引退していない
こういう話を読むと「昔のトラブル話だろう」と片付けたくなる。ところが32bit Explorer互換層はWindows 11にも残っており、C:\Windows\SysWOW64\explorer.exeは今も所定の位置にいる。サードパーティのアンインストーラは、今この瞬間もそこを踏み台にコード注入を続けている可能性がある。
コメント欄ではイェスアン・シャオ(Yexuan Xiao)が「自分がアンインストーラを書くなら、tempフォルダにコピーして再起動させる」と書いている。デバッガAPIに頼る設計そのものがシステム不安定の温床だ、という指摘は正鵠を射ている。ユルキ・ヴェステリネン(Jyrki Vesterinen)は「せめて注入コードを読み取り専用(read-only)に設定しておけば、書き換え時点で即クラッシュするのでデバッグが楽になるのに」と皮肉を返している。アンインストーラの作者が最低限の防御コードも書けていない、という現実が滲む。
Windowsの「バグっぽく見えるもの」のうち、一定割合はWindows自身の問題ではない。今回の一件はそれを地味に思い出させてくれる。
参照元
関連記事
- サードパーティのアンチウイルスは不要、Microsoftが明言
- タスクマネージャーCPU使用率の嘘、設計者が語る
- KB5083769がPCを壊す:Windows 11 4月更新で起動不能ループ
- Defenderが悪性ファイルを元の場所に書き戻す奇妙な挙動
- Windows 11 24H2偽更新サイト、検出ゼロのマルウェア配布
- 80KBのタスクマネージャー動画に、開発者たちが呻いた理由
- Windows 11/10のロック画面時計、最大30秒の遅れはMicrosoftが「仕様」と明言
- Windows 11が「100%ネイティブ」に回帰、PWA脱却とデザイン刷新の全容
- KB5086672がインストールできない不具合と対処法
- Windows 11、24H2ユーザーに25H2への強制アップデートを開始