はじめに
Age of Mythologyは、プレイヤーが文明を築き、すべての敵を倒そうとするリアルタイム戦略ゲームです。 標準モードでは、プレーヤーは、完全に黒で塗りつぶされたマップでゲームを開始します。これは、未知の未開の領域を意味します。


ゲーム中、ユーザーはマップを調べます。調査したエリアには、地形、資源、敵の建物の兆候があり、「霧」に重ねて表示されます。


この投稿の目標は、マップ全体を開くハックを作成して、プレーヤーに大きな利点を与えることです。 これにより、プレイヤーはマップ上で対戦相手が何をしているかを確認し、攻撃に最適な時間と場所を選択できます。 このハックはゲームのオリジナルバージョン用に開発されますが、後でSteamで配布されている新しい高度なバージョンに適用する方法を示します。
この記事のリバースエンジニアリングによって解析されるメインの実行可能ファイルのハッシュは次のとおりです。
CRC32 :7F1AF498
MD5 :09876F130D02AE760A6B06CE6A9C92DB
SHA-1 :AAAC9CD38B51BEB3D29930D13A87C191ABF9CAD4
パート1:難しい道
はじめに
私たちの目標は、すべてのマップデータをプレーヤーに開くものを開発し、プレーヤーにゲームで何が起こっているかについての完全な情報を提供することです。 良いニュースは、カードの公開と非表示がゲームの組み込み機能であることです。 ゲームは記録されたゲームの再生をサポートし、カードのオープン非表示オプションはこの機能のUIの一部です。

「戦争の霧」ボタンは、マップの開始を制御し、通常の状態に戻ります。このモードでは、プレーヤーは自分が調べたものだけを見ることができます。 計画では、このボタンのハンドラーを見つけて、マップ展開ロジックの場所を追跡します。 見つかったら、DLLをゲームに挿入して、マップ展開関数を呼び出します。 この作業には、 チートエンジンなどのツールが適しています。これは、ハッキングゲームのコンテキストでのメモリの調査と変更、デバッグ、分解、およびその他の操作に役立ちます。 この記事では、このツールの使用については説明しませんが、他にも多くのリソースがあります。
Cheat Engineを起動して接続した後、問題はボタンと対話するコードがどこにあるかです。 見つける最も簡単な方法は、標準のプログラミング手法を適用することです。 特に、アクティブなボタンの値はメモリのどこかに1があり、非アクティブなボタンの値は0になります。したがって、これはテストと忍耐の問題になります。 プロセスメモリで値「1」を検索すると(ボタンがアクティブな場合)、337,597の結果が返されました。 同じことをしようとする場合、値が同じであると期待しないでください。

これは確認するには多すぎます。 もう一度ボタンを押して非アクティブにし、値「0」を検索します。 プログラムは376を返します-まだ多すぎます。

このプロセスを数回繰り返すと、検索エリアがすでに非常に便利な21個のアドレスに縮小されました。

これらの21の20は互いに非常に近かった。 0x08FC71A4は、このシリーズの例外のようです。 より慎重に検討し、値「0」を変更すると、ボタンを非アクティブ状態に切り替えることができました。 したがって、目的のアドレスが見つかり、残りの20個は安全に破棄できます。 次のステップは、何が記録されているかを調べることです。

この時点で、チートエンジンはデバッガを接続し、 0x08FC71A4へのすべての書き込みを監視します。 ボタンを数回押した後、次のコマンドが識別されました。 これらのコマンドは0x08FC71A4に書き込みました 。

次に、これらの記録操作の次に何が起こっているかをより理解するために、それらを調べてブレークポイントを設定する必要があります。 書き込みコマンドにブレークポイントを設定する

