屏幕取词编程学习总结

来源:互联网 发布:知乎为什么垃圾 编辑:程序博客网 时间:2024/06/05 10:58

屏幕取词的研究


现在词典市场金山词霸占了绝对优势,所以再做字典也没什么前途了。我就是这么认为的,所以我虽然

掌握了这项技术,却没去做字典软件。只做了一个和词霸相似的软件自己用,本来想拿出来做共享软件

,但我的词库是“偷”来的,而且词汇不多,所以也就算了,词库太小,只能取词有什么用呢?而且词

霸有共享版的。但既然很多人想了解这项技术,我也不会保留。我准备分多次讲述这项技术的所有细节

。大约每周一两次。想知道的人就常常来看看吧!一.基础知识首先想编这种程序需要一些基础知识。会

用Vc++,包括16/32位。精通Windows API特别是GDI,KERNEL部分。 懂汇编语言,会用softice调试程序

,因为这种程序最好用softice调试。二.基本原理在Window 3.x时代,windows系统提供的字符输出函数

只有很少的几个。 TextOutExtTextOutDrawText......其中DrawText最终是用ExtTextOut实现的。所以

Windows的所有字符输出都是由调用TextOut和ExtTextOut实现的。因此,如果你可以修改这两个函数的

入口,让程序先调用你自己的一个函数再调用系统的字符输出,你就可以得到Windows所有输出的字符了

。到了Windows95时代,原理基本没变,但是95比3.x要复杂。开始的时候,一些在windows3.x下编写的

取词软件仍然可以是使用。但是后来出了个IE4,结果很多词典软件就因为不支持IE4而被淘汰了,但同

时也给一些软件创造了机会,如金山词霸。其实IE4的问题并不复杂,只不过它的输出的是unicode字符

,是用TextOutW和ExtTextOutW输出的。知道了这一点,只要也截取就可以了。不过实现方法复杂一点,

以后会有详细讲解。现在又出了个IE5,结果词霸也不好用了,微软真是#^@#$%$*&^&#@#@..........我

研究后找到了一种解决办法,但还有些问题,有时会取错,正在继续研究,希望大家共同探讨。另外还

有WindowsNT,原理也是一样,只是实现方法和95下完全不同。三.技术要点要实现取词,主要要解决以

下技术问题。1.截取API入口,获得API的参数。2.安全地潜入Windows内部,良好地兼容Windows的各个

版本3.计算鼠标所在的单词和字母。4.如果你在Window95下,做32位程序,还涉及Windows32/16混合编

程的技术。今天先到这里吧!最好准备一份softice for 95/98和金山词霸,让我们先来分析一下别人是

怎么做的。 欢迎与我联系E-Mail:yeedong@163.netGuest  1999-04-30 16:00:48 请问用VC自己的

DEBUGGER不行吗?为什么要用SOFTICE? 我没用过SOFTICE,它有什么特别之处吗? 葫芦  1999-04-30 

19:15:03 本人对这个问题也有兴趣,以前研究过16位版本截获TextOut和ExtTextOut的过程;但对金山

词霸,用Softice跟踪,发现SetWindowsHookEx在程序装载时就安装了鼠标钩子,暂停取字/恢复取字只

是设置的内部变量,但本人发现金山词霸并没有象16位版本那样修改TextOut和ExtTextOut。苍蝇 

(555021552)  1999-05-02 08:56:57 有哪位大虾愿意先介绍一下SOFTICE?蟑螂  1999-05-04 

13:58:22 把金山词霸的cjktl95.dll用tdump分析可以看到它根本用的不是hook,而是用了一些

kernel32.dll中的Win32 SDK里没有包含的函数,使用这些函数来替换改写原先的几个GDI函数。看来是

Win95未公开的一些东西。有些人知道,为什么不肯说出来呢?未必见得多么高深! 葫芦  1999-05-05 

23:28:07 你可能没有研究过它的目标代码,怎么能断言没有使用hook呢? 其实金山词霸一运行就安装

了鼠标钩子。 亦东  1999-05-07 09:52:42 未必见得多么高深?你知道有些人是怎么知道这些Win95未

公开的东西的吗?你不会是以为微软偷偷告诉他们的吧?其实他们大多和我一样是用softice自己跟踪

Win95跟出来的。还有你从cjktl95.dll里tdump出的未公开函数和hook无关,那是做别的用的。

setwindowshook在user里亦东  1999-05-10 16:16:14 从cjktl95.dll里tdump出的未公开函数和32位和

16位之间的互调有关。主题  屏幕取词技术系列讲座(二) 作者   亦东 很抱歉让大家久等了!我看

了一些人的回帖,发现很多人对取词的原理还是不太清楚。首先我来解释一下hook问题。词霸中的确用

到了hook,而且他用了两种hook其中一种是Windows标准hook,通过SetWindowHook安装一个回调函数,它

安装了一个鼠标hook,是为了可以及时响应鼠标的消息用的和取词没太大关系。另一种钩子是API钩子,

这才是取词的核心技术所在。他在TextOut等函数的开头写了一个jmp语句,跳转到自己的代码里。你用

softice看不到这个跳转语句是因为它只在取词的一瞬间才存在,平时是没有的。你可以在TextOut开头

设一个读写断点bpm textout 再取词,就会找到词霸用来写钩子的代码

了。/**********************************所以我在次强调,想学这种技术一定要懂汇编语言和熟练使

用softice.**********************************/至于从cjktl95中dump出来的未公开函数是和

Windows32/16混合编程有关的,以后我会提到他们。我先来讲述取词的过程,0 判断鼠标是否在一个地

方停留了一段时间 1 取得鼠标当前位置 2 以鼠标位置为中心生成一个矩形 3 挂上API钩子 4 让这个矩

形产生重画消息 5 在钩子里等输出字符 6 计算鼠标在哪个单词上面,把这个单词保存下来 7 如果得到

单词则摘掉API钩子,在一段时间后,无论是否得到单词都摘掉API钩子 8 用单词查词库,显示解释框。 

很多步骤实现起来都有一些难度,所以在中国可以做一个完善的取词词典的人屈指可数。其中0,1,2,7,8

