19: 水平打ちとビーム
概要
16回の柱打ちの記事では、一度に一発しか打てない炎魔法を、柱状にして打つ事ができました。 その一方で、17回 でプレイヤーの向きの情報を取得する方法と、向きの情報からプレイヤーの前後左右上下ベクトルを算出する方法を知り、18回 で、それを実際にPlayerクラス等に実装しました。今回は、向きの情報から得た前後左右ベクトルを使用して、柱状ではなく、水平状とビーム状に炎を打つ事を目標にやっていきます。
チート対象のゲームは、x86でチートの練習用に作られた、Pwn Adventure 3を使います。
Windows対象で、
C++
を言語として用いる。ソースコードはまとめてページの一番下にある。また、この記事は18回の記事で作成したヘッダーファイルを使用し、 dllmain.cppの方は16回の記事のコードを元に作成します。
※ 今回は dllmain.cpp の方しか変更しません。
ビーム
先にビームの方を実装します。柱打ちでは、ループ毎に
goup
関数でプレイヤーを上げていましたが、ビームの場合は上げるのではなく前に進ませれば良いだけです。dllmain.cpp以外を18回から、dllmain.cppは16回からコピーして持ってきた後、dllmain.cppに以下の関数を追加します。 続いて、以下の
goup
だった所を上記のgoforward
に変えればなんと終了です。
それでは、実際にビルドして動かしてみましょう。下図のようになるはずです。
水平打ち
水平打ちは、一回左に半分ずらしてから、右に移動して打つループをさせないといけないので、一手間かかりまが、dllmain.cppだけ変更する事には代わり無く、追加するコードも少しです。 まず、下図のように必要な関数を用意します。 そして、インラインフックで埋め込むコードの方は以下のようにします。 これで、ビルドして動かしてみると、以下のようになるはずです。ソースコード
ビームのdllmain.cpp
#include "pch.h"
#include "general-cheats.h"
#include "player.h"
using namespace std;
Player* player;
Vector3 curLoc;
void __stdcall SetCurrentLoc() {
curLoc = player->GetCurrentLocation();
}
void __stdcall RestoreLoc() {
player->SetLocation(curLoc, { 1, 1, 1 });
}
void __stdcall goup() {
Vector3 value;
value.x = 0; value.y = 100.0; value.z = 0;
player->MoveLocation(value, { 0, 1, 0 });
}
void __stdcall goforward() {
Vector3 fv = player->GetCurrentForwardVector();
player->MoveLocation(fv, 300.0, { 1, 1, 1 });
}
DWORD jmpBackAddr;
void __declspec(naked) cheats() {
__asm
{
push ebx
pushad
call SetCurrentLoc
popad
mov ebx, 0x20
push ebx
mov eax, [ebp + 0x8]
mov ecx, [ebp + 0xc]
lea edx, [eax + 0x70]
push edx
mov eax, [ecx]
call[eax + 0x80]
call goforward
pop ebx
dec ebx
jne $ - 0x1a
pushad
call RestoreLoc
popad
pop ebx
mov ecx, jmpBackAddr
jmp ecx
}
}
// cheatsに飛ばす命令を作成して返す
// B9 xx xx xx xx - mov ecx, cheatsの先頭アドレス
// FF E1 - jmp ecx
BYTE* initJmpToCheats(DWORD size) {
BYTE* jmpToCheats = (BYTE*)malloc(size);
memset(jmpToCheats, 0x90, size);
BYTE buf1[] = { 0xB9 };
memcpy(jmpToCheats, buf1, sizeof(buf1));
DWORD* cheatsAddr = (DWORD*)cheats;
memcpy(&jmpToCheats[sizeof(buf1)], &cheatsAddr, sizeof(DWORD));
BYTE buf2[] = { 0xFF, 0xE1 };
memcpy(&jmpToCheats[5], buf2, sizeof(buf2));
return jmpToCheats;
}
DWORD WINAPI ThreadMain(LPVOID params) {
DWORD baseAddr = (DWORD)GetModuleHandle("GameLogic.dll");
DWORD* target = (DWORD*)(baseAddr + 0x3b2ba);
DWORD size = 15;
// 8b 4d 0c MOV ECX, [EBP+0xC] <-- target
// 8d 50 70 LEA EDX, [EAX+0x70]
// 52 PUSH EDX
// 8b 01 MOV EAX, [ECX]
// ff 90 80 00 00 00 CALL [EAX+0x80]
jmpBackAddr = (DWORD)target + size;
BYTE* jmpToCheats = initJmpToCheats(size);
player = new Player();
DWORD curProtection;
VirtualProtect(target, size, PAGE_EXECUTE_READWRITE, &curProtection);
memcpy(target, jmpToCheats, size);
DWORD temp;
VirtualProtect(target, size, curProtection, &temp);
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 "player.h"
using namespace std;
Player* player;
Vector3 curLoc;
void __stdcall SetCurrentLoc() {
curLoc = player->GetCurrentLocation();
}
void __stdcall RestoreLoc() {
player->SetLocation(curLoc, { 1, 1, 1 });
}
void __stdcall goup() {
Vector3 value;
value.x = 0; value.y = 100.0; value.z = 0;
player->MoveLocation(value, { 0, 1, 0 });
}
void __stdcall goforward() {
Vector3 fv = player->GetCurrentForwardVector();
player->MoveLocation(fv, 300.0, { 1, 1, 1 });
}
void __stdcall goleft() {
Vector3 lv = player->GetCurrentLeftVector();
player->MoveLocation(lv, 100.0, { 1, 0, 1 });
}
void __stdcall goright() {
Vector3 rv = Invert(player->GetCurrentLeftVector());
player->MoveLocation(rv, 100.0, { 1, 0, 1 });
}
void __stdcall SetHalfLeft() {
int i;
for (i = 0; i < 0x4; i++)
goleft();
}
DWORD jmpBackAddr;
void __declspec(naked) cheats() {
__asm
{
push ebx
pushad
call SetCurrentLoc
call SetHalfLeft
popad
mov ebx, 0x8
push ebx
mov eax, [ebp + 0x8]
mov ecx, [ebp + 0xc]
lea edx, [eax + 0x70]
push edx
mov eax, [ecx]
call[eax + 0x80]
call goright
pop ebx
dec ebx
jne $ - 0x1a
pushad
call RestoreLoc
popad
pop ebx
mov ecx, jmpBackAddr
jmp ecx
}
}
// cheatsに飛ばす命令を作成して返す
// B9 xx xx xx xx - mov ecx, cheatsの先頭アドレス
// FF E1 - jmp ecx
BYTE* initJmpToCheats(DWORD size) {
BYTE* jmpToCheats = (BYTE*)malloc(size);
memset(jmpToCheats, 0x90, size);
BYTE buf1[] = { 0xB9 };
memcpy(jmpToCheats, buf1, sizeof(buf1));
DWORD* cheatsAddr = (DWORD*)cheats;
memcpy(&jmpToCheats[sizeof(buf1)], &cheatsAddr, sizeof(DWORD));
BYTE buf2[] = { 0xFF, 0xE1 };
memcpy(&jmpToCheats[5], buf2, sizeof(buf2));
return jmpToCheats;
}
DWORD WINAPI ThreadMain(LPVOID params) {
DWORD baseAddr = (DWORD)GetModuleHandle("GameLogic.dll");
DWORD* target = (DWORD*)(baseAddr + 0x3b2ba);
DWORD size = 15;
// 8b 4d 0c MOV ECX, [EBP+0xC] <-- target
// 8d 50 70 LEA EDX, [EAX+0x70]
// 52 PUSH EDX
// 8b 01 MOV EAX, [ECX]
// ff 90 80 00 00 00 CALL [EAX+0x80]
jmpBackAddr = (DWORD)target + size;
BYTE* jmpToCheats = initJmpToCheats(size);
player = new Player();
DWORD curProtection;
VirtualProtect(target, size, PAGE_EXECUTE_READWRITE, &curProtection);
memcpy(target, jmpToCheats, size);
DWORD temp;
VirtualProtect(target, size, curProtection, &temp);
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;
}