|
前言
最近学习了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
|