ゲームに夢中になったことで、この機能がボタンごとに呼び出されることがわかりました。 ここで、 ECXはボタンへのポインタであり、+ 0x1A4にはおそらくIsToggledプロパティが含まれており、対応する状態をボタンに割り当てます。 この割り当ては2番目の書き込みコマンドで実行され、 EDXは「0」(非アクティブ)または「1」(アクティブ)になります。 コードは少し複雑に見えるかもしれませんが、含まれる状態が正しいことを確認し、 IsToggledプロパティを設定してから関数を呼び出して戻ります。
受信者アドレス+ 0x14B670は、すべてのボタンに適用されるコードでもあります。 ここで、すべてをゆっくりと調べて、「Fog of War」ボタンに関連する可能性のあるコードの領域を見つける必要があります。 さまざまなアプローチを適用できますが、通常は以下を使用します。
- ケースごとに計算された呼び出しアドレス。 これは、ボタンの状態が変化した後に実行されるイベント処理メカニズム、 OnChanged / OnEnabled / OnDisabledなどの機能、または同様の機能を意味します。
- 関数へのポインターである関数パラメーター。
- 引数1または0を受け取る関数の呼び出し。
+ 0x14B670を入力する手順により、以下に示す次の(部分的な)アセンブラコードが得られます。 アセンブラコードでは、 メモリ内のモジュールの開始アドレス+ offsetではなく、絶対アドレスが示されます 。これは、チートエンジンからよりもIDAからコピーする方がはるかに簡単だからです。
.text:0054B670 mov eax, large fs:0
.text:0054B676 push 0FFFFFFFFh
.text:0054B678 push offset SEH_54B670
.text:0054B67D push eax
.text:0054B67E mov large fs:0, esp
.text:0054B685 sub esp, 8
.text:0054B688 push esi
.text:0054B689 mov esi, ecx
.text:0054B68B mov eax, [esi+148h]
.text:0054B691 push edi
.text:0054B692 mov edi, [esi]
.text:0054B694 push eax
.text:0054B695 push esi
.text:0054B696 lea ecx, [esp+24h+var_10]
.text:0054B69A call sub_4D7470
.text:0054B69F mov ecx, [eax]
.text:0054B6A1 push ecx
.text:0054B6A2 push 1
.text:0054B6A4 mov ecx, esi
.text:0054B6A6 call dword ptr [edi+54h]
.text:0054B6A9 cmp [esp+1Ch+arg_0], 0Dh
.text:0054B6AE jnz loc_54B769
.text:0054B6B4 lea edi, [esi+154h]
...
0x004D7470 ( 赤 )を呼び出した呼び出しは非常に迅速に戻るため、ここには表示されません。 + 0x14B6A6での次の呼び出し( 青 )は、レジスタを介して呼び出しを行います。 これは入念な研究の良い候補です。 この関数は、2つの可能なアドレスを呼び出すことができます。
...
.text:0054BF98 push 0Ch
.text:0054BF9A call dword ptr [eax+0CCh]
.text:0054BFA0
.text:0054BFA0 loc_54BFA0: ; CODE XREF: sub_54BF80+Fj
.text:0054BFA0 ; sub_54BF80+14j
.text:0054BFA0 mov ecx, [esp+0Ch+arg_8]
.text:0054BFA4 push ecx
.text:0054BFA5 push edi
.text:0054BFA6 push ebx
.text:0054BFA7 mov ecx, esi
.text:0054BFA9 call sub_4D4EF0
.text:0054BFAE pop edi
...
+ 0x14BF9A ( 赤 )のコマンドは、デバッグおよびパス中に呼び出されることはないため、調査する意味はありません。 研究のために、次のコールのみが+ 0x14BFA9 ( 青 )のままです。 この関数はサイズが非常に大きく、多くの可能性のある呼び出しポイントを備えた広い分岐があります。 デバッグでは、このロジックのほとんどをスキップできます。 Fog of Warボタンがアクティブな状態でのみ実行されるコードをトレースすることにより、3つのコールロケーションのみを選択します。
...
.text:004D504C cmp esi, dword_A9D068
.text:004D5052 jz short loc_4D5087
.text:004D5054 push esi
.text:004D5055 call sub_424750
.text:004D505A mov edi, eax
.text:004D505C add esp, 4
.text:004D505F test edi, edi
.text:004D5061 jz short loc_4D5070
.text:004D5063 push esi
.text:004D5064 call sub_4D58B0
.text:004D5069 add esp, 4
.text:004D506C test edi, edi
.text:004D506E jnz short loc_4D5079
.text:004D5070
.text:004D5070 loc_4D5070: ; CODE XREF: sub_4D4EF0+171j
.text:004D5070 pop edi
.text:004D5071 pop esi
.text:004D5072 pop ebp
.text:004D5073 xor al, al
.text:004D5075 pop ebx
.text:004D5076 retn 0Ch
.text:004D5079 ; ---------------------------------------------------------------------------
.text:004D5079
.text:004D5079 loc_4D5079: ; CODE XREF: sub_4D4EF0+17Ej
.text:004D5079 mov eax, [esp+10h+arg_4]
.text:004D507D mov edx, [edi]
.text:004D507F push ebp
.text:004D5080 push eax
.text:004D5081 push ebx
.text:004D5082 mov ecx, edi
.text:004D5084 call dword ptr [edx+54h]
.text:004D5087
.text:004D5087 loc_4D5087: ; CODE XREF: sub_4D4EF0+157j
.text:004D5087 ; sub_4D4EF0+162j
.text:004D5087 pop edi
...
トレース後の+ 0xD5055 ( 赤 )での呼び出しは、行き止まりになります。 同じことが+ 0xD5064 ( オレンジ )にも当てはまります。 デバッガを使用してそれらにアクセスし、コード実行パスのトレースを開始すると、これら2つの関数の動作は非常に似ていることがわかります。 ただし、マップとのやり取りに関して、[戦争の霧]ボタンの操作と共通点があることを示唆するものは何もありません。 これら2つのコマンドにブレークポイントを設定すると、それらは常にどこかから呼び出され、呼び出し元のオブジェクトのロジックのみを実行することがわかります。 この段階では、まだUIとボタンのクリックに関連する一般的なコードを使用しているため、これら2つの機能はマップの表示とは無関係であると考えるのは十分安全です。
呼び出す最後の場所は+ 0xD5084 ( 青 )です。 それを入力すると、 + 0xD4EF0につながります。これは別の素晴らしい機能です。
.text:004D4EF0 push ebx
.text:004D4EF1 mov ebx, [esp+4+arg_0]
.text:004D4EF5 push ebp
.text:004D4EF6 mov ebp, [esp+8+arg_8]
.text:004D4EFA push esi
.text:004D4EFB mov esi, ecx
.text:004D4EFD mov ecx, [esi+0B8h]
...
コントロールポイントを配置すると、常に機能します。つまり、標準の処理コードでもあります。 さらに進むと、前のリストで示したコードに戻ることがわかります。 0x00424750と0x004D58B0への同じ2つの呼び出しが行われます。 次に[EDX + 0x54]が呼び出されますが、今回はEDXの意味が異なります。 この2番目の呼び出しで、これは+ 0xD0C70で次の関数につながります。
.text:004D0C70 mov ecx, [ecx+14Ch]
.text:004D0C76 test ecx, ecx
.text:004D0C78 jz short loc_4D0C91
.text:004D0C7A mov edx, [esp+arg_8]
.text:004D0C7E mov eax, [ecx]
.text:004D0C80 push edx
.text:004D0C81 mov edx, [esp+4+arg_4]
.text:004D0C85 push edx
.text:004D0C86 mov edx, [esp+8+arg_0]
.text:004D0C8A push edx
.text:004D0C8B call dword ptr [eax+30h]
.text:004D0C8E retn 0Ch
.text:004D0C91 ; ---------------------------------------------------------------------------
.text:004D0C91
.text:004D0C91 loc_4D0C91: ; CODE XREF: sub_4D0C70+8j
.text:004D0C91 xor al, al
.text:004D0C93 retn 0Ch
.text:004D0C93 sub_4D0C70 endp
呼び出す実際の場所は1つしかないため、この関数は非常に簡単に分析できます。 チェックポイントの設定は、どこからでも呼び出されること、つまり、一般的なコードであることを示します。 [EAX + 0x30]を呼び出すと、 + 0x680D0になります。 コントロールポイントを使用してプロセスを繰り返すと、どこからでも呼び出されることがわかります。したがって、ここでは何も役に立ちません。
.text:004680D0 push 0FFFFFFFFh
.text:004680D2 push offset SEH_4680D0
.text:004680D7 mov eax, large fs:0
.text:004680DD push eax
.text:004680DE mov large fs:0, esp
.text:004680E5 sub esp, 0F8h
.text:004680EB mov eax, [esp+104h+arg_8]
.text:004680F2 push ebx
.text:004680F3 push ebp
.text:004680F4 push esi
.text:004680F5 mov esi, [esp+110h+arg_0]
.text:004680FC push edi
.text:004680FD mov ebp, ecx
.text:004680FF mov ecx, [esp+114h+arg_4]
.text:00468106 push eax
.text:00468107 push ecx
.text:00468108 push esi
.text:00468109 mov ecx, ebp
.text:0046810B mov [esp+120h+var_F0], ebp
.text:0046810F call sub_4718B0
.text:00468114 test al, al
...
特定のコードを検索する
アドレス+ 0x6810Fで呼び出しの最初の場所を入力するステップにより、巨大な遷移テーブルを含む関数に移動します(下のスクリーンショットを参照)。 これは、イベントを制御したり、イベント処理メカニズムを処理したりする領域を見つけたという有望な兆候かもしれません。

