12558网页游戏私服论坛

 找回密码
 立即注册
游戏开服表 申请开服
游戏名称 游戏描述 开服状态 游戏福利 运营商 游戏链接
攻城掠地-仿官 全新玩法,觉醒武将,觉醒技能 每周新区 经典复古版本,长久稳定 进入游戏
巅峰新版攻 攻城掠地公益服 攻城掠地SF 新兵种、新武将(兵种) 进入游戏
攻城掠地公 散人玩家的天堂 新开 进入游戏
改版攻城掠 上线即可国战PK 稳定新区 全新改版,功能强大 进入游戏
少年江山 高福利高爆率 刚开一秒 江湖水落潜蛟龙 进入游戏
太古封魔录 开服送10亿钻石 福利多多 不用充钱也可升级 进入游戏
神魔之道 签到送元宝 稳定开新区 送豪华签到奖励 进入游戏
神奇三国 统帅三军,招揽名将 免费玩新区 激情国战,征战四方 进入游戏
龙符 三日豪礼领到爽 天天开新区 助你征战无双 进入游戏
王者之师 免费领豪华奖励 免费玩新区 6元送6888元宝 进入游戏
查看: 303|回复: 0

植物大战僵尸Hook僵尸CALL--实践&踩坑

[复制链接]

59

主题

59

帖子

128

积分

实习版主

Rank: 7Rank: 7Rank: 7

积分
128
发表于 2022-1-28 20:22:20 | 显示全部楼层 |阅读模式
前言

最近学习了Hook技能,就想找个东西拿来练练实战一下,于是看见了文件夹里的植物大战僵尸,emmm,好,就你了
原来是只是想自己练练,没想写下来的,但无奈实践过程中遇到了坑,害得我调试了一下午,才发现原来是这么基础的问题,害,还是自己基础知识的意识不到位
这里本文将记录一下整个操作的过程,以及代码编写,以及我遇到的坑(没注意函数调用约定....)
这里的目标是:找到召唤僵尸CALL,并且Hook召唤僵尸CALL让僵尸仅出现在第二行
找到僵尸CALL

找僵尸CALL过程很简朴,先说思绪:一局游戏最后是否胜利,在于判定僵尸有没有打完,游戏里肯定有个地方在记录当前出现的僵尸数量,而这个僵尸的数量是在什么时候增加的呢,那必然是在召唤僵尸的时候增加喽(就好像数量是类里的静态变量,僵尸是类的实例,共同访问同一个变量),找到僵尸数量增加的地方,很可能就是僵尸生成的call内部。
接下来开始实操,通过CE搜索当局游戏僵尸数量,找到记录僵尸数量的地址:

这里找到两个地址,一个是全局地址,一个应该是某个类内里的地址,假如这个数量就是某个类的静态变量,那很可能第二个地址的值就是在召唤僵尸call的时候被修改:

点击反汇编,进入反汇编界面,直接在当前指令处下断点,然后观察调用堆栈:

从上往下看,第一个函数从参数来看,很有嫌疑,双击进去,再次下断点:

这里函数调用前push了两个参数和一个值到eax里,刚刚下断点看到的那个调用堆栈的函数的两个参数是0,2,游戏运行起来后,僵尸出现在了第三行:

可以猜测,这里第二个参数就是僵尸出现的行数,召唤僵尸须要的信息除了行数,就是僵尸的种类了(调用call是一次只加1个僵尸数量,以是每次调用只召唤一个僵尸,以是必要召唤数量的参数)
等了一会,断点断下来了:

这里将栈里的两个0都改成1看看:

在第1行(最上面是第0行)出现了种类为1的僵尸(旗子僵尸),猜想精确
然后接下来的问题是给eax的值:27B3F3E8是哪来的?
直接拿这个值去CE搜索:

搜出来数量不多,一个一个看吧,挨个点击右键,是什么访问了这个地址(因为经过多次断下观察,这是个固定的值)其中会找到一个可疑的偏移:

先不管这个代码在干嘛,这里最要紧的是知道这个值是从哪得到的,记下偏移0x868:

取出内里的base:026B9E80再次搜索:

就搜到基址了,这个固定值的位置是:
[[PlantsVsZombies.exe+355E0C]+0x868]添加指针来验证:

找对值了
到此就找到召唤僵尸CALL了,整理一下相干信息:
召唤僵尸CALL地址:PlantsVsZombies.exe+19A60参数1:僵尸类型参数2:僵尸出现位置eax应该是个对象首地址:[[PlantsVsZombies.exe+355E0C]+0x868]接下来开始编写代码调用一下看看
写代码调用僵尸CALL

