<font color=red><big><big><big>$300</big></big&

  • 主题发起人 主题发起人 hwave
  • 开始时间 开始时间
H

hwave

Unregistered / Unconfirmed
GUEST, unregistred user!
&lt;font color=red&gt;&lt;big&gt;&lt;big&gt;&lt;big&gt;$300&lt;/big&gt;&lt;/big&gt;&lt;/big&gt;&lt;/font&gt; &lt;font color=black&gt;求 :有关屏幕取词的例程(DELPHI)、文献等等(300分)<br />&lt;pre class="text"&gt;论坛中有关屏幕取词的问题已看过,并下载了"GUIhook95"(感谢pegasus),可小弟的'C' 是
菜鸟级的,本人的脾气有点爱钻死牛角 @_@,有了问题非要搞个明白,咳,积习难改,请大家多多关照:
&lt;font color=blue&gt;&lt;big&gt; 有关屏幕取词的东东,来者不拒,多多益善 . &lt;a href="mailto:hwave@etang.com"&gt;这是小弟 的Email&lt;/a&gt;
&lt;/big&gt;&lt;/font&gt;
 
既然“GUIhook95”你都有了就不多讲了。主要是16 to 32的那里比较难理解。
 
http://cheka.163.net/mywebstuff/articles/others/屏幕抓字技术揭密.txt

http://cheka.163.net/mywebstuff/articles/win/hook.htm
 
不管你用Ring0的vxd,还是Ring3的dll.
设一个钩子,HOOK到 outtext 函数,windows的屏幕上的所有文字信息
多数都由你来控制了。
不过,vxd比dll的效率可是高多了!
 
GUIhook95到哪下载????我在
ftp://202.120.100.49/VCL/new/GUIhook95.zip
下载不了。。请各位告知。。谢谢。
也可以&lt;a href=mailto:mynameisbb@sina.com&gt;妹给我。&lt;/a&gt;
 
我正在作一个例子,请稍等几天.
 
http://grwy.online.ha.cn/happydelphi/pmzhz.htm
 
Croco介绍的地方不错。
另:给爱好DELPHI的朋友推荐本好书:人民邮电出版社出版的 《Delphi 4 开发大全》(cd)上下 徐新华译,
共1586页,175元(身价太高),特点:在其他书籍很少提及的win32 API 、GDI、消息、动态链接库、
外部设备接口、COM、编写控件等都有比较细的内容,例子非常丰富。
 
&lt;h1&gt;test&lt;/h1&gt;
 
Delphi 是作不了虚拟设备程序的(.vxd)
再说NT已不在使用VXD.

但是可以用Delphi的TDump.exe看一下《金山词霸》《MagicWin》《南极星》
中的EXE,DLL,VXD他们调用的API可帮上忙!
 
这个问题好像以前问过。
在&lt;&lt;电脑商情报&gt;&gt;上有的。可惜我把报纸放家里了,不然可以贴上来。你也可以查一
下以前回答过的问题。
 
屏幕抓词的技术实现


  屏幕上的文字大都是由gdi32.dll的以下几个函数显示的:TextOutA、TextO

utW、ExtTextOutA、
ExtTextOutW。实现屏幕抓词的关键就是截获对这些函数的调用,得到程序发给他

们的参数。

  我的方法有以下三个步骤:

  一、得到鼠标的当前位置

  通过SetWindowsHookEx实现。

  二、向鼠标下的窗口发重画消息,让它调用系统函数重画

  通过WindowFromPoint,ScreenToClient,InvalidateRect 实现。

  三、截获对系统函数的调用,取得参数(以TextOutA为例)

  1.仿照TextOutA作成自己的函数MyTextOutA,与TextOutA有相同参数和返回

