アンインストーラがExplorerを自滅させた真相

「Windowsが悪い」と思われていたExplorerのクラッシュの正体は、アンインストーラが自分のコードを自分で食い尽くす自滅劇だった。レイモンド・チェン(Raymond Chen)がダンプを一目見て犯人を言い当てた話がなかなか濃い。

アンインストーラがExplorerを自滅させた真相

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自身の問題ではない。今回の一件はそれを地味に思い出させてくれる。


参照元

関連記事

Read more

DDR5メモリ、日本で4ヶ月ぶり値下がりの兆し

DDR5メモリ、日本で4ヶ月ぶり値下がりの兆し

64GBキットが80,000円を割り込み、日本国内の一部小売で記録的な高値からの反落が始まっている。とはいえ昨年比では依然として数倍の水準で、AIデータセンター需要が支配する構造は何も変わっていない。 4ヶ月ぶりに5万円台前半まで戻した64GBキット 日本市場で異変が起きている。Wccftechが報じたところによれば、4月中旬の時点で複数のDDR5メモリキットが直近4ヶ月で最も低い水準まで価格を落とし、64GB(32GBx2)のDDR5-4800キットが80,000円を下回って販売されている。 ドル換算でおよそ489ドル、前月比で 約21.8% の値下がりだ。世界の大半の地域では同容量のDDR5デスクトップ向けキットが800ドル超で取引されている現状を踏まえれば、これは控えめに言っても「異例の安さ」になる。 ただし喜んでいい話なのかは別問題だ。昨年同時期と比べれば、依然として数倍の価格帯にとどまっている。10%程度の下落で「安い」と感じてしまう感覚そのものが、この一年でメモリ市場がどれほど歪んだかを物語っている。 4ヶ月ぶりという但し書きが示すのは、回復ではなく「束の間の踊