29: TrackBarでチートDLLの移動速度を変える

概要

18回の記事 で、Ctrl-wasd でその方向にちょっとワープする事で壁抜け移動できるチートを作りました。
今回は、ワープする距離をGUIのトラックバーでリアルタイムで変更するという事を目標にやっていきます。
チート対象のゲームは、x86でチートの練習用に作られた、Pwn Adventure 3を使います。
Windows対象で、Visual Studio 2019を使用しC++/CLIで記述する。ソースコードはまとめてページの一番下にある。
この記事は 25回 からの続きで、18回の内容も使用します。

移動距離を変更するエクスポート関数の追加

外部からチートDLL内のパラメーターを変更する事が今回のメイン目的ですが、 やる事自体はそのパラメーターをグローバルに定義し、それに値をセットするDLLエクスポート関数を作るだけです。

18回のdllmain.cppだけ変更します。
ローカル変数として設けていた boost が移動距離(速度)なので、これを以下のように変更しましょう。
また、これは前の記事の二種類のチートの内、「ゲーム内にInject後ずっと無限ループで居座り続けるDLL(ループ系)」のチートなので、 後からオン・オフを切り替えられるように Disable 関数も設けておきます。 これで終わりなので忘れずにビルドしておきましょう。

サーバー側にboostコマンドを実装

サーバー側に上記のSetBoostを使って移動距離(速度)を変更するコマンドを実装します。
Disableを使ったオン/オフに関しては、27回の記事 で追加したサーバー側のコードによって、load cheatfilenamefree cheatfilenamecheatfilename の部分をこの壁抜けチートのファイル名にすれば機能するようになっているので新しくコードを追加する必要はありません。

移動距離(速度)を変えるコマンドは boost 速度の値 にします。
まずは、サーバー側のdllmain.cppの最初の方に以下のようにして SetBoost関数の関数型を定義しましょう。 そして、以下のようにしてコマンドの内容を実装します。
(自分は壁抜けチートのDLLファイル名を quaternion.dll にしているのでこうなってます) これでビルドしましょう。

クライアント側の実装

クライアント側で壁抜けチートをオン/オフするボタンに関しては、27回の記事 の方法と全く同じです。27回のinvincibleボタンとラベルをまんまコピーし、ボタンのNameプロパティだけPlayerTelemoveButtonに変更しましょう。
("壁抜け" っていう名前がなんかイマイチだと気づいたので、telemove と表記しています) ボタンのNameプロパティをPlayerTelemoveButtonに変更した後、ボタンをダブルクリックしてハンドラーの内容は以下の用に記述しましょう。
終ったらいよいよトラックバーの追加です。
PlayerWindow.h のデザインビューで「TrackBar」コンポーネントを追加しましょう。
プロパティは以下のようにします。 これで以下のようになるはずです。
これでUIは整ったので、次はこのトラックバーの値が変った時にチートサーバーに boost コマンドを送るようにします。
「ボタンが押されたとき」や「トラックバーの値が変った時」のようなイベントは、各コンポーネントの以下の雷マークみたいな所にまとまってます。 TrackBarコンポーネントには ValueChanged というイベントが備わっており、そこに PlayerTelemoveTrackBar_ValueChanged という名前のイベントを入力しましょう。入力後、以下のように PlayerWindow.h にイベントハンドラーのコードが自動で追加されるはずなので、 そこに以下のように記述しましょう。 まとめると、TrackBarの値に変化があるとこの PlayerTelemoveTrackBar_ValueChanged イベントが発火し、 そのイベントハンドラーである上記のコード内にて、トラックバーの現在の値を読み取り、sc->Sendを使ってソケットを通じて boost コマンドをサーバーに送っています。

トラックバーの値はintなので文字列に変換する必要があります。
このチートクライアントはC++/CLIというマネージドコードで書かれているので、マネージドコードの文字列型もありますが、 受け取り側であるチートサーバーの方は普通のC++で書かれているので、文字列の方は受け取り側に合わせて char*型で送らないといけません。 幸いC++/CLIはアンマネージドコードも混在して書けるので、以下のように #include <string> を追加しましょう。
※ ここらへんの話は 24回の記事で詳しく書いてます。 これで赤線も消えたはずです。

根本的な機能は備わりましたが、このチートをオフにした時にトラックバーを動かせないようにしたいので、 以下のコードを PlayerTelemoveButton_CheckedChanged の方に追加しましょう。

connection accepted (-1) の対処

これで出来たはずなのですが、実は一つまだ問題があります。
実際にこれで試してみると、DbgView等でデバッグ出力を見るとわかりますが、トラックバーを動かすと大量のデバッグ出力が流れ、 connection accepted (-1) という出力で埋め尽くされます。このエラー分自体はSocketServer.cppの RecvCmd 関数内で自分で定義してるもので、カッコ内の数字にはソケットのファイルディスクリプタの値が入るはずですが、-1 なのでなんかおかしいです。 原因はよくわかりませんが、対処法は地味で、サーバー側のプログラムにて、SocketServer.cppで CloseSocket関数の以下の WSACleanup() を消すと治ります。

