41: TriggerBot

概要

Aimbotは近くの敵を自動でエイムしてくれる物である一方、TriggerBotは敵をエイムすると自動で弾を打ってくれる物です。
今回は、TriggerBotを作る事を目標にやっていきます。
チート対象のゲームは、x86でオープンソースのAssault Cube v1.2.0.2を使います。
Windows対象で、Visual Studio 2019を使用しC++で記述する。
尚、この記事はGuidedHackingさんのこの動画を参考に書いてます。

TriggerBotの作り方概要

TriggerBotは、敵をエイムすれば自動で弾を打つ機能です。
敵をエイムしてるかどうかを判別する方法ですが、AssaultCubeでエイムしてる時としていない時で違いが出る所と言えば、左下の名前の表示が思いつきます。 左下の名前(エイムしてる敵の名前)を取得する関数(GetCrossHairEnt)の結果を見る事により、エイムしてるかどうかを区別できそうです。
また、エイムした後に弾を打つ方法に関しては、Shoot関数を特定して呼び出せば良さげですが、どうやら動画によるともっと簡単な方法があるらしく、 AssaultCubeのPlayerクラスのオフセット0x224に、trueで攻撃、falseで攻撃しないを切り替えられる BOOL があるらしいので、今回は簡単にそれを使っていきます。

GetCrossHairEnt関数の特定

CheatEngineでエイムしてる敵の名前を特定し、その後別の敵をエイムして、値がその敵の名前になっているアドレスをまずは特定します。 それがたぶんGetCrossHairEnt関数の戻り値を利用しているはずなので、そのアドレスに書き込みを行っている命令周辺をリバースして、GetCrossHairEntを特定していきます。

まず、以下のようにエイムしてる敵の名前でFirstScanをします。 次に、もう一人の敵をエイムすれば、複数の候補の内一つだけ値がその敵の名前に変わってるはずです。 よって、そのアドレスをアドレスリストに追加し、そのアドレスに書き込みを行っている命令を確認します。 オフセットが 0xadb7 だと分かったので、Ghidraに切り替えてみてみましょう。
変数名だけ変えましたが、ハイライトしている所がCheatEngineで特定したアドレスへの書き込み命令なので、entName が表示されてる名前の文字列である事がわかり、その上の行でその文字列が entNamePtr というポインタから来てるのがわかります。 そしてそのポインタは、entPtr?+0x225 から来ているのですが、敵の名前はどうせ敵のクラスのメンバ変数だと思うので、 entPtr? はその敵(エンティティ)のポインタであることがわかります。そして、entPtr? は赤枠の関数の戻り値なので、これが GetCrossHairEnt だろうと予測できます。

これが GetCrossHairEnt かどうかは実際どうでもよく、大事なのは 「この関数を使う事でエイムしてるかしてないかを判別できるか」なので、 実際にエイムしてる時としてない時とでこの関数の戻り値が変わるかを見てみましょう。 関数の戻り値は、eaxレジスタに入るので、この関数のcall直後にBPを打って、eax を読み取ればわかります。また、事前に緑枠の部分のコードから、エイムしてない時は 0 (NULL) が返ってくるんだろうなと予測はできます。 実験してみた結果、この関数の戻り値がNULLかそうじゃないかを見れば、エイムしてるかどうかを判別できそうです。

後気になるのは、呼び出し規約ですが、今回この関数は引数を取らないのでぶっちゃけ何でもいいです。 そもそも呼び出し規約は引数をレジスタにいれるのかスタックにいれるのかだったり、 引数をスタックに入れた分を関数の呼び出し側と呼び出した関数内のどちらで消すかなので、 引数が無いなら適当に __stdcall とかにしておけば動きます。

GetCrossHairEntの呼び出しに必要な情報をまとめると、以下になります。

TriggerBotの実装

では、実際に実装していきます。
新しいC++のDLLプロジェクトを立ち上げ、文字列をマルチバイト、Debug/x86でのビルドに設定しましょう。

コードは実は驚くほどシンプルなので、以下にいきなり完成形を置きます。
#include "pch.h"

typedef DWORD* (__stdcall* _GetCrossHairEnt)();
_GetCrossHairEnt GetCrossHairEnt;

DWORD WINAPI ThreadMain(LPVOID params) {
    DWORD baseAddr   = (DWORD)GetModuleHandle("ac_client.exe");
    DWORD playerAddr = *(DWORD*)(baseAddr + 0x10f4f4);

    GetCrossHairEnt = (_GetCrossHairEnt)(baseAddr + 0x607c0);

    while (1) {
        if (GetCrossHairEnt() != NULL) 
            *(bool*)(playerAddr + 0x224) = 1;
        else
            *(bool*)(playerAddr + 0x224) = 0;
    }
    return 0;
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
        CreateThread(0, 0, ThreadMain, hModule, 0, 0);
    }
    return TRUE;
}
プレイヤーのアドレスは、AssaultCubeではグローバル変数 baseAddr + 0x10f4f4 にいつも置いてあるので、それを参照していて、そのオフセット 0x224 に、攻撃する/しない を切り替えられるboolがあります。

やってみる

実際にDLLをインジェクトしてやってみると、敵をエイムした時に自動で打ちはじめ、 エイムをはずすと打たなくなることが確認できると思います。