段階的なコード実行により、次のケースが発生します。
.text:00471DB4 loc_471DB4: ; CODE XREF: sub_4718B0+4FDj
.text:00471DB4 ; DATA XREF: .text:off_471FA0o
.text:00471DB4 push edi ; jumptable 00471DAD case 4
.text:00471DB5 call sub_54E7D0
.text:00471DBA mov esi, eax
.text:00471DBC add esp, 4
.text:00471DBF test esi, esi
.text:00471DC1 jz loc_471F5F ; jumptable 00471DAD case 3
.text:00471DC7 push edi
.text:00471DC8 call sub_4D58B0
.text:00471DCD add esp, 4
.text:00471DD0 test esi, esi
.text:00471DD2 jz loc_471F5F ; jumptable 00471DAD case 3
.text:00471DD8 mov edx, [esi+1A4h]
.text:00471DDE mov ecx, [esp+50h+var_40]
.text:00471DE2 cmp edx, ebx
.text:00471DE4 setz al
.text:00471DE7 push eax
.text:00471DE8 call sub_58EA10
.text:00471DED mov al, 1
.text:00471DEF jmp loc_471F65
...
コントロールポイントを+ 0x71DB4 ( ピンク )に設定して続行すると、ここには常に何も届かないことがわかりました。 「Fog of War」ボタンをクリックすると、 + 0x71DB4が実行されていることがわかります 。 そして最後に、長いトレースの後、Fog of Warボタンに関連するコード内にいるという証拠を取得します。 最初の呼び出しコマンドは+ 0x71DB5 ( 赤 )です。 この関数はEDIを介して1つのパラメーターを受け取り、常に定数値です。 コードをステップごとに実行し、すべてのパラメーターの値またはアドレス可能/ロードされたアドレスを注意深く観察すると、ボタンスイッチの値について言及するものは何も見つかりません。 特に、マップのオープン/非表示ボタンをクリックして機能をトレースしたところ、何も変化しなかったため、それを除外しました。 アドレス+ 0x71DC8 ( オレンジ )のコマンドは、既に上で調べたアドレス0x004D58B0を呼び出します。 同じことが関数でも起こります。 常に前の関数と同じ値を取得し、スイッチ値を書き込むか、その値に基づいてコードを制御するかについては何も言いません。
次の呼び出しは+ 0x71DE8です。 この関数も1つのパラメーターを受け取ります。これは、遷移テーブルを処理する関数を終了する前に呼び出される最後の関数でもあります。 ターコイズブロックには非常に興味深いコードがあります。 値は[ESI + 0x1A4]からロードされ、 EBXと比較されます。 この比較の結果は、 ALバイトを0または1に設定します。0または1になるEAXは、引数として0x0058EA10で関数に渡されます。 ゲームでボタンを押すと、ステップごとに実行すると、 EBXには常に値1が含まれ、 EDXにはカードが非表示か表示かに応じて0または1が含まれることが示されます。 これは、カードの表示と非表示に使用される機能であると想定できます。 0x0058EA10のアセンブラリストを以下に示します。
.text:0058EA10 sub_58EA10 proc near ; CODE XREF: sub_4718B0+538p
.text:0058EA10 ; sub_58DF30+919p ...
.text:0058EA10
.text:0058EA10 arg_0 = dword ptr 4
.text:0058EA10
.text:0058EA10 push ebx
.text:0058EA11 mov ebx, [esp+4+arg_0]
.text:0058EA15 mov [ecx+53h], bl
.text:0058EA18 mov eax, dword_A9D244
.text:0058EA1D mov ecx, [eax+140h]
.text:0058EA23 test ecx, ecx
.text:0058EA25 jz short loc_58EA43
.text:0058EA27 push 1
.text:0058EA29 push ebx
.text:0058EA2A call sub_5316B0
.text:0058EA2F mov ecx, dword_A9D244
.text:0058EA35 mov ecx, [ecx+140h]
.text:0058EA3B push 1
.text:0058EA3D push ebx
.text:0058EA3E call sub_5316D0
.text:0058EA43
.text:0058EA43 loc_58EA43: ; CODE XREF: sub_58EA10+15j
.text:0058EA43 pop ebx
.text:0058EA44 retn 4
.text:0058EA44 sub_58EA10 endp
値0または1をさらに2つの関数に渡します。各関数は2つのパラメーターを取ります。 最初のパラメーターは切り替え値0または1、2番目は常にハードセット値1です。これら2つの関数を見ると、値0または1をオブジェクトに書き込んでから関数を呼び出すことがわかります。
.text:005316B0 ; =============== SUBROUTINE =======================================
.text:005316B0
.text:005316B0
.text:005316B0 public sub_5316B0
.text:005316B0 sub_5316B0 proc near ; CODE XREF: sub_442070+1684p
.text:005316B0 ; sub_4C91E0+14Cp ...
.text:005316B0
.text:005316B0 arg_0 = byte ptr 4
.text:005316B0 arg_4 = dword ptr 8
.text:005316B0
.text:005316B0 mov edx, [esp+arg_4]
.text:005316B4 mov al, [esp+arg_0]
.text:005316B8 push edx
.text:005316B9 push 1
.text:005316BB mov [ecx+40Eh], al
.text:005316C1 call sub_5316F0
.text:005316C6 retn 8
.text:005316C6 sub_5316B0 endp
.text:005316C6
.text:005316C6 ; ---------------------------------------------------------------------------
.text:005316C9 align 10h
.text:005316D0
.text:005316D0 ; =============== SUBROUTINE =======================================
.text:005316D0
.text:005316D0
.text:005316D0 sub_5316D0 proc near ; CODE XREF: sub_442070+1698p
.text:005316D0 ; sub_4C91E0+137p ...
.text:005316D0
.text:005316D0 arg_0 = byte ptr 4
.text:005316D0 arg_4 = dword ptr 8
.text:005316D0
.text:005316D0 mov edx, [esp+arg_4]
.text:005316D4 mov al, [esp+arg_0]
.text:005316D8 push edx
.text:005316D9 push 1
.text:005316DB mov [ecx+40Fh], al
.text:005316E1 call sub_5316F0
.text:005316E6 retn 8
.text:005316E6 sub_5316D0 endp
交換パッチ
mov al、[esp + arg_0]
に
mov al、0
いや
いや
Fog of Warボタンの状態に関係なく、ミニマップが常に開いているという事実につながります。 私たちは、カードを明らかにし、隠すためのコードを見つけました。
ハック開発
この段階で、ハックを作成できます。必要なカードの状態に応じて、true / falseの値で0x0058EA10を呼び出すだけです。 ただし、小さな問題があります。 [ECX + 0x53]のアドレス0x0058EA15に書き込みコマンドがあります。 これは、記録可能なフィールドを持つオブジェクトをアドレス+ 0x53に転送する必要があることを意味します。このアドレスは、通常__thiscallを使用してECX経由で送信される「 this 」パラメーターとして機能します。 さらに、 ECX関数では、固定アドレスからロードした後に上書きされるため、これは安全なアプローチのように思えます。 このタスクのダーティコードを以下に示します。
#include <Windows.h> struct DummyObj { char Junk[0x53]; }; DummyObj dummy = { 0 }; using pToggleMapFnc = void (__thiscall *)(void *pDummyObj, bool bHideAll); int APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: { (void)DisableThreadLibraryCalls(hModule); pToggleMapFnc ToggleMap = (pToggleMapFnc)0x0058EA10; while (!GetAsyncKeyState('0')) { if (GetAsyncKeyState('7')) { ToggleMap(&dummy, true); } else if (GetAsyncKeyState('8')) { ToggleMap(&dummy, false); } Sleep(10); } break; } case DLL_PROCESS_DETACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; } return TRUE; }
DLLがゲームに挿入された後、「7」および「8」キーを使用して、カードを完全に開いた状態または閉じた状態に切り替えることができます。


