Featured image of post 扫雷外挂

扫雷外挂

# 1.分析过程

# (1)寻找游戏基础信息存储位置

调整扫雷游戏难度等级,使地雷数量发生改变,然后在 CE 界面填写新的地雷数量,单击“再次扫描”按钮。不断重复这样的操作,直至 CE 左侧找到的地址数降为个位数,双击所找到的地址,将其添加到 CE 下方地址列表中。

存储地雷数量的地址:0x01005194、0x01005330、0x010056A4

寻找地图高存储位置,将地图高从24修改为20

存储地图高的地址为:0x01005338和0x010056A8

寻找地图宽存储位置,将地图宽从30修改为26

存储地图宽的地址为:0x01005334和0x010056AC

寻找存储游戏时间的地址,首次扫描数值为0

开始游戏后,不断扫描增加的值,直至扫描结果地址变为个位数

存储游戏时间的地址为0x0100579C

寻找已扫数量存储的位置,通过不断增值查找

存储已扫数量的地址为0x010057A4

# (2)使用ollydbg找到游戏计时指令存储位置

在0x0100579C除设置硬件写入断点,找到计时指令存储位置为0x01003830和0x01002ff5。可在程序中修改这些地址的数据为0x90(nop指令),因此可以在游戏运行时不执行计时递增指令,从而做到游戏计时器停止。

# (3)点到雷不结束游戏

通过调试发现,程序会将点击位置所在地址的内容与80进行test操作,然后执行je 0x01003595跳转。若点击位置所在地址的内容的若为0x8F,则不执行je跳转;若点击位置所在地址的内容的若为0x0F,则执行je跳转。

通过对0x01003595的跟踪调试,发现后续执行的是继续游戏的操作,而在不跳转0x01003595则会执行到0x01003593处执行jmp 0x010035AB指令结束游戏。因此可以将内存中0x01003591-0x01003594处的指令修改为nop指令填充,从而满足无条件的游戏继续的跳转(点到雷继续游戏)。

# (4)自动游戏获胜功能1——确定雷区真实的内存起始地址

在扫描得到的多组地址中,依次通过右键“浏览相关内存区域”,打开地址所对应的内存空间,然后通过开始新一局游戏和点击雷区左上角的操作,确定雷区真实的内存起始地址,以及地雷和非地雷的标识,地址为0x01005361

地雷标识数据为0X8F,非地雷标识数据为0X0F 。

修改地图宽

修改地图高

最后通过创建新一局游戏,依次点击第 1、2、3 行最左侧格子的操作确定雷区每行的起始地址,寻找起始地址与雷区行和列之间的对应关系,即确定每行格子的数量为32。

# (5)自动游戏获胜功能2——确定扫雷游戏格子大小

打开 Spy++,单击“监视–日志消息”,勾选“隐藏 Spy++选项”,拖动“查找程序工具”至扫雷游戏主界面,如图 8-4 所示,显示的内容包括扫雷窗口类(WNDCLASS)的名称以及标题名。

切换至Spy++的“消息”窗口,先清除所有消息,然后勾选 “WM_LBUTTONDOWN 和 WM_LBUTTONUP”两个消息,如图 8-5 所示,单击确定后返回 Spy++主界面。

返回扫雷游戏界面,依次单击左上角、紧邻左上角右方、紧邻左上角下方的三个格子,查看 Spy++记录的鼠标单击时的 xPos 和 yPos 的值,以此计算每个格子的宽度和高度。

首个雷区坐标为:

xPos =14

yPos =58

在Spy++界面,右键–清除消息日志,然后重复上述操作,也可间隔数个格 子单击,以此计算每个格子的平均值,作为每个格子的宽和高度值。格子的宽度为16 ,格子的高度为16 。

(6)自动游戏获胜功能3——根据雷区存储数据自动游戏

1)创建控制台项目;

2)打开扫雷进程,根据雷区的行和列所在内存地址,获取行、列的值;

3)根据雷区首地址和雷区范围,读取雷区数据;

4)将扫雷游戏界面置顶,读取雷区第i行第j列的数值,判断是否为地雷,如果不是则模拟鼠标左键单击操作,如果是地雷则模拟鼠标右键单击操作。

5)根据扫雷游戏界面的客户坐标系,计算第i行第j列的坐标位置,然后将鼠标移动到该位置,执行第3步的鼠标操作。

6)重复 3-4,直至雷区数据全部被扫描完成。

程序源码以及可执行文件见附录

# 2. 使用手册

(1)显示游戏运行时基础信息(地图大小、地雷总数等)——输入1

修改地图信息后重新查看基础信息

游戏运行后重新查看游戏运行时基础信息

(2)计时器停止——输入2

(3)点到雷不会结束游戏——输入3

