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のプロジェクトを立ち上げ、以下の設定をする。 これで、準備が整った。
新規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;
}