屏幕取词技术实现原理与关键源码

 

                                            屏幕取词

    虽然屏幕取词技术早已经不是什么秘密,以至于除了汉化工具、翻译工具、中文平台等等这些东西之外,连像SnagIt这样的抓图软件也能把抓取屏幕文本的功能做得像模像样,但金山词霸的取词技术就细节而言还是有着众多的独特之处,所以,作为在金山词霸组工作期间的一点积累,我最终还是决定把有关的一些东西写出来,这样也作为直到2006年为止金山词霸取词技术的一个比较稳定版本的记录。

    单机版的金山词霸很难再出什么新花样了,这是在现实的环境下一个通用软件产品的生存期规律决定的,随之而来的,单机版金山词霸的结构和技术也基本不会有什么大变动了,这其中也包括屏幕取词——虽然词霸组从05年开始就一直想对当时的屏幕取词方式进行升级以适应越来越苛刻的系统安全要求,不过后来由于种种原因一直没有能够实施。

    金山词霸的屏幕取词技术是一种基于Win32API的,只能应用于客户端的偏底层操作技术,在这个互联网的时代,在追求注意力,追求现实效益的行业大环境下,金山词霸的取词技术不容易再有什么比较大的发展了,短期之内其应用也仅限于一些需要此功能的小型客户端程序(如词霸豆豆)以及作为OCX插件来支持B/S结构产品的用户体验提升。至于Windows Vista出来之后在Avalon和GDI+模式下的技术更新,则不是我现在能够预料得到的了,其可行性将在后面稍作讨论。

    好了,说了这么多废话,也该进入正题了,不过在此之前要申明的一点是:本文所涉及的所有细节技术和方法,都是行业内所共知或者从业者通过正规方式能够获知和了解的,而宏观的思路和逻辑也是具有相当技术水平的软件开发人员通过思考能够获得的;因此本文不会侵犯到金山公司的商业机密和知识产权,也不会违反本人与金山公司之前签署的保密协定。实际上我并不是金山词霸取词技术的主要开发人,所以即便我有心说一些什么也无法触及比较秘密的细节内容,呵呵。仅此。

    之前有不少文章来讨论或者“揭密”金山词霸的取词技术,似乎这样一种技术瞬间从神秘无比就变成了一层窗户纸,不过在接触了实际的代码之后,我想要说的是,这是一种十分正常的软件开发技术,这样一种技术的开发、积累和完善,同许多其他技术一样也是由简而繁,从基础的思路到最终的产品一步步走过来的;那种以为只要懂得了API Hook就了解了屏幕取词的全部技术的想法是有偏差的。

    API Hook是一种常规的核心编程技术,其基础的实现方式和思路请参照《Windows核心编程》的第22章——顺带说一下,这本书是所有触及Windows底层应用的程序开发人员应该储备的工具书之一。

    先说说屏幕取词的基本设计思路。

    对Windows编程有所了解的的人都知道,Windows为每个进程分配了2GB的虚地址空间,并使用了一系列的措施来保证每个进程各行其道,不会互相影响——这点就比Linux要好一些,那些说Linux安全性比Windows要高的人很多时候并不知道——原则上进程间的信息交互只能由相互信任的进程采用约定的方法——比如消息传递、共享内存、内存映射文件、Socket(Network),甚至磁盘文件系统等等;但是屏幕取词的要求本质上是要取得一个未知进程里的某个特别操作的执行数据,那么,在没有标准方法来执行这一点的时候,我们要想办法将位置的进程编程与我们的取数进程相互信任并且已经约定好数据交互方法的进程——目前看来比较现实的方法,或者说唯一的方法,是让目标进程执行我们设计好的代码,这样,我们的代码取得宿主进程的执行权限,并了解如何把数据传递给我们的取词进程,如果再能够获得特定操作时的数据(例如TextOut),我们的架构就完整了。

    对于第一个需求,金山词霸的操作简单的就是几个函数的序列:WriteProcessMemory,CreateRemoteThread,ReadProcessMemory。这是我之前提到几种方法之一的变形;对于第二个需求,插入进去的代码会修改程序的运行指令,将需要获得其操作数据的函数地址强行更改为我们自己编写的具有相同形式定义的函数,在我们的函数处理完成之后,再调用原本应该处理那些数据的函数去执行,而我们则可以通过事先约定好的方法得到操作数据的一个副本。修改原本函数的执行地址的方法,我们称为挂接,其表现形式类似于插入一个函数调用。

    实际上这种方法很像原先在Windows 9X上使用的外壳DLL的处理方式,有一些程序出于各种目的(有些甚至是为了增强系统安全,但实际上利用了系统的不安全隐患)将系统DLL替换成自己的DLL文件,并将原来的系统DLL改名,然后在自己的DLL文件中模拟出系统DLL的所有接口,这样程序调用系统接口的时候自然就会把数据传到新的DLL中去,新DLL处理完成后再以同样的数据去调用那个被改了名的系统DLL中的对应接口。不过由于Win2000内核的逐渐兴起,这种方法由于适应性差,工作量大,问题比较多而逐渐被废弃了。现在使用这个办法的程序大多只替换一些用户级的DLL库,干得一般也不是什么上得了台面的事情。

    剩下来的就是一些细枝末节的问题,但却是比较麻烦的地方。

    1、取到需要的数据。并不是所有的目标程序都使用TextOut进行文本输出,相当多的程序使用自己的缓存DC来进行文本显示,对于自绘缓存的情况,原则上来说任何方法都不可能覆盖所有的可能,特别是对于那些带有排版、阅读甚至权限控制功能的程序。简单的对文本输出函数的挂接常常会得到多到无法筛选处理的数据,要么就是根本监测不到函数调用。对于这种情况,无法绕开的解决办法是监视所有可能用于绘制的函数调用,并保存所有可能用于绘制的数据,然后根据目标进程的操作来智能判断有效数据,比如在预计目标进程进行屏幕输出的时候,监测到一些内存DC的文本绘制操作,接着又监测到屏幕DC的一些BitBlt之类的缓存覆盖操作,则要判断当前取词位置的屏幕DC被哪个内存DC所占有的缓冲区覆盖了,然后看看这个缓冲区之前曾经输出过哪些文本数据,如此等等。数据筛选的另外一个问题是定位,知道用户的鼠标位置处于取到的数据中那一个字符之上是很重要的,是后期的单词匹配和模式分析所不可缺少的。可惜的是GDI32并没有提供方便的方法来搞定这件事情,我们只能用一些间接的办法来实现,比如先获得字体,再执行模拟排版,这是个很麻烦的事情,对于各种字符的处理都要和GDI32完全一致。

    2、挂接代码的执行、数据交换。由于是将代码注入到目标进程去执行,无形中就增加了许多限制。函数地址的计算是个比较大的问题,所有自定义的函数地址都要从一个易于通过系统标准方法获得的基准地址计算偏移量来获得,调用任何一个函数的时候都要明确的意识到在目标进程执行的情况,如此等等。而且,随着对系统安全性越来越高的要求,这种使用WriteProcessMemory进行代码注入的方式也逐渐暴露出来一些问题,例如在DEP环境下无法执行数据段代码的问题,取词时屏幕闪烁的问题,还有某些杀毒软件对可能造成系统危险的进程间操作进行屏蔽和报警的问题。金山词霸组曾经有一段时间考虑过使用适应性更好的DLL注入方式来替换掉挂接模块,但由于种种原因而没有实现。同时,对于一些比较复杂的数据对象,有时并不是很容易取到其内部的数据,这样就往往要辗转几次才能迂回的完成任务,有时甚至需要修改系统文件定义才能取到Private成员这样的东西。

    3、现场清理、与其它挂接的兼容性。对于挂接API这样一种搭车行为,做完要做的事情之后最好是能够不留痕迹的清理好现场,这既是出于系统执行效率和资源消耗的考虑,也是为了系统安全的

  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
