
2026/01/28 4:16
**General Graboids:Command & Conquer におけるワームとリモートコード実行**
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
## Summary この記事では、*Command & Conquer: Generals* におけるいくつかの重大なメモリ破損脆弱性を詳細に説明しています。具体的には、`NetPacket::readFileMessage` での境界チェックがないコピー操作と、`NetCommandWrapperListNode::copyChunkData` での境界外書き込みがあります。これらのバグは32ビット版でリモートコード実行を可能にし、攻撃者が `ConnectionManager::processFile` を介して任意の DLL をドロップできるようにします。著者は、IATフックを使って `recvfrom` をフックし、UDPポート8086/8088(XOR暗号化された)でマジックパケット (`0xdead4ead`) を傍受し、OSコマンドやゲーム内スクリプトアクション(全ユニットの売却など)を発行する自己複製型ワームを構築しました。 EAは2025年初頭にゲームのソースコードを公開しましたが、レガシータイトルにはCVE番号を割り当てず、ユーザーにコミュニティパッチへの移行を促しました。*GeneralsGameCode* のメンテナは2025年12月に発見事項を受領し、`readFileMessage` の破損、フラグメント化問題、およびファイル拡張子検証の修正を適用しました。未修正のプレイヤーはマルウェア感染やゲームデータ破損のリスクに直面しており、公式サポート期間終了後も継続的なセキュリティメンテナンスが必要であることと、レガシータイトルに対するベンダー責任問題を示しています。
本文
[本作業はブライアン・アレクサンダー氏とジョーダン・ホワイトヘッド氏が共同で行いました]
概要
この投稿では、オンラインゲーム Command & Conquer: Generals において発見された複数の脆弱性について詳述します。
最近、情報セキュリティ会議にて本研究を発表しました。本稿には、ゲームのネットワークアーキテクチャ、公開される攻撃面、検出された脆弱性、およびインパクトを示すために開発したワームの完全な詳細が含まれています。
PoC を含む全ソースコードは、当社の公開 GitHub リポジトリ(リンク)で入手できます。
EA は本作業対象ゲームを End‑of‑Life とみなしていますが、コミュニティによるパッチが公開されており、詳細はプロジェクトをご参照ください。
研究の背景
2025 年初頭、EA Games は Command & Conquer: Generals(C&C:G)―1990年代末〜2000年代初期に人気を博した RTS シリーズの最終作―のソースコードをリリースしました。
同時に 2003 年に発売された唯一の拡張パック Zero Hour も公開されました。
本ゲームはシングルプレイヤーとマルチプレイヤーをサポートしており、後者は LAN または GameSpy を介したオンラインロビーで行われます。GameSpy は 2014 年に停止し、公式 C&C:G サーバも閉鎖されました。
Junkyard は End‑of‑Life のレガシー製品(ハードウェア・ソフトウェア・ファームウェア)向けのゼロデイ脆弱性を発表するイベントです。インパクト、プレゼンテーションの魅力度、全体的なユーモリズムでポイントが付与されます。このイベントは Washington DC で開催される毎年恒例のセキュリティ会議 DistrictCon の期間中に行われます。
C&C:G は「興味深い」「レガシー」「サポート停止」という条件を満たし、魅力的な標的でした。
対象概要
ソースコードにはエンジン・ネットワークスタック・クライアントのすべてが含まれますが、モデルや第三者ライセンス付きツールなどのプロプライエタリ資産は除外されています。そのため、リポジトリから直接ビルドすることはできません。Steam で必要なライセンスを取得し、動的解析と静的コードレビューを併用しました。
クライアントがロビーを開始すると、UDP ポート 8086 がメタゲームコマンド(参加/退出/チャット)用に開きます。プレイが始まると別のポート 8088 が状態同期および戦闘操作用に開かれます。
ネットワークアーキテクチャ
C&C:G はピアツーピア方式を採用しています。ホストはパケットをすべてのクライアントへ転送します。各クライアントは両ポートで到達可能である必要があります。LAN 上では
0.0.0.0:8086 と 0.0.0.0:8088 がルーティング可能でなければなりません。
両ポートのパケット形式は似ており、いくつかのフィールドだけが異なります:
+-------------------------------------------------------------+ | Wordwise XOR/Endian‑swap Encrypted Payload | | +----------------------+--------------------------------+ | | | CRC32 (LE) | 4 bytes | | | +----------------------+--------------------------------+ | | | Magic | 0D F0 | | | +----------------------+--------------------------------+ | | | Header | 1 byte | | | +----------------------+--------------------------------+ | | | Data | up to MAX_FRAG_SIZE bytes | | | +----------------------+--------------------------------+ | | | Padding | 4‑byte boundary | | | +-------------------------------------------------------+ | +-------------------------------------------------------------+
- 必須の 4 バイト CRC32 と 2 バイトマジックヘッダー。
- ハードコードされたキーで XOR エンコードされ、頑健な分割処理が施されています。
ヘッダーは TLV(Type‑Length‑Value)形式を採用し、受信側クライアントによって再帰的に解析されます。例として
NETCOMMANDTYPE_FILE パケット(ロビーポート)の構造です:
| オフセット | バイト数 | 説明 |
|---|---|---|
| 00–03 | fc 37 a9 53 | CRC32 (LE) |
| 04–05 | 0d f0 | マジック |
| 06 | 54 | コマンドタイプタグ('T') |
| 07 | 12 | コマンドタイプ値 |
| 08 | 44 | データタイプタグ('D') |
| 09–N | | 最初のデータ値 |
| N–N+4 | 04 00 00 00 | データ長(LE uint32) |
| N–N+4 | 41 41 41 41 | 第二データ値 ("AAAA") |
| N–N | 40 40 | パディング(4 バイト境界) |
NetPacket オブジェクト内で大きな if/else チェーンにより解析されます:
if (commandType == NETCOMMANDTYPE_GAMECOMMAND) msg = readGameMessage(data, offset); else if (commandType == NETCOMMANDTYPE_ACKBOTH) msg = readAckBothMessage(data, offset); ...
その後、ハンドラがデータ部分を解析し、対応する処理を実行します。
脆弱性
1. スタックオーバーフロー – readFileMessage
/ readFileAnnounceMessage
readFileMessagereadFileAnnounceMessageこれらのハンドラは信頼できないファイル名を固定バッファ(
char filename[_MAX_PATH];)にコピーする際、境界チェックを行っていません:
while (data[i] != 0) { *c = data[i]; ++c; ++i; } *c = 0;
マルチプレイヤーゲーム内の任意のピアからトリガー可能です。
Frida と Python クライアントを使用して実際にエクスプロイトを確認しました。
影響: ターゲットマシン上で遠隔コード実行(32‑bit、ASLR 非適用)。
PoC: 分割 (
NetCommandWrapperList) を利用した静的ペイロードが任意のシェルコードを送信し、ROP により RWX 領域を作成して実行します。
2. 任意ファイルドロップ – processFile
processFileConnectionManager::processFile はファイル名/パスを検証せずに受け取ります:
File *fp = TheFileSystem->openFile(msg->getRealFilename().str(), File::CREATE | File::BINARY | File::WRITE);
悪意のあるピアが
.dll を送ると、ゲーム起動時にロードされ、遠隔コード実行が可能になります。
3. バウンダリ外書き込み – copyChunkData
copyChunkData分割ロジックはチャンクをコピーする際に境界チェックを行いません:
memcpy(m_data + offset, msg->getData(), msg->getDataLength());
offset と dataLength は送信側が完全に制御できるため、m_data への任意書き込みが可能です。
ワーム実装
上記脆弱性を組み合わせ、マルチプレイヤーゲーム内で拡散するピアツーピアワームを構築しました。
配信
- DLL ドロップ:ファイル書き込み脆弱性を利用して
をゲーム根ディレクトリに配置。dbghelp.dll - 起動時ロード:DLL はプロセスアタッチ時に実行され、IAT パッチで
(WSOCK32)をフックします。recvfrom
マジックパケット
- マジックヘッダー (
) を持つ特殊パケット。0xdead4ead - チャットメッセージが隠しコマンドのトリガーとなります。
ワームはこれらのパケットを解析し、任意 OS コマンド実行やゲーム内スクリプティングエンジン操作を行います。
例:全アイテム販売
typedef void(__thiscall* SellEverything_t)(void* thisplayer); #define FUN_Player_SellEverything ((SellEverything_t)0x454fa0) FUN_Player_SellEverything(pLocalPlayer);
拡散
- ピア検出:参加メッセージを傍受し、IP アドレスを抽出。
- 感染追跡:ローカルで感染状況を管理し、“感染していますか?” というマジックパケットに応答可能。
- ペイロード送信:確立した配信チャネルを介してペイロードを送付。
緩和策と公開手順
- 2025 年 8 月、EA Games に発見内容を報告しました;同社はサポート停止を確認しつつ、サービス終了の旨を明示。
- コミュニティパッチは GeneralsGameCode(Discord、2025 年 12 月)でメンテナと協議して作成されました。
- パッチには以下が含まれます:
のメモリ破損修正readFileMessage- 分割ロジックのメモリ破損パッチ
- ファイル拡張子検証パッチ
タイムライン
| 日付 | イベント |
|---|---|
| 2025‑08‑06 | ベンダーへの初期通知(Atredis Partners) |
| 2025‑08‑06 | EA が受領を確認 |
| 2025‑08‑07 | 追加プラットフォーム情報の要求 |
| 2025‑08‑11 | EA が脆弱性を検証し、重大度を割り当て |
| 2025‑08‑11 | 緩和策/公開手順に関するフォローアップ |
| 2025‑08‑26 | 公開手順/パッチ情報の明確化 |
| 2025‑12‑03 | Discord(GeneralsGameCode)でコミュニティ公開 |
迅速な対応とパッチ作成を行ってくださったコミュニティ開発者の皆様に感謝いたします。あなた方のおかげでレガシータイトルが生き続けています!