鼠标屏幕取词技术的原理和实现
白瑜
“鼠标屏幕取词”技术是在电子字典中得到广泛地应用的,如四通利方和金山
词霸等软件,这个技术看似简单,其实在windows系统中实现却是非常复杂的,总
的来说有两种实现方式:
第一种:采用截获对部分gdi的api调用来实现,如textout,textouta等。
第二种:对每个设备上下文(dc)做一分copy,并跟踪所有修改上下文(dc)的
操作。
第二种方法更强大,但兼容性不好,而第一种方法使用的截获windowsapi的
调用,这项技术的强大可能远远超出了您的想象,毫不夸张的说,利用windowsapi
拦截技术,你可以改造整个操作系统,事实上很多外挂式windows中文平台就是
这么实现的!而这项技术也正是这篇文章的主题。
截windowsapi的调用,具体的说来也可以分为两种方法:
第一种方法通过直接改写winapi 在内存中的映像,嵌入汇编代码,使之被
调用时跳转到指定的地址运行来截获;第二种方法则改写iat(import address table
输入地址表),重定向winapi函数的调用来实现对winapi的截获。
第一种方法的实现较为繁琐,而且在win95、98下面更有难度,这是因为虽
然微软说win16的api只是为了兼容性才保留下来,
程序员应该尽可能地调用32位
的api,实际上根本就不是这样!Win 9x内部的大部分32位api经过变换调用了同
名的16位api,也就是说我们需要在拦截的函数中嵌入16位汇编代码!
我们将要介绍的是第二种拦截方法,这种方法在win95、98和nt下面运行都
比较稳定,兼容性较好。由于需要用到关于windows虚拟内存的管理、打破进程
边界墙、向应用
程序的进程空间中注入代码、pe(portable executable)文件
格式和iat(输入地址表)等较底层的知识,所以我们先对涉及到的这些知识大
概地做一个介绍,最后会给出拦截部分的关键代码。
先说windows虚拟内存的管理。Windows9x给每一个进程分配了4gb的地址空
间,对于nt来说,这个数字是2gb,系统保留了2gb到 4gb之间的地址空间禁止进
程访问,而在win9x中,2gb到4gb这部分虚拟地址空间实际上是由所有的win32进
程所共享的,这部分地址空间加载了共享win32 dll、内存映射文件和vxd、内存
管理器和文件系统码,win9x中这部分对于每一个进程都是可见的,这也是win9x
操作系统不够健壮的原因。Win9x中为16位操作系统保留了0到4mb的地址空间,
而在4mb到2gb之间也就是win32进程私有的地址空间,由于 每个进程的地址空间
都是相对独立的,也就是说,如果
程序想截获其它进程中的api调用,就必须打
破进程边界墙,向其它的进程中注入截获api调用的代码,这项工作我们交给钩
子函数(setwindowshookex)来完成,关于如何创建一个包含系统钩子的动态链
接库,《电脑高手杂志》在第?期已经有过专题介绍了,这里就不赘述了。所有
系统钩子的函数必须要在动态库里,这样的话,当进程隐式或显式调用一个动态
库里的函数时,系统会把这个动态库映射到这个进程的虚拟地址空间里,这使得
dll成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈,也就是
说动态
链接库中的代码被钩子函数注入了其它gui进程的地址空间(非gui进程,
钩子函数就无能为力了),当包含钩子的dll注入其它进程后,就可以取得映射
到这个进程虚拟内存里的各个模块(exe和dll)的基地址,如:
hmodule hmodule=getmodulehandle(“mypro.exe”);
在mfc
程序中,我们可以用afxgetinstancehandle()函数来得到模块的基地址。
Exe和dll被映射到虚拟内存空间的什么地方是由它们的基地址决定的。它们的基
地址是在链接时由链接器决定的。当你新建一个win32工程时,vc++链接器使
用缺省的基地址0x00400000。可以通过链接器的base选项改变模块的基地址。
Exe通常被映射到虚拟内存的0x00400000处,dll也随之有不同的基地址,通常被
映射到不同进程的相同的虚拟地址空间