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の呼び出しに必要な情報をまとめると、以下になります。
- オフセット:
ac_client.exe+0x607c0
- 呼び出し規約: とりあえず
__stdcall
- 引数: 無し
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があります。