実行してみる

では実際にサーバーをインジェクトしてクライアントを起動して試してみましょう。
以下のようにTrackBarの値分移動していれば成功です。また、途中で壊れないか等見るためにDbgViewで動作確認する事もオススメします。

ソースコード

壁抜けチートのdllmain.cpp (quaternion.dll)

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

using namespace std;

bool enabled = true;
Player* player;

extern "C" void __declspec(dllexport) Disable() {
    enabled = false;
}

float boost;
extern "C" void __declspec(dllexport) SetBoost(float val) {
    boost = val;
}

DWORD WINAPI ThreadMain(LPVOID params) {
    player = new Player();
    boost = 700.0;
    DWORD wait  = 250;

    while (enabled) {
        if (isWindowFocused()) {
            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x57)) { // w
                Vector3 fv = player->GetCurrentForwardVector();
                player->MoveLocation(fv, boost, { 1, 1, 1 });
                Sleep(wait);
            }
            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x53)) { // s
                Vector3 bv = Invert(player->GetCurrentForwardVector());
                player->MoveLocation(bv, boost, { 1, 1, 1 });
                Sleep(wait);
            }
            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x41)) { // a
                Vector3 lv = player->GetCurrentLeftVector();
                player->MoveLocation(lv, boost, { 1, 1, 1 });
                Sleep(wait);
            }
            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x44)) { // d
                Vector3 rv = Invert(player->GetCurrentLeftVector());
                player->MoveLocation(rv, boost, { 1, 1, 1 });
                Sleep(wait);
            }
            if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(VK_SPACE)) {
                Vector3 uv = player->GetCurrentUpVector();
                player->MoveLocation(uv, boost, { 0, 1, 0 });
                Sleep(wait);
            }
        }
    }

    delete player;
    FreeLibraryAndExitThread((HMODULE)params, 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;
}

サーバー側のdllmain.cpp

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

using namespace std;

SocketServer* server;
typedef void* (Disable)();
typedef void* (Hook)();
typedef void* (UnHook)();
typedef void* (SetBoost)(float val);

void init() {
    std::vector<const char*> cheatDlls = {
        "demo-nop-damage2"
    };

    for (const char* cheatDll : cheatDlls) {
        char path[MAX_PATH];
        snprintf(path, MAX_PATH, "C:\\Users\\<username>\\Desktop\\dlli\\%s\\Debug\\%s.dll", cheatDll, cheatDll);
        LoadLibrary(path);
    }
}

DWORD WINAPI ThreadMain(LPVOID params) {
    server = new SocketServer();

    init();

    while (1) {
        CmdProtocol msg;
        server->RecvCmd(&msg);

        if (strcmp(msg.cmd, "load") == 0) {
            char path[MAX_PATH];
            snprintf(path, MAX_PATH, "C:\\Users\\<username>\\Desktop\\dlli\\%s\\Debug\\%s.dll", msg.argument, msg.argument);
            LoadLibrary(path);
        }
        if (strcmp(msg.cmd, "free") == 0) {
            HMODULE hDll = GetModuleHandle(msg.argument);
            Disable* disable = (Disable*)GetProcAddress(hDll, "Disable");
            disable();
            CloseHandle(hDll);
        }
        if (strcmp(msg.cmd, "hook") == 0) {
            HMODULE hDll = GetModuleHandle(msg.argument);
            Hook* hook = (Hook*)GetProcAddress(hDll, "Hook");
            hook();
            CloseHandle(hDll);
        }
        if (strcmp(msg.cmd, "unhook") == 0) {
            HMODULE hDll = GetModuleHandle(msg.argument);
            UnHook* unhook = (UnHook*)GetProcAddress(hDll, "UnHook");
            unhook();
            CloseHandle(hDll);
        }
        if (strcmp(msg.cmd, "boost") == 0) {
            HMODULE hDll = GetModuleHandle("quaternion.dll");
            SetBoost* setboost = (SetBoost*)GetProcAddress(hDll, "SetBoost");
            setboost(atof(msg.argument));
        }

        server->CloseSocket();
    }

    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;
}
※ SocketServer.cppのCloseSocket関数のWSACleanup()を消すのを忘れずに

クライアント側で追加したハンドラーとヘッダー

#include <string>
            
private: System::Void PlayerTelemoveButton_CheckedChanged(System::Object^ sender, System::EventArgs^ e) {
    SocketClient* sc = new SocketClient();

    if (PlayerTelemoveButton->Checked) {
        PlayerTelemoveButton->Text = "Enabled";
        sc->Send("load", "quaternion");
        PlayerTelemoveTrackBar->Enabled = true;
    }
    else {
        PlayerTelemoveButton->Text = "Disabled";
        sc->Send("free", "quaternion");
        PlayerTelemoveTrackBar->Enabled = false;
    }
}
private: System::Void PlayerTelemoveTrackBar_ValueChanged(System::Object^ sender, System::EventArgs^ e) {
    SocketClient* sc = new SocketClient();
    std::string s = std::to_string(PlayerTelemoveTrackBar->Value);
    sc->Send("boost", (char*)s.c_str());
}