1.CE修改游戏特例说明
模拟器游戏不能直接修改游戏的程序代码(即不能直接使用代码注入的手段修改code段代码),因为游戏并非使用平台语言所写,只有模拟器是使用平台语言写的,即壳是汇编写的,壳用来翻译跨平台程序,因此不能直接修改跨平台程序。
2.精确搜索
精确搜索时需要根据具体的数值范围(数据类型)搜索数据:
例如:
搜索时间,可能使用的浮点数存储时间
搜索字符串时,可能使用字符数组存储字符串
3.模糊搜索
当模糊搜索的值再次搜索过滤多次还是很多时,可以先精确搜索到能搜索到的地址,然后根据精确地址找模糊地址(因为都是变量数据,都会存储在data中,所以他们的地址应该很近)
大部分游戏显示的数值都经过了加密(*2+1),因此需要模糊搜索来查找,以植物大战僵尸的金币为例:搜索值应为游戏中金币数/10(植物大战僵尸的金币最小单位为10),才可以搜到结果。
4.指针与多级指针
基址:
基址, 即是程序代码被编译成二进制码时就已经规定好的一个绝对地址值.一般反汇编后为: mov 寄存器, [xxxxxx] // 基址[xxxxxx]=模块地址+偏移
基址是一串16进制数字代表的地址值, 除非程序被破坏,或者被重新编译生成,否则是不会改变的。详细可参考PE文件结构。
基址地址一般在程序模块中(>00401000)(<7FXXXXXX系统领空)(绿色基地址)
指针:
查找动态地址指针时使用 是什么改写 搜索
查找指针地址时使用 是什么访问 搜索
CE中基地址找到动态地址:
[基地址(绿色模块地址)]+偏移 取基地址值加上偏移值后的值作为地址(一级指针)
[[基地址(绿色模块地址)]+偏移]+偏移 取一级指针的值+偏移值作为地址(二级指针)
32位游戏一般多级指针搜索到4级即可,64位一般最多则为8级或12级,但是一般我们超过4级指针后可以通过人造指针来实现。
5.代码注入—AOB注入
AOB注入是最常用的注入脚本,选中需要注入的代码行,
AOB注入
弹出自动汇编窗口中选择AOB注入
AOB注入
生成代码如图:
AOB代码
AOB脚本支持ENABLE与DISABLE,因此可以加入到CT列表中,一键开启或关闭脚本功能。
注入点
有时直接对当前代码行注入可能会造成程序报错,选择当前代码行的上一行注入可能会避免程序报错问题。
代码注入点
6.汇编知识
寄存器
可以理解为一个临时变量,每个寄存器有他们惯例的用途,不过这仅仅是惯例,你想用他们做其他事也可以(除了 esp 栈顶指针以外)。
通常我们使 eax, ebx,ecx, edx 做普通的操作,虽然这些寄存器也有一些特殊的用途,但是用途较少所以一般的操作用这些寄存器就可以。
esi和edi:看他们的名字,叫做“源索引寄存器”和“目标索引寄存器” (source index、destination index),因为在很多字符串操作指令用的,在其他的寄存器的时候不用这几个,当然如果你能掌控得好的话 esi 和 edi 其实也可以随便使用。
esp:esp 是不可以乱动的,esp 指向堆栈顶部,push 和 pop 指令会影响 esp,由于寄存器的数量太少了,我们编写程序时需要的变量有很多,所以使用内存来辅助我们,函数的局部变量就会保存在栈中,调用函数的参数也会保存在栈中。
比如说,现在我们的寄存器都用完了,我需要腾出一个寄存器来做其他事情。那么就把寄存器的变量放到内存(栈)中(push),然后就可以对这个寄存器为所欲为了,然后用完之后,再把栈中的值提取出来(pop),放回寄存器。
push eax
; 做一堆有关eax的事
; 比如:
; mov eax, [ebp+04]
; add eax, [edx]
; mov [ebp+04], eax
pop eax
常见指令
基础指令集 —— 8086指令集
8086汇编指令总结
sse指令集
movss 表示 Move Scalar Single,移动标量单精度浮点值。
xorps 表示 XOR Packed Single,压缩单精度浮点值逻辑位异或。
cvtps2pd 表示 Convert Packed Single to Packed Double,压缩单精度浮点值转换成压缩双精度浮点值。
addsd 表示 Add Scalar Double,标量单精度浮点值加法。
mulsd 表示 Multiply Scalar Double,标量单精度浮点值乘法。
这些可以直接进行浮点运算的指令、可以直接操作XMM寄存器的指令,都属于 SSE 指令集。
每条汇编指令的名字起得都是有意义的,好好学习英语可以帮助我们更好地理解他们。
看到一条 SSE 指令,要把他拆成两部分,“操作”和“数据类型”,
第一部分:操作。mov、xor、add、mul 这些指令,x86 最基础的指令集中也有。
第二部分:数据类型。ss、sd、ps、pd 这一部分又要拆分成两部分来看。第二位是 s 表示是 single 单精度浮点型,一个数据占 32 位,第二位是 d 表示 double 双精度浮点型,一个数据占 64 位。第一位是 s 表示只操作 XMM 寄存器的第一个数据(ss 就是 32 位,sd 就是 64 位),第一位是 p 表示同时操作全部 128 位数据(ps 就是 4 个 32 位,pd 就是 2 个 64 位)。
xor 后面的两个操作数相同的话就是用来清零的,比如 xor eax,eax 就是令 eax 为 0,这是最简单最快捷的寄存器清零方法。对于 XMM 寄存器同样也是清零。
cvt 指令就是浮点数精度的转换,主要看 s 和 d 的位置,s2d 就是 single to double 单精度浮点数到双精度浮点数,从只占 32 位变成占 64 位,反之 d2s就是双精度到单精度的转换。
常见的函数开头
push ebp ; 保存上一个 ebp
mov ebp, esp ; 把 esp 给 ebp
sub esp, 08 ; 分配8字节栈空间用于局部变量
; 函数主体内容…
; 在这里可以使用 ebp 来定位与函数有关的变量
; [ebp-08] 代表第 2 个局部变量
; [ebp-04] 代表第 1 个局部变量
; [ebp] 刚才 push 进来那个 ebp
; [ebp+04] 函数返回之后的要执行的指令所在位置
; [ebp+08] 代表函数的第 1 一个输入参数
; [ebp+0C] 代表函数的第 2 个输入参数
add esp, 08
pop ebp ; 复原 ebp
ret 08 ; 返回函数调用位置,并且把栈指针 +8,把调用的参数从栈中移除
范例
以植物大战僵尸为例:
阳光地址是一个二级地址。查看反汇编代码是这样子的:
mov [eax2 + edi + 00005578],esi
其中,edi是指针,eax2 + 00005578是偏移值,esi是阳光数量,这句代码的作用是把阳光的值放在阳光的内存地址中。
7.共用代码段的利用与处理
公共代码段是代码复用的体现,好的代码复用性很高,也就是说一个函数很可能供多个调用者调用。
针对公共代码处理:
以火炬之光为例:
当汇编nop了掉血代码时,发现不仅人物血不掉,而且怪物血也不掉了(因为掉血函数是人物与怪物扣血的共用函数)。
使用代码注入的方式时,要利用cmp 和 je jne jle等条件转移指令判断是否是己方还是敌方,达到己方不掉血。
以CE:STEP9为例:
STEP9中扣除血量的代码就是公用代码
CE:STEP9
先使用浮点搜索找到一个血量地址后,使用什么改写搜索到代码段地址,右键找出指令访问的地址
公共代码段
依次点击攻击,4个单位血量分别降低,可以看到4个单位的血量地址依次显示出来。
4个单位的血量地址
全选地址,右键打开选中地址的分析数据,弹窗默认确认即可,显示如图所示。
数据结构
代码注入(模版使用全部注入)如下:
代码注入
汇编代码
此时点击攻击,友军不再会掉血。
利用公共代码找相关地址:
当我们找到物品栏的某个格子物品数量时,可以通过扫描谁改写了值找到改写代码处,再右键选择“找出指令访问的地址”打开扫描窗口,然后游戏中移动物品到其他格子,就能找到这个代码访问的其他格子地址。原理即是利用公共代码操作多个地址。
8.特征代码的应用 (针对程序段地址会变动)
- 被迫使用特征码:
以flash游戏国王的战争为例:
由于使用的flash启动的游戏,游戏的内存地址每次都会不同(类似于模拟器游戏),因此只能根据特征码(指令的字节数组)找到修改的地址,再做修改:
特征码—代码的字节数组
CEAA提供一个函数可以查询特征码的地址:
aobscan //CEAA函数 搜索特征码,并将地址赋给变量
例:
aobscan(Money,特征码) 把特征码的地址给了money
money+修改地址的相对偏移量 即要修改代码的地址
2. 主动使用特征码: 实现多版本兼容修改器
以植物大战僵尸为例:有两个版本(无尽版、年度版)进程名相同,代码实现相同(特征码一样),使用特征码便可以实现通用的CT脚本。
9.人造指针 (针对指针级别过多、找不到基址的情况)
以植物大战僵尸为例:
原理:阳光的地址每次重开游戏都会变化,有一个固定的程序段记录阳光的地址
方法一【固定地址】:
(1) 右键阳光的地址是谁访问了地址(读取地址的值时触发,阳关地址是变动的,只有访问断点可以找到是谁访问了它),找到固定的程序段
(2) 找到固定程序段-代码注入
(3) (a)找一处可读可写内存模块的空闲地址(固定地址)保存阳光地址
(b)当可读可写内存地址没有空闲时,我们也可以使用只读内存的空闲地址,但是需要使用一条CE指令[fullaccess(只读内存地址,修改为可读的字节长度)修改内存地址保护为可读写,这个指令不一定每次成功]
(4) 建立指针指向空闲地址
方法二【申请地址】:
(1) 右键阳光的地址是谁访问了地址(读取地址的值时触发,阳关地址是变动的,只有访问断点可以找到是谁访问了它),找到固定的程序段
(2) 找到固定程序段-代码注入
(3) 使用alloc(标识名,地址长度)动态申请内存,将阳光地址保存到申请的内存中
(4) 由于申请的内存地址目前只能在当前脚本中使用,我们需要将其保存到一个全局变量中,使得我们可以在其它脚本中也能访问
CEAA提供了一个函数命令[regiestersymbol(标识名),表示将参数标识加入到全局表]
(5) 在[disable]中添加释放申请内存的指令dealloc(标识名,地值长度)
(6) 前面将申请的内存标识加入了全局表,那么在关闭脚本时须将标识从全局表中剔除
[unregiestersymbol(标识名),将参数标识从全局表中剔除]
(7) CE手动添加地址,地址为标识名
10.线程注入的应用
以植物大战僵尸修改阳光为例:
脚本代码:
[ENABLE]
alloc(newmem,256)
lable(exit)
Createthread(newmem)
newmem:
mov eax,[755e0c] //阳光基址
mov eax,[eax+868]
add [eax+5578],1F4
cmp [eax+5578],2709 //阳光上限是9990
jle exit
mov [eax+5578],2706
exit:
ret
[DISABLE]
线程注入效果:当启动一次脚本阳光数量就+500,相当于游戏修改器中点一次加一次属性的操作。
11.Windows函数应用
以火炬之光为例:
模拟自动回蓝:判断蓝的值小于15时,自动摁2键吃药
[ENABLE]
alloc(newmem.1024)
lable(returnhere)
lable(originalcode)
label(exit)
newmem:
fld dword ptr [ecx+000003B8]
cmp [ecx+000003B8],40A00000
ja originalcode
push 0
push 0
push 0
push 32 //2键的键码
call keybd_event
push 0
push 0
push 0
push 32 //2键的键码
call keybd_event
originalcode:
exit:
jmp returnhere
Torchlight.exe+85FE0:
jmp newmem
nop
......
12.CE的断点选择与跟踪
以皇家守卫军怪物秒杀为例:
在找到怪物血量减少的地方后应该会有判断怪物是否该死亡的跳转,修改跳转完成秒杀功能
以植物大战僵尸自动收集阳光为例:
收集阳光的时候阳光会增加,下内存写入断点,找到阳光增加的语句,返回到上一层,在阳光增加call的上面会有点击阳光的call,修改跳转完成自动收集阳光的功能
【三种断点的原理】
(1)int 3断点,即cc断点,这是一种基于软中断机制断点,3为中断号。OD中,当你在代码区某行按F2即可实现,其机理是把所在代码的第一个字节保存到一张表上,然后将其修改为CC,当程序运行到此代码时,就会产生中断,从而转至中断服务程序。当你去除断点时,OD会从表里读取出当前断点原来的字节内容。
(2)内存断点,假如你用int 3断点对数据区下断,OD会提示你断点可能不会实现,其实也是必然,程序不可能执行数据区,然而我们却可以当数据被读取或写入时进行下断,这种原理主要基于内存属性,当下读写断点是,OD会修改断点处读写属性,如果程序对此数据读写的话,会产生读写异常,OD捕捉此异常并分析,其可以知道运行到何处,对代码段也可以下此断点,机理相似。
(3)硬件断点,这是由硬件实现(这里是CPU实现),其由CPU调试器实现,断点长度有限,其只用两位记录断点长度,所以只支持4个硬件断点,调试寄存器中有3位表示断点状态及属性,000 保留 001 执行断点 010 访问断点 011 写入断点 100 保留 101 临时断点 110 保留 111 保留