吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 6149|回复: 29
收起左侧

[游戏安全] 通杀爆改 Unity FPS 游戏系列-第二章:HOOK实现全屏改血+秒杀

  [复制链接]
lyl610abc 发表于 2023-9-12 22:16
本帖最后由 lyl610abc 于 2023-9-18 20:10 编辑

本篇为 Windows 篇,Android 篇将和 @正己  梦幻联动,敬请期待

索引

通杀爆改 Unity FPS 游戏系列-序章:介绍及游戏下载

通杀爆改 Unity FPS 游戏系列-第一章:常规搜索+通杀结构解析

通杀爆改 Unity FPS 游戏系列-第三章:il2cpp mono 差异

本章内容

本篇以 windows il2cpp 包为例实现功能:

全屏改血

全屏秒杀


思路

先对全屏秒杀这个功能进行拆解

  • 全屏:作用范围为作用域内的所有敌人
  • 秒杀:修改血量为 0  触发死亡

作用域

出于性能考虑,许多游戏并不会将整个地图上的所有敌人都加载出来

比如经典的吃鸡,其作用域就是几百米,游戏只加载了玩家附近几百米的敌人,透视自瞄也只能读到整个作用域内的敌人

当然我的 demo 肯定没有这个限制,作用域内的敌人就是所有敌人了


全屏

有了作用域的概念后,所谓的全屏实质上就是得要能够作用到作用域内的敌人

这里就有 2 个思路:遍历和公共函数HOOK

遍历

通常来说,游戏开发者为了方便管理,都会将所有敌人放到一个数组/列表 中

所谓的遍历,通俗来讲,就是每次从敌人数组/列表中取一个敌人出来,一直取到所有敌人都取完为止

取出来 --> 执行秒杀操作


公共函数HOOK

公共函数

公共函数,即所有目标都会调用到的函数

在此次案例中就是所有敌人都会调用到的函数

既然每个敌人都会调用到,在调用的时候通过 HOOK ,加点私货就可以达到秒杀的效果


HOOK

HOOK:钩子,粗浅来说就是拦截后再放行

好比:正己老师某天要从家里去上班,但好巧不巧,路上被人打劫了,被劫色后(杰哥不要{{{(>_<)}}}),还是被放行回去上班

把这个事情转换一下

类对象是:正己老师

函数是:上班

HOOK是:打劫

HOOK干了啥:劫色

正己老师的目标是什么:上班

目标完成了没:完成了,还是一瘸一拐地上班去了。这里的一瘸一拐是什么造成的? 劫色

也就是可以通过 劫色 对 正己老师 造成 一瘸一拐 的效果;通过 HOOK 对 类对象 的 状态 进行修改


浅浅总结一下:通过 HOOK,可以对被 HOOK 的对象进行状态修改,但对象原本要做的事情还是会去做


由此,到这里的全屏就是:

找到一个所有敌人都会调用到的函数,HOOK 这个函数,劫色敌人,不好意思走错片场了,是把敌人的状态弄成死亡,最后再去执行函数原本的逻辑


实战

遍历

有了第一章的铺垫,直接使用 mono + 关键词法,可以迅速定位到存储所有敌人的列表:fps.AI 下的 Unity.FPS.AI.EnemyManager

image-20230912183202539


然后使用第一章的 Lookup instances 大法,可以很轻松地过滤得到所有敌人:

image-20230906114714781


把这个内存地址丢到 Structure Dissect 里(忘记在哪的回去翻第一章!╰(‵□′)╯)

展开 _items 后,这里的 _size 表示的是当前敌人数量,毕竟刚开始,所以只有 3 个敌人

image-20230906115012397


然后展开到这个敌人的血量,改成 0 试试

image-20230906114947915


改成 0 后返回游戏可以看到是有效果的,但是敌人没有死掉 QAQ

这是因为死亡逻辑是在敌人受到伤害时做判断,如果受伤后血量小于 0  才会执行死亡函数