比较简单就不提了。先说如何挂钩子:所谓钩子其实就是在WindowsAPI入口写一个JMP XXXX:XXXX语句,

跳转到自己的代码里。 步骤如下:1.取得Windows API入口,用GetProcAddress实现 2.保存API入口的

前五个字节,因为JMP是0xEA,地址是4个字节3.写入跳转语句这步最复杂Windows的代码段本来是不可以

写的,但是Microsoft给自己留了个后门。有一个未公开函数是AllocCsToDsAlias,UINT WINAPI 

ALLOCCSTODSALIAS(UINT); 你可以取到这个函数的入口,把API的代码段的选择符(要是不知道什么是选

择符,就先去学学保护模式编程吧)传给他,他会返回一个可写的数据段选择符。这个选择符用完要释

放的。用新选择符和API入口的偏移量合成一个指针就可以写windows的代码段了。这就是取词技术的最

核心的东东,不止取词,连外挂中文平台全屏汉化都是使用的这种技术。现在知道为什么这么简单的几

句话却很少知道了吧?因为太多的产品使用他,太多的公司靠他赚钱了。这些公司和产品有:中文之星

,四通利方,南极星,金山词霸,实达铭泰的东方快车,roboword,译典通,即时汉化专家等等等等。。

。。还有至少20多家小公司。他们的具体实现虽然不同,但大致原理是相同的。我这些都是随手写的,

也没有提纲之类的东西,以后如果有机会我会整理一下,大家先凑合着看吧!xixi...葫芦  1999-05-

06 14:52:30 你说的这个技术是16位的,至少金山词霸III没有采用16位的AllocCsToDsAlias函数,也根本

没有修改TextOut开始的语句为JMP XXXXXXXX。softice本人非常精通,是绝对不会错的! 葫芦  1999

-05-06 15:38:40 谁假冒吾名,坏吾名声?!本人在此郑重宣布,以上贴子非本人所发!经过跟踪分析

,金山词霸III确实修改了TextOut的入口为转跳到自身代码的JMP XXXXXXXX语句,只是修改没有调用

AllocCsToDsAlias,也不是在取字时才去修改,而是常常修改(估计是在Timer中进行的)。

接上帖:nn_zdm (555031742)  1999-05-18 14:14:19 上面写漏了,应是WriteProcessMemory

(...,"LoadLibrary(...,"mydll.dll",..)."); CreateRemoteThread(...) ; 主题  关于屏幕取词的讨

论(三) 作者   亦东 让大家久等,很抱歉,前些时候工作忙硬盘又坏了,太不幸了。这回来点真格的

。咱们以截取TextOut为例。下面是代码://截取TextOuttypedef UINT (WINAPI* ALLOCCSTODSALIAS)

(UINT); ALLOCCSTODSALIAS AllocCsToDsAlias; BYTE NewValue[5];//保存新的入口代码 BYTE 

OldValue[5];//API原来的入口代码 unsigned char * Address=NULL;//可写的API入口地址 UINT 

DsSelector=NULL;//指向API入口的可写的选择符 WORD OffSetEntry=NULL;//API的偏移量 BOOL 

bHookAlready = FALSE; //是否挂钩子的标志 BOOL InitHook() {HMODULE hKernel,hGdi; hKernel = 

GetModuleHandle("Kernel"); if(hKernel==NULL)return FALSE; AllocCsToDsAlias = 

(ALLOCCSTODSALIAS)GetProcAddress(hKernel,"AllocCsToDsAlias");//这是未公开的API所以要这样取

地址 if(AllocCsToDsAlias==NULL)return FALSE; hGdi = GetModuleHandle("Gdi"); if

(hmGdi==NULL)return FALSE; FARPROC Entry = GetProcAddress(hGdi,"TextOut"); if(Entry==NULL)

return FALSE; OffSetEntry = (WORD)(FP_OFF(Entry));//取得API代码段的选择符 DsSelector = 

AllocCsToDsAlias(FP_SEG(Entry));//分配一个等同的可写的选择符 Address = (unsigned char*)

MK_FP(DsSelector,OffSetEntry);//合成地址 NewValue[0]=0xEA;*((DWORD*)(NewValue+1)) = 

(DWORD)MyTextOut; OldValue[0]=Address[0];*((DWORD*)(OldValue+1)) = *((DWORD*)(Address+1)); 

}BOOL ClearHook() {if(bHookAlready)HookOff();FreeSelector(DsSelector);}BOOL HookOn() {if(!

bHookAlready){for(int i=0;i <5;i++){ Address[i]=NewValue[i];}bHookAlready=TRUE;}}BOOL 

HookOff() {if(bHookAlready){for(int i=0;i <5;i++){ Address[i]=OldValue[i];}

bHookAlready=FALSE;}}//钩子函数,一定要和API有相同的参数和声明BOOL WINAPI MyTextOut(HDC 

