22: ゲーム内関数をC++で呼出し任意のアイテムを取得

概要

ゲーム内にアイテムを追加する関数があった場合、それをInjectしたDLLから呼び出す事ができる。
PwnAdventure3の場合、GetItemByNameAddItemの二つの関数を呼び出すことにより、ゲーム上の任意のアイテムを取得することができる。
今回は、ゲーム内のアイテムを取得する関数をInjectするDLLから呼び出し、任意のアイテムを取得するという事を目標にやっていく。
チート対象のゲームは、x86でチートの練習用に作られた、Pwn Adventure 3を使います。
Windows対象で、C++を言語として用いる。ソースコードはまとめてページの一番下にある。 また、18回の記事general-cheatsのコードをそのまま使っています。
※ ネタバレ注意: PwnAdventure3で取得できるアイテムの一部が載っています

使用する関数

本来ならばゲーム上のどの関数がアイテムを取得する関数なのかなどは長く苦しいリバースエンジニアリングをしてからわかるものですが、 今回は本題ではないので、他の方のチートコード を参考に、GetItemByNameAddItemの引数の意味等を確認します。

まず、GetItemByNameをGhidraで見ると、このようになっています。 これはGameAPIというクラスのメソッドっぽく、呼び出し規約が__thiscallなので第一引数は GameAPIへのポインタになります。また、第二引数に関しては上記の方のソースコードを見ると、 アイテムの名前が入る事がわかります。尚、ここに欲しいアイテム名を入れるわけですが、実際にゲーム上でアイテムを取得した際にどのような値がこの引数に入っているのかは 21回の記事 で確認しました。
また、GameLogic.dll+0x1de20の位置にこの関数がある事がわかります。

GameAPIのアドレスも第一引数にセットしてこの関数を呼ばなければなりません。
GameAPIは幸いグローバルにポインタが保管してあり、場所は以下のGameLogic.dll+0x97d80にあります。 これはどこかGameAPIが使われている箇所を探してそこからXREFで辿るなりして見つけてもらえれば。

続いて、AddItemをGhidraで見ると、このようになっています。 第一引数はPlayerクラスのthisで、これに関してはグローバルで定義されていないので、 何度もデバッガでこの引数の値を読み取り、CheatEngineでポインタリストを作る必要があります。 第二引数が上記のGetItemByNameからの戻り値です。第三引数は上記のソースコードから常に 1であり、第四引数についても常に0であることがわかります。
自分はカンニングしましたが、もし自分で確かめたい場合はデバッガ等で見てみたり、静的解析して見てもらえれば。
また、場所はGameLogic.dll+0x51ba0にある事がわかります。

ゲーム上の関数を呼び出す

今までゲーム上のコードを扱う時はインラインフック等でアセンブラで記述していましたが、今回はC++で書いてしまうというのが新しい点です。
C++でゲーム上の関数を呼び出す際の流れは、以下の通りです。
  1. 呼び出す関数の関数型を宣言する (typedef int* (_Func)(int arg);)。
  2. 関数型のポインタ変数にゲーム上の関数のアドレスをセットする (_Func Func = 0xdeadbeef)
  3. 呼び出す (Func(123))
今回は、GetItemByNameAddItemの二つを使うので、それぞれの関数型をdllmain.cppに以下のように宣言しましょう。 次に、以下のようにして先程求めた引数や関数のアドレスをセットしましょう。 後は呼び出すだけです。以下のようにして一気にfor文でありったけ追加しちゃいましょう。

試してみる

これでもう終了なので、ビルドして実行しちゃいましょう。
DLLをInjectすると、以下のように一気にアイテムが手に入ると思います。

ソースコード

アイテムを一気に取得するDLL

#include "pch.h"
#include "general-cheats.h"

typedef DWORD* (__thiscall* _GetItemByName)(DWORD* gameapi, const char* itemname);
_GetItemByName GetItemByName;

typedef bool (__thiscall* _AddItem)(DWORD* player, DWORD* iitem, UINT count, bool allowPartial);
_AddItem AddItem;

std::vector<const char*> Items = {
	"GreatBallsOfFire",
	"ZeroCool",
	"ROPChainGun",      
	"StaticLink",
	"CharStar",
	"RemoteExploit",
	"HeapSpray",
	"GoldenMaster",
	"HolyHandGrenade",
	"HandCannon",
	"AKRifle",
	"Pistol",
	"CowboyCoder",
	"RubicksCube",
};

DWORD WINAPI ThreadMain(LPVOID params) {
	DWORD GameLogic = (DWORD)GetModuleHandle("GameLogic.dll");
	DWORD GameAPI   = GameLogic + 0x97d80;
	DWORD Player    = ResolveAddress(GameLogic + 0x97d7c, { 0x1c, 0x6c, 0x00 });

	GetItemByName = (_GetItemByName)(GameLogic + 0x1de20);
	AddItem       = (_AddItem)(GameLogic + 0x51ba0);

	for (const char* item : Items) {
		AddItem((DWORD*)Player, GetItemByName((DWORD*)GameAPI, item), 1, 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;
}