「Pimp my game」、またはAPIなしでゲームを「ポンピング」する方法

画像



皆さんの多くは、あなたの人生で少なくとも一度はお気に入りのコンピューターゲームを変更することに関与していると思います。 これには、リソースの編集(例: GTAのキャラクターモデルとオブジェクト)、さまざまなスクリプトの作成(例: DFHackを使用して起動されるDwarf Fortressのタスクスケジューラー)、またはMinecraft Forgeを実行しているMinecraftサーバーのmodの開発があります。



多くの場合、ゲーム開発者は機能を拡張するための公式APIを提供せず、関連するコミュニティに独自のアイデアや要望を残します。



ある晩、 UnReal World (以降-URW)と呼ばれる私のお気に入りのゲームの1つに座って、私は再び信じられないほど不快な行動に遭遇しました。 多かれ少なかれ強力な敵に出会うとき、ほとんどすべてのプレイヤーの自然な欲求は、彼が現時点で達成したすべてを失わないように即座に生き残ることです(はい、一部はこの「無作法」/「チート」などを考慮しますが、彼らは好みについて議論しません) 。 問題は、URWでの迅速な保存と読み込みが単に機能しないことです。 キャラクターを保存すると、自動的にメインメニューに戻り、保存せずに終了して前のセーブポイントに戻ると、 taskmgrを介してのみゲームを強制終了できます。 その結果、ほとんどの場合、ゲームの後半の段階での複雑な戦い(長い間蓄積してきたものすべてを失うのが本当に残念なとき)は、クレイジーな組み合わせと記憶されたキーボードショートカットで終わりました。



そして、私は考えました。 また、独自のクイックダウンロードおよび保存メニューを追加できないのはなぜですか? デバッガー、エディター、およびPEファイルアナライザーを装備して、作業を開始しました。



プロセスがどのように進み、どのようになったのか、カットの下で読んでください(慎重に、 多くのスクリーンショット )。 この記事を読む前に、以前の記事をよく理解しておくことを強くお勧めします。 ここでは省略されている点のいくつかをすでに説明しています。



ゲーム自体についてのいくつかの言葉。 UnReal Worldは、フィンランドの2人の開発者によって1992年から開発されたコンピューターゲームであり、現在も開発が続けられています。 このゲームはローグライクのジャンルの代表であり、鉄器時代後期の北部の部族の生活に飛び込むことができます。 執筆時点では、URWは完全に無料です(ただし、これは常にそうであるとは限りませんでした)。あらゆるサイズの寄付に対して、ゲームの作者による感謝のビデオが届きます。一定以上の金額を支払うと、いわゆる賞金がもらえます。 ライフタイムメンバーシップ。これにより、ベータリリースやその他の特典があるフォーラムの非表示セクションにアクセスできます。 一般的に、ゲームは本当にプレイする価値があります。



前の記事を注意深く読んだら、おそらくバイナリの研究をどこから始めるかをすでに知っているでしょう。 確かに、 DiEおよびPEiDユーティリティによるurw.exe実行可能ファイルの分析から:



画像



画像



パッカーとプロジェクターは報告されていませんでしたが、これに関連して、今回は何も撮影する必要がないと考えられます。



次に、 ASLRテクノロジーの使用テストに移りましょう。 urw.exeをPE Toolsにロードし、「オプションヘッダー」ボタンをクリックして、以前の記事-0x8140で既におなじみの「DLLフラグ」オプションの値を確認します。



画像



これを0x8100に変更し(これは、たとえばここで説明されている理由です )、2つの「Ok」ボタンをクリックして、Image Baseと同じアドレス(この場合は0x00400000 )に毎回実行可能ファイルを強制的にロードします。



urw.exeをOllyDbgにロードし、手順を検討します。



最初の段落から始めましょう。



キャラクターを作成して保存し、メインメニューに移動して、ロードを試みます。 「Loading%character_name%」というメッセージに注意を向ける必要があります。