hdc,int nXStart,int nYStart,LPCSTR lpszString,UINT cbString) {BOOL ret; HookOff();ret = 

TextOut(hdc,nXStart,nYStart,lpszString,cbString);//调原来的TextOut HookOn();return ret; }上

面的代码是一个最简单的挂API钩子的例子,我要提醒大家的是,这段代码是我凭记忆写的,我以前的代

码丢了,我没有编译测试过因为我没有VC++1.52.所以代码可能会有错。建议使用Borland c++,按16位

编译。 如果用VC++1.52,则要改个选项在VC++1.52的Option里,有个内存模式的设置,选大模式,

和"DS!=SS DS Load on Function entry.",切记,否则会系统崩溃。 
========

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

http://www.cnblogs.com/seacryfly/archive/2012/01/08/2316511.html


虽然屏幕取词技术早已经不是什么秘密,以至于除了汉化工具、翻译工具、中文平台等等这些东西之外

,连像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这样一种搭车行为,做完要做的事情之后最好是


能够不留痕迹的清理好现场,这既是出于系统执行效率和资源消耗的考虑,也是为了系统安全的目的,


用于挂接和数据传递的代码区域在使用完成之后应该进行资源释放,对于执行失败甚至异常的操作也应


该有相对稳妥的办法去把垃圾代码清除掉。金山词霸挂接了一个不常用的函数作为自身挂接状态的标记


,除了每次挂接任务完成后要执行自身清理之外,每次挂接前还要检查一下这个标记来确定是否有未解


除成功的以前的挂接,并根据需要执行清理。对于其它进程同时进行挂接的情况,如果不加判断直接将


系统API挂接地址修改为自身的函数入口地址,则另外的挂接程序就可能发生不可预知的执行问题。实际


开发中发现东方快车、中文之星这样的软件在遇到挂接冲突时的确会发生问题。因此比较柔和的办法是


等待其它挂接程序先摘除自身的挂接,再执行我们的操作,同时还要保证我们的挂接代码被其它程序强


行拆掉之后不给目标进程造成不良影响,且能被再次挂接的操作识别从而完成清理。


    4、特殊的目标。一些对绘制任务执行了比较复杂处理的软件,比如Acrobat、Word、IE等等,如果


使用基本的API Hook方法会使出错崩溃的机会大大增加,而且由于其不公开的执行逻辑和复杂的处理方


式,使得针对其进行的调试工作难于进行,不过好在它们大多数提供了另外的方法来完成我们的任务,


我们可以将这些方法以插件的方式集成到取词的模块当中。比如Acrobat的SDK就提供了获取正在显示文


档某区域文本的功能,Word支持的Automation则允许在取词插件被启用的时候向外部进程暴露出一部分


数据,IE则直接支持了获取显示窗口的Document;比较有趣的是Apabi,它的开发人员发现词霸没有为其


独立制作可用的取词插件(实际上是没办法),就在每次自己进行绘制缓存输出的时候,调用了一次空


的 TextOut方法,用来配合金山词霸的取词方式,哈。这里还想顺带说一下触发目标进程重绘屏幕的方


法,正常情况下我们会用一个透明窗口把用户鼠标焦点附近挡一下,这样Windows就会自动给目标窗口发


一个区域无效的消息提醒目标进程重新绘制被遮挡的部分;但仅仅是这样的话会有不少软件和你闹别扭


,比如大名鼎鼎的QQ,在某些版本里那个家伙被一个透明窗口挡住都会出现一片白色的未正常绘制的区


域,而且根本不会自己重绘,对于这样的问题,呵呵,只能具体问题具体分析了。


    5、未来可行性。前面提到了Avalon和GDI+,这些新出来的东西是金山词霸在最初开发时没有考虑的


。GDI+的问题已经解决了,毕竟它目前还是运行在Win32平台上的,通过分析它的Flat API和更底层的非


文档接口,我们用同样的方法解决了取词问题,甚至,由于GDI+提供了方便的计算字符位置的方法,获


取用户鼠标焦点位置字符的方法也变得容易了许多。有限的一点感慨就是:没有文档的接口还真是不容


易用啊。Avalon现在被设计为与GDI+平级的一个显示层接口,由于集成了2D和3D显示接口,其内部结构


目前看来是相当的复杂,但是由于其仍然支持Win32平台,并且考虑到目前的3D设备在系统中的位置,个


人认为Avalon的2D部分的API Hook取词也有着相当的可行性。实际上金山游侠也是金山词霸组的产品,


所以我们当初考虑DirectX方式下的取词和显示也是可行的,不过由于其实现成本比较高,预期效益也并


不大,就没有做。WinFX我没有太深的研究,更深的细节现在还没法说,嘿嘿。还有一个麻烦的事情是


Java,Java的桌面程序总让人感觉不伦不类,分析下来在Windows平台下它有一部分文本输出是调用了一


些W非文档的函数,而有一些则是使用自带字库进行绘制——对于后者,虽然不能说是一点办法没有,但


实际的商业价值似乎不大,只是不知道在Windows Vista上的Java虚拟机会怎么做。最后一个潜在的问题


是移动设备,受用户输入方式和系统资源的限制目前对屏幕取词的需求还不是很强烈,但在可以预见的


未来,还是有一些苗头的。


    6、后记。从现在的趋势看来,即便Windows Vista给我们提供了更加丰富的接口功能,更高效的应


用软件开发模式,更强悍的界面表现方式,更方便的数据通信和沟通方式,B/S的大潮仍然无法阻挡的,


这甚至代表了整个软件生产和使用的趋势,Browser功能的不断扩充与其说是网络应用进化的必然,不如


说是埋下了系统表示层浏览器化的伏笔,而微软在Windows Vista上所作的一切也使我多少嗅到了这样的


味道——如果这是真的,那么金山词霸取词技术现在这个样子,则有可能随着不可挽回的Win32落潮而成


为这个时代的终篇之一。


    能想起来的都说了,再想起来什么的话,再改吧,


在金山词霸中2005中带了一个XdictGrb.dll,添加引用


废话不多说了,还是把源码放上


using System; using System.Collections.Generic; using System.ComponentModel; using 


System.Data; using System.Text; using System.Windows.Forms; using XDICTGRB;//金山词霸组件


namespace WindowsApplication1 { public partial class Form1 : Form,IXDictGrabSink { public 


Form1() { InitializeComponent(); 
} private void Form1_Load(object sender, EventArgs e) { 
GrabProxy gp = new GrabProxy(); gp.GrabInterval = 1;//指抓取时间间隔 
gp.GrabMode = XDictGrabModeEnum.XDictGrabMouse;//设定取词的属性 gp.GrabEnabled = true;//是


否取词的属性 gp.AdviseGrab(this); } //接口的实现 int IXDictGrabSink.QueryWord(string 


WordString, int lCursorX, int lCursorY, string SentenceString, ref int lLoc, ref int 


lStart) { this.textBox1.Text = SentenceString;//鼠标所在语句 //this.textBox1.Text = 


SentenceString.Substring(lLoc + 1,1);//鼠标所在字符 return 1; } } }


B.Nhw32.dll法


这个是C++写的一个组件


nhw32.dll 主要引出两个函数:


1. DWORD WINAPI BL_SetFlag32(UINT nFlag, 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(LPSTR 


lpszCurWord, 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 。


"鼠标屏幕取词"技术是在电子字典中得到广泛地应用的,如四通利方和金山词霸等软件,这个技术看似


简单,其实在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也随之有不同的基地址,通常被映射到不同进程的相同的虚拟地址空间处。 
系统将exe和dll原封不动映射到虚拟内存空间中,它们在内存中的结构与磁盘上的静态文件结构是一样


的。即pe (portable executable) 文件格式。我们得到了进程模块的基地址以后,就可以根据pe文件的


格式穷举这个模块的image_import_descriptor数组,看看进程空间中是否引入了我们需要截获的函数所


在的动态链接库,比如需要截获"textouta",就必须检查"gdi32.dll"是否被引入了。说到这里,我们有


必要介绍一下pe文件的格式,如右图,这是pe文件格式的大致框图,最前面是文件头,我们不必理会,


从pe file optional header后面开始,就是文件中各个段的说明,说明后面才是真正的段数据,而实际


上我们关心的只有一个段,那就是".idata"段,这个段中包含了所有的引入函数信息,还有iat(import 


address table)的rva(relative virtual address)地址。 
说到这里,截获windowsapi的整个原理就要真相大白了。实际上所有进程对给定的api函数的调用总是通


过pe文件的一个地方来转移的,这就是一个该模块(可以是exe或dll)的".idata"段中的iat输入地址表(


import address table)。在那里有所有本模块调用的其它dll的函数名及地址。对其它dll的函数调用


实际上只是跳转到输入地址表,由输入地址表再跳转到dll真正的函数入口。 
具体来说,我们将通过image_import_descriptor数组来访问".idata"段中引入的dll的信息,然后通过


image_thunk_data数组来针对一个被引入的dll访问该dll中被引入的每个函数的信息,找到我们需要截


获的函数的跳转地址,然后改成我们自己的函数的地址……具体的做法在后面的关键代码中会有详细的


讲解。 
讲了这么多原理,现在让我们回到"鼠标屏幕取词"的专题上来。除了api函数的截获,要实现"鼠标屏幕


取词",还需要做一些其它的工作,简单的说来,可以把一个完整的取词过程归纳成以下几个步骤: 
1. 安装鼠标钩子,通过钩子函数获得鼠标消息。 使用到的api函数:setwindowshookex 2. 得到鼠标


的当前位置,向鼠标下的窗口发重画消息,让它调用系统函数重画窗口。 
使用到的api函数:windowfrompoint,screentoclient,invalidaterect 3. 截获对系统函数的调用,


取得参数,也就是我们要取的词。 
对于大多数的windows应用程序来说,如果要取词,我们需要截获的是"gdi32.dll"中的"textouta"函数


。 
我们先仿照textouta函数写一个自己的mytextouta函数,如: bool winapi mytextouta(hdc hdc, int 


nxstart, int nystart, lpcstr lpszstring,int cbstring) { // 这里进行输出lpszstring的处理 // 


然后调用正版的textouta函数 } 
把这个函数放在安装了钩子的动态连接库中,然后调用我们最后给出的hookimportfunction函数来截获


进程对textouta函数的调用,跳转到我们的mytextouta函数,完成对输出字符串的捕捉。


hookimportfunction的用法: 
hookfuncdesc hd; proc porigfuns; hd.szfunc="textouta"; 
hd.pproc=(proc)mytextouta; hookimportfunction (afxgetinstancehandle


(),"gdi32.dll",&hd,porigfuns); 
下面给出了hookimportfunction的源代码,相信详尽的注释一定不会让您觉得理解截获到底是怎么实现


的很难,ok,let s go: 
///////////////////////////////////////////// begin 


/////////////////////////////////////////////////////////////// #include <crtdbg.h> // 这里


定义了一个产生指针的宏 #define makeptr(cast, ptr, addvalue) (cast)((dword)(ptr)+(dword)


(addvalue)) // 定义了hookfuncdesc结构,我们用这个结构作为参数传给hookimportfunction函数 


typedef struct tag_hookfuncdesc { lpcstr szfunc; // the name of the function to hook. 
proc pproc; // the procedure to blast in. } hookfuncdesc , * lphookfuncdesc; // 这个函数监


测当前系统是否是windownt bool isnt(); // 这个函数得到hmodule -- 即我们需要截获的函数所在的


dll模块的引入描述符(import descriptor) 
pimage_import_descriptor getnamedimportdescriptor(hmodule hmodule, lpcstr szimportmodule); 


// 我们的主函数 bool hookimportfunction(hmodule hmodule, lpcstr szimportmodule, 


lphookfuncdesc pahookfunc, proc* paorigfuncs) { 
/////////////////////// 下面的代码检测参数的有效性 //////////////////////////// 
_assert(szimportmodule); _assert(!isbadreadptr(pahookfunc, sizeof(hookfuncdesc))); #ifdef 


_debug if (paorigfuncs) _assert(!isbadwriteptr(paorigfuncs, sizeof(proc))); 
_assert(pahookfunc.szfunc); _assert(*pahookfunc.szfunc != \0 ); 
_assert(!isbadcodeptr(pahookfunc.pproc)); #endif if ((szimportmodule == null) || 


(isbadreadptr(pahookfunc, sizeof(hookfuncdesc)))) { 
_assert(false); setlasterrorex(error_invalid_parameter, sle_error); 
return false; } 
////////////////////////////////////////////////////////////////////////////// 
// 监测当前模块是否是在2gb虚拟内存空间之上 // 这部分的地址内存是属于win32进程共享的 if (!


isnt() && ((dword)hmodule >= 0x80000000)) { _assert(false); 
setlasterrorex(error_invalid_handle, sle_error); return false; } 
// 清零 if (paorigfuncs) memset(paorigfuncs, null, sizeof(proc)); // 调用


getnamedimportdescriptor()函数,来得到hmodule -- 即我们需要 // 截获的函数所在的dll模块的引入


描述符(import descriptor) pimage_import_descriptor pimportdesc = getnamedimportdescriptor


(hmodule, szimportmodule); if (pimportdesc == null) return false; // 若为空,则模块未被当前


进程所引入 // 从dll模块中得到原始的thunk信息,因为pimportdesc->firstthunk数组中的原始信息已


经 // 在应用程序引入该dll时覆盖上了所有的引入信息,所以我们需要通过取得pimportdesc-


>originalfirstthunk // 指针来访问引入函数名等信息 pimage_thunk_data porigthunk = makeptr


(pimage_thunk_data, hmodule, pimportdesc->originalfirstthunk); // 从pimportdesc->firstthunk


得到image_thunk_data数组的指针,由于这里在dll被引入时已经填充了 // 所有的引入信息,所以真正的


截获实际上正是在这里进行的 pimage_thunk_data prealthunk = makeptr(pimage_thunk_data, 


hmodule, pimportdesc->firstthunk); // 穷举image_thunk_data数组,寻找我们需要截获的函数,这是


最关键的部分! while (porigthunk->u1.function) { // 只寻找那些按函数名而不是序号引入的函数 


if (image_ordinal_flag != (porigthunk->u1.ordinal & image_ordinal_flag)) 
{ // 得到引入函数的函数名 pimage_import_by_name pbyname = makeptr(pimage_import_by_name, 


hmodule, porigthunk->u1.addressofdata); 
// 如果函数名以null开始,跳过,继续下一个函数 if ( \0 == pbyname->name[0]) continue; 
// bdohook用来检查是否截获成功 bool bdohook = false; // 检查是否当前函数是我们需要截获的函


数 
if ((pahookfunc.szfunc[0] == pbyname->name[0]) && 
(strcmpi(pahookfunc.szfunc, (char*)pbyname->name) == 0)) { // 找到了! if (pahookfunc.pproc) 


bdohook = true; } if (bdohook) { 
// 我们已经找到了所要截获的函数,那么就开始动手吧 // 首先要做的是改变这一块虚拟内存的内存保


护状态,让我们可以自由存取 
memory_basic_information mbi_thunk; virtualquery(prealthunk, &mbi_thunk, sizeof


(memory_basic_information)); 
_assert(virtualprotect(mbi_thunk.baseaddress, mbi_thunk.regionsize, 
page_readwrite, &mbi_thunk.protect)); // 保存我们所要截获的函数的正确跳转地址 if 


(paorigfuncs) paorigfuncs = (proc)prealthunk->u1.function; // 将image_thunk_data数组中的函


数跳转地址改写为我们自己的函数地址! // 以后所有进程对这个系统函数的所有调用都将成为对我们自


己编写的函数的调用 prealthunk->u1.function = (pdword)pahookfunc.pproc; // 操作完毕!将这一块


虚拟内存改回原来的保护状态 dword dwoldprotect; 
_assert(virtualprotect(mbi_thunk.baseaddress, mbi_thunk.regionsize, 
mbi_thunk.protect, &dwoldprotect)); setlasterror(error_success); 
return true; } } // 访问image_thunk_data数组中的下一个元素 
porigthunk++; prealthunk++; } return true; } // getnamedimportdescriptor函数的实现 


pimage_import_descriptor getnamedimportdescriptor(hmodule hmodule, lpcstr szimportmodule) { 


// 检测参数 _assert(szimportmodule); _assert(hmodule); if ((szimportmodule == null) || 


(hmodule == null)) { _assert(false); 
setlasterrorex(error_invalid_parameter, sle_error); return null; } 
// 得到dos文件头 pimage_dos_header pdosheader = (pimage_dos_header) hmodule; 
// 检测是否mz文件头 if (isbadreadptr(pdosheader, sizeof(image_dos_header)) || 
(pdosheader->e_magic != image_dos_signature)) { _assert(false); 
setlasterrorex(error_invalid_parameter, sle_error); return null; } 
// 取得pe文件头 pimage_nt_headers pntheader = makeptr(pimage_nt_headers, pdosheader, 