屏幕取词程序VC源码 nhw32.dll 主要引出两个函数: 1. DWORD WINAPI BL_SetFlag32(UINTnFlag, HWND hNotifyWnd, int MouseX, int MouseY) 功能: 启动或停止取词。 参数: nFlag [输入] 指定下列值之一: GETWORD_ENABLE: 开始取词。在重画被取单词区域前设置此标志。 nhw32.dll是通过 重画单词区域,截取TextOutA, TextOutW,ExtTextOutA, ExtTextOutW等Windows API函数的参数来取词的。 GETWORD_DISABLE: 停止取词。 hNotifyWnd [输入] 通知窗口句柄。当取到此时,向该通知窗口发送一登记消息: GWMSG_GETWORDOK。 MouseX [输入] 指定取词点的X坐标。 MouseY [输入] 指定取词点的Y坐标。 返回值: 可忽略。 2. DWORD WINAPI BL_GetText32(LPSTRlpszCurWord, int nBufferSize, LPRECT lpWordRect) 功能: 从内部缓冲区取出单词文本串。对英语文本,该函数最长取出一行内以 空格为界的三个英文单词串,遇空格,非英文字母及除‘-’外的标点符 号,则终止取词。对汉字文本,该函数最长取出一行汉字串,遇英语字 母,标点符号等非汉语字符,则终止取词。该函数不能同时取出英语和 汉语字符。 参数: lpszCurWord [输入] 目的缓冲区指针。 nBufferSize [输入] 目的缓冲区大小。 lpWordRect [输出] 指向 RECT 结构的指针。该结构定义了被取单词所在矩形区域。 返回值: 当前光标在全部词中的位置。 此外,WinNT/2000版nhw32.dll 还引出另两个函数: 1. BOOL WINAPI SetNHW32() 功能: Win NT/2000 环境下的初始化函数。一般在程序开始时,调用一次。 参数: 无。 返回值: 如果成功 TRUE ,失败 FALSE 。 2. BOOL WINAPI ResetNHW32() 功能: Win NT/2000 环境下的去初始化函数。一般在程序结束时调用。 参数: 无。 返回值: 如果成功 TRUE ,失败 FALSE

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值