• 屏幕取词的实现方法(Windows 9x)

  • http://developer.51cto.com  2009-08-27 15:50  51CTO整理  CSDN论坛  我要评论(0)
  • 屏幕取词现在已经是广泛应用的一个功能,但其实这个功能在Windows中的实现还是非常复杂的。本文介绍屏幕取词的实现方法。
  • 有关屏幕取词

    "鼠标屏幕取词"技术是在电子字典中得到广泛地应用的,如四通利方和金山词霸等软件,这个技术看似简单,其实在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. bool winapi mytextouta(hdc hdc, int nxstart, int nystart, lpcstr lpszstring,int cbstring)
    2. {
    3. // 这里进行输出lpszstring的处理
    4. // 然后调用正版的textouta函数
    5. }

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

    1. hookfuncdesc hd;
    2. proc porigfuns;
    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. #define makeptr(cast, ptr, addvalue) (cast)((dword)(ptr)+(dword)(addvalue))
    5. // 定义了hookfuncdesc结构,我们用这个结构作为参数传给hookimportfunction函数
    6. typedef struct tag_hookfuncdesc
    7. {
    8. lpcstr szfunc; // the name of the function to hook.
    9. proc pproc; // the procedure to blast in.
    10. } hookfuncdesc , * lphookfuncdesc;
    11. // 这个函数监测当前系统是否是windownt
    12. bool isnt();
    13. // 这个函数得到hmodule -- 即我们需要截获的函数所在的dll模块的引入描述符(import descriptor)
    14. pimage_import_descriptor getnamedimportdescriptor(hmodule hmodule, lpcstr szimportmodule);
    15. // 我们的主函数
    16. bool hookimportfunction(hmodule hmodule, lpcstr szimportmodule,
    17. lphookfuncdesc pahookfunc, 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. return false;
    33. }
    34. //
    35. // 监测当前模块是否是在2gb虚拟内存空间之上
    36. // 这部分的地址内存是属于win32进程共享的
    37. if (!isnt() && ((dword)hmodule >= 0x80000000))
    38. {
    39. _assert(false);
    40. setlasterrorex(error_invalid_handle, sle_error);
    41. return false;
    42. }
    43. // 清零
    44. if (paorigfuncs) memset(paorigfuncs, nullsizeof(proc));
    45. // 调用getnamedimportdescriptor()函数,来得到hmodule -- 即我们需要
    46. // 截获的函数所在的dll模块的引入描述符(import descriptor)
    47. pimage_import_descriptor pimportdesc = getnamedimportdescriptor(hmodule, szimportmodule);
    48. if (pimportdesc == null)
    49. return false; // 若为空,则模块未被当前进程所引入
    50. // 从dll模块中得到原始的thunk信息,因为pimportdesc->firstthunk数组中的原始信息已经
    51. // 在应用程序引入该dll时覆盖上了所有的引入信息,所以我们需要通过取得pimportdesc->originalfirstthunk
    52. // 指针来访问引入函数名等信息
    53. pimage_thunk_data porigthunk = makeptr(pimage_thunk_data, hmodule,
    54. pimportdesc->originalfirstthunk);
    55. // 从pimportdesc->firstthunk得到image_thunk_data数组的指针,由于这里在dll被引入时已经填充了
    56. // 所有的引入信息,所以真正的截获实际上正是在这里进行的
    57. pimage_thunk_data prealthunk = 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_name pbyname = makeptr(pimage_import_by_name, hmodule,
    66. porigthunk->u1.addressofdata);
    67. // 如果函数名以null开始,跳过,继续下一个函数
    68. if ( /0 == pbyname->name[0])
    69. continue;
    70. // bdohook用来检查是否截获成功
    71. bool bdohook = 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_information mbi_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. dword dwoldprotect;
    96. _assert(virtualprotect(mbi_thunk.baseaddress, mbi_thunk.regionsize,
    97. mbi_thunk.protect, &dwoldprotect));
    98. setlasterror(error_success);
    99. return true;
    100. }
    101. }
    102. // 访问image_thunk_data数组中的下一个元素
    103. porigthunk++;
    104. prealthunk++;
    105. }
    106. return true;
    107. }
    108. // getnamedimportdescriptor函数的实现
    109. pimage_import_descriptor getnamedimportdescriptor(hmodule hmodule, lpcstr szimportmodule)
    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. return null;
    119. }
    120. // 得到dos文件头
    121. pimage_dos_header pdosheader = (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. return null;
    129. }
    130. // 取得pe文件头
    131. pimage_nt_headers pntheader = makeptr(pimage_nt_headers, pdosheader, pdosheader->e_lfanew);
    132. // 检测是否pe映像文件
    133. if (isbadreadptr(pntheader, sizeof(image_nt_headers)) ||
    134. (pntheader->signature != image_nt_signature))
    135. {
    136. _assert(false);
    137. setlasterrorex(error_invalid_parameter, sle_error);
    138. return null;
    139. }
    140. // 检查pe文件的引入段(即 .idata section)
    141. if (pntheader->optionalheader.datadirectory[image_directory_entry_import].virtualaddress == 0)
    142. return null;
    143. // 得到引入段(即 .idata section)的指针
    144. pimage_import_descriptor pimportdesc = makeptr(pimage_import_descriptor, pdosheader,
    145. pntheader->optionalheader.datadirectory[image_directory_entry_import].virtualaddress);
    146. // 穷举pimage_import_descriptor数组寻找我们需要截获的函数所在的模块
    147. while (pimportdesc->name)
    148. {
    149. pstr szcurrmod = makeptr(pstr, pdosheader, pimportdesc->name);
    150. if (stricmp(szcurrmod, szimportmodule) == 0)
    151. break; // 找到!中断循环
    152. // 下一个元素
    153. pimportdesc++;
    154. }
    155. // 如果没有找到,说明我们寻找的模块没有被当前的进程所引入!
    156. if (pimportdesc->name == null)
    157. return null;
    158. // 返回函数所找到的模块描述符(import descriptor)
    159. return pimportdesc;
    160. }
    161. // isnt()函数的实现
    162. bool isnt()
    163. {
    164. osversioninfo stosvi;
    165. memset(&stosvi, nullsizeof(osversioninfo));
    166. stosvi.dwosversioninfosize = sizeof(osversioninfo);
    167. bool bret = getversionex(&stosvi);
    168. _assert(true == bret);
    169. if (false == bret) return false;
    170. return (ver_platform_win32_nt == stosvi.dwplatformid);
    171. }