pdosheader->e_lfanew); // 检测是否pe映像文件 if (isbadreadptr(pntheader, sizeof


(image_nt_headers)) || 
(pntheader->signature != image_nt_signature)) { _assert(false); 
setlasterrorex(error_invalid_parameter, sle_error); return null; } 
// 检查pe文件的引入段(即 .idata section) if (pntheader->optionalheader.datadirectory


[image_directory_entry_import].virtualaddress == 0) return null; // 得到引入段(即 .idata 


section)的指针 
pimage_import_descriptor pimportdesc = makeptr(pimage_import_descriptor, pdosheader, 
pntheader->optionalheader.datadirectory[image_directory_entry_import].virtualaddress); 
// 穷举pimage_import_descriptor数组寻找我们需要截获的函数所在的模块 while (pimportdesc-


>name) { pstr szcurrmod = makeptr(pstr, pdosheader, pimportdesc->name); if (stricmp


(szcurrmod, szimportmodule) == 0) 
break; // 找到!中断循环 // 下一个元素 pimportdesc++; } // 如果没有找到,说明我们寻找的模块没


有被当前的进程所引入! if (pimportdesc->name == null) return null; // 返回函数所找到的模块描


述符(import descriptor) return pimportdesc; } 
// isnt()函数的实现 bool isnt() { osversioninfo stosvi; 
memset(&stosvi, null, sizeof(osversioninfo)); 
stosvi.dwosversioninfosize = sizeof(osversioninfo); bool bret = getversionex(&stosvi); 


