游戏安全的学习(4)—— 猫里奥外挂编写。

这也就是一个单机游戏,如果游戏作者觉得侵权了可以联系删除。

游戏分析

突然想起了之前一个很搞人心态的游戏,就是这个猫里奥,各种陷阱防不胜防,因此今天打算用点科技的力量去对抗这个东西。八关版的外挂已经有人做了,这里我用九关版的,下载地址

坐标读取和修改

首先最简单的就是获取坐标,使用CE反复横跳搜索内存,最终确定了 X 和 Y 坐标的地址分别是在 +78DDC+78DE0 这两个地方,基于此可以做出一个读取坐标信息,修改的目的。

对应代码也非常简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
DWORD CCatMariGuaDlg::FindProcess() {
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe32;
pe32 = { sizeof(pe32) };
BOOL ret = Process32First(hSnap, &pe32);
while (ret)
{
CString s;
GetDlgItemText(IDC_EDIT1, s);
if (!wcsncmp(pe32.szExeFile, s, 11)) {
return pe32.th32ProcessID;
}
ret = Process32Next(hSnap, &pe32);
}
return 0;
}


void CCatMariGuaDlg::GetPos(DWORD PID) {
DWORD pid = PID;
HANDLE hProcess = OpenProcess(PROCESS_VM_READ, FALSE, pid);
if (hProcess) {
if(1) {
DWORD Xaddr = 0x478DE0;
DWORD Yaddr = 0x478DDC;
WCHAR chX[50], chY[50];
DWORD X, Y;
SIZE_T Readen;
ReadProcessMemory(hProcess, (LPVOID)Xaddr, &X, 4, &Readen);
ReadProcessMemory(hProcess, (LPVOID)Yaddr, &Y, 4, &Readen);
wsprintf(chX, L"%d", X);
wsprintf(chY, L"%d", Y);
SetDlgItemText(IDC_EDIT2, chX);
SetDlgItemText(IDC_EDIT3, chY);
}
}
}

void CCatMariGuaDlg::SetPos(DWORD PID) {
DWORD pid = PID;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (hProcess) {
if (1) {
DWORD Xaddr = 0x478DE0;
DWORD Yaddr = 0x478DDC;
CString chX, chY;
DWORD X, Y, old;
SIZE_T Wriiten;
GetDlgItemText(IDC_EDIT4, chX);
GetDlgItemText(IDC_EDIT5, chY);
X = _ttoi(chX);
Y = _ttoi(chY);
VirtualProtectEx(hProcess, (LPVOID)Yaddr, 8, PAGE_EXECUTE_READWRITE, &old);
WriteProcessMemory(hProcess, (LPVOID)Xaddr, &X, 4, &Wriiten);
WriteProcessMemory(hProcess, (LPVOID)Yaddr, &Y, 4, &Wriiten);
VirtualProtectEx(hProcess, (LPVOID)Yaddr, 8, old, &old);
}

}

}

void CCatMariGuaDlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
PID=FindProcess();
if (PID != 0) {
SetDlgItemText(IDC_STATIC2, L"游戏状态:打开游戏成功");
GetPos(PID);
}
else {
SetDlgItemText(IDC_STATIC2, L"游戏状态:未找到游戏进程");
}
}

void CCatMariGuaDlg::OnBnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
SetPos(PID);
}

演示:

也是可以成功实现功能。0,0坐标是当前屏幕的左上角,并且只对屏幕做相对坐标运算。

如果想要实时获取目标的坐标可以考虑

无限跳跃

首先可以确定一点是,代码实现跳跃功能肯定要加一个变量限制,如果不加那么肯定会实现无限跳跃的功能。加了之后我们可以选择,如果碰撞器碰到地面,那么这个时候变量恢复成1,表示可以按↑跳跃,在空中就不能跳跃。

经过分析,是在 0x0478D98 地址的变量保存了这个值。

如果我们把把这个值改成 0 的代码 patch 掉,就可以达到无限跳跃的目的,但是同时会失去碰撞体积,这里有两个代码,都patch掉才能无限跳跃。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
BYTE NOP[10]={ 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
void CCatMariGuaDlg::PatchJumpCode() {
DWORD ins1 = 0x41C787,ins2=0x41FCEA;
WriteGameMemory(ins1, NOP, 6);
WriteGameMemory(ins2, NOP, 6);
}

void CCatMariGuaDlg::RestoreJumpCode() {
DWORD ins1 = 0x41C787, ins2 = 0x41FCEA;
BYTE CODE1[] = {
0x89,0x0D,0x98,0x8D,0x47,0x00
};
BYTE CODE2[] = {
0x89,0x3D,0x98,0x8D,0x47,0x00
};
WriteGameMemory(ins1, CODE1, 6);
WriteGameMemory(ins2, CODE2, 6);
}

这里我选择了一个复选框,让它勾选可以允许无限跳,做一下控件的API调用。

1
2
3
4
5
6
7
8
9
10
11
void CCatMariGuaDlg::OnBnClickedCheck1()
{
// TODO: 在此添加控件通知处理程序代码
int k=UnlimitedJump.GetCheck();
if (k) {
PatchJumpCode();
}
else {
RestoreJumpCode();
}
}

最终实现:

不死

这应该是我们比较追求的一个目标,但是由于场景中坑人的元素很多,它们代码不是共用的,所以我们要一一patch掉这些能让我们死的代码。最关键的数据地址在 0x478DD0,它标识了我们的状态,找到对它写的代码。共有以下的分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
碰到怪物,发射火焰 2465D:
mov [00478DD0],eax
吃了毒蘑菇 249F8:
dec [00478DD0]
吃了碰到陷阱 1D7E8:
dec [00478DD0]
吃了花 24A18:
dec [00478DD0]
吃了星星 1FAD8:
mov [00478DD0],eax
碰到火焰陷阱 1E6C3:
dec [00478DD0]
尖刺 23E12:
dec [00478DD0]
也是火焰陷阱 1E37B

一一做一个patch和恢复的控件和代码,其实原理都一样。

最后展示一下外挂的最终状态:

这里不一一演示其它的功能了

放一下最终外挂的exe程序,Y轴锁定和吃星星会死的设定我还没搞明白,也没有那么多时间去钻研了,也就图一乐,现在我也是能两分钟速通猫里奥的人了hhh。