ゲームWarCraft 3で既に修正された脆弱性の歴史と説明

3月の2009年で、ゲームWarCraft 3のマップを作成するのが好きでした。ゲームを開始したときに、ゲームを開始したときにコンソールを作成し、「Hello World!」 控えめに言っても、私はumb然としました-これは、システムで任意のコードを実行できることを意味していました。



カットオフの下には、脆弱性とその閉鎖履歴の説明があります。



私たちの知り合いの1人は、「modmaker」パーティとも呼ばれるもので、外部プログラムを使用してゲームの機能を拡張する方法を説明した記事の翻訳を投稿しました。 それから、ニックネームを呼ばない誰かが彼に個人的な「外部プログラムなしでやれる」と書いてこのカードを送った。 私は彼の連絡先を尋ね、ICQを介して彼に連絡することができました。 驚いたことに、彼は脆弱性とは何か、そしてその使用方法についての包括的な情報を提供しました。 しかし、全体像を把握するために、ゲームのスクリプトを記述する際のニュアンスを説明することから始めなければなりません。



バグを返す


WarCraft 3のスクリプトは、Blizzardが開発したJASS(Just Another Script System)で記述されています。 一般に、それは非常に柔軟です(標準キャンペーン全体はこの言語の視覚的なアドオンで作成されます)が、問題は次のとおりです:構造がなく、標準のスリープはゲームが一時停止されていても時間をカウントし、一言で言えば、ユニットをスムーズに動かすなど.03秒ごとに移動することはできません。 しかし、開始することができ、かなり正確で、カウントの終わりにコールバック引数で指定された関数を呼び出すことができるタイマーがあります。 しかし、それらは問題を解決しません:たとえば、いくつかのアクションを遅延させる機能は、2回使用すると正しく機能しません:タイマーの最後に呼び出される関数に引数を渡すことは不可能であるため、どのインスタンスを見つけることは不可能ですこの関数が処理する能力。



そして、ここでReturn Bugが発見されました(それはかなり前のことでしたが、2005年、おそらくそれ以前と思われます。この瞬間は見つかりませんでした)。 JASSは強く型付けされた言語ですが、このバグにより回避できました。 このように見えた:



function RB takes unit u return integer return u //    ,     , //     return 0 //       endfunction
      
      







しかし、エラーは表示されませんでした-明らかに、戻り値の型は別のフィールドに書き込まれましたが、次の戻り命令はこのフィールドを再び書き換えます。 このコードの実行の結果、関数は特定の数値を返します。これは、渡されたユニット関数のゲーム内記述子です。 そして、ユニットごとにこの番号は一意になります。



この言語にはキャッシュなどのツールもありました。キャンペーンにデータを保存し、あるミッションから別のミッションにデータを転送することを目的としており、通常は数字、行、論理変数のみを書き込むことができ、2行はインデックスとして機能しました。 その結果、文字列に変換されたタイマー記述子のインデックスのキャッシュに、遅延アクションを実行するために必要なユニットの記述子が書き込まれ、関数のコールバックでタイマー記述子を取得し、目的のユニットでアクションを実行できるメソッドが発明されました。 これは勝利であり、マップメーカーの能力を大幅に拡大しました。



また、ゲームの開発者がこの抜け穴をふさぎ、その使用が広まったことは奇妙です。 実際、ユニットの興味深い非標準能力はこのように作成されました。



JASSインタープリターの仕組み


マップスクリプトに基づいて、ロードすると、ゲームはアセンブラーを思わせる擬似コードを作成します。 ゲームは8バイトのオペコードを使用します。最初の4バイトはオペレーションコードとオプションの仮想レジスタ(256あり)を示し、2番目の4バイトは引数を示します。たとえば、値がレジスタにロードされると、値自体が直接そこにあります。 コード内の後続の各操作のレジスタIDはインクリメントされます。たとえば、foo = 0; バー= 2; -レジスタ#00を使用して値fooを割り当てます。バー#01については、レジスタ#FFを使用した後、#00が再び使用されます。 同様に、グローバル変数のインデックスも増加し、順番に進みます。最初に宣言された変数のインデックスはnで、次のn + 1です。



また、通常の変数と配列には大きな違いがあります:通常の変数に値自体が含まれている場合、配列には、配列のサイズに関するデータを含む構造へのリンクと、配列データを直接含むメモリ領域へのリンクが含まれます。 大きなインデックスで書き込むときに最初はサイズが小さい配列はreallocを行いますが、配列のサイズは8192要素に制限されています。



脆弱性自体


JASSにはコードなどのタイプがあり、関数への相対ポインターであり、関数にコールバックを渡すために使用されます。たとえば、そのようなオブジェクトを使用して特定の関数を呼び出すために使用されます。 原則として、数学的な操作はできません。また、コードc = function fooなどのリテラルによってのみ設定されます。 また、擬似コードが配置されているメモリを直接指すのではなく、処理擬似コード全体の先頭からインデントを設定するだけです。 そして、ここに救助のための戻りバグがあります:



 function StubFunc takes nothing returns nothing endfunction function I2C takes integer ic returns code return ic return (function StubFunc) //   ,    . endfunction function C2I takes code c returns integer return c return 0 endfunction
      
      







