屏幕取词现在已经是广泛应用的一个功能,但其实这个功能在Windows中的实现还是非常复杂的。本文介绍屏幕取词的实现方法。

有关屏幕取词

屏幕取词的完成办法(Windows 9x)(win10屏幕取词)  屏幕取词 第1张

"鼠标屏幕取词"技能是在电子字典中得到广泛地运用的,如四通利方和金山词霸等软件,这个技能看似简略,其实在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中被引进的每个函数的信息,找到咱们需求截获的函数的跳转地址,然后改成咱们自己的函数的地址……

废话不说了,下面供给一个屏幕取词详细的完结办法。

1. 装置鼠标钩子,经过钩子函数获得鼠标音讯。

运用到的api函数:setwindowshookex

2. 得到鼠标的当时方位,向鼠标下的窗口发重画音讯,让它调用体系函数重画窗口。

运用到的api函数:windowfrompoint,screentoclient,invalidaterect

3. 截获对体系函数的调用,获得参数,也便是咱们要取的词。

关于大多数的windows运用程序来说,假如要取词,咱们需求截获的是"gdi32.dll"中的"textouta"函数。

咱们先模仿textouta函数写一个自己的mytextouta函数,如:

  1. boolwinapimytextouta(hdchdc,intnxstart,intnystart,lpcstrlpszstring,intcbstring)
  2. {
  3. //这儿进行输出lpszstring的处理
  4. //然后调用正版的textouta函数
  5. }

把这个函数放在装置了钩子的动态衔接库中,然后调用咱们***给出的hookimportfunction函数来截获进程对textouta函数的调用,跳转到咱们的mytextouta函数,完结对输出字符串的捕捉。hookimportfunction的用法:

  1. hookfuncdeschd;
  2. procporigfuns;
  3. hd.szfunc="textouta";
  4. hd.pproc=(proc)mytextouta;
  5. hookimportfunction(afxgetinstancehandle(),"gdi32.dll",&hd,porigfuns);