_assert(true == bret); if (false == bret) return false; return (ver_platform_win32_nt == 


stosvi.dwplatformid); } 
/////////////////////////////////////////////// end 


////////////////////////////////////////////////////////////////////// 
不知道在这篇文章问世之前,有多少朋友尝试过去实现"鼠标屏幕取词"这项充满了挑战的技术,也只有


尝试过的朋友才能体会到其间的不易,尤其在探索api函数的截获时,手头的几篇资料没有一篇是涉及到


关键代码的,重要的地方都是一笔代过,msdn更是显得苍白而无力,也不知道除了 


image_import_descriptor和image_thunk_data,微软还隐藏了多少秘密,好在硬着头皮还是把它给攻克


了,希望这篇文章对大家能有所帮助。


========

屏幕取词汇编源码

http://blog.chinaunix.net/uid-20547722-id-1647248.html
文件1--HOOKAPIFAR.ASM
; 完成钩子和挂钩DLL,完成向文件输出取词结果
.386
.model flat,stdcall
option casemap:none
;******************************************************************************************


***************************
include e:\masm32\include\windows.inc
include e:\masm32\include\kernel32.inc
includelib e:\masm32\lib\kernel32.lib
include e:\masm32\include\user32.inc
includelib e:\masm32\lib\user32.lib
include e:\masm32\include\gdi32.inc
includelib e:\masm32\lib\gdi32.lib
;******************************************************************************************