值,放在系统钩子所在
的DLL里。

  SysFunc1=(DWORD)GetProcAddress(GetModuleHandle("gdi32.dll"),"TextO

utA");

  BOOL WINAPI MyTextOutA(HDC hdc, int nXStart, int nYStart, LPCSTR l

pszString,int cbString)

  { //输出lpszString的处理

return ((FARPROC)SysFunc1)(hdc,nXStart,nYStart,lpszString,cbString);}



  2.由于系统鼠标钩子已经完成注入其它GUI进程的工作,我们不需要为注入再

做工作。

  如果你知道所有系统钩子的函数必须要在动态库里,就不会对“注入”感到

奇怪。当进程隐式或显式
调用一个动态库里的函数时,系统都要把这个动态库映射到这个进程的虚拟地址

空间里(以下简称“地址空
间”)。这使得DLL成为进程的一部分,以这个进程的身份执行,使用这个进程的

堆栈(见图1)。


  图1 DLL映射到虚拟地址空间中

  对系统钩子来说,系统自动将包含“钩子回调函数”的DLL映射到受钩子函数

影响的所有进程的地址
空间中,即将这个DLL注入了那些进程。

  3.当包含钩子的DLL注入其它进程后,寻找映射到这个进程虚拟内存里的各个

模块(EXE和DLL)的
基地址。EXE和DLL被映射到虚拟内存空间的什么地方是由它们的基地址决定的。

它们的基地址是在链接
时由链接器决定的。当你新建一个Win32工程时,VC++链接器使用缺省的基地址

0x00400000。可以通
过链接器的BASE选项改变模块的基地址。EXE通常被映射到虚拟内存的0x0040000

0处,DLL也随之有
不同的基地址,通常被映射到不同进程的相同的虚拟地址空间处。

  如何知道EXE和DLL被映射到哪里了呢?

  在Win32中,HMODULE和HINSTANCE是相同的。它们就是相应模块被装入进程的

虚拟内存空间的
基地址。比如:

  HMODULE hmodule=GetModuleHandle(″gdi32.dll″);

  返回的模块句柄强制转换为指针后,就是gdi32.dll被装入的基地址。

  关于如何找到虚拟内存空间映射了哪些DLL?我用如下方式实现:

while(VirtualQuery (base, &mbi, sizeof (mbi))〉0)

{ if(mbi.Type==MEM—IMAGE)

ChangeFuncEntry((DWORD)mbi.BaseAddress,1);

base=(DWORD)mbi.BaseAddress+mbi.RegionSize; }

  4.得到模块的基地址后,根据PE文件的格式穷举这个模块的IMAGE—IMPORT—

DESCRIPTOR数组,
看是否引入了gdi32.dll。如是,则穷举IMAGE—THUNK—DATA数组,看是否引入了

TextOutA函数。

  5.如果找到,将其替换为相应的自己的函数。

  系统将EXE和DLL原封不动映射到虚拟内存空间中,它们在内存中的结构与磁

盘上的静态文件结构
是一样的。即PE (Portable Executable) 文件格式。

  所有对给定API函数的调用总是通过可执行文件的同一个地方转移。那就是一

个模块(可以是EXE或
DLL)的输入地址表(import address table)。那里有所有本模块调用的其它DLL的

函数名及地址。对其它DLL
的函数调用实际上只是跳转到输入地址表,由输入地址表再跳转到DLL真正的函数

入口。例如:


  图2 对MessageBox()的调用跳转到输入地址表,从输入地址表再跳转到Mess

ageBox函数



  IMAGE—IMPORT—DESCRIPTOR和IMAGE—THUNK—DATA分别对应于DLL和函数。

它们是PE
文件的输入地址表的格式(数据结构参见winnt.h)。

  BOOL ChangeFuncEntry(HMODULE hmodule)

  { PIMAGE—DOS—HEADER pDOSHeader;

  PIMAGE—NT—HEADERS pNTHeader;

  PIMAGE—IMPORT—DESCRIPTOR pImportDesc;

/?get system functions and my functions′entry?/

  pSysFunc1=(DWORD)GetProcAddress(GetModuleHandle(″gdi32.dll″),″T

extOutA″);

  pMyFunc1= (DWORD)GetProcAddress(GetModuleHandle(″hookdll.dll″),″

MyTextOutA″);

pDOSHeader=(PIMAGE—DOS—HEADER)hmodule;

  if (IsBadReadPtr(hmodule, sizeof(PIMAGE—NT—HEADERS)))

   return FALSE;

  if (pDOSHeader-〉e—magic != IMAGE—DOS—SIGNATURE)

   return FALSE;

  pNTHeader=(PIMAGE—NT—HEADERS)((DWORD)pDOSHeader+(DWORD)pDOSHead

er-〉e—
lfanew);

  if (pNTHeader-)Signature != IMAGE—NT—SIGNATURE)

   return FALSE;

  pImportDesc = (PIMAGE—IMPORT—DESCRIPTOR)((DWORD)hmodule+(DWORD)

pNTHeader
-)OptionalHeader.DataDirectory

   [IMAGE—DIRECTORY—ENTRY—IMPORT].VirtualAddress);

  if (pImportDesc == (PIMAGE—IMPORT—DESCRIPTOR)pNTHeader)

