|
最近放假,搞得鸡血了,作息时间无规律,这么早就起来了,一会儿估计又想睡觉。不知道搞什么写点心得吧,高手勿喷,同时希望有兴趣的可以探讨下去。
在新生物插件中给生物解决自定义箭矢文件问题的。
由于h3以及早期的wog没有考虑太多的设计以外的生物的问题(这是时代背景和开发背景决定的,并非是设计不周密)。在处理箭矢的时候使用一个switch结构,类似于如下结构
switch(生物ID)
case a,b,c,d:
箭矢="yy.def"
case e,f,g,h:
箭矢="zz.def"
如果都没找到,返回了个默认的箭矢文件。就是说有自己箭矢的生物,是“写死”在代码里的。这个结构紧接着调用了一个函数,姑且叫函数A
箭矢保存在ecx中作为函数A的隐含参数,斗胆猜测这是个fastcall调用约定的函数,当然这个不是重点。
就是说只要在调用函数A前改变ecx中保存的箭矢文件指针,就可以自定义了。思路有了,于是作者用inline hook把这段调用函数A的代码,改成了调用自己的函数B
函数B修改ecx后跳转回函数A执行,具体的实现网上有不少例子不多说。
hook的汇编代码并不多,但是如果要完成一些复杂的功能,比如读取配置文件来选择对这些生物的处理方式,都会造成堆栈结构的破坏和寄存器取值的变化。极容易引起崩溃。因为我们是“硬塞”代码进去,而且我们在编译自己的DLL时还会被编译器进行优化,我们来一步步解决这些问题。
1.得到想要的汇编代码以及他的地址。
先上代码
LPVOID GetAsmHandlerPtr()
{
LPVOID pRet = NULL;
__asm
{
lea eax, __label_code
mov pRet, eax
jmp __label_ret
}
__asm
{
__label_code:
[跳转后要执行的代码]
}
__asm {__label_ret: }
return pRet;
}
上面的函数第一个asm段取标签__label_code的地址,就是我们真正要得asm的代码的地址,然后返回则个地址。从jmp __label_ret知道我们调用这个函数只会返回地址而执行不到__label_code里面的汇编代码,这样变相的避开函数优化得到一个纯粹的汇编代码段
2.完美解决寄存器和堆栈“污染”
当然这里说的“完美”其实是我自卖自夸的,因为没有解决标志位寄存器这些,不过在这里没有影响就是了。
首先是堆栈指针esp ebp
我们的目标是调用一个自己的C++函数,在这个函数里面很舒服的写一眼就能看得懂的c/c++代码。要达成这目标就必须要用到堆栈,然而堆栈是属于游戏线程的,这里就要用到我提出的“伪堆栈”。简单的说,我在dll中定义一个大小为1M的全局的字符数组变量,因为编译后在变量区,是可读写的,本身内存页属性就和堆栈类似。
要注意的问题是这个变量地址是从低地址向高地址增长的,就是在虚拟内存里面字符abc, b字符的内存地址比a字符大。而堆栈正好相反,是从高地址向低地址“增长”的,比如我们函数调用时压入参数esp反而变小了。这个问题立即就可以了,实际上系统并不知道自己在使用假堆栈,嘿嘿。
具体来实现就是定义全局变量dwESP, dwEBP用来保存原来的esp ebp,那么我们上面提到的__label_code里面的汇编代码就要开始做这些工作
//保存堆栈寄存器,注意这个时候堆栈还在游戏线程堆栈,这时的操作不影响堆栈指针
mov dwESP, esp
mov dwEBP, ebp
//取出伪堆栈的地址给esp,注意此后堆栈变化了,以后的函数调用会使用这个伪堆栈
lea esp, FACK_STACK
//上面提到增长方式的不同,我们给esp加上0x20000来使esp位于这个伪堆栈的里面
add esp, 0x20000
//一般的,把ebp也移动过来
mov ebp, esp
sub esp,0x200
//现在准备就绪,要保存其余的寄存器,用pushad全部压栈就可以了,注意这时候ecx(我们要修改的寄存器)也在第二个被压入堆栈
pushad
//下面和游戏场景有关系,这里原本的函数使用ebx-一些数据结构,在0x34偏移保存有正在处理的生物ID,ecx为上面switch下来的箭矢字符串指针
//这也是我们需要的,于是我们针对这个定义一个函数LPTSTR WINAPI 处理函数(LPTSTR 参数1,LPDWORD 参数2)
//原因是我们这里要在汇编里面调用他,定义WINAPI子函数会自己处理我们的压栈参数对堆栈的影响,比较方便,推荐使用。
//函数在调用后会按照我们的安排把寄存器的值给我们的参数,我们可以很方便的使用我们要得数据,这也算一个小技巧
push ebx //参数2
push ecx //参数1
call 处理函数
//简单的,我们写个配置文件定义好枪兵的箭矢文件是pikeshot.def,在处理函数里面发现时枪兵就返回pikeshot.def的指针
//函数调用过后 eax保存有这个指针,此时堆栈(esp)已经平衡到我们pushad时候的值,还记得之前我们保存所有寄存器的值
//其中第二个压入的是ecx,那么此时[esp+0x18]就是保存原来ecx值得地方,我们要用eax改写它
mov [esp+0x18], eax
//新生物插件的作者在这里把原本游戏调用的函数地址保存的eax然后jmp跳到eax
//这样变相的改变的eax的值,在这里当然是没有问题,不过我得对得起帖子标题,我们把它保存到FACK_STACK里面去,反正可以用的空间很大
lea [eax], FACK_STACK
mov dword ptr [eax], 游戏原函数地址
//恢复所有寄存器,注意恢复后相当于只有ecx被我们偷偷改写了,除了esp ebp都恢复了进入这段代码时的状态
popad
//恢复ebp, esp,离开“伪堆栈”,还记得之前我们保存的值吗
mov esp, dwESP
mov ebp, dwEBP
//至此,完全恢复了游戏原函数调用前的寄存器和堆栈状态,现在jmp到原来调用的函数
//这里有个细节,就是为什么不用call,因为call会压入函数调用后要执行的语句的地址入栈,而这个工作在进入这段代码前就做了。
//还记得我们是怎么来的吗,也是一个call指令啊
jmp dword ptr [FACK_STACK]
这样就完成了一次不着痕迹的代码hook,非常安全,环保。
呵呵,到这里了,高手笑过罢了。
[ 本帖最后由 梦魇骑士 于 2013-5-4 07:14 编辑 ] |
评分
-
3
查看全部评分
-
|