おわりに
これで、ゲーム用のハックカードの開発が完了しました。 このアプローチは非常に複雑で複雑でした。記事の次の部分では、実行可能ファイルに開発者が残した有用な情報を使用して、すべてを大幅に簡素化する方法を示します。 記事を読んで、最初から最後までの作業が非常に線形であると判断できますが、実際には、簡潔にするために、行き止まりに至るコードの多くのパスが欠落していました。 もし彼らが留まるなら、彼ら自身と彼らの説明が論文にボリュームをもたらす可能性があります。 ハックの初期開発中に、コードを何度も実行するさまざまな方法を試し、必要な可能性があるものについてメモしました。 その結果、この記事では有用な情報のみを収集し、全体的かつほぼ線形のガイドにまとめました。
パート2:簡単な方法
前のパートでは、ゲームの組み込み機能を使用してハックカードを作成する方法について説明しました。 この手法では、非表示/オープン状態を切り替える機能を使用しました。 これらの組み込み関数を一貫して適用して、アセンブリコードを検索しています。 その結果、これにより、これらの機能を呼び出すハックを作成できるマップを非表示にして表示するロジックに導かれました。 このパートでは、はるかに単純な手法について説明します。これは、バイナリに含まれる有用な文字列のおかげでのみ可能です。
このパートでは、優れたデバッガーおよび逆アセンブラーであるx64dbgを使用します。これは、すでに廃止されたOllyDbgの後継と見なされます。 残念ながら、この部分では、コードを実行するプロセスで分析をほとんど必要としなかったため、あまり使用しませんでした(最終的に、この部分は「簡単な方法」と呼ばれます)。 アセンブリされたフラグメントはIDA Proから貼り付けられます。コピーペースト形式が最も読みやすいためです。
プロセスへの接続と、メインの実行可能ファイルの文字列(マウスの右ボタン->検索->現在のモジュール->文字列参照)のダンプから始めて、25817行(検索用のかなり大きな領域)がありました。

