27: GUIクライアントにソケット通信機能搭載
概要
23回の記事 では、コマンドを受け付けてその内容を実行するチートサーバーをゲームにInjectし、コマンドラインのチートクライアントからコマンドを送る事ができました。今回は、GUIチートクライアントにソケット通信機能を追加し、Injectしたサーバーにコマンドを送れるようにする事を目標にやっていきます。
かつ効果を試すために、ワープチートを有効化/無効化できるように改変し、ボタンでオン/オフを切り替えるようにする事もやっていきます。
チート対象のゲームは、x86でチートの練習用に作られた、Pwn Adventure 3を使います。
Windows対象で、Visual Studio 2019を使用し
C++/CLI
で記述する。ソースコードはまとめてページの一番下にある。
この記事は 25回
と 26回
の記事の続きです。
SocketClientクラス
コマンドラインの方ではクラス化しなかったですが、今回はします。まず、SocketClient.hを以下のように作成しましょう。 サーバーと同じプロトコルの構造体の定義をしています。 また、
WSA
の初期化処理はinit
に入れ、このクラスを使う側は
Send()
のみを使えば良い設計にします。これら関数の内容はSocketClient.cppに以下のように書きます。
コードの内容は 23回の記事 で紹介しているのでここでは省略します。 HTTPとかと同じように、一つのコマンドを送信するのに毎回
Send
内でソケットを作成して通信します。
ワープチートをオン/オフ出来るようにする
実際に試すために、13回の記事 で作成したワープチートのDLLを使用し、これをロードする命令load warpcheatfilename
をクライアントから送りたい。しかし、一回Injectしたらずっとワープチートを有効にするのではなく、オン/オフを切り替えるようにしたい。
よって、まずはワープチートのDLL側に外部から機能を停止する事ができる機能を追加する。
まず、以下のように
enabled
変数をグローバルに設け、それをDLLのエクスポート関数で
false
にできるようにする。
「キーが押されたらワープする」という処理を無限ループで回していたが、無限ループではなく、以下のように変更する。
これでオフには出来るが、もう一度オンにする場合、もう一回このループを回すようにすると問題が一つ出てくる。
例えばゲーム上でプレイヤーがやられ、メニュー画面に戻って再開を押したとき、プレイヤーの座標が同じアドレスに格納されているとは限らない。
このような場合に備え、オフにした後は一回このDLLごとアンロードしてしまい、再度有効化する時はもう一度ロードさせるようにした方が良い。よって、ループを抜けた後にこれを追加する。 まとめると、無限ループで処理を行う系のチートDLLをオン/オフするには、以下のようにする。
- オン: DLLをロード
- オフ:
Disable
関数を外部から叩いてフラグを切り替えてループを抜け、FreeLibraryAndExitThread
サーバー側にチート機能をオフにするコマンドの実装
23回 でload
コマンドを実装しましたが、Disable
を実行して機能オフにする free
コマンドを実装します。free cheatfilename
のようにコマンドを送ると、cheatfilename.dll
のエクスポート関数である Disable
を実行するような仕組みです。23回 で作成したサーバープログラムに以下を追加しましょう。
GetModuleHandle
で無効化するチートDLLのハンドルを取得し、そのハンドルから GetProcAddress
で Disable
関数のアドレスを割り出します。そして Disable
関数を実行し、ハンドルを閉じて終了します。終ったら忘れずにビルドしましょう。
クライアント側にオン/オフできるボタンを設置
Warpのウィンドウに追加します。まず、「Panel」を追加し、以下のようにプロパティを設定しましょう。
- デザイン
- Name: WarpTopPanel
- 配置
- Dock: Top
- Size: 745, 70
そして、「Label」を追加し、以下のようにプロパティを設定しましょう。
- 配置
- Anchor: Top, Left
- Location: 27, 22
- 表示
- Font: Microsoft Sans Serif, 12pt
- Text: key-warp
最後に、「CheckBox」を追加し、以下のようにプロパティを設定しましょう (「Button」ではないので注意!)。
- デザイン
- Name: WarpKeyWarpButton
- 配置
- Location: 109, 18
- Anchor: Top, Left
- Size: 85, 30
- 表示
- Appearance: Button
- FlatAppearance
- CheckedBackColor: Green
- MouseDownBackColor: Green
- FlatStyle: Flat
- Text: Disabled
ボタンクリックでコマンドを送る
では、WarpKeyWarpButtonをダブルクリックしてそこにコードを書きましょう。まずは、WarpWindow.hの一番上でSocketClient.hをインクルードしましょう(
#include "SocketClient.h"
)。次に、ボタンダブルクリックにより生成されたイベントハンドラー内に以下を記述しましょう。 ボタンが押される度にソケットを生成し、コマンドを送ってみます。
実行してみる
では、PwnAdventure3を立ち上げてまずはサーバーDLLをInjectしましょう。そして、このチートクライアントをデバッグ実行でもいいので起動し、ボタンを押してみましょう。 有効化/無効化を何回も押してもちゃんと機能していたら成功です。
ソースコード
SocketClient.h
#pragma once
#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
typedef struct {
char cmd[8];
char argument[512];
} CmdProtocol;
class SocketClient {
public:
SocketClient() {
init();
}
void Send(char* cmd, char* argument);
private:
void init();
};
SocketClient.cpp
#include "SocketClient.h"
void SocketClient::init() {
WSADATA wsa;
WSAStartup(MAKEWORD(2, 2), &wsa);
}
void SocketClient::Send(char* cmd, char* argument) {
SOCKET sockfd;
struct sockaddr_in servaddr;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8888);
InetPton(servaddr.sin_family, L"127.0.0.1", &servaddr.sin_addr.S_un.S_addr);
connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
CmdProtocol msg;
strcpy(msg.cmd, cmd);
strcpy(msg.argument, argument);
send(sockfd, (char*)&msg, sizeof(msg), 0);
}
ワープチートのDLL (pwn3-location.dll)
#include "pch.h"
#include <stdio.h>
#include <vector>
#include <sstream>
typedef std::vector<UINT> VUINT;
typedef struct LocationAddr {
DWORD xaddr;
DWORD yaddr;
DWORD zaddr;
} LocationAddr;
typedef struct Location {
float x;
float y;
float z;
} Location;
DWORD ResolveAddress(DWORD baseAddr, VUINT offsets) {
DWORD curAddr = baseAddr;
for (UINT i = 0; i < offsets.size(); ++i) {
curAddr = *(DWORD*)curAddr;
curAddr += offsets[i];
}
return curAddr;
}
void warp(LocationAddr locAddr, float x, float y, float z) {
*(float*)locAddr.xaddr = x;
*(float*)locAddr.yaddr = y;
*(float*)locAddr.zaddr = z;
}
HWND gameWnd = NULL;
BOOL CALLBACK EnumWndProc(HWND hwnd, LPARAM lparam) {
DWORD pid;
GetWindowThreadProcessId(hwnd, &pid);
if (pid == GetCurrentProcessId()) {
gameWnd = hwnd;
return false;
}
return true;
}
bool isWindowFocused() {
if (gameWnd == NULL)
EnumWindows(EnumWndProc, (LPARAM)NULL);
if (gameWnd != NULL && gameWnd == GetForegroundWindow())
return true;
return false;
}
bool enabled = true;
extern "C" void __declspec(dllexport) Disable() {
enabled = false;
}
DWORD WINAPI ThreadMain(LPVOID params) {
DWORD modBase = (DWORD)GetModuleHandle("GameLogic.dll");
DWORD baseAddr = modBase + 0x97D7C;
VUINT offsets = { 0x1C, 0x4, 0x280, 0x98 };
LocationAddr locAddr;
Location loc;
locAddr.yaddr = (DWORD)ResolveAddress(baseAddr, offsets);
locAddr.xaddr = locAddr.yaddr - 0x4;
locAddr.zaddr = locAddr.yaddr - 0x8;
while (enabled) {
if (isWindowFocused()) {
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x42)) { // Ctrl-b
loc.x = *(float*)locAddr.xaddr;
loc.y = *(float*)locAddr.yaddr;
loc.z = *(float*)locAddr.zaddr;
std::stringstream ss;
ss << "x:" << loc.x << " y:" << loc.y << " z:" << loc.z << std::endl;
ss << std::hex << "x:" << locAddr.xaddr << " y:" << locAddr.yaddr << " z:" << locAddr.zaddr;
MessageBox(NULL, ss.str().c_str(), "current coordinate", MB_OK);
}
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x43)) // c spell book
warp(locAddr, -55980.4, 320.759, -43606.2);
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x46)) // f rats cave entrance
warp(locAddr, -41099.7, 750.585, -53473.1);
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x47)) // g pwnie island entrance
warp(locAddr, -35814, 1203.15, -44167.1);
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x48)) // h houses
warp(locAddr, -17770.8, 2404.02, -39940);
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x4B)) // k ballmer peak
warp(locAddr, -11726.4, 10627.1, -2454.97);
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x4D)) // m bridge
warp(locAddr, -13168.6, 865.574, 20057);
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x4E)) // n lots of bears
warp(locAddr, -40404, 3242.52, -11314.1);
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x51)) // q lots of angry-bears
warp(locAddr, 23306.3, 1299.9, 78270.123);
if (GetAsyncKeyState(VK_CONTROL) & GetAsyncKeyState(0x54)) // t pirate ship
warp(locAddr, 58591.3, 309.947, 43392);
}
}
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;
}
チートサーバーのDLL (dllmain.cpp)
#include "pch.h"
#include "general-cheats.h"
#include "SocketServer.h"
using namespace std;
SocketServer* server;
typedef void* (Disable)();
DWORD WINAPI ThreadMain(LPVOID params) {
server = new SocketServer();
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);
}
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;
}
WarpWindow.hに追加したイベントハンドラーのコード
#include "SocketClient.h"
private: System::Void WarpKeyWarpButton_CheckedChanged(System::Object^ sender, System::EventArgs^ e) {
SocketClient* sc = new SocketClient();
if (WarpKeyWarpButton->Checked) {
sc->Send("load", "pwn3-location");
WarpKeyWarpButton->Text = "Enabled";
}
else {
sc->Send("free", "pwn3-location");
WarpKeyWarpButton->Text = "Disabled";
}
}