这里直接修改血量,并不会触发死亡,所以遍历后只是修改血量还不够,还得再调用死亡函数

死亡函数的内容放到后面 HOOK 里,这里主要是介绍一下遍历和重温下上一章的内容

image-20230906120135236


公共函数HOOK

挑选公共函数

按照前面的前面的思路,第一步就是要寻找合适的公共函数

所谓的合适指的是函数执行的时机,还是以前面的正己老师为例

温故下例子:正己老师某天要从家里去上班,但好巧不巧,路上被人打劫了,被劫色后(杰哥不要{{{(>_<)}}}),还是被放行回去上班

这里的时机,就是上班

但万一某天正己老师没上班(未触发函数),那不就没法对他进行劫色(HOOK)了吗

因此可以换个更好的时机,比如正己老师呼吸的时候

正己老师每呼吸一次,就对他进行劫色,这个时机就可以说是能够覆盖到正己老师的大多情况了


回归主题,对应到游戏内敌人的情况,则是需要找一个函数

这个函数的触发时机最好能够覆盖到所有敌人,且不需要特殊的触发条件,正常就能触发到

先定位到敌人对应的类:fps.AI.dll 下的 Unity.FPS.AI.EnemyController

image-20230912183248311


虽然敌人没有呼吸这个函数,但有个关键的函数 Update 刷新

这个刷新函数能够完美满足我们的需求,于此同时其实还有一些其他函数,HOOK 他们能实现其他效果

比如 TryAttack 函数,敌人尝试攻击我们的时候直接让它死掉 w(゚Д゚)w

这里就选定 Update 函数了


关联公共函数和功能

先理一下联系

在公共函数处能拿到的类地址是:EnemyController 敌人

实现敌人死亡需要的类地址是:Health 生命

所以需要做的事:EnemyController → m_Health → CurrentHealth 修改为 0  → 调用死亡函数

只是修改敌人血量为 0 不会触发死亡,还需要额外调用死亡函数,死亡函数具体在后面再展开


HOOK实现全屏改血

先从相对简单的改血开始,死亡函数先放在后面

先下双击 Update 函数,跳转到函数头部,然后快捷键下断点(这部分在第一章详细演示过,这里稍微简略些)

查看寄存器和堆栈数据

image-20230908155028281


根据第一章的找血量部分的内容

对照函数的参数,其实是可以推测出堆栈中内存地址的含义的

内容 含义
ESP 7900BB9C 完成调用后要返回的内存地址
ESP+4 1887A720 我们想要的指向 EnemyController 的内存地址

再使用结构解析面板解析获取到的 EnemyController 的内存地址

可以验证所获取的地址就是我们想要的

image-20230908160418383


接下里就是要记下我们目标对应的偏移量

EnemyController 偏移 00D8 得到 m_Health

m_Health 偏移 0020 得到 CurrentHealth

image-20230908160813079


至此,HOOK 实现全屏改血所需要的信息已经足够了,Ready Perfectly ( ̄( ̄ )

回到 CE 修改器的 Memory Viewer 界面,Tools -> Auto Assemble (或使用快捷键 Ctrl + A)

PS:注意要选中 Update 函数头部,这关系到 HOOK 的位置,这里选择在头部进行 HOOK

image-20230911163519744


点击以后出现了新窗口:Auto assemble

image-20230911164949731


先通过 Template → Cheat Table Framework Code  载入模板 (或使用快捷键 Ctrl  + Alt + T)

image-20230911165124667


载入以后可以看到多了几行代码,主要是用来控制脚本的开关

image-20230911165315579


再通过 Template → Code Injection 载入模板 (或使用快捷键 Ctrl + I)

image-20230911165508515


弹出新窗口,要求输入想要 HOOK 的地址,默认是当前选中的地址,这也是前面提到要选中函数头部的原因

image-20230911165620671


确认以后得到一串代码,我们只需要关注 ENABLE 部分

image-20230911175719340


先贴出生成的代码,然后稍微说明一下生成代码的含义

[ENABLE]
//code from here to '[DISABLE]' will be used to enable the cheat
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit)

