0x00 前言

保卫萝卜PC版内存修改器分析、制作详细过程

001.png

近期看到 @空竹 大佬分享了基于 Cheat Engine 的 保卫萝卜破解教程 ,欲初探一下逆向知识,并锻炼 Win32 编程技能,故有此篇。

文章尽可能详细记录了各个过程,既是自己学习路上的一点经验记录,又是一篇简单教程,期望能对一些像我一样的入门小白有所帮助。

0x01 游戏内存分析

(这一部分我写得详细些方便小白入门)

首先运行游戏,然后打开 CE - 打开 LuoBo.exe ,点击 Attach debugger to process 附加调试器,然后随便开一个关卡,把游戏暂停。

我们看到游戏中的金币数量是 450 。因此在右侧,填入当前金币数  450 ,并使用 First Scan 进行初次扫描。可以看到出现了一大堆地址。

保卫萝卜PC版内存修改器分析、制作详细过程

2022-08-24_151540.png

接下来种一个炮塔,让金币发生变化,然后暂停游戏,此时金币值是 350。点击 Next Scan 筛选出变化后的金币值。

看到左侧列表中只有一个内存地址了,我们先右键把它加入地址列表。

保卫萝卜PC版内存修改器分析、制作详细过程

2022-08-24_152159.png

因为当前获取到的是金币的动态地址,每次打开游戏,这个值都是变化的,无法制作成修改器。所以下面我们来寻找基址(手动),通过静态的指针实现对这个内存区域的稳定访问。

对金币的内存地址右键-Find Out what accesses this address,然后继续游戏,打一个怪。这时暂停游戏,可以发现窗口中出现了一些汇编指令。

保卫萝卜PC版内存修改器分析、制作详细过程

2022-08-24_153209.png

很明显,这个 add 指令就是用于增加金币的,而且金币的地址是 esi+0x74 。我们往下拉一下,可以看到 esi 的地址。我们把它选中复制下来。

保卫萝卜PC版内存修改器分析、制作详细过程

2022-08-24_153311.png

回到 CE 主窗口,点一下 New Scan ,勾选 Hex 复选框,把刚才复制的粘贴进去,点击 First Scan 开始新的扫描。

可以看到,左边的地址中出现了相应结果。好消息是,列表中出现了绿色字样的 Luobo.exe+105E68 ,这表明这是个静态地址,至此,金币基址的寻找工作告一段落。

保卫萝卜PC版内存修改器分析、制作详细过程

2022-08-24_153924.png

接下来我们手动添加指针,尝试访问金币所在的内存区域,作为验证。

点击右下方 Add Address Manaually 按钮-勾选 Pointer ,在最下方填入 Luobo.exe+105E68 ,倒数第二个框填写偏移量 74,观察到最上方的内存地址中,运算出的值为 364,恰好为金币数,说明没有问题。(有兴趣可以退掉游戏重开下,发现该指针仍可以访问到金币,而之前的动态地址不行)

保卫萝卜PC版内存修改器分析、制作详细过程

2022-08-24_155240.png

按照前文提到的大佬的分析,金币是有校验的。我们需要破坏这个校验,防止程序闪退。第一步还是需要找到校验指令所在内存区域的基址。

在小窗口中选中 add 指令,点击右侧 Show disassembler 查看汇编与内存。

保卫萝卜PC版内存修改器分析、制作详细过程

2022-08-24_172248.png

可以发现,金币在增加之后,有如下一段指令:

mov eax,[esi+74]dec eaxmov [esi+000000EC],eax

在其他金币减少的过程中,这段指令也出现,所以高度怀疑是验证代码。

上述代码实现了这样的功能:

  1. 把金币的数量([esi+74])存入 eax
  2. 使用 dec 指令使得 eax 减一
  3. eax 的值存入 [esi+0xEC]

可见,检测的机制大致就是比较两个地址上的金币相差是否为1.

所以接下来,只需要对 [esi+0xEC] 查访问就行了。

在主窗口下方,将金币的指针复制一份,把偏移量改成 EC,右键查访问,选择 Find what accesss the address pointed at by this pointer ,接着再随意打怪或者升级下炮塔,然后暂停游戏。

保卫萝卜PC版内存修改器分析、制作详细过程

2022-08-24_175311.png

保卫萝卜PC版内存修改器分析、制作详细过程

2022-08-24_175445.png

从图中可以发现有 mov 指令把这个校验值读取到了 ecx,选中这条指令,与之前相似,在右侧点击“查看汇编”按钮。

保卫萝卜PC版内存修改器分析、制作详细过程

2022-08-24_175756.png

汇编有如下指令:

mov ecx,[esi+000000EC]mov eax,00000001add ecx,eaxcmp ecx,[esi+74]je Luobo.exe+245F2

这些代码实现的是“将校验值加一之后与金币值比较,如果相等则跳转到 Luobo.exe+245F2”。(对应操作码:0x74 0x70

我们双击 je 所在的那一行指令,将指令改为 jmp 即可实现无条件跳转。(对应操作码:0xEB 0x70

保卫萝卜PC版内存修改器分析、制作详细过程

2022-08-24_180254.png

至此,金币修改就完全被破解了。随意修改金币,都不会造成游戏闪退。

这里,我们留意记下这条 je 指令的静态地址为 Luobo.exe+24580 ,为后续编写内存修改器做准备。

0x02 修改器程序流程构建

总体思路

保卫萝卜PC版内存修改器分析、制作详细过程

mermaid-1.svg.png

具体一点点

和函数名做一个对应。

保卫萝卜PC版内存修改器分析、制作详细过程

mermaid-2.svg.png

0x03 修改器代码实现

(模块化叙述,可能有不准确之处,具体代码见开源工程)

CMakeLists

管理项目的 CMakeLists.txt

比较重要的是添加上 UAC 权限的请求。

cmake_minimum_required(VERSION 3.23)project(LuoBo_Mod C)add_executable(LuoBo_Mod main.c)# 添加UAC请求管理员权限set_target_properties(LuoBo_Mod PROPERTIES LINK_FLAGS " /MANIFESTUAC:\"level='requireAdministrator' uiAccess='false'\" ")

Headers

普普通通头文件,win32编程的必备。

#include <windows.h>#include <psapi.h>#include "print.h"

查找窗体

先来个全局变量用于储存句柄信息:

HWND hwnd;

然后开始查找游戏窗体,以便后续获取 PID

void findWindow(void){    hwnd = FindWindow(NULL, "保卫萝卜Beta");    if (hwnd == NULL)    {        printf("无法获取窗口句柄,请检查进程是否存在!  Code: %lu\n", GetLastError());        exit(0);    }}

上面的代码加入了判断,如果不存在就报错。

获取PID

拿到句柄以后,可以据此来找到 PID ,方便后续访问进程。

还是来个全局变量储存下 PID

DWORD pid;

接下来是函数:

void getPID(void){    GetWindowThreadProcessId(hwnd, &pid); // 获取 pid    if (pid == 0)    {        printf("无法获取PID!  Code: %lu\n", GetLastError());        exit(0);    }    printf("PID: %lu\n", pid);}