****************************
jmpinto struct;数据结构,用来存储跳转代码
a db ?
newapi dd ?
b db ?
d db ?
jmpinto ends
HookApi proto :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
_SetWindowText proto :DWORD,:DWORD,:DWORD
NTextOutA PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
NTextOutW PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
NExtTextOutA PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
NExtTextOutW PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
_ptextout typedef PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD
ptextout typedef ptr _ptextout
_pexttextout typedef PROTO :DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD,:DWORD
pexttextout typedef ptr _pexttextout
;******************************************************************************************


*****************************
.data?
hText  dd ?
Hhost  dd ?
.data
hHook dd 0
code_ta db 10 dup(0)
code_tw db 10 dup(0)
code_ea db 10 dup(0)
code_ew db 10 dup(0)
tajmpinto jmpinto<0,0,0,0>
twjmpinto jmpinto<0,0,0,0>
eajmpinto jmpinto<0,0,0,0>
ewjmpinto jmpinto<0,0,0,0>
OldTextOutA ptextout 0
OldTextOutW ptextout 0
OldExtTextOutA pexttextout 0
OldExtTextOutW pexttextout 0
hProcess dd ?
PHandle dd 0
PId dd 0
numused  dd 0
szTextOutA db "TextOutA",0
szTextOutW db "TextOutW",0
szExtTextOutA db "ExtTextOutA",0
szExtTextOutW db "ExtTextOutW",0
szGdi32 db "gdi32.dll",0
szFile db "c:\dbg.debug",0
settext dd 0
FileHeader db 0ffh,0feh
;******************************************************************************************


*********************************************
.code
DllMain proc hInst:HINSTANCE,reason:DWORD,reserved1:DWORD;挂钩API
.if reason==DLL_PROCESS_ATTACH
 push hInst
 pop hProcess
 invoke GetCurrentProcessId
 mov PId,eax
 invoke OpenProcess,PROCESS_ALL_ACCESS,FALSE,PId
 .if(eax==0)
  mov eax,FALSE
  ret
 .endif
 mov PHandle,eax 
 mov eax,hProcess
 .if(eax!=Hhost);在我的XP下似乎只有最后一个函数是输出字符的,但为了扩展我保存了前三个的位置
;  invoke HookApi,addr szGdi32,addr szTextOutA,addr NTextOutA,addr code_ta,addr 


OldTextOutA,addr tajmpinto
;  invoke HookApi,addr szGdi32,addr szTextOutW,addr NTextOutW,addr code_tw,addr 


OldTextOutW,addr twjmpinto
;  invoke HookApi,addr szGdi32,addr szExtTextOutA,addr NExtTextOutA,addr code_ea,addr 


OldExtTextOutA,addr eajmpinto
  invoke HookApi,addr szGdi32,addr szExtTextOutW,addr NExtTextOutW,addr code_ew,addr 


OldExtTextOutW,addr ewjmpinto
 .endif
 mov eax,TRUE
 ret
.elseif reason==DLL_PROCESS_DETACH;解除挂钩
 mov eax,hProcess
 .if(eax!=Hhost)
;  invoke WriteProcessMemory,PHandle,OldTextOutA,addr code_ta,sizeof code_ta,addr numused
;  invoke WriteProcessMemory,PHandle,OldTextOutW,addr code_tw,sizeof code_tw,addr numused 
;  invoke WriteProcessMemory,PHandle,OldExtTextOutA,addr code_ea,sizeof code_ea,addr 


numused 
  invoke WriteProcessMemory,PHandle,OldExtTextOutW,addr code_ew,sizeof code_ew,addr numused
 .endif
 mov eax,TRUE
 ret
.endif
mov eax,TRUE
ret
DllMain endp
;//////////////////////////////////////////////////////////////////////////////////////////


///////////////////////////////////
MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD;名为鼠标,其实是键盘钩子
local mousepos:POINT
local rect:RECT
LOCAL hWnd:HWND
.if wParam==VK_CONTROL;如果CONTROL被按下
 invoke GetCursorPos,addr mousepos
 invoke WindowFromPoint,mousepos.x,mousepos.y
 mov hWnd,eax
 invoke ScreenToClient,hWnd,addr mousepos
 push mousepos.x
 pop rect.left
 push mousepos.y
 pop rect.top
 push mousepos.x
 pop rect.right
 inc rect.right
 push mousepos.y
 pop rect.bottom
 inc rect.bottom
 invoke InvalidateRect,hWnd,addr rect,TRUE ;发重画消息
.endif
invoke CallNextHookEx,hHook,nCode,wParam,lParam
ret
MouseProc endp
InstallHook proc _hStatic:DWORD,_Hhost;装钩子
 push _Hhost
 pop Hhost
 push _hStatic
 pop hText
 invoke SetWindowsHookEx,WH_KEYBOARD,addr MouseProc,hProcess,NULL
 mov hHook,eax
 ret
InstallHook endp
UnInstallHook proc
 invoke UnhookWindowsHookEx,hHook
 ret
UnInstallHook endp
;//////////////////////////////////////////////////////////////////////////////////////////


///////////////////////////////////
HookApi proc uses ebx edi esi szlib,szproc,lpfunc,lpbuffer,lpOld,lpjmpinto;挂钩API
;save first bits
local hDll,lpproc
local meminfo:MEMORY_BASIC_INFORMATION
local numdid
mov esi,lpjmpinto
assume esi:ptr jmpinto
invoke RtlZeroMemory,esi,sizeof jmpinto
mov [esi].a,0b8h
mov eax,lpfunc
mov [esi].newapi,eax
mov [esi].b,0ffh
mov [esi].d,0e0h
invoke LoadLibrary,szlib
mov hDll,eax
invoke GetProcAddress,hDll,szproc
mov edi,lpOld
mov dword ptr[edi],eax
invoke VirtualQueryEx,PHandle,[edi],addr meminfo,sizeof meminfo
invoke VirtualProtectEx,PHandle,meminfo.BaseAddress,0ah,PAGE_EXECUTE_READWRITE,addr 