newmem: //this is allocated memory, you have read,write,execute access
//place your code here

originalcode:
push ebp
mov ebp,esp
push -01

exit:
jmp returnhere

"GameAssembly.dll"+1F4800:
jmp newmem
returnhere:

[DISABLE]
//code from here till the end of the code will be used to disable the cheat
dealloc(newmem)
"GameAssembly.dll"+1F4800:
db 55 8B EC 6A FF
//push ebp
//mov ebp,esp
//push -01

正常来说,一般是从上到下来看代码,但是这里以实际的逻辑顺序进行说明

整体的逻辑为:

  1. 修改原本的游戏逻辑为跳转到我们自己的逻辑 (会覆盖原本的逻辑)
  2. 执行自己的逻辑
  3. 执行被我们覆盖掉的部分
  4. 跳转回去

对应段 相关代码 说明 例子
修改游戏逻辑为跳转到自己的逻辑 "GameAssembly.dll"+1F4800: jmp newmem<br/>returnhere: 跳转到自己逻辑,同时记录要跳回的地址 拦路抢劫
执行自己的逻辑 newmem: 自己的逻辑,还没写 劫色?
执行被覆盖掉的部分 originalcode: push ebp<br/>mov ebp,esp<br/>push -01 jmp newmem 覆盖掉的部分 被拦路的人继续走路
跳转回去 exit: jmp returnhere 跳转回原本的逻辑 被拦路的人完成剩下的路

通过分析生成的代码,可以看到 CE 修改器已经十分智能地为我们搭好了框架,接下来就是编写自己的逻辑了

为了避免大家往回翻,这里贴出之前得到的必要信息:

内容
ESP 返回地址
ESP+4 EnemyController
EnemyController+00D8 m_Health
m_Health+20 CurrentHealth
newmem: //this is allocated memory, you have read,write,execute access
//place your code here
//随便找个寄存器保存,因为中途需要借助寄存器来赋值,这里也可以改成 ebx ecx ...
push eax
//注意这个地方,原本 EnemyController 的地址应该是 ESP+4 但是因为我们保存了个寄存器,导致位置变化,所以需要再加 4
//这里就相当于是 eax = EnemyController 
mov eax,[esp+4+4]
//这里就相当于是 eax = EnemyController+d8 = m_Health
mov eax,[eax+d8]
//这里就相当于是 [eax+20]= [m_Health+20] = [CurrentHealth] = 0
mov [eax+20],(float)0
//用完以后要还原回去,保存的是什么寄存器就还原什么寄存器
pop eax
originalcode:

修改完以后得到的完整代码是:

[ENABLE]
//code from here to '[DISABLE]' will be used to enable the cheat
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit)

newmem: //this is allocated memory, you have read,write,execute access
//place your code here
push eax
mov eax,[esp+4+4]
mov eax,[eax+d8]
mov [eax+20],(float)0
pop eax
originalcode:
push ebp
mov ebp,esp
push -01

exit:
jmp returnhere

"GameAssembly.dll"+1F4800:
jmp newmem
returnhere:

[DISABLE]
//code from here till the end of the code will be used to disable the cheat
dealloc(newmem)
"GameAssembly.dll"+1F4800:
db 55 8B EC 6A FF
//push ebp
//mov ebp,esp
//push -01

写好代码以后,不急着点执行,先通过 File → Assign to current cheat table 把它保存下来

image-20230912174111477


保存后,可以在 cheat table 里找到,点击 Active 激活

image-20230912174323106


全屏改血效果展示

可以看到所有的敌人的血量全部为 0

image-20230912175536278


死亡函数

虽然成功把敌人的血量改成了 0,但很尴尬的是敌人并没有立刻死亡,于是得开始寻觅死亡函数

针对 Health 的死亡函数并不难找,就在 fps.Game.dll → Unity.FPS.Game.Health → HandleDeath 里

image-20230912183000721