このようなコードは、転送された関数の最初のオペコードの相対アドレスを数値として返します。



 function HackArrayW takes nothing returns nothing local code ctcode = I2C(C2I(function zOmgFunc2) + 1) // ...
      
      







そのため、関数を呼び出すことも、それ自体ではなく、オフセットを使用して呼び出すこともできます。 さらに、変数を順番に宣言して選択し、意味のない割り当て操作を記述することで、ゲームに独自の特別な擬似コードを生成させることができます。 ちなみに、x86アセンブラーでcall命令を記述し、別の命令をアドレスとして指定し、それに移行する場合、同様の方法がアンチデバッグに使用されます。 JASSでは、このタスクは、通訳者がなじみのない指示に会ったときに...次のステップにジャンプするという事実によって著しく促進されます! したがって、このコードのおかげで、通常の変数の値を配列変数に割り当てることができます



ゲームでは、パッチで更新されない1つのライブラリ-Storm.dllを使用することが知られています。 そのアドレス空間では、アドレスが配列変数に割り当てられているデータを見つけることができ、データが特別に割り当てられた場所ではなく、プロセスの実行可能コードに直接ある配列を取得することがわかります。 サイズのフィールドが大きく、高いアドレスに何かを書き込もうとするとreallocが生成されないことが必要です。2番目のフィールドは、書き込み先のアドレスを示します。 その結果、プロセスメモリの書き込みと読み取りが可能になります。 このような配列への簡単な書き込み:



 function Inj_PrepareInjector takes nothing returns nothing set zg0oI[0x200]=0xE8575653 set zg0oI[0x201]=0x000000F3 set zg0oI[0x202]=0x9F2DC88B set zg0oI[0x203]=0xFF000006 set zg0oI[0x204]=0x72656BE0 // ...
      
      







目的のアドレスでプロセスメモリに書き込みます。 それから技術的な問題...脆弱性の作者はスタックを見つけ、その中に何かを書いた。 正しく覚えていれば、* .dllをマップのアーカイブに入れることもできますが、スクリプトでコードを暗号化することもできます。



物語


しばらくの間、検索の使用方法について説明しました。 カードにウイルスを配置することは無視できるように見えました。 ゲームの機能を拡張するライブラリを開発したくありませんでした。2つの理由:脆弱性がカバーされる可能性と悪用の可能性(もう一度、誰かがそれがどのように機能し、ウイルスを書き始めるか)。 役に立たなかったため、地図はさらに多くの人に見せられました。 そしてブリザードに着いた。 そしてサーカスが始まりました-彼らはまだ脆弱性がどのように機能するか理解できませんでした。 まず、彼らは、コードタイプを返す関数を含むBattle.netでマップをホストする技術的な手段を禁止しました。 これで問題は解決しましたが、言語の機能は削減されました。



私は自分のパッチを書くことを約束しました。その結果、ゲームのライブラリの1つをコピーして上書きする小さなプログラムが現れました... 5バイト。 はい、それは完全な救いでした。 そして、タイプコードの処理を傍受するために(脆弱性開発者からアドレスが提案されました)しました。 ゲームは常にオペコードの相対ゲームアドレス(Return Bugを使用して取得できるアドレス)を実際のアドレスに変換するため、操作と0xfffffff8を追加するだけで、誤った擬似コードの実行を防止しています。 WarCraft 3のマップ作成専用のフォーラムに投稿し、インターネットのないコテージに行きました。 私は無責任に行動したことを理解しています;)



誰もがゲームの開発者と連絡を取ろうとしましたが、少し後にパッチ1.24をリリースしましたが、これはReturn Bugを完全に禁止し、法的な対応を追加しましたが、実際には逆変換を許可しませんでした-リストからオブジェクトを取得することができなくなりました その結果、JASSでインテリジェントに記述されたカードの一部は、機能をすばやく置き換えて正常に動作し始めましたが、数値からオブジェクトへの逆変換を使用した他の部分は破損しました。 視覚的なアドインで作成されたマップは、このイベントにはまったく反応しませんでした。 一般に、Blizzardは最適なオプションを選択せず​​、互換性も破壊しました。



おわりに


最後に何を言いたいですか?



文書化されていない機能を使用する場合は、ベンチをカバーできることを注意して理解する必要があります。



ユーザーの近くにいることで、人生を大幅に簡素化できます。この脆弱性の作者は、以前はBlizzardの技術サポートと通信する際に否定的な経験がありました。 そして彼は彼らに脆弱性を知らせたくありませんでした。



そして最も重要なこと常に常に常に 入力をチェックすることです。 エンジンに組み込まれた機能によってリターンバグがすぐに置き換えられた場合、この脆弱性は発生しませんでした。 不明なオペコードにつまずいたときにインタープリターがパニックに陥り始めていたら、それはなかったでしょう。



追加:



habr.com/en/post/148543/#comment_20641885



All Articles