(4)自动游戏获胜功能——输入4

(5)退出程序——输入5

# 3.代码附录

  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
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
#include<Windows.h>
#include<iostream>
using namespace std;

int width, height;
HANDLE handle;
HWND hwnd;

int init_minesweeper() {
    const char* text = "Minesweeper";
    hwnd = FindWindow(text, text);
    if (hwnd == NULL)
    {
        MessageBox(NULL, TEXT("Winmine is not found!"), TEXT("AutoMineSweeper"), MB_ICONSTOP);
        return 1;
    }
    DWORD pid;
    GetWindowThreadProcessId(hwnd, &pid);
    handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
}

void show_information() {
    int Mines_num = -1;
    int play_time = -1;
    int aleardy_mines = -1;
    ReadProcessMemory(handle, (LPVOID)0x01005330, &Mines_num, 4, NULL);
    ReadProcessMemory(handle, (LPVOID)0x01005334, &width, 4, NULL);
    ReadProcessMemory(handle, (LPVOID)0x01005338, &height, 4, NULL);
    ReadProcessMemory(handle, (LPVOID)0x0100579C, &play_time, 4, NULL);
    ReadProcessMemory(handle, (LPVOID)0x010057A4, &aleardy_mines, 4, NULL);
    printf("地图高:%d\n", height);
    printf("地图宽:%d\n", width);
    printf("地雷总数:%d\n", Mines_num);
    printf("已扫数量:%d\n", aleardy_mines);
    printf("游戏运行时间:%d\n", play_time);
}

void stop_timing() {
    char nop[6] = { 0x90, 0x90, 0x90, 0x90, 0x90, 0x90 };
    int stop_time = 1;
    WriteProcessMemory(handle, (LPVOID)0x01003830, &nop, 6, NULL);
    WriteProcessMemory(handle, (LPVOID)0x01002ff5, &nop, 6, NULL);
}

void Invincible_mode() {
    char nop[4] = { 0x90,0x90,0x90,0x90 };
    WriteProcessMemory(handle, (LPVOID)0x01003591, &nop, 4, NULL);
}

int auto_minesweeper() {
    ReadProcessMemory(handle, (LPVOID)0x01005334, &width, 4, NULL);
    ReadProcessMemory(handle, (LPVOID)0x01005338, &height, 4, NULL);
    BYTE data[32 * 32] = { 0 };
    ReadProcessMemory(handle, (LPVOID)0x01005361, &data, 32 * height, NULL);

    ShowWindow(hwnd, SW_RESTORE);
    SetForegroundWindow(hwnd);
    Sleep(300);

    int width_num = 32;
    int x_pos = 14;
    int y_pos = 58;

    RECT rc;
    BOOL bRet = GetClientRect(hwnd, &rc);

    for (int y = 1; y <= height; y++)
    {
        for (int x = 1; x <= width; x++)
        {
            UINT downMsg = x_pos, upMsg = y_pos;
            if (data[width_num * (x - 1) + (y - 1)] == 0x10)
                break;
            else
            {
                if (data[(x - 1) + width_num * (y - 1)] == 0x0F)
                {
                    downMsg = MOUSEEVENTF_LEFTDOWN;
                    upMsg = MOUSEEVENTF_LEFTUP;
                }
                else
                {
                    downMsg = MOUSEEVENTF_RIGHTDOWN;
                    upMsg = MOUSEEVENTF_RIGHTUP;
                }
            }
            POINT curPos = { rc.left + x_pos + (x - 1) * 16, rc.top + y_pos + (y - 1) * 16 };
            ClientToScreen(hwnd, &curPos);
            SetCursorPos(curPos.x, curPos.y);
            mouse_event(downMsg, 0, 0, 0, 0);
            mouse_event(upMsg, 0, 0, 0, 0);
            Sleep(1);

        }
    }
    CloseHandle(handle);
    return 0;
}

void menu() {
    int option = -1;
    while (option) {
        cout << "1.显示游戏运行时基础信息(地图大小、地雷总数等)\n2.计时器停止\n3.点到雷不会结束游戏\n4.自动游戏获胜功能\n5.退出\n选择功能:";
        cin >> option;
        if (option == 5)
            break;
        switch (option) {
        case 1:
            show_information();
            break;
        case 2:
            stop_timing();
            break;
        case 3:
            Invincible_mode();
            break;
        case 4:
            auto_minesweeper();
            break;
        }
    }
}

int main() {
    init_minesweeper();
    menu();
    cout << "程序结束......" << endl;
}
Licensed under CC BY-NC-SA 4.0
使用 hugo 构建 🖤 Stack
版权声明:Licensed under CC BY-NC-SA 4.0「署名-非商业性使用-相同方式共享 4.0 国际」