下面给出了hookimportfunction的源代码,信任翔实的注释必定不会让您觉得了解截获到底是怎样完结的很难,ok,let s go:

  1. /////////////////////////////////////////////begin///////////////////////////////////////////////////////////////
  2. #include<crtdbg.h>
  3. //这儿界说了一个发生指针的宏
  4. #definemakeptr(cast,ptr,addvalue)(cast)((dword)(ptr)+(dword)(addvalue))
  5. //界说了hookfuncdesc结构,咱们用这个结构作为参数传给hookimportfunction函数
  6. typedefstructtag_hookfuncdesc
  7. {
  8. lpcstrszfunc;//thenameofthefunctiontohook.
  9. procpproc;//theproceduretoblastin.
  10. }hookfuncdesc,*lphookfuncdesc;
  11. //这个函数监测当时体系是否是windownt
  12. boolisnt();
  13. //这个函数得到hmodule--即咱们需求截获的函数地点的dll模块的引进描述符(importdescriptor)
  14. pimage_import_descriptorgetnamedimportdescriptor(hmodulehmodule,lpcstrszimportmodule);
  15. //咱们的主函数
  16. boolhookimportfunction(hmodulehmodule,lpcstrszimportmodule,
  17. lphookfuncdescpahookfunc,proc*paorigfuncs)
  18. {
  19. ///////////////////////下面的代码检测参数的有效性////////////////////////////
  20. _assert(szimportmodule);
  21. _assert(!isbadreadptr(pahookfunc,sizeof(hookfuncdesc)));
  22. #ifdef_debug
  23. if(paorigfuncs)_assert(!isbadwriteptr(paorigfuncs,sizeof(proc)));
  24. _assert(pahookfunc.szfunc);
  25. _assert(*pahookfunc.szfunc!=\0);
  26. _assert(!isbadcodeptr(pahookfunc.pproc));
  27. #endif
  28. if((szimportmodule==null)||(isbadreadptr(pahookfunc,sizeof(hookfuncdesc))))
  29. {
  30. _assert(false);
  31. setlasterrorex(error_invalid_parameter,sle_error);
  32. returnfalse;
  33. }
  34. //////////////////////////////////////////////////////////////////////////////
  35. //监测当时模块是否是在2gb虚拟内存空间之上
  36. //这部分的地址内存是归于win32进程同享的
  37. if(!isnt()&&((dword)hmodule>=0x80000000))
  38. {
  39. _assert(false);
  40. setlasterrorex(error_invalid_handle,sle_error);
  41. returnfalse;
  42. }
  43. //清零
  44. if(paorigfuncs)memset(paorigfuncs,null,sizeof(proc));
  45. //调用getnamedimportdescriptor()函数,来得到hmodule--即咱们需求
  46. //截获的函数地点的dll模块的引进描述符(importdescriptor)
  47. pimage_import_descriptorpimportdesc=getnamedimportdescriptor(hmodule,szimportmodule);
  48. if(pimportdesc==null)
  49. returnfalse;//若为空,则模块未被当时进程所引进
  50. //从dll模块中得到原始的thunk信息,因为pimportdesc->firstthunk数组中的原始信息现已
  51. //在运用程序引进该dll时掩盖上了一切的引进信息,所以咱们需求经过获得pimportdesc->originalfirstthunk
  52. //指针来拜访引进函数名等信息
  53. pimage_thunk_dataporigthunk=makeptr(pimage_thunk_data,hmodule,
  54. pimportdesc->originalfirstthunk);
  55. //从pimportdesc->firstthunk得到image_thunk_data数组的指针,因为这儿在dll被引进时现已填充了
  56. //一切的引进信息,所以真实的截获实际上正是在这儿进行的
  57. pimage_thunk_dataprealthunk=makeptr(pimage_thunk_data,hmodule,pimportdesc->firstthunk);
  58. //穷举image_thunk_data数组,寻觅咱们需求截获的函数,这是最要害的部分!
  59. while(porigthunk->u1.function)
  60. {
  61. //只寻觅那些按函数名而不是序号引进的函数
  62. if(image_ordinal_flag!=(porigthunk->u1.ordinal&image_ordinal_flag))
  63. {
  64. //得到引进函数的函数名
  65. pimage_import_by_namepbyname=makeptr(pimage_import_by_name,hmodule,
  66. porigthunk->u1.addressofdata);
  67. //假如函数名以null开端,越过,持续下一个函数
  68. if(\0==pbyname->name[0])
  69. continue;
  70. //bdohook用来查看是否截获成功
  71. boolbdohook=false;
  72. //查看是否当时函数是咱们需求截获的函数
  73. if((pahookfunc.szfunc[0]==pbyname->name[0])&&
  74. (strcmpi(pahookfunc.szfunc,(char*)pbyname->name)==0))
  75. {
  76. //找到了!
  77. if(pahookfunc.pproc)
  78. bdohook=true;
  79. }
  80. if(bdohook)
  81. {
  82. //咱们现已找到了所要截获的函数,那么就开端着手吧
  83. //首先要做的是改动这一块虚拟内存的内存维护状况,让咱们能够自在存取
  84. memory_basic_informationmbi_thunk;
  85. virtualquery(prealthunk,&mbi_thunk,sizeof(memory_basic_information));
  86. _assert(virtualprotect(mbi_thunk.baseaddress,mbi_thunk.regionsize,
  87. page_readwrite,&mbi_thunk.protect));
  88. //保存咱们所要截获的函数的正确跳转地址
  89. if(paorigfuncs)
  90. paorigfuncs=(proc)prealthunk->u1.function;
  91. //将image_thunk_data数组中的函数跳转地址改写为咱们自己的函数地址!
  92. //今后一切进程对这个体系函数的一切调用都将成为对咱们自己编写的函数的调用
  93. prealthunk->u1.function=(pdword)pahookfunc.pproc;
  94. //操作结束!将这一块虚拟内存改回本来的维护状况
  95. dworddwoldprotect;
  96. _assert(virtualprotect(mbi_thunk.baseaddress,mbi_thunk.regionsize,
  97. mbi_thunk.protect,&dwoldprotect));
  98. setlasterror(error_success);
  99. returntrue;
  100. }
  101. }
  102. //拜访image_thunk_data数组中的下一个元素
  103. porigthunk++;
  104. prealthunk++;
  105. }
  106. returntrue;
  107. }
  108. //getnamedimportdescriptor函数的完结
  109. pimage_import_descriptorgetnamedimportdescriptor(hmodulehmodule,lpcstrszimportmodule)
  110. {
  111. //检测参数
  112. _assert(szimportmodule);
  113. _assert(hmodule);
  114. if((szimportmodule==null)||(hmodule==null))
  115. {
  116. _assert(false);
  117. setlasterrorex(error_invalid_parameter,sle_error);
  118. returnnull;
  119. }
  120. //得到dos文件头
  121. pimage_dos_headerpdosheader=(pimage_dos_header)hmodule;
  122. //检测是否mz文件头
  123. if(isbadreadptr(pdosheader,sizeof(image_dos_header))||
  124. (pdosheader->e_magic!=image_dos_signature))
  125. {
  126. _assert(false);
  127. setlasterrorex(error_invalid_parameter,sle_error);
  128. returnnull;
  129. }
  130. //获得pe文件头
  131. pimage_nt_h
转载请说明出处
知优网 » 屏幕取词的完成办法(Windows 9x)(win10屏幕取词)

发表评论

您需要后才能发表评论