meminfo.Protect
invoke GetLastError
invoke ReadProcessMemory,PHandle,[edi],lpbuffer,10,addr numdid
invoke WriteProcessMemory,PHandle,[edi],esi,sizeof jmpinto,addr numdid
ret
HookApi endp
;//////////////////////////////////////////////////////////////////////////////////////////


///////////////////////////////////
_SetWindowText proc uses ebx edi esi hWnd,lpBuffer,cbCount;在C:\DBG.DEBUG中自启动之后截获的


全部词句
local written
local lpMultiByteStr[256]:BYTE
local @end[2]:BYTE
invoke RtlZeroMemory,addr lpMultiByteStr,sizeof lpMultiByteStr
invoke CreateFile,addr szFile,GENERIC_READ or GENERIC_WRITE,FILE_SHARE_READ or 


FILE_SHARE_WRITE,NULL,OPEN_EXISTING,NULL,NULL
mov hText,eax
;invoke WriteFile,hText,addr FileHeader,2,addr written,NULL
invoke SetFilePointer,hText,NULL,NULL,FILE_END
;invoke WideCharToMultiByte,CP_ACP,WC_COMPOSITECHECK,lpBuffer,255,addr 


lpMultiByteStr,sizeof lpMultiByteStr,NULL,NULL
invoke WriteFile,hText,lpBuffer,cbCount,addr written,NULL
;mov @end,0
;mov @end+1,'#'
;invoke WriteFile,hText,addr @end,2,addr written,NULL
invoke CloseHandle,hText
;SendMessage,hWnd,WM_CHAR,'#',1
ret
_SetWindowText endp
;//////////////////////////////////////////////////////////////////////////////////////////


///////////////////////////////////
NTextOutA proc uses ebx edi esi hdc,nXStart,nYStart,lpString,cbString;四个被钩函数
pusha
invoke WriteProcessMemory,PHandle,OldTextOutA,addr code_ew,sizeof code_ew,addr numused
push cbString
mov eax,cbString
add cbString,eax
invoke GetAsyncKeyState,VK_CONTROL
and eax,8000h
.if eax
 invoke _SetWindowText,hText,lpString,cbString
.endif
pop cbString
popa
invoke OldTextOutA,hdc,nXStart,nYStart,lpString,cbString
invoke WriteProcessMemory,PHandle,OldTextOutA,addr tajmpinto,sizeof tajmpinto,addr numused
ret 
NTextOutA endp
NTextOutW proc uses ebx edi esi hdc,nXStart,nYStart,lpString,cbString
pusha
invoke WriteProcessMemory,PHandle,OldTextOutW,addr code_ew,sizeof code_ew,addr numused
push cbString
mov eax,cbString
add cbString,eax
invoke GetAsyncKeyState,VK_CONTROL
and eax,8000h
.if eax
 invoke _SetWindowText,hText,lpString,cbString
.endif
pop cbString
popa
invoke OldTextOutW,hdc,nXStart,nYStart,lpString,cbString
invoke WriteProcessMemory,PHandle,OldTextOutW,addr twjmpinto,sizeof twjmpinto,addr numused
ret 
NTextOutW endp
NExtTextOutA proc uses ebx edi esi hdc,X,Y,fuOptions,lprc,lpString,cbCount,lpDx
pusha
invoke WriteProcessMemory,PHandle,OldExtTextOutA,addr code_ew,sizeof code_ew,addr numused
push cbCount
mov eax,cbCount
add cbCount,eax
invoke GetAsyncKeyState,VK_CONTROL
and eax,8000h
.if eax
 invoke _SetWindowText,hText,lpString,cbCount
.endif
pop cbCount
popa
invoke OldExtTextOutA,hdc,X,Y,fuOptions,lprc,lpString,cbCount,lpDx
invoke WriteProcessMemory,PHandle,OldExtTextOutA,addr ewjmpinto,sizeof eajmpinto,addr 


numused
ret
NExtTextOutA endp
NExtTextOutW proc uses ebx edi esi hdc,X,Y,fuOptions,lprc,lpString,cbCount,lpDx
pusha
invoke WriteProcessMemory,PHandle,OldExtTextOutW,addr code_ew,sizeof code_ew,addr numused
push cbCount
mov eax,cbCount
add cbCount,eax
invoke GetAsyncKeyState,VK_CONTROL
and eax,8000h
.if eax
 invoke _SetWindowText,hText,lpString,cbCount
.endif
pop cbCount
popa
invoke OldExtTextOutW,hdc,X,Y,fuOptions,lprc,lpString,cbCount,lpDx
invoke WriteProcessMemory,PHandle,OldExtTextOutW,addr ewjmpinto,sizeof ewjmpinto,addr 


numused
ret 
NExtTextOutW endp
end DllMain
文件2-HOOKAPIFAR.DEF
EXPORTS InstallHook 
  UnInstallHook
文件3-HOOKAPIFARMAIN.ASM-尚未完成的主界面,也是我求教的地方
.386
.model flat,stdcall
option casemap:none
;******************************************************************************************


***************************
include e:\masm32\include\windows.inc
include e:\masm32\include\kernel32.inc
includelib e:\masm32\lib\kernel32.lib
include e:\masm32\include\user32.inc
includelib e:\masm32\lib\user32.lib
;******************************************************************************************


****************************
DialogMain proto :DWORD,:DWORD,:DWORD,:DWORD
InstallHookA typedef proto :DWORD,:DWORD
InstallHookB typedef ptr InstallHookA
UnInstallHookA typedef proto 
UnInstallHookB typedef ptr UnInstallHookA
;******************************************************************************************


*****************************
.const
IDD_MAIN equ 1000
IDC_WORD equ 1001
;******************************************************************************************


*****************************
.data?
numused  dd ?
hProcess dd ?
hText  dd ?
.data
FileHeader db 0ffh,0feh
szlib db "hookapifar.dll",0
hlib dd 0
InstallHook InstallHookB 0
UnInstallHook UnInstallHookB 0
szinstall db "InstallHook",0
szuninstall db "UnInstallHook",0
szClassNotePad db "Notepad",0
szFile db "c:\dbg.debug",0;这个文件输出最后的取词结果,可以用记事本打开看之
hNotepad dd ?
written dd 0
;******************************************************************************************


