众所周知,前端传过来的啥都是不可信的,所以正常情况都是前端数据加密、签名,后端解密、验签,这套逻辑确实能挡住一大部分的hacker,但是H5这玩意也套用这套体系的话其实挺尴尬的,因为前端数据处理的js逻辑是用户可见的。
即使你把代码混淆、加密、搞一堆花指令、甚至搞一堆异步调用导致调用堆栈花里胡哨,但是依旧可以通过各种下断点、跟堆栈找到核心的数据处理代码,也就是数据还是明文状态时的那个片区域,只要在那边下断点该数据,无论怎么加密、签名。数据它都会被认为是合法的。
而今天的主角是企鹅俱乐部重制版(Club Penguin Rewritten)
,一款热度并不怎么高甚至挺凉凉的情怀向游戏,游戏代码本身也确实有混淆,数据也有加密,但是抵不住浏览器调试呀。
目标的话自然是刷金币咯。
抓包
当然万事先抓包看看数据包有没有加密和签名,如果没有的话直接改。
数据包有加密,直接爆改数据包的思路可以打消了。
看控制台
先玩一局小游戏,看看控制台有没有一些相关的日志可以跟堆栈。
还真有日志,生产环境打日志虽然挺常见的但我个人感觉还是尽量减少为好,最好的做法是日志函数加个prod
判断,非生产的情况才正常打日志。
其中getCoins()
非常得吸引我的目光,毕竟是在Sent
之前的金钱相关调用,很大概率就是我们的目标代码块,因为输出的数据都还是明文形式的。
不过跟过去发现是个日志函数,那么就有点麻烦了,因为给日志函数下断点跟堆栈的话,会有一堆不必要的中断去干扰程序的正常运行,但是没办法,只能尽量在小游戏结束的时候下断点,减少干扰吧。
成功在目标日志中断,尝试跟调用堆栈。
跟过去之后在函数开头下个断点,准备下回直接改数据,看看能不能成。
从0分改成12分,余额从419变成431,刷新重新进游戏看看余额变化是不是真的生效了。
余额成功变成431。
那么就可以想办法进行简化操作了,现在利用调用堆栈去跟到目标代码块进行修改比较繁琐,而且手动改比较累,那么可以利用Fiddler
的自动回复功能直接插入js代码硬写入每次小游戏获得的金币数。
不过首先得定位到这个js是哪个文件来的,因为这个游戏是动态加载的js,没法直接定位到来源,所以去开发者工具里面搜一下特征代码,比如说'3|2|1|0|4'['split']('|')
,成功定位到资源https://play.cprewritten.net/assets/client/system/RuffleManager.js?v=1.7.130-beta
。
那么浏览器手动访问一下这个资源,然后在Fiddler里面改代码就完事。
直接加一句_0x24442f.coins=65535;_0x24442f.score=65535;
,把成绩改成65535,然后浏览器强刷一下这个资源,确保浏览器的缓存是咱们hack过的。
然后刷新游戏,再去玩一次小游戏,看看余额是不是+65535
了。
大功告成。
一些想法
对于H5游戏开发,数据加密、签名是自然要的,代码的混淆处理也是必不可少的,但是一些细微的点也应当关注:
- 生产环境不打非报错日志,将运行日志以其他方式收集,如有必要再由用户上传至开发处进行bug分析。
- 反调试断点,虽然
while(true)
这种轮询会导致游戏性能下降,但是周期拉大一点还是可以的,不过这种也可以直接Fiddler自动回复给屏蔽掉相应代码,但有总比没有好。
但是服务器端也同样应当对前端传来的数据进行合法性判断,如:
- 小游戏可以前端限制最高分,若后端收到了比最高分还高的数据,那么直接数据丢弃并上报该账号。
- 一些不太复杂的非实时游戏,可以将游戏过程数据化后在结算时一起上传,然后服务器端进行验证,如果过程不合理也数据丢弃并上报该账号。这个思路在各种交互式的验证码上比较常见,比如说上传鼠标轨迹、用户操作时间间隔。
- 游戏的参与数据由服务器下发,如随机地图、随机种子等,并且配合实时的或结算时的用户操作数据上传,如果用户操作与参与数据的预期情况不一致则同2。
Comments 1 条评论
博主 jimmy
这是一条私密评论