22: ゲーム内関数をC++で呼出し任意のアイテムを取得
概要
ゲーム内にアイテムを追加する関数があった場合、それをInjectしたDLLから呼び出す事ができる。PwnAdventure3の場合、
GetItemByName
とAddItem
の二つの関数を呼び出すことにより、ゲーム上の任意のアイテムを取得することができる。今回は、ゲーム内のアイテムを取得する関数をInjectするDLLから呼び出し、任意のアイテムを取得するという事を目標にやっていく。
チート対象のゲームは、x86でチートの練習用に作られた、Pwn Adventure 3を使います。
Windows対象で、
C++
を言語として用いる。ソースコードはまとめてページの一番下にある。
また、18回の記事のgeneral-cheatsのコードをそのまま使っています。※ ネタバレ注意: PwnAdventure3で取得できるアイテムの一部が載っています
使用する関数
本来ならばゲーム上のどの関数がアイテムを取得する関数なのかなどは長く苦しいリバースエンジニアリングをしてからわかるものですが、 今回は本題ではないので、他の方のチートコード を参考に、GetItemByName
とAddItem
の引数の意味等を確認します。まず、
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++でゲーム上の関数を呼び出す際の流れは、以下の通りです。
- 呼び出す関数の関数型を宣言する (
typedef int* (_Func)(int arg);
)。 - 関数型の 引数/戻り値 の 個数/型 は元の関数と揃えるように
- 関数型のポインタ変数にゲーム上の関数のアドレスをセットする (
_Func Func = 0xdeadbeef
) - 呼び出す (
Func(123)
)
GetItemByName
とAddItem
の二つを使うので、それぞれの関数型を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;
}