但是经过测试会发现一个问题,在 il2cpp 打包的情况下,HandleDeath 函数并不会被触发,但在 mono 打包的情况下又会被触发

这个放到之后的篇章细说 il2cpp 和 mono 的区别

本篇是以 il2cpp 为例,因此死亡函数就不能选择 HandleDeath

但好在死亡函数不止一个,可以找到另一个死亡函数: fps.Game.dll → Unity.FPS.AI.EnemyController → OnDie

image-20230912204313373


HOOK实现全屏秒杀

在第一章中,有提到过:相比函数原型,在汇编之中其实还会多出一个参数:这个类实例本身的内存地址

因此想要调用 EnemyController.OnDie 函数,来让敌人死亡,实际上只需要传入 EnemyController 的地址即可

下面给出伪代码:

push EnemyController的地址
call EnemyController.OnDie
//这里之所以要 add esp,04 是因为压入了一个参数,如果是压入了两个参数,则应是 add esp,08
//这里是做了一个堆栈的外平衡,且因为调用协定是 stdcall,所以才这么写
add esp,04

有关堆栈相关知识点可回顾:逆向基础笔记七 堆栈图(重点)

有关调用协定相关的知识点可回顾:逆向基础笔记九 C语言内联汇编和调用协定


可以根据伪代码结合 Update 的上下文写出 HOOK 代码:

newmem: //this is allocated memory, you have read,write,execute access
//place your code here
//随便找个寄存器保存,因为中途需要借助寄存器来赋值,这里也可以改成 ebx ecx ...
push eax
//注意这个地方,原本 EnemyController 的地址应该是 ESP+4 但是因为我们保存了个寄存器,导致位置变化,所以需要再加 4
//这里就相当于是 eax = EnemyController 
mov eax,[esp+4+4]
//将 EnemyController 作为参数
push eax
//这里就是调用 EnemyController.OnDie
call GameAssembly.dll+1F2D50
//堆栈外平衡
add esp,4
//用完以后要还原回去,保存的是什么寄存器就还原什么寄存器
pop eax
originalcode:

稍微说明一下 GameAssembly.dll+1F2D50 这个地址的由来,以免有些小伙伴不解

双击 OnDie 函数

image-20230912204313373


双击后跳转到 Memory Viewer 界面

image-20230912213312658


最后再给出完整的全屏秒杀代码:

[ENABLE]
//code from here to '[DISABLE]' will be used to enable the cheat
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit)

newmem: //this is allocated memory, you have read,write,execute access
//place your code here
push eax
mov  eax,[esp+4+4]
push eax
call GameAssembly.dll+1F2D50
add esp,4
pop eax
originalcode:
push ebp
mov ebp,esp
push -01

exit:
jmp returnhere

"GameAssembly.dll"+1F4800:
jmp newmem
returnhere:

[DISABLE]
//code from here till the end of the code will be used to disable the cheat
dealloc(newmem)
"GameAssembly.dll"+1F4800:
db 55 8B EC 6A FF
//push ebp
//mov ebp,esp
//push -01

保存后,可以在 cheat table 里找到,点击 Active 激活

image-20230912174323106


全屏秒杀效果展示

5ede2a4e0a9d04760e8cf5512de1320b


作业

你已经学完 1+1=2 了,是时候来点高等数学了(〃 ̄︶ ̄)( ̄︶ ̄〃)

前面已经提到了 mono 版本是可以 HOOK Health.HandleDeath 的

实现 mono 版本的全屏秒杀就当作是作业了

总结

  • 通过 HOOK,可以对被 HOOK 的对象进行状态修改(用骚操作去搞别的对象也可以,但这里说的是通常情况),但对象原本要做的事情还是会去做
  • HOOK 时需要注意时机,即被 HOOK 函数的上下文环境是否能够获取到想要的数据(本章例子为死亡函数和血量)和被调用的频率
  • 相比函数原型,在汇编之中其实还会多出一个参数:这个类实例本身的内存地址,这个参数往往能作为修改的突破点
  • il2cpp 和 mono 在部分地方会有些差异,但 CE 修改器的 mono 功能通常情况都是通用的,都能够解析游戏的结构,无需太过在意打包方式