这里用DLL注入进去比较方便,功能代码如下:
void CPvZHelper::OnBnClickedButton_callOneZombie(){        // 获取模块地址        HANDLE hModule = GetModuleHandleW(L"PlantsVsZombies.exe");        // 获取call地址        DWORD callAddr = (DWORD)hModule + 0x19A60;        // 获取模块+偏移地址(基址)        DWORD moduleBase = (DWORD)hModule + 0x355F6C;        // 设置两个参数,僵尸位置,僵尸类型        srand((int)time(NULL));        DWORD para1ZombiePos = RANDOM(5);        DWORD para2ZombieType = 0;        // 普通僵尸        // 将参数入栈,将固定值给eax,调用call        __asm {                mov eax, para1ZombiePos;                mov ebx, para2ZombieType;                push eax;                push ebx;                mov eax, moduleBase;                mov eax, [eax];                mov ebx, callAddr;                add eax, 0868h;                mov eax, [eax];                call ebx;        }}测试一下,狂点按钮10下:

出现了好多僵尸,测试成功!
Hook僵尸CALL

到这里为止不停都很顺遂,当时我在这里遇到了坑,调试了一下午才发现问题地点,这里跟各人分享一下调的过程
起首是5字节的InlineHook,套路是固定的,网上找即可,这里就不多啰嗦了,这里介绍一下Hook类的函数功能:
class CLHook{public:        CLHook();        // 构造函数        ~CLHook();        // 析构函数        BOOL Hook(PROC funcAddr,PROC hookFuncAddr);        // Hook,第一次Hook把原本字节码都记录下来,下次再Hook就用reHook函数了        VOID unHook();        // 取消Hook        BOOL reHook();        // 重新Hookprivate:        PROC m_pfnOrig;       // 函数地址        BYTE m_oldBytes[5];    // 函数入口代码        BYTE m_newBytes[5];    // Inline代码        BOOL bRet;};接下来是界面复选框点击函数的功能:
void CPvZHelper::OnBnClickedCheck_lockZombiePos(){        // 因为是使用复选框控件来进行操作的,以是必要开启一下这个UpdateData,是从界面上取数据的        UpdateData(TRUE);        // 获取模块地址        HANDLE hModule = GetModuleHandleW(L"PlantsVsZombies.exe");        // 获取CALL地址        DWORD callAddr = (DWORD)hModule + 0x19A60;        // 获取我们自己的CALL的地址        DWORD callAddrHook = (DWORD)myZombieCall;        if (m_lockZombiePos) {                ZombieCallHook.Hook((PROC)callAddr, (PROC)callAddrHook);        }        else {                ZombieCallHook.unHook();        }        UpdateData(FALSE);}然后是我们自己的CALL(出现问题的地方,本函数运行会导致游戏奔溃):
DWORD myZombieCall(DWORD type, DWORD line) {        //为了正常调用僵尸CALL,把修改掉的内容改回来        ZombieCallHook.unHook();        // 获取地址        HANDLE hModule = GetModuleHandleW(L"PlantsVsZombies.exe");        DWORD callAddr = (DWORD)hModule + 0x19A60;        DWORD moduleBase = (DWORD)hModule + 0x355F6C;        // 设置参数        DWORD para1ZombiePos = 1;        DWORD para2ZombieType = type;        DWORD ret = 0;        // 调用CALL        __asm {                mov eax, para1ZombiePos;                mov ebx, para2ZombieType;                push eax;                push ebx;                mov eax, moduleBase;                mov eax, [eax];                mov ebx, callAddr;                add eax, 0868h;                mov eax, [eax];                call ebx;                lea ecx, ret;                mov[ecx], eax;        }        // 再重新Hook        ZombieCallHook.reHook();        return ret;}我们自己的函数跟调用僵尸CALL召僵尸的函数功能差不多一样,区别在于功能开始前后的unHook和reHook,这些问题都不大,看起来没啥问题,就注入DLL去运行,游戏很快就奔溃了,崩溃之前,超高频率在召唤僵尸(奇怪)
我专门对比了一下Hook前后的僵尸CALL执行流程,看起来没啥区别,但就是无限崩溃(崩溃界面就不截图了哈),啥环境啊!!!这小单机游戏另有保护不成?
经过一下午的琢磨,抄起我的ida,发现了问题地点:

这里召唤完僵尸后,会从栈里取个值,就叫他varA好了,第一次取值的时候一定是取到0,然后在这里+1后,跳转走:

跳走之后,会取出刚刚栈里的那个值varA,作为索引去一个地址寻找FFFFFFFF,如果没找到,就再来一遍召唤僵尸并且给varA+=1,然后再次索引找值
下断点后,正常环境下来说varA的值是从0开始,然后基本上很快就跳出这个循环了:

而我Hook了之后栈里获取的值变成了A:

从A开始遍历,这就会循环很多很多次都挑不出,然后游戏连续召唤僵尸,然后就奔溃了

不难发现问题的地点,Hook后,函数调用完,栈的位置不对,压入的两个参数提高了栈顶,但没有给加(add esp,8)回来,无脑在Hook函数里加了add esp,8之后发现没用,忽然意识到了!!!
Cpp默认是__cdecl,是调用者来平栈,这个游戏的调用者没有来平栈,那大概率是在函数内平栈了,那就是__stdcall了,函数必要声明为这个函数调用约定才行!
经过一番修改:
DWORD __stdcall myZombieCall(DWORD type, DWORD line) {游戏正常运行了,这么简朴的问题折腾一下午。。。。
总结

最后说两句,调试了一下午,我做了的那些事(还是自己见识太少思绪太少)
当时调试了一下午,我先后对比了CALL内部的执行流程,看有没有啥区别,无果,
当时看召唤了这么多僵尸,比正常环境下多,我以为除了这个地方另有其他地方调用这个CALL,我就把这个地方的CALL地址改了,然后把Hook地址提前了5字节,这样一来,我以为就会正常了,结果还是召唤出好多僵尸,无果。。。
最后才对比召唤CALL调用位置前后的区别,发现从栈里取出来的值不一样,才发现问题地点
原来中途都差点想放弃了,还好对峙下来了,有时候真就是离目标很靠近了的时候放弃的想法很大。

来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
楼主热帖
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|12558网页游戏私服论坛 |网站地图

GMT+8, 2024-4-26 01:11 , Processed in 0.093750 second(s), 31 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

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