画像



逆アセンブルされたリストでこの行への参照を見つけましょう。



画像



それに非常に似ています! 休憩を入れて、メインメニューに移動し、キャラクターを再度読み込みます。



画像



このプロシージャ全体がキャラクターの読み込みを担当していると仮定すると、コールスタックのあるウィンドウを使用してそのコールにジャンプします



画像



そして、明らかに、彼女は仕事に「環境」を必要としないことがわかります。



画像



2番目のポイント-キャラクターを保存する手順の検索に進みましょう。



手順は、原則として、前の手順と同様です。 最初に、キャラクターが保存されたときに何が起こるかを見て、この場合ゲームは対応するメッセージも書き込むという事実に注意を払います:



画像



これで、urwモジュールの「参照テキスト文字列」で文字列「Saving your character ...」を探しています。



画像



この行を参照する指示に内訳を付けて、キャラクターの保存を再度試みます。



画像



次に、現在のプロシージャコールの場所にジャンプします



画像



「環境」を見てください:



画像



ご覧のとおり、文字保存プロシージャを呼び出す前に、引数である可能性が最も高い数値1がスタックにプッシュされます。 プロシージャの本体を見ると、実際にいくつかの場所でEBP + 8の呼び出しが表示されます( EBPアドレスはEBPレジスタの古い値で、 EBP + 4は現在のプロシージャが完了した後にEBPアドレスから制御を移すべき命令のアドレスです) +8個の引数が開始されます(もちろん、存在する場合、そして原則として、ローカル変数は負のオフセットにあります)。



画像



画像



簡単に見ると、この引数の値がダウンロードの開始と終了に関するメッセージを表示する必要があることを推測できます。



同様に、キャラクターを保存するプロシージャの呼び出しの直後に続く命令ADD ESP 4に注意を払うことも重要です。 プロシージャの引数(この場合は1つだけ)によって占有されていたスタック上の場所を「クリーン」にします。 スタックの「クリーンアップ」を実行する必要のあるトピックに関する要件は、さまざまな呼び出し規約によって決定されます。



それで、ゲームワールドのロードと保存の手順を理解したので、次のステップに進みましょう。空いているホットキーを見つけてメニューを描画します。



幸いなことに、以前のバージョンのゲームでは、「拡張チーム」のメニューは「カットアウト」でした。これは、以前は「#」文字を入力して呼び出されていました。 これで、アクティブ化しようとすると、ゲームに次のメッセージが表示されます。



画像



パッチを適用するのに理想的な場所は何ですか?



「拡張コマンドメニューが削除されました」という行への参照を探しています



画像



そのうちの1つにジャンプします。



画像



手順の先頭を左クリックし、Ctrl-Rを押して、呼び出し元の場所を見つけます。



画像



そうですね、そのような魅力は1つだけです。 私たちはそれに飛び乗って、スイッチブロックのケースの1つで自分自身を見つけます:



画像



さて、パッチを実装する場所を見つけました。 では、クイックロードと保存メニューをどのように描画するかを考えてみましょう。



当然、このメニューは、釣りメニューなどで行われているように、ゲームの残りのビジュアルコンポーネントに収まる必要があります。



画像



釣りオプションの行にはヒットが多すぎるので、ネットを取得するための参照を見つけてみましょう。



画像



指定された命令にジャンプして、case'ovの1つに入ります。



画像



休憩を入れて、「釣り」メニューを開き、どこから呼び出されたかを確認します。



画像



画像



この手順の最初に休憩を入れて、OlyDbgでF9を押して、もう一度釣りメニューをアクティブにします。 ステップバイステップデバッグの結果、プロシージャ0x004FC530が既に確認したスイッチブロックから次のメニュー項目のテキストを取得し、 0x004BEF10にあるプロシージャがメニュー項目をレンダリングすることを想定しています。



画像



画像