「map」行のフィルターは、より便利なセットを提供します。 それを調べてみると、何か面白いことにつながる可能性のある行がいくつか見つかりました。
「TrSetFogAndBlackmap(<true / false> <true / false>):霧と黒のマップのオン/オフ。」
「TrRevealEntireMap-表示モードがどのように機能するかに似たマップ全体を表示します」
「TrPlayerResetBlackMap(:特定のHUMANプレイヤーのブラックマップをリセットします。」
「マップの可視性」
「ブラックマップ([integerState]):未探索のブラックマップレンダリングを切り替えまたは設定します。」
オレンジ色で強調表示された2つの最も有望な行。 行は、関数が何をするのかを明確に示しており、パラメーター引数を伝えます。 trX機能は、マップ作成者がエフェクトと条件を追加できるゲーム内トリガーシステムに関連しているようです。 最初の行のリンクを調べると、次のことがわかります。
...
.text:008B2B76 loc_8B2B76: ; CODE XREF: sub_8AE4A0+46CDj
.text:008B2B76 mov ecx, esi
.text:008B2B78 call sub_59C270
.text:008B2B7D push 1
.text:008B2B7F push offset loc_8AAEE0
.text:008B2B84 push offset aTrsetfogandbla ; "trSetFogAndBlackmap"
.text:008B2B89 mov ecx, esi
.text:008B2B8B call sub_59BE80
.text:008B2B90 test al, al
.text:008B2B92 jnz short loc_8B2BAE
.text:008B2B94 push offset aTrsetfogandbla ; "trSetFogAndBlackmap"
.text:008B2B99 push offset aSyscallConfigE ; "Syscall config error - Unable to add th"...
.text:008B2B9E push esi ; int
.text:008B2B9F call sub_59DBC0
...
ここのコードは、文字列、関数へのポインター、および定数(1)を別の関数( turquoise )の引数として渡すことから始まります。 この呼び出しの戻り値は、エラー状態( 青 )である等号0でチェックされます。 逆アセンブラで何が起こるかを見ると、このテンプレートがどこでも使用されていることがわかります。 このコードとそれを取り巻くコードは、トリガーの登録を試み、トリガーの名前、トリガーコードがある場所にイベントを処理するメカニズム、および未知の定数1を報告します。これを念頭に置いて、イベントを処理するメカニズムで検索を続行する必要があります。
イベント処理メカニズムへの移行は、次のコードフラグメントにつながります。
.text:008AAEE0 loc_8AAEE0: ; DATA XREF: sub_8AE4A0+46DFo
.text:008AAEE0 mov eax, dword_A9D244
.text:008AAEE5 mov ecx, [eax+140h]
.text:008AAEEB test ecx, ecx
.text:008AAEED jz short locret_8AAF13
.text:008AAEEF mov edx, [esp+4]
.text:008AAEF3 push 0
.text:008AAEF5 push edx
.text:008AAEF6 call sub_5316B0
.text:008AAEFB mov eax, [esp+8]
.text:008AAEFF mov ecx, dword_A9D244
.text:008AAF05 mov ecx, [ecx+140h]
.text:008AAF0B push 0
.text:008AAF0D push eax
.text:008AAF0E call sub_5316D0
.text:008AAF13
.text:008AAF13 locret_8AAF13: ; CODE XREF: .text:008AAEEDj
.text:008AAF13 retn
記事の最初の部分を注意深く読んだ場合、ここでの2つの呼び出し( 緑 )はおなじみでしょう。 これらは、私たちが発見したように、カードの表示と非表示を制御する2つの機能です。 各関数は「 this 」へのポインターを受け取ります。これは、ここで見られるように、永続アドレスからロードされ、ほとんどの場合、メインプレーヤーのクラスと、マップで何が起こるかを説明するtrue / falseの値です。 3番目の不変パラメーター0があります。これは、記事の前の部分からの呼び出しの他の場所で不変パラメーター1とは異なります。 おそらく、カードの状態がプレーヤーまたはトリガーによって変更されたことを示しています。
これを知って、前の部分からのハックを少し良くすることができます。 古いハックでは、記録可能なフィールドが必要な偽の「 this 」ポインターの提供に問題があり、スイッチオプションはtrue / falseのみでした。 文字列ダンプが受け取ったドキュメントに基づいて、この関数は2つのブール値を取ります。 おそらく、それらは重ねられた黒い色と戦争の霧を制御し、プレイヤーが既に探索したがプレイヤーが現在見ない領域を覆い隠します。
新しい(まだ少し汚い)を以下に示します。
#include <Windows.h> using pToggleMapFnc = void (__cdecl *)(bool bEnableBlackOverlay, bool bEnableFogOfWar); int APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved) { switch (dwReason) { case DLL_PROCESS_ATTACH: { (void)DisableThreadLibraryCalls(hModule); pToggleMapFnc ToggleMap = (pToggleMapFnc)0x008AAEE0; while (!GetAsyncKeyState('0')) { if (GetAsyncKeyState('6')) { ToggleMap(true, true); } else if (GetAsyncKeyState('7')) { ToggleMap(true, false); } else if (GetAsyncKeyState('8')) { ToggleMap(false, true); } else if (GetAsyncKeyState('9')) { ToggleMap(false, false); } Sleep(10); } break; } case DLL_PROCESS_DETACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; } return TRUE; }
パラメーターのさまざまな組み合わせで関数を呼び出すことにより、次の動作を取得することができました。
True / True-戦争の霧でオーバーレイされた黒
真/偽-黒が重なることはなく、戦争の霧があります。 マップ上にマークはありません。
False / True-戦争の霧のない黒を重ね合わせます。 調査対象エリアは常に表示されます。
False / False-黒のオーバーレイなし、霧の霧なし。 マップ全体が表示されます。
以下は、4つの状態すべてのスクリーンショットです。




ハックは直接関数呼び出しを実行し、未知のものを渡す必要がないため、よりクリーンになりました。 明らかに、長いデバッグとトレースを必要とする以前のソリューションとは異なり、これを「簡単な方法」と考える理由を願っています。
記事の次の最後の部分では、このハックを少しクリーンでプロフェッショナルにする方法について考えます。 さらに、ハッキングを新しいバージョンのゲームExtended Editionに移植するために必要なものを検討します。
パート3:すべてをまとめる
前の2つのパートでは、Age of Mythologyマップハックの開発方法について説明しました。 これを行うには、マップの状態の切り替えを担当するゲームの部分(重ねられた黒いレイヤー、霧の霧、完全に公開されたマップ)を見つけてリバースエンジニアリングし、ゲームプロセスに挿入されたDLLを通じてこれらの関数を呼び出します。 この短い部分では、Age of Mythologyプロセスに開発したハックDLLを挿入するソースコードにインジェクターを追加して、トピックを終了します。 ハッキングは、マルチプレイヤーモード、元のゲーム、および高度なバージョンで動作します。
コードはgithubに投稿されており、通常は説明は不要です。 マップハックDLLはKeyboardProcコールバックをエクスポートします。これは、ユーザーが押したキー(7、8、9、0)に応じてマップの状態を切り替えるロジックを制御します。 インジェクターはキーボードフックをゲームプロセスに設定します。これにより、ハックDLLがゲームプロセスに挿入され、KeyboardProcコールバックがアクティブになります。 その後、ゲームに送信されるすべてのキーストロークがインターセプトされ、マップの状態を切り替える4つのキーへの準拠をチェックします。 スイッチキーが押されると、カードの状態を変更する対応する関数が呼び出されます。