7: DLL Injectionでゲーム上の値を変える
概要
前回の記事でメッセージボックスを表示するだけのDLLをInject して、適当なプログラムを走らせる手法はわかった。しかし、まだInjectした先のゲームプロセス上の値をどう改竄するのかというのはわからないのでそれをやっていく。 基本的に、CheatEngineで改竄したい値のアドレスを特定し、それのポインタリストを作成し、それを使ってDLLからそのアドレスにアクセスという流れになる。今回は、ゲーム上のお金の値を書き換えるDLLをInjectすることを目標とする。
Unityで作ったゲームを対象にやっていき、ゲームのサンプルはココからダウンロードできる。 また、Windows対象で、
C++
を言語として用いる。ソースコードはまとめてページの一番下にある。
ポインタリストの復習と下準備
CheatEngineでお金の値を特定し、そのポインタリストを作成すれば、ゲーム再起動後でもそのポインタリストはお金のアドレスを指すことができる (これがわからない場合は、以前のこの記事を参照)。 とりあえず、CheatEngineでお金のポインタリストを特定し、以下のような結果になったとする。 これの意味を確認すると、以下のような感じだった。a = (mono.dllの先頭アドレス + 0x00264110) のアドレスに入ってる値
b = (a + 0x310) のアドレスに入ってる値
c = (b + 0x38) のアドレスに入ってる値
(c + 0x4A0) のアドレスに入ってる値 = 0x192FC0694A0
では、この情報を使ってDLLからお金のアドレスを特定する。
まずは前回の記事のようにDLLのプロジェクトを立ち上げ、以下の設定をする。
- x86 → x64 に変える
- ソリューション名を右クリックしてプロパティを開き、"詳細" から Unicode → マルチバイト に変える
- 同じくプロパティの "リンカ" から、
/SUBSYSTEM:WINDOWS
に変更する
新規DLLプロジェクトを始めると、
DllMain
関数がテンプレとして書かれていると思う。
switch
文で色々どのタイミングでのDLLMainの実行なのかで条件分岐されてるが、今回はこんなに要らないので、下図のように整理しましょう。
また、とりあえずよく使うstdio.h
を#include
しておくのと、32bitのアドレスにはよくDWORD
という型が使われ、
64bitのアドレスはしばしばQWORD
という型が付くのだが、なぜかQWORD
だけ用意されてないので、これもtypedef unsigned __int64 QWORD;
のように宣言しておきましょう。
お金のアドレスをDLLから特定
それでは本題に入っていく。最初の図のポインタリストを愚直にまずは書いてみると、"概観" は以下のようになる。 まず、ベースアドレスが
mono.dll
の先頭アドレスに0x264110
を足したものであり、それとoffsets
を使って
ポインタを辿っていき(ResolveAddress
)、goldAddr
になるというシナリオである。ちなみに、GetModuleHandle
とはまさに名前で指定した
DLLの先頭アドレスを返してくれるWindowsAPIなので、最初の二つは問題無いが、後半二行をもっと具体的に書いていく必要がある。C++では配列というかリストは、
#include <vector>
をして、std::vector<型> hoge = {0,1,2,...}
という感じで使えるが、ちょっと長いので、下図のように整理する。
これで、後はポインタをたどる
ResolveAddress
を実装すればいい。最初の方でポインタリストの読み方を参考に、
for
文で順番にポインタを解決していくと、以下のような実装になると思う。
curAddress
に初期値としてbaseAddr
を入れ、offsets
の値を順番に足してはそのアドレスが指す値で更新していく関数である。ここで少しややこしいのが、
*(QWORD*)curAddr
の部分だと思う。これは、日本語で言えばcurAddress(アドレス)に格納されている値
と同じ意味で、
もしcurAddress
がポインタなら、*curAddress
の事である。しかし、curAddr
はポインタではなくただの整数なので、単純に*curAddress
とはできない。
それなので、一回(QWORD*)
を付けてQWORD
のポインタ型にキャストしてから、*(QWORD*)
にしてcurAddr
に格納されている値を取り出している。なんと、これでもう既にお金のアドレスは取得できた!
お金のアドレスに値を書き込む
では、このアドレスに高額な値を書き込んでお金持ちになる。今回は、とりあえず10000
という値を書き込む。これも少しややこしい型キャストをしてますが、
goldAddr
というアドレスに格納されてる値に、newGoldVal
を書き込むという意味です。最後に、メッセージボックスを出して、完了の旨を表示するようにします。
それでは、
Ctrl-b
を押してビルドし、ゲームを起動して、DLL Injection用の実行ファイルやツールでこのDLLをInjectしましょう
(DLL Injectのやり方が分からない場合は、前回の記事参照)。注意としては、書き換えた瞬間に "お金の表示" も
10000
になるわけではなく、書き換えてからどこかで一回お金を拾って更新された際に、変化が見られる。
ソースコード
お金の値を取得して書き換えるgold.dll
#include "pch.h"
#include <stdio.h>
#include <vector>
typedef unsigned __int64 QWORD;
typedef std::vector<UINT> VUINT;
QWORD ResolveAddress(QWORD baseAddr, VUINT offsets) {
QWORD curAddr = baseAddr;
for (UINT i = 0; i < offsets.size(); ++i) {
curAddr = *(QWORD*)curAddr;
curAddr += offsets[i];
}
return curAddr;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
QWORD modBase = (QWORD)GetModuleHandle("mono.dll");
QWORD baseAddr = modBase + 0x264110;
VUINT offsets = { 0x310, 0x38, 0x4a0 };
QWORD goldAddr = ResolveAddress(baseAddr, offsets);
int newGoldVal = 10000;
*(QWORD*)goldAddr = newGoldVal;
MessageBox(NULL, TEXT("overwritten gold value to 10000"), TEXT("complete!"), MB_OK);
}
return TRUE;
}