Arg1は、対応するメニュー項目を選択するために入力する必要がある文字のASCIIコードを示し、Arg2はその名前を担当します。



メニュータイトルを引数として取るプロシージャ0x004BF3B0は 、その名前でメニューをレンダリングする役割を果たします。 また、何らかの理由で、このプロシージャを呼び出す前に0xAE16068の値が1に変更され、その後に0に変更されます。



画像



これらの手順の場合、スタックのクリーンアップの責任は呼び出し元の肩にもあることに注意してください。



手順0x004BF3B0は、メニューオプションの1つを選択するか、Escキーを押して、入力した文字のASCIIコードを示す数字をEAXレジスターに入れた後にのみ、呼び出したコードに制御を戻します(Escの場合は0



パッチのコードについて考えてみましょう。



;    PUSH "Save" PUSH 0x53 ; 'S' CALL 0x004BEF10 ;    ,    ADD ESP,8 PUSH "Load" PUSH 0x4C ; 'L' CALL 0x004BEF10 ;    ,    ADD ESP,8 ;      PUSH "Quick save-load" MOV DWORD PTR DS:[0xAE16068],1 CALL 0x004BF3B0 ADD ESP,4 MOV DWORD PTR DS:[0xAE16068],0 ; ,    CMP EAX,53 JE save ;   'S' CMP EAX,4C JNZ exit ;    'L' JMP load save: PUSH 1 ;          CALL 0x005030E0 ;   ,    ADD ESP,4 JMP exit load: CALL 0x0050CB90 ;   ,    JMP exit exit: ;   default-case switch-,    ;   ,     '#' JMP 0x0050C8C1
      
      





実行可能イメージの最後にコードケイブの場所を探しています。 アドレス0x0051039Bで開始することにしました。



画像



起こったことは次のとおりです。



 0051039B . 53 61 76 65 00 ASCII "Save",0 005103A0 . 4C 6F 61 64 00 ASCII "Load",0 005103A5 . 51 75 69 63 6B 20 73 61 76 65 2D 6C 6F 61 64 00 ASCII "Quick save-load",0 005103B5 68 9B035100 PUSH urw.0051039B ; ASCII "Save" 005103BA 6A 53 PUSH 53 005103BC E8 4FEBFAFF CALL urw.004BEF10 005103C1 83C4 08 ADD ESP,8 005103C4 68 A0035100 PUSH urw.005103A0 ; ASCII "Load" 005103C9 6A 4C PUSH 4C 005103CB E8 40EBFAFF CALL urw.004BEF10 005103D0 83C4 08 ADD ESP,8 005103D3 68 A5035100 PUSH urw.005103A5 ; ASCII "Quick save-load" 005103D8 C705 6860E10A 01000000 MOV DWORD PTR DS:[AE16068],1 005103E2 E8 C9EFFAFF CALL urw.004BF3B0 005103E7 83C4 04 ADD ESP,4 005103EA C705 6860E10A 00000000 MOV DWORD PTR DS:[AE16068],0 005103F4 83F8 53 CMP EAX,53 005103F7 74 07 JE SHORT urw.00510400 005103F9 83F8 4C CMP EAX,4C 005103FC 75 13 JNZ SHORT urw.00510411 005103FE EB 0C JMP SHORT urw.0051040C 00510400 6A 01 PUSH 1 00510402 E8 D92CFFFF CALL urw.005030E0 00510407 83C4 04 ADD ESP,4 0051040A EB 05 JMP SHORT urw.00510411 0051040C E8 7FC7FFFF CALL urw.0050CB90 00510411 ^ E9 ABC4FFFF JMP urw.0050C8C1
      
      





caseブロックのコードケイブに無条件ジャンプを追加しましょう。これは、高度なコマンドのメニューのオープンを処理する役割を果たします。



画像



ゲームで「#」を押すと...



画像



これは何ですか どうやら、前のメニューを開いた後、アイテムのリストはクリアされず、新しいアイテムでのみ拡張されました。 「フィッシング」メニューがどのように描画されるかをもう一度見てみましょう。メニューの場合、少なくとももう1つのプロシージャ0x004BED40を呼び出さないことに注意してください 。 おそらく、すべてのアイテムを描画する直前に呼び出しが行われるため、クリーンアップを担当するのは彼女です。



画像



このプロシージャを呼び出す前に、 EBXレジスタの値がスタックにプッシュされるという事実にもかかわらず、実装を見るとわかるように、このプロシージャの引数ではありません。



画像



すべての命令を「シフト」せず、すでに記述されているコードケイブのアドレスを変更しないために、case-blockのコードにこのプロシージャへの呼び出しを追加しましょう



画像



そして、もう一度「#」記号を入力してみてください。



画像



いいね!



セーブロードにふける



画像



そして、一見したところすべてが正常に機能していることに気付きます。



テストの過程で、私たちは村に来ます



画像



もう一度#-Sを押します。



画像



人々はどこへ行きましたか?



どうやら、ゲームワールドを保存する手順には、ゲームオブジェクトのクリーンアップコードも含まれています。 メインメニューにアクセスするコンテキスト以外で使用するためのものではありません。 この状況から抜け出すには2つの方法があります-クリーニングコードがどこにあるかを正確に把握する(そしてそこにネストされた呼び出しがたくさんあります、信じてください)か、通常の保存の代わりにチートして保存してから読み込みます。 最後の選択肢に留まることを提案します。 0x0051040Aにある命令JMP 0x00510411を入力して、コードケーブを変更します。



 0051039B . 53 61 76 65 00 ASCII "Save",0 005103A0 . 4C 6F 61 64 00 ASCII "Load",0 005103A5 . 51 75 69 63 6B 20 73 61 76 65 2D 6C 6F 61 64 00 ASCII "Quick save-load",0 005103B5 68 9B035100 PUSH urw.0051039B ; ASCII "Save" 005103BA 6A 53 PUSH 53 005103BC E8 4FEBFAFF CALL urw.004BEF10 005103C1 83C4 08 ADD ESP,8 005103C4 68 A0035100 PUSH urw.005103A0 ; ASCII "Load" 005103C9 6A 4C PUSH 4C 005103CB E8 40EBFAFF CALL urw.004BEF10 005103D0 83C4 08 ADD ESP,8 005103D3 68 A5035100 PUSH urw.005103A5 ; ASCII "Quick save-load" 005103D8 C705 6860E10A 01000000 MOV DWORD PTR DS:[AE16068],1 005103E2 E8 C9EFFAFF CALL urw.004BF3B0 005103E7 83C4 04 ADD ESP,4 005103EA C705 6860E10A 00000000 MOV DWORD PTR DS:[AE16068],0 005103F4 83F8 53 CMP EAX,53 005103F7 74 07 JE SHORT urw.00510400 005103F9 83F8 4C CMP EAX,4C 005103FC 75 13 JNZ SHORT urw.00510411 005103FE EB 0C JMP SHORT urw.0051040C 00510400 6A 01 PUSH 1 00510402 E8 D92CFFFF CALL urw.005030E0 00510407 83C4 04 ADD ESP,4 0051040A 90 NOP 0051040B 90 NOP 0051040C E8 7FC7FFFF CALL urw.0050CB90 00510411 ^ E9 ABC4FFFF JMP urw.0050C8C1
      
      





わあ! オブジェクトは消えますが、その後の読み込みによりすぐに表示されます。



変更をディスクに保存してみましょう。



画像



気づくのは悲しいことですが、実行可能ファイルのイメージの終わり近くに追加した指示は、以前の記事の1つにあったように、「物理的な境界」を超えてrawいました。



PE Toolsのurw.exeセクション情報を見て、上部の「境界」を計算しましょう。



画像



仮想オフセット( 0x00001000 )+生サイズ( 0x0010F400 )+画像ベース( 0x00400000 )= 0x00510400



つまり 強調表示された指示は「境界線」から「外に出た」



画像



もちろん、 NOPは削除できますが、まだ20バイト残っています。 コード洞窟の前でさらに5バイトを借りることができます。



画像



しかし、残りの15バイトをどうすればよいでしょうか? たとえば、「S」、「L」、「S&L」と等しくすることで、メニュー項目とそのタイトルを担当する行のサイズを小さくすることができますが、これは非常にエレガントなソリューションではありません。



逆アセンブルされたリストの途中に、独自のコードを追加できる場所もある可能性があります。 これらはゼロである可能性があり、通常、実行可能ファイルイメージ、 NOP、またはたとえばINT3命令の末尾にあり、プロシージャ本体間で互いに続きます。 最後のケースは、urw.exeで確認されています。 例えば



画像



行をこぼして、そのような場所で処理を保存およびロードしましょう。



 00409FBA . 53 61 76 65 00 ASCII "Save",0 00409FBF . 4C 6F 61 64 00 ASCII "Load",0 00409FC4 . 51 75 69 63 6B 20 73 61 76 65 2D 6C 6F 61 64 00 ASCII "Quick save-load",0 005007A2 6A 01 PUSH 1 005007A4 E8 37290000 CALL urw.005030E0 005007A9 ^ E9 C6F5FFFF JMP urw.004FFD74 004FFD74 83C4 04 ADD ESP,4 004FFD77 ^ E9 16FFFFFF JMP urw.004FFC92 004FFC92 E8 F9CE0000 CALL urw.0050CB90 004FFC97 E9 25CC0000 JMP urw.0050C8C1 0050C438 > \BE 20C75300 MOV ESI,urw_.0053C720 ; ASCII "Extended commands"; Case 23 ('#') of switch 0050C3FB 0050C43D . E8 FE28FBFF CALL urw.004BED40 0050C442 E9 4F3F0000 JMP urw.00510396 0050C447 . E9 75040000 JMP urw.0050C8C1 00510396 68 BA9F4000 PUSH urw.00409FBA ; ASCII "Save" 0051039B 6A 53 PUSH 53 0051039D E8 6EEBFAFF CALL urw.004BEF10 005103A2 83C4 08 ADD ESP,8 005103A5 68 BF9F4000 PUSH urw.00409FBF ; ASCII "Load" 005103AA 6A 4C PUSH 4C 005103AC E8 5FEBFAFF CALL urw.004BEF10 005103B1 83C4 08 ADD ESP,8 005103B4 68 C49F4000 PUSH urw.00409FC4 ; ASCII "Quick save-load" 005103B9 C705 6860E10A 01000000 MOV DWORD PTR DS:[AE16068],1 005103C3 E8 E8EFFAFF CALL urw.004BF3B0 005103C8 83C4 04 ADD ESP,4 005103CB C705 6860E10A 00000000 MOV DWORD PTR DS:[AE16068],0 005103D5 83F8 53 CMP EAX,53 005103D8 ^ 0F84 C403FFFF JE urw.005007A2 005103DE 83F8 4C CMP EAX,4C 005103E1 ^ 0F85 B0F8FEFF JNZ urw.004FFC97 005103E7 ^ E9 A6F8FEFF JMP urw.004FFC92
      
      





今回は、画像が正常に保存され、すぐにロードして保存するための新しいメニューを使用して、URWで遊ぶことができます。



あとがき



怠azineは、ほとんどすべての人と活動分野で最も厄介な性質の1つです。 初めてではなく直面している問題の解決に少し時間を費やすことを怠らないでください-これにより、将来的にはより多くの時間を節約できるでしょう。



ご清聴ありがとうございました。また、この記事が誰かに役立つことを願っています。



All Articles