最近发现金山词霸屏幕取词功能导致连连看在ET内执行的时候堆栈溢出
而连连看单机版则无此问题。郁闷异常,分析金山词霸的取词功能如下:
1屏幕抓词
屏幕抓词(或者叫动态翻译)是指随着鼠标的移动,软件能够随时获知屏幕上鼠标位置的单词或汉字,并翻译出来提示用户。它对於上网浏览、在线阅读外文文章等很有帮助作用,因此许多词典软件都提供了屏幕抓词功能。
屏幕抓词的关键是如何获得鼠标位置的字符串,Windows的动态链接和消息响应机制为之提供了实现途径。 概括地说,主要通过下面的几个步骤来取得屏幕上鼠标位置的字符串:
. 代码拦截:Windows以DLL方式提供系统服务,可以方便地获取Windows字符输出API的地址,修改其入口代码,拦截应用程序对它们的调用。
. 鼠标HOOK:安装WH—MOUSEPROC类型的全局鼠标HOOK过程,监视鼠标在整个屏幕上的移动。
. 屏幕刷新:使鼠标周围一块区域无效,并强制鼠标位置的窗口刷新屏幕输出。窗口过程响应WM—NCPAINT和WM—PAINT消息,调用ExtTextOut/TextOut等字符输出API更新无效区域里面的字符串。这些调用被我们截获,从堆栈里取得窗口过程传给字符API的参数,如字符串地址、长度、输出坐标、HDC、裁剪区等信息。
2 取词的过程,
0 判断鼠标是否在一个地方停留了一段时间
1 取得鼠标当前位置
2 以鼠标位置为中心生成一个矩形
3 挂上API钩子
4 让这个矩形产生重画消息
5 在钩子里等输出字符
6 计算鼠标在哪个单词上面,把这个单词保存下来
7 如果得到单词则摘掉API钩子,在一段时间后,无论是否得到单词都摘掉API钩子
8 用单词查词库,显示解释框。
3 出错原因(转)
开着金山词霸浏览网页的时候经常就会浏览器自动关闭,实在烦人。刚才又遇到,就稍微分析了一下,觉得因为我们也经常使用钩子,可能会有借鉴,就把分析结果稍微写写,让大家以后编程少犯这样的错误。
翻译软件等关键技术之一就是拦截操作系统的显示函数,从中抓到显示的单词,这点大家都明白不用再多说。比如金山词霸会拦截系统库GDI32.DLL的输出函数ExtTextOutA等,拦截方法是找到这个函数地址,在这入口填写一个JMP 指令,跳转到金山词霸的一个入口,当然其也保存了这个入口开始被JMP指令覆盖的几个字节。金山词霸的这个入口里面又要用这个函数,所以其得到控制权后又恢复了ExtTextOutA这个函数开始的指令,进行调用。这种不好的钩子处理办法就埋下了祸根。可能是其内部信号标记等处理不好,某种情况下钩子想调用恢复后的ExtTextOutA函数的时候,但可能没有恢复成功,就又进入了金山词霸的钩子,这样就导致循环了,堆栈迅速消耗,最终因为堆栈消耗完再进行堆栈操作的时候出现非法操作而导致程序关闭退出。
这就告诉我们如果钩子里面要调用要钩的函数本身,最好要错过钩子。比如在函数入口用写JMP指令做钩子,那么再调用的时候最好调用其下面的哪个入口,或者钩子种在上面调用这个函数的指令那。要不就要保证恢复入口指令一定正确。再就是注意重入问题,就是钩子里面再调用别的函数的时候可能别的函数会用到钩子钩的函数,那你就得保证你的代码是可重入的。其实写过病毒的人恐怕都或多或少接触了重入问题。
程序1: call dword ptr [ExtTextOutA]
要钩的函数:
GDI32.DLL
ExtTextOutA:
push ebp
...
call ExtTextOutW
....
ret
假设环境如上面,钩子方案可以:
1、改写程序1的call dword ptr [ExtTextOutA]中[]内存的值,指向钩子,钩子里面要再用到ExtTextOutA的话正常调用GDI32.DLL中的这个函数就是了。这个方法缺点可能就是要修改地方多。
2、修改GDI32.DLL的函数ExtTextOutA开始指令,指向钩子,但钩子里面再调用这个函数就用里层的ExtTextOutW(这儿是假设的,我也不知道这里面有什么接口)。
3、就是金山词霸用的修改开始指令,要用的时候就恢复开始指令后调用。
(2、3两点有个不好就是要钩的函数开始一定要有几个这个函数体的指令,这几个指令这儿还不能是别的什么函数入口,这点对一般函数没问题,对于一些特殊函数恐怕就不行了)。
4、修改GDI32.DLL的函数引出表的ExtTextOutA的值,指向钩子,这样加载别的DLL的时候用到此函数操作系统就会自动把钩子地址填写,这样就不用程序操心去改写第1个方法要改写的地方了。缺点嘛就是先加载了的DLL无效,再就是恢复麻烦。
5、......
大家可以综合考虑使用自己喜欢的。