return FALSE;

  while (pImportDesc-)Name)

  { PIMAGE—THUNK—DATA pThunk;

  strcpy(buffer,(char?)((DWORD)hmodule+(DWORD)pImportDesc-)Name))

;

CharLower(buffer);

if(strcmp(buffer,"gdi32.dll"))

{ pImportDesc++;

continue;

}else

{ pThunk=(PIMAGE—THUNK—DATA)((DWORD)hmodule+(DWORD)pImportDesc-)Fi

rstThunk);

while (pThunk-)u1.Function)

{ if ((pThunk-)u1.Function) == pSysFunc1)

{ VirtualProtect((LPVOID)(&pThunk-)u1.Function),

   sizeof(DWORD),PAGE—EXECUTE—READWRITE, &dwProtect);

   (pThunk-)u1.Function)=pMyFunc1;

   VirtualProtect((LPVOID)(&pThunk-)u1.Function), sizeof(DWORD),dw

Protect,&temp); }

pThunk++; } return 1;}}}

  替换了输入地址表中TextOutA的入口为MyTextOutA后,截获系统函数调用的

主要部分已经完成,当
一个被注入进程调用TextOutA时,其实调用的是MyTextOutA,只需在MyTextOutA

中显示传进来的字符
串,再交给TextOutA处理即可。



 
1 屏幕抓词
  屏幕抓词(或者叫动态翻译)是指随着鼠标的移动,软件能够随时获知屏幕上鼠标位置的单词或汉字,并翻译出来提示用户。它对於上网浏览、在线阅读外文文章等很有帮助作用,因此许多词典软件都提供了屏幕抓词功能。
  屏幕抓词的关键是如何获得鼠标位置的字符串,Windows的动态链接和消息响应机制为之提供了实现途径。 概括地说,主要通过下面的几个步骤来取得屏幕上鼠标位置的字符串:
符串:
  . 代码拦截:Windows以DLL方式提供系统服务,可以方便地获取Windows字符输出API的地址,修改其入口代码,拦截应用程序对它们的调用。
  . 鼠标HOOK:安装WH-MOUSEPROC类型的全局鼠标HOOK过程,监视鼠标在整个屏幕上的移动。
  . 屏幕刷新:使鼠标周围一块区域无效,并强制鼠标位置的窗口刷新屏幕输出。窗口过程响应WM-NCPAINT和WM-PAINT消息,调用ExtTextOut/TextOut等字符输出API更新无效区域里面的字符串。这些调用被我们截获,从堆栈里取得窗口过程传给字符API的参数,如字符串地址、长度、输出坐标、HDC、裁剪区等信息。
2 Windows 95/98的字符输出方法
  Windows95/98不是一个纯32位的操作系统,它从16位操作系统发展而来,为了保持兼容,其内部仍然是32位和16位代码的混合体。系统通过gdi.exe 、user.exe和krnl386.exe提供16位API,供16位程序调用;通过gdi32.dll、user32.dll和kernel32.dll提归结如下:
图1 Windows 95/98的字符输出机制
  . 16位程序通过16位gdi.exe的ExtTextOut/TextOut函数输出字符;
  . 32位程序通过gdi32.dll的ExtTextOutA/TextOutA输出ANSI格式的字符,通过ExtTextOutW/TextOutW输出UNICODE格式的字符;
  . 32位的ExtTextOutA/TextOutA转换到16位的ExtTextOut/TextOut,完成ANSI格式字符的输出;
  . 32位的ExtTextOutW/TextOutW转换到16位gdi.exe的两个未公开API,完成UNICODE格式字符的输出。为方便叙述,本文对这两个未公开API称为ExtTextOut16W和TextOut16W。
t16W。
  因此,只要拦截四个16位函数:TextOut、ExtTextOut、TextOut16W和ExtTextOut16W,就能截获32位和16位应用程序的所有字符串输出。
3 拦截字符输出API
3.1 代码拦截的基本思路
  为了实现对ExtTextOut/TextOut等API的拦截,需要在函数入口放入一条动态生成的"JMP&lt;替代函数&gt;"指令,JMP的操作数是我们提供的一个拦截替代函数的地址。当该API被调用时,JMP指令首先执行,跳转到替代函数。替代函数负责从堆栈中获取参数,计算字符串坐标,分出鼠标位置的单词等工作。执行完成后,替代函数再调用原来的被拦截函数,完成正常的字符输出,然后返回。 图2表明了应用程序对TextOut的正常调用流程和TextOut被拦截后的流程。
a)
① 程序调用TextOut;
② 从TextOut返回程序。
① 程序调用TextOut(其入口已经被修改);
② 转入拦截替代函数myTextOut;
③ 从myTextOut调用原来的TextOut;
④ 从TextOut返回myTextOut;
⑤ 从myTextOut返回程序。
图2 对TextOut的正常调用流程
  和TextOut被拦截后的流程
3.2 提供拦截替代函数
  拦截替代函数插入被拦截API的流程中执行,需要有相同的参数和返回值原型。以对  拦截替代函数插入被拦截API的流程中执行,需要有相同的参数和返回值原型。以对TextOut的拦截为例,下面是替代函数myTextOut的伪代码:
BOOL myTextOut(HDC hdc,int x,int y,LPSTR lpstr,int cbstr)
{
DoSpy(hDC,x ,y,lpstr,cbstr);
//保存参数,作抓词的所有工作
RestoreCode();//恢复TextOut入口原来的指令
TextOut(hDC,x,y,lpstr,cbstr);//调用原来的API
SpyCode();//再次放入JMP指令
return TRUE;
}
  函数首先调用DoSpy()来作抓词的具体工作,然后RestoreCode()函数恢复被拦截函
数入口的代码,再调用TextOut()执行正常的字符输出,接下来SpyCode()在被拦截函数
入口再次放入JMP指令,最后返回调用进程。
3.3 获取被拦截API的动态链接地址
  TextOut和ExtTextOut的地址可以通过GetProcAddress取得,而TextOut16W和ExtTextOut16W既未公开于文档,也没有用字符串或序号从gdi.exe中引出,无法使用GetProcAddress取得它们的地址。下面讨论如何解决这个问题。
  第一种方法:用softICE可以跟踪发现,ExtTextOut16W位于公开函数GetTextMetrics 入口的偏移20H处,TextOut16W位于GetTextMetrics的偏移7CH处。所以可以先取得GetTextMetrics的地址,再分别在加上20H和7CH,就是ExtTextOutW和TextOutW的地址。

  第二种方法:从指令里面析出地址。
  第二种方法:从指令里面析出地址。
  Windows 95/98维持一个DWORD类型的数组,称为32位-16位替换表,表中每个元素存
放了一个Win32 API对应的16位API的段:偏移地址。需要转入16位运行的Win32API使用
不同的索引来从该表格里面取目的地址。ExtTextOutW和TextOutW分别使用索引B2H和30
H,取表中相应元素作为ExtTextOut16W和TextOut16W的地址。取得这个数组的起始位置
,根据索引号即可找到16位函数的地址。由于这个数组是一个重定位项,在内存的地址
不固定,所以需要从指令流里面动态析出它的地址。
3.4 动态生成JMP指令
  动态生成的JMP指令占用5个字节,保存在一个数组里面。以生成跳转到TextOut的替