这一章稍微引入了一些汇编代码,在难度上有一点点提升,但实现的功能强度也有所提升,可以不用每次手动操作了 ( ̄︶ ̄*))

同时也抛砖引玉,稍微表现出了 il2cpp 和 mono 不同的点,但大同小异,限于篇幅:异处放到后面的章节再细说

最后附上成本的 CE 修改器脚本,即开即用:

image-20230912221133006

下载地址:

https://610-pic-bed.oss-cn-shenzhen.aliyuncs.com/FpsDemo_By_lyl610abc_il2cpp.CT

免费评分

参与人数 17威望 +1 吾爱币 +42 热心值 +17 收起 理由
Van42 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
moriv4 + 1 + 1 用心讨论,共获提升!
MJ_B + 1 + 1 谢谢@Thanks!
skupcll + 1 + 1 谢谢@Thanks!
gunxsword + 1 + 1 我很赞同!
涛之雨 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
che_shen + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
乔帮主 + 1 + 1 这个真牛逼
670246038 + 1 + 1 我很赞同!
HillBoom + 1 + 1 用心讨论,共获提升!
明月相照 + 1 + 1 谢谢@Thanks!
debug_cat + 1 + 1 用心讨论,共获提升!
shengruqing + 1 热心回复!
BonnieRan + 1 + 1 谢谢@Thanks!
忆魂丶天雷 + 2 + 1 用心讨论,共获提升!
allspark + 1 + 1 用心讨论,共获提升!
正己 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

查小布 发表于 2023-9-12 23:50
期待安卓篇
ryanu 发表于 2023-9-13 23:02
感谢大佬的教程,来交作业了
[Asm] 纯文本查看 复制代码
[ENABLE]
//code from here to '[DISABLE]' will be used to enable the cheat
alloc(newmem,2048)
label(returnhere)
label(originalcode)
label(exit)

newmem: //this is allocated memory, you have read,write,execute access
//place your code here
push eax
mov eax,[esp+4+4]
//将 EnemyController 作为参数
push eax
//调用EnemyController.OnDie
call 1F4F6970
//堆栈外平衡
add esp,4
//还原寄存器
pop eax

originalcode:
push ebp
mov ebp,esp
push edi
sub esp,74

exit:
jmp returnhere

Unity.FPS.AI.EnemyController:Update:
jmp newmem
nop 2
returnhere:


 
 
[DISABLE]
//code from here till the end of the code will be used to disable the cheat
dealloc(newmem)
Unity.FPS.AI.EnemyController:Update:
db 55 8B EC 57 83 EC 74
//push ebp
//mov ebp,esp
//push edi
//sub esp,74

但是只要重启游戏后就失效了。。
QQ截图20230913225341.png

免费评分

参与人数 1吾爱币 +3 热心值 +1 收起 理由
lyl610abc + 3 + 1 失效的原因在于 mono 中,EnemyController.OnDie 是 JIT 即时编译的,这个.

查看全部评分

艾莉希雅 发表于 2023-9-12 23:20
那万一时机不对呼吸的正己老师没劫到,反而把所有呼吸的人给劫了是不是就大罪过了
duokebei 发表于 2023-9-13 08:03
属于是遇见大佬了,学习
scbzwv 发表于 2023-9-13 08:40
讲解的非常详细,感谢分享
898522783 发表于 2023-9-13 09:02
好家伙,杰哥都来了。楼主好高产。
BonnieRan 发表于 2023-9-13 09:09
看完了 强强强,举的例子也是生动形象 一眼看懂,越来越期待Android篇啦
Chenda1 发表于 2023-9-13 09:23
非常多干货  哈哈
尹铭 发表于 2023-9-13 09:44
膜拜大佬~
纯情小变态 发表于 2023-9-13 10:03
关注系列帖子,
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-21 20:18

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表