屏幕取词的实现方法(Windows 9x)相关推荐

  1. 金山词霸无法屏幕取词的解决方法

    金山词霸是个好软件啊, window server 2003sp1也是好系统, 不过他们俩在一起时就让我很郁闷了.昨天装了发现没法屏幕取词,结果让我ctrl+c, ctrl+v了一天. 今天实在受不了 ...

  2. 解决金山词霸和有道词典不能对pdf文档屏幕取词的方法

    解决金山词霸和有道词典不能对pdf文档屏幕取词的方法 http://www.docin.com/p-14101033.html

  3. Android金山屏幕取词,金山词霸屏幕取词方法分析

    随着使用英语的人数越来越多,它逐渐成为我们日常生活中交流.阅读的语言,现在我们出去,如果不懂点英语,还真的无法快乐地和其他小伙伴愉快的玩耍.可是自己平时工作或者学习的原因,根本没有时间静下心来去学习, ...

  4. 鼠标屏幕取词技术的原理和实现

    鼠标屏幕取词技术的原理和实现 "鼠标屏幕取词"技术是在电子字典中得到广泛地应用的,如四通利方和金山词霸等软件,这个技术看似简单,其实在WINDOWS系统中实现却是非常复杂的,总的来 ...

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

    转自: https://www.cnblogs.com/seacryfly/archive/2012/01/08/2316511.html 虽然屏幕取词技术早已经不是什么秘密,以至于除了汉化工具.翻译 ...

  6. 鼠标屏幕取词原理 (VC++)

    "鼠标屏幕取词"技术是在电子字典中得到广泛地应用的,如四通利方和金山词霸等软件,这个技术看似简单,其实在windows系统中实现却是非常复杂的,总的来说有两种实现方式:  第一种: ...

  7. “鼠标屏幕取词”技术

    "鼠标屏幕取词"技术是在电子字典中得到广泛地应用的,如四通利方和金山词霸等软件,这个技术看似简单,其实在WINDOWS系统中实现却是非常复杂的,总的来说有两种实现方式: 第一种:采 ...

  8. 金山词霸”屏幕取词技术揭密(讨论稿)

    金山词霸"屏幕取词技术揭密(讨论稿) 主题 屏幕取词技术系列讲座(一) 作者 亦东 很多人对这个问题感兴趣. 原因是这项技术让人感觉很神奇,也很有商业价值. 现在词典市场金山词霸占了绝对优势 ...

  9. c# 实现金山词霸一样的屏幕取词

    c# 屏幕取词想做金山词霸一样的屏幕取词 告诉你两个方法: 第一: 在金山词霸中2005中带了一个XdictGrb.dll,添加引用 然后下面是代码 using System; using Syste ...

最新文章

  1. Linux添加新硬盘、分区、格式化、自动挂载
  2. 如何招聘一个优秀的产品经理?Google主管的六条心得
  3. Java 集合系列17之 TreeSet详细介绍(源码解析)和使用示例
  4. 【转】VC6.0附带小工具软件一览
  5. JS高级-自执行函数-垃圾回收机制及内存管理
  6. Oracle Golden Gate 系列十四 -- 监控 GG 状态 说明
  7. 【bzoj1911】 Apio2010—特别行动队
  8. 牛客练习赛 60(待补E-长链剖分或者dsu)
  9. php获取微信小程序用户头像,微信小程序获取用户头像+昵称+openid,小程序登录!附前端后端源码!...
  10. 数据结构上机实践第九周项目3 - 利用二叉树遍历思想解决问题
  11. liunx中安装软件的几种方式
  12. perl脚本进制转换
  13. 8250cdn清零 lh_兄弟 HL-L8250CDN驱动
  14. emoji无法显示_微信昵称emoji表情,特殊表情导致列表不显示,导出EXCEL报错等问题解决!...
  15. python打印等腰三角形_Python 打印各种三角形
  16. J2EE体系架构设计
  17. 灰色预测模型【GM(1,1)模型】 【matlab代码】
  18. javaMail 发送邮件 标题过长出现乱码
  19. 数学奥赛VS信息奥赛 | 为孩子选择更好地升学之路!
  20. STM8L151 使用硬件SPI驱动W25Q16 Flash

热门文章

  1. 2019年5G手机大爆发:目前已有9款,哪款最值得入手?
  2. mysql数据库中的存储引擎
  3. 全栈必备 网络编程基础
  4. python零基础cap_零基础学Python语言CAP
  5. 微信支付同时回调多次的解决办法
  6. templatespider_v2.2 扒网站工具软件下载(含模版计算工具)
  7. 计算机基本操作(一) ——连接双显示器
  8. textrank 算法
  9. 关于Aegisub的插件开发工作及个人看法
  10. 【精简推导】支持向量机(拉格朗日乘子法、对偶函数、KKT条件)