这个话题比较敏感, 呵呵,不过先写吧。
1. 例子
首先我们自己写一个小程序, 这个程序的目的就是模拟游戏的HP MP 以及CALL 的功能。程序用MFC 写成,代码非常简单。 声明一个结构体:
1 struct role 2 { 3 int HP; 4 int MP; 5 CString name; 6 };
在类中使用它:
1 class CgameDlg : public CDialog 2 { 3 role role_; 4 }
在相关事件函数中增加减少其数值,然后显示,如图:
我们的目的就是写一个程序, 判断人物的HP,MP, 以及自动加血、见血、使用技能。
2. 原理
我们知道windows 32程序的数据都是会存在虚拟内存之中, 如果我们需要修改某一数据,只需要修改其对应的内存地址上的值就可以了。 但是win32又是一个进程独立的系统, 所以要实现一个外挂需要做两步:
1. 分析数据
2. 注入目标进程
3. 开始分析数据
3.1 寻找人物基址。
我们用cheat engine 和 Ollydbg 来分析这个程序。
首先我们分析人物(role)的基址:
a). 打开EC, 搜索1000
b). 改变HP,再次搜索。
得在内存 0012FF04 处保存着role_.HP.
c).理论上我们更改这个值就可以改变HP了, 尝试一下,确实是, 但是这样是有一个问题,就是我们role_的地址依赖于CgameDlg 的:
1 class CgameDlg : public CDialog 2 { 3 role role_; 4 }
显然在 在每一次程序启动的时候role_的地址就会改变, 所以我们还得继续找下去,找CgameDlg 的对象是如何创建的。
这里我再分析下: 004015A4 add dword ptr [esi+78],64 汇编语句的意思。
这句指令的意思就是往[esi+78]加0x64(100)的值, [esi+78]是role_的地址, 根据c++的对象模型分析, esi 就是CgameDlg 的对象的地址, 0X78就是成员变量role_在此地址的偏移量。 所以我们记下esi的值,并再次搜索这个值。此时ESI= 0012FE8C.
e). 再次开始新的搜索:0012FE8C, 发现好多,不过不要紧, 我们在做一些操作, 疑问 CgameDlg 的对象已经被创建, 所以我在做其他操作的时候不会改变CgameDlg 的对象的地址,所以我们可以用未改变的值进行过滤。结果还有这么多:
f). 为每一个值重复d). 操作,去掉有操作会参数汇编指令的值。然后为剩下的选择:
去掉没有改变的, 剩下两个, 0039AEBC,004054E8。 因为绿色的表示基地, 所以我们首先实验下004054E8:
g). 退出game.exe,重新登录,发现这个地址的值还是正确的, 所以我们role_.HP的基地址base=[004054E8 + 78]指向的值,role.MP的地址为[base+0x4], [base+0x8]。
实际上, role_. 是经过如下方式定义出来的:
1. CgameApp theApp;(声明全局变量,这个就是每一次程序启动都不变化的基址)
2.
1 BOOL CgameApp::InitInstance() 2 { 3 CgameDlg dlg; 4 }
3.
1 class CgameDlg : public CDialog 2 { 3 role role_; 4 }
3.2 寻找加血减少血call
汇编级别的函数调用总是call 一个地址, 而我们要模拟加血的动作, 只需要周到加血函数的入口点地址,然后 call 这个地址就好了。
我们知道, 调用加血函数的时候, 必定有一个读写内存的过程, 我们只需要在这个内存地址下断点,然后当有值写入的时候断下,就可以分析相关的函数了。
a). 打开 od, 在role_.HP的当前内存地址下断
经过分析, 我们知道 00401580 就是这个函数的入口点,我们也可以调用其上层的call:
78A6D225 处:
c) 这里我们就分析出, 加血函数的地址为 0x 00401580 或 0x78A6D225 。