代函数myTextOut的JMP指令为例:
BYTE ins[5]; //保存JMP指令
ins[0] = 0xea; //操作码:1 byte
*(WORD *)(ins+1) = FP-OFF(myTextOut);
 //操作数:2byte的偏移
*(WORD *)(ins+3) = FP-SEG(myTextOut);
 //操作数:2byte的段
3.5 修改被拦截函数的入口
  Windows运行在保护模式下,16位进程采用了段保护机制,代码段描述符的属性被标记为只读。如果直接向被拦截函数的入口写入JMP指令,会引起CPU保护错误,因此在修改代码前要采用下面的方法绕过段保护机制:
  . 获取GDI代码段的基地址和界限;
  . 用当前DS复制一个新的段选择子,该选择子的描述符具有可读写的属性;
  . 将复制得到的选择子的基地址和界限设置为GDI代码段的基地址和界限;
  . 将复制得到的选择子的基地址和界限设置为GDI代码段的基地址和界限;
  . 用新的选择子作为段,同被拦截API的偏移组合成一个新的地址。
  这样就获得了一个指向被拦截代码的可写指针。先保存5字节内容,再将数组ins[
]复制到该地址,即完成对TextOut等API的拦截修改。
4 使用HOOK监视鼠标移动
  安装一个WH-MOUSEPROC类型的全局HOOK可以监视鼠标在全屏幕的移动。每当有鼠标事件产生,HOOK过程被调用,它判断出鼠标移动了后,就向主窗口发送消息,通知主窗口鼠标位置发生了变化。
  主窗口收到消息后,设置定时器,监视鼠标在某一位置停留时间的长短。如果鼠标停留超过设定的时间,才启动抓词功能,否则抓词功能保持禁止状态。这样可以减少代码拦截对系统性能的影响。
  全局HOOK过程需要放入DLL里面。
5 刷新屏幕输出
  预先创建一个小的工具窗口,当鼠标停留在某个位置超过设定时间后,使工具窗口在鼠标位置上显示一下,然后隐含掉,这样就会在目标窗口--鼠标位置的窗口产生无效区域。然后调用UpdateWindow强制目标窗口刷新屏幕输出。在响应WM-PAINT /WM-N
CPAINT中,目标窗口对字符输出API的调用将被我们截获和处理,完成一次抓词过程。
6 其它
  本文所介绍的Windows 95/98环境下屏幕抓词的原理和取得鼠标位置字符串的实现方法。对于进一步的探索,笔者提出下面的几点看法:
  . 只有通过响应WM-PAINT /WM-NCPAINT消息输出的字符串才会被捕捉到;
  . 有些软件(例如Internet Explorer)在刷新屏幕时,为了消除闪烁,不直接把字符串输出到屏幕DC,而是创建一个兼容的内存DC,先将字符串写到内存DC,再复制到效区域。然后调用UpdateWindow强制目标窗口刷新屏幕输出。在响应WM-PAINT /WM-N屏幕上去。由于内存DC坐标和屏幕DC坐标的不同,不能直接依赖内存DC的坐标来计算字符串的屏幕位置;
  . 本文讨论的方法不适用于Windows NT下的抓词。Windows NT的系统功能完全由32位代码提供,NT下的抓词需要拦截Win32 API。其实现机制同在Windows 95/98下也不一样。在NT里,KERNEL、USER、GDI模块位於进程的私有地址空间内,所以需要使用HOOK来将拦截替代函数动态映射到不同的被拦截进程的地址空间里面。而且由于控制台窗口禁止大多数HOOK,还需要创建远程线程来获取控制台窗口的字符输出。

 
本人用Delphi1.0成功改写了TextOut和ExtTextOut函数,有没有办法在32位系统
中改写系统函数?谢谢各位的关照
 
多人接受答案了。
 

Similar threads

D
回复
0
查看
1K
DelphiTeacher的专栏
D
I
回复
0
查看
603
import
I
I
回复
0
查看
800
import
I
后退
顶部