*********************************
.code
_main:
invoke GetModuleHandle,NULL
mov hProcess,eax
invoke LoadLibrary,addr szlib
mov hlib,eax
invoke GetLastError
invoke GetProcAddress,hlib,addr szinstall
mov InstallHook,eax
invoke GetProcAddress,hlib,addr szuninstall
mov UnInstallHook,eax
invoke CreateFile,addr szFile,GENERIC_READ or GENERIC_WRITE,FILE_SHARE_READ or 


FILE_SHARE_WRITE,NULL,CREATE_ALWAYS,NULL,NULL
mov hText,eax
invoke WriteFile,hText,addr FileHeader,2,addr written,NULL;UNICODE的记事本文件头写入
invoke CloseHandle,hText
invoke DialogBoxParam,hProcess,IDD_MAIN,NULL,DialogMain,NULL
invoke ExitProcess,NULL
;//////////////////////////////////////////////////////////////////////////////////////////


/////////////////////////////////
DialogMain proc uses ebx edi esi hWnd,uMsg,wParam,lParam
local _buffer[100]:BYTE
local hdc:HDC
local mousepos:POINT
local rect:RECT
local keystate[256]:BYTE
.if uMsg==WM_CLOSE
 invoke EndDialog,hWnd,NULL
 invoke UnInstallHook
.elseif uMsg==WM_INITDIALOG
 invoke SetWindowPos,hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOSIZE
 invoke InstallHook,hText,hProcess
.else
 mov eax,FALSE
 ret
.endif
mov eax,TRUE
ret
DialogMain endp
end _main


#include 
#define IDD_MAIN 1000
#define IDC_WORD 1001
文件四-HOOKAPIFAR.RC
IDD_MAIN DIALOG DISCARDABLE  0, 0, 187, 60
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "getword"
FONT 10, "System"
BEGIN
hookapifar.obj /Dll /Section:.bss,S /Def:f:\technique\hookapifar.def
    EDITTEXT        IDC_WORD,16,22,149,12,ES_LEFT
END
文件五:ML.BAT:
e:
cd masm32
cd bin
ml /c /coff /Zi /Cp f:\technique\hookapifarmain.asm
link /subsystem:windows /DEBUG /DEBUGTYPE:CV hookapifarmain.obj f:\technique\hookapifar.res
ml /c /coff /Zi /Cp f:\technique\hookapifar.asm
link /subsystem:windows /DEBUG /DEBUGTYPE:CV
编译时要保证MASM32在E盘中,或者改下我的程序中路径
========

谈谈你是怎么学windows核心编程的

https://www.zhihu.com/question/27492642


教你个简单方法,写一个屏幕取词程序。


写一个屏幕取词,需要用到 进程管理,多线程,虚拟地址空间,内存管理,各种锁,进程间通信,汇编


,编译原理,可执行文件结构,GDI,窗口消息,自然语言支持,windbg调试技巧。


我当年就是直奔“DLL注入以及API挂接”那个章节,写出一个取词程序,前面的也就差不多都懂了。


简单解释下屏幕取词,就是鼠标指向某个文本的时候,可以获取鼠标所指向的单词,并且给出翻译,词


典软件必备功能,比如 金山词霸,有道词典都有这个,现在中文信息丰富了,词典软件用得比较少了。
效果如图:


最厉害的,把sysinternals的那些工具挑几个自己重写一边,比如process explorer, process 


monitor, psexec等。


1.首先打基础,比如C/C++/ASM/MFC等,要大致熟悉这些语言和框架,再开始学习《windows核心编程》


才不会太吃力。


2.然后熟悉Ring3常用的api,各种进程线程操作,文件操作,内存操作,同步机制,注册表,网络同步


异步等等。尝试去写一些练手的Demo程序,根据兴趣去写,比如我当初写了简单的多人聊天室以及一个


远程控制的木马程序。


3.找好在windows上学习的方向,想清楚自己喜欢做啥,想研究学习什么方向。比如我选择了windows底


层安全方向,驱动开发之类。


4.有了基础和方向以后,开始给自己提一些需求,并且试着努力去实现需求。在这个过程中,会切身体


会到开发一个程序是很系统的事情。你又要开始重温以前的知识,并且还要学习很多新的知识。这个过


程是漫长的,期间你会发现完成任何一个模块都扩展了很多知识。比如我写了一个ark工具,完成了进程


操作,文件管理,注册表管理,服务管理,ssdt hook,minifilter等等功能。


5.遇到问题时,除了翻书以外,可以在网上查找已经有的解决办法。如果有合适的,取其精华去其糟粕


。如果没有,则需要自己调试解决。要善用搜索引擎,多读msdn。平时可以多逛一些论坛,比如看雪论


坛,csdn论坛等等。看到不错的文章或博客多收藏一下。


6.经历完成以上几步,就可以继续了解windows系统的框架,真正的入门了。


-写一个wine-


写一个沙盘


我觉得核心编程的主题思想不在编程而在核心……我身边绝大多数搞内核的基本都不是从写驱动开始,


而是从windbg调试内核开始的。你不调试光想靠读书背函数名根本就没完,导出函数列表几页a4纸都写


不下……而调试内核熟悉了调用关系之后也就明白了该去找什么,写驱动也就比较得心应手。技术上的


话基本的算法得会一点,但是c和x86汇编必须精通(c++可以不会,因为内核里基本没有c++代码)。另


外建议从内存管理模块看起(目录名mm),那块比较容易理解。书的话除了有网友提到的《Windows核心


编程》外,如果真的不是闹着玩而是想学好的话建议先啃了潘爱民先生的《Windows内核原理与实现》(


配合着Windows Research Kernel源码看)。
p.s. Windows内核在Windows 8和Windows 10都有较大改动,但是NT部分变动基本为零,所以你啃下来一


个内核基本就通吃了。


《Windows核心编程》应该就是Windows via C/C++这本书吧。CSDN的BBS上讨论过,其实就是讲Windows 


API,入门级的书。我觉得适合那些写应用程序而有性能优化要求的人读此书。如果你的程序没有性能瓶


颈限制,你用C#、Python或者C/C++的运行时库就够用了,还有开发效率
========
0 0
原创粉丝点击