屏幕抓字技术揭密(欢迎灌水!)(50分)

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

hsw

Unregistered / Unconfirmed
GUEST, unregistred user!
屏幕抓字技术揭密
----------深入WINDOWS内部探险手记
郑州 马飞涛
一 公开它!
四通利方和金山词霸的用户都曾见识过屏幕抓字技术,鼠标指哪就翻译哪个单
词,这个技术看似简单,其实在WINDOWS系统中实现却是非常复杂和有趣的。 经过
半年多的艰辛探索,笔者终于破解了其中的秘密,并在今天决定公开它,这个人人
都曾见过但是却鲜有人知的秘密,这个只被几家软件公司垄断从未在公开的报刊资
料披露过只言片语的秘密!
回想这半年多的探索,其中浸润了多少笔者的苦闷与欢乐,绝望与兴奋,挫折
与收获,现在都终于有了结果:将屏幕抓字技术的秘密公开,献给孜孜不倦辛勤工
作的程序员们。如果这样做能为国产软件事业的发展效微薄之力,对笔者来说,也
是一桩快事!
二 初识屏幕抓字
最初知道屏幕抓字, 是在购买了〖英汉通〗软件之后。 当时笔者还只是一个
VISUAL BASIC 的初学者, 对 WINDOWS 系统内部的知识了解并不多, 认为在
WINDOWS系统中屏幕抓字的实现应该和DOS系统中的一样,调用一个DOS 中断取屏幕
上的字符或直接读显示内存的内容就可以了。
三 看似很简单,其实不然
随着对WINDOWS系统的认识不断深入,才发现问题并不象想得那么简单。首先,
翻阅了WINODWS应用程序接口(API)中的上千个函数,并没有发现有一个现成的类
似于getWordFromPoint()的函数;根据使用经验,经过判断发现屏幕抓字采用的也
不是图像识别技术;翻阅了SDK的联机文档中没有,DDK的联机文档中也没有;显示
卡编程接口的资料则很难获得,有的也只是CGA到VGA显存的基本知识。回想当时坐
在机子前,面对一屏屏的联机资料(如果是纸,将堆积如山),感觉就是在黑暗中
的大海里航行,没有方向,没有灯光,但强烈的兴趣紧抓着我,一定要把这个谜解
开。
四 选择合适的编程工具
突然又有了一些新的想法:
可否试着截获WINDOWS中关于字符的消息呢?
DC(设备描述表)到底是什么?
WINDOWS的TextOut函数是否将TEXT放在DC的某个单元中?
显然,用VISUAL BASIC就力不从心了。在DOS中用TURBO C编程笔者还算熟练,
因此先尝试用VISUAL C++,但是奇慢的编译速度使人难以忍受, 高度抽象的类让
人一头雾水,开发商务软件可能还行,但开发这样一个深入WINDOWS 内部的系统软
件,望着一堆缠绕不清的类和消息,真有点牛刀宰鸡、刺刀耕田的感觉。
最后选择了DELPHI,第一印象是编译速度真快,在我的祖父型386 机子上编
译一个WINDOWS程序,速度和用TURBO
C的速度感觉差不多,真让人兴奋得爱不释手。
随着不断使用,发觉DELPHI真是一个好的快速开发工具,(快速并不意味着简单粗
糙,而是和WINDOWS系统有混然一体良好接口的表现)让初学者也很容易上手。 调
用各种WINDOWS 函数(包括很多未公开的函数)都非常直接迅速,用它来作开发工
具,大有刺刀见红、一剑封喉的痛快感觉。
五 山穷水尽疑无路
随着对WINDOWS系统了解的深入,我逐渐明白了在向屏幕输出文字时,WINDOWS
系统仅仅只是对某个应用程序发送WM_PAINT消息,告诉该应用程序窗口用户区已经
“无效”而需要重画。具体的“绘制”工作(选择字体,颜色,文字)由应用程序
完成。
应用程序在处理WM_PAINT消息时,调用begin
Paint和EndPaint来获得和释放设
备描述表,调用DrawText、ExtTextOut、 TextOut等函数在设备描述表中“绘制”
文字。
应用程序“绘制”文字, 就象学生(应用程序)奉命(获得 WM_PAINT消息)
用老师(WINDOWS)提供的画笔(DrawText ExtTextOut TextOut等) 在黑板上画画
一样,虽然大家能看到画的是什么字,但是画笔作为绘图工具并不知道画的是什么。
老师(WINDOWS)不知道学生(应用程序)到底用什么字体,颜色,画哪些文字。
   总之 ,WINDOWS并不知道应用程序“绘制”的是什么。“文字”对 WINDOWS
来说只是画笔留在黑板(屏幕)上的 郾视。 只是绘画的 奂!! 文字”只存在于
应用程序的模块中,对WINDOWS系统是“不可见”的。
到处走投无路,真想掂5000块钱,跑到“英汉通”公司买回这个秘密。仔细一
想,钱太少,就是多掂10倍,人家也不一定说。
六 柳暗花明又一村
经过再三考虑,我联想到在DOS系统中编程,会采取改变中断向量地址, 设置
新的中断向量的技术:如果系统调用这个中断,就会先进入新的中断服务程序,然
后再调用原来的中断服务程序。
那末,在WINDOWS系统中也采取这种技术,使系统如果调用某个函数, 先进入
一个跟踪函数,取得原函数的参数,再调用原来的函数。听起来是否象病毒传染和
发作?其实很多程序都采用过类似技术。大学毕业设计声卡时我就用过。
至此, 我认识到应该放弃常规的思路, 采取一些技巧, 截获 TextOut 、
ExtTextOut等函数,使之转向我的跟踪函数,在此查看应用程序(学生)的堆栈中
传递给画笔(TextOut、ExtTextOut等函数)的参数, 从而获得应用程序要在屏幕
上写的“文字”。
七 “ 屏幕抓字”的实现
1 用SetWindowsHookEx()安装鼠标钩子MouseProc;
2 在屏幕上移动鼠标时,系统就会调用鼠标钩子MouseProc;
3 进入MouseProc,获得鼠标的坐标(x,y),
设置对TextOut()、ExtTextOut()等的跟踪程序,
用invalidateRect()告诉系统该点(x,y)“失效”;

系统发出WM_PAINT消息,指示该点(x,y)处的应用程序重绘“失效”的区域。
5 负责绘制该点()的应用程序在受到 WM_PAINT 消息后, 就有机会调用
TextOut()、 ExtTextOut()等函数。
6 调用的函数被拦截进入跟踪程序:设置好了的跟踪程序截获了该次调用,从
应用程序的堆栈中取出 该点(x,y)“文字”的指针;
7 从应用程序的数据段中将“文字”指针的内容取出,即完成了一次“屏幕抓
字”;
8 退出跟踪程序,返回到鼠标钩子MouseProc;
9 在MouseProc中解除对TextOut() ExtTextOut()的跟踪;
10 退出MouseProc鼠标钩子程序,控制权交给系统。
11 在屏幕上移动鼠标,开始下一次“屏幕抓字”,返回步骤2。
八 前景展望
掌握了“屏幕抓字”的技术秘密,稍加改变,我们就可对WINDOWS 系统中的
任意一个函数调用进行动态地拦截、跟踪、修改和恢复,就可让WINDOWS 系统中的
任意一个函数按我们的设想工作,就可构造自己的外挂汉字平台,设计改变字体的
放大镜、改变颜色的变色镜,保护视力的软件视保屏等等。
九 后记
希望此文能抛砖引玉,为大家编程时能找到捷径,开拓出新的思路;
对拦截、跟踪感兴趣的朋友也请来信交流切磋,如需DLL 或“抓字”的源代
码,敬请
与 mafeitao@371.net 联系;
如能得到“四通利方”、“金山词霸”、“英汉通”等公司的教导与指正,
笔者不胜感激。
----------------------------
作者:马飞涛
地址:郑州市经七路23号 河南省粮油食品进出口公司 450002
Email: mafeitao@371.net
 
高手,有韧劲
 
呵呵,这篇文章在bbs上都有几年了。
 
用Delphi实现远程屏幕抓取
---- 在网络管理中,有时需要通过监视远程计算机屏幕来了解网上微机的使用
情况。虽然,市面上有很多软件可以实现该功能,有些甚至可以进行远程控制,
但在使用上缺乏灵活性,如无法指定远程计算机屏幕区域的大小和位置,进而无
法在一屏上同时监视多个屏幕。其实,可以用Delphi自行编制一个灵活的远程屏
幕抓取工具,简述如下。
---- 一、软硬件要求。
---- Windows95/98对等网,用来监视的计算机(以下简称主控机)和被监视的
计算机(以下简称受控机)都必须装有TCP/IP 协议,并正确配置。如没有网络
,也可以在一台计算机上进行调试。
---- 二、实现方法。
---- 编制两个应用程序,一个为VClient.exe,装在受控机上,另一个为
VServer.exe,装在主控机上。VServer.exe指定要监视的受控机的IP地址和将要
在受控机屏幕上抓取区域的大小和位置,并发出屏幕抓取指令给VClient.exe,
VClient.exe得到指令后,在受控机屏幕上选取指定区域,生成数据流,将其发
回主控机,并在主控机上显示出抓取区域的BMP图象。由以上过程可以看出,该
方法的关键有二:一是如何在受控机上进行屏幕抓取,二是如何通过TCP/IP协议
在两台计算机中传输数据。
---- UDP(User Datagram Protocol,意为用户报文协议)是Internet上广泛采用
的通信协议之一。与TCP协议不同,它是一种非连接的传输协议,没有确认机制
,可靠性不如TCP,但它的效率却比TCP高,用于远程屏幕监视还是比较适合的。
同时,UDP控件不区分服务器端和客户端,只区分发送端和接收端,编程上较为
简单,故选用UDP协议,使用Delphi 4.0提供的TNMUDP控件。
---- 三、创建演示程序。
---- 第一步,编制VClient.exe文件。新建Delphi工程,将默认窗体的Name属性
设为“Client”。加入TNMUDP控件,Name属性设为“CUDP”;LocalPort属性设
为“1111”,让控件CUDP监视受控机的1111端口,当有数据发送到该口时,触发
控件CUDP的OnDataReceived事件;RemotePort属性设为“2222”,当控件CUDP发
送数据时,将数据发到主控机的2222口。
---- 在implementation后面加入变量定义
const BufSize=2048;{ 发送每一笔数据的缓冲区大小 }
var
BmpStream:TMemoryStream;
LeftSize:Longint;{ 发送每一笔数据后剩余的字节数 }
为Client的OnCreate事件添加代码:
procedure TClient.FormCreate(Sender: TObject);
begin
BmpStream:=TMemoryStream.Create;
end;

为Client的OnDestroy事件添加代码:
procedure TClient.FormDestroy(Sender: TObject);
begin
BmpStream.Free;
end;

为控件CUDP的OnDataReceived事件添加代码:
procedure TClient.CUDPDataReceived(Sender: TComponent;
NumberBytes: Integer;
FromIP: String);
var
CtrlCode:array[0..29] of char;
Buf:array[0..BufSize-1] of char;
TmpStr:string;
SendSize,LeftPos,TopPos,RightPos,BottomPos:integer;
begin
CUDP.ReadBuffer(CtrlCode,NumberBytes);{ 读取控制码 }
if CtrlCode[0]+CtrlCode[1]+CtrlCode[2]+CtrlCode[3]='show' then
begin
{ 控制码前4位为“show”表示主控机发出了抓屏指令 }
if BmpStream.Size=0 then
{ 没有数据可发,必须截屏生成数据 }
begin
TmpStr:=StrPas(CtrlCode);
TmpStr:=Copy(TmpStr,5,Length(TmpStr)-4);
LeftPos:=StrToInt(Copy(TmpStr,1,Pos(':',TmpStr)-1));
TmpStr:=Copy(TmpStr,Pos(':',TmpStr)+1,Length(TmpStr)
-Pos(':',TmpStr));
TopPos:=StrToInt(Copy(TmpStr,1,Pos(':',TmpStr)-1));
TmpStr:=Copy(TmpStr,Pos(':',TmpStr)+1,Length(TmpStr)-
Pos(':',TmpStr));
RightPos:=StrToInt(Copy(TmpStr,1,Pos(':',TmpStr)-1));
BottomPos:=StrToInt(Copy(TmpStr,Pos(':',TmpStr
)+1,Length(TmpStr)-Pos(':',TmpStr)));
ScreenCap(LeftPos,TopPos,RightPos,BottomPos);
{
截取屏幕 }
end;
if LeftSize>BufSize then
SendSize:=BufSize
else
SendSize:=LeftSize;
BmpStream.ReadBuffer(Buf,SendSize);
LeftSize:=LeftSize-SendSize;
if LeftSize=0 then
BmpStream.Clear;{ 清空流 }
CUDP.RemoteHost:=FromIP;
{ FromIP为主控机IP地址 }
CUDP.SendBuffer(Buf,SendSize);
{ 将数据发到主控机的2222口 }
end;
end;

其中ScreenCap是自定义函数,截取屏幕指定区域,
代码如下:
procedure TClient.ScreenCap(LeftPos,TopPos,
RightPos,BottomPos:integer);
var
RectWidth,RectHeight:integer;
SourceDC,DestDC,Bhandle:integer;
Bitmap:TBitmap;
begin
RectWidth:=RightPos-LeftPos;
RectHeight:=BottomPos-TopPos;
SourceDC:=CreateDC('DISPLAY','','',nil);
DestDC:=CreateCompatibleDC(SourceDC);
Bhandle:=CreateCompatibleBitmap(SourceDC,
RectWidth,RectHeight);
SelectObject(DestDC,Bhandle);
BitBlt(DestDC,0,0,RectWidth,RectHeight,SourceDC,
LeftPos,TopPos,SRCCOPY);
Bitmap:=TBitmap.Create;
Bitmap.Handle:=BHandle;
BitMap.SaveToStream(BmpStream);
BmpStream.Position:=0;
LeftSize:=BmpStream.Size;
Bitmap.Free;
DeleteDC(DestDC);
ReleaseDC(Bhandle,SourceDC);
end;
存为“C:/VClient/ClnUnit.pas”和“C:/VClient/VClient.dpr”,
并编译。

---- 第二步,编制VServer.exe文件。新建Delphi工程,将窗体的Name属性设为
“Server”。加入TNMUDP控件,Name属性设为“SUDP”;LocalPort属性设为“
2222”,让控件SUDP监视主控机的2222端口,当有数据发送到该口时,触发控件
SUDP的OnDataReceived事件;RemotePort属性设为“1111”,当控件SUDP发送数
据时,将数据发到受控机的1111口。加入控件Image1,Align属性设为“
alClient”;加入控件Button1,Caption属性设为“截屏”;加入控件Label1,
Caption属性设为“左:上:右:下”;加入控件Edit1,Text属性设为“
0:0:100:100”;加入控件Label2,Caption属性设为“受控机IP地址”;加入控
件Edit2,Text属性设为“127.0.0.1”;
在implementation后面加入变量定义
const BufSize=2048;
var
RsltStream,TmpStream:TMemoryStream;
为Server的OnCreate事件添加代码:
procedure TServer.FormCreate(Sender: TObject);
begin
RsltStream:=TMemoryStream.Create;
TmpStream:=TMemoryStream.Create;
end;

为Client的OnDestroy事件添加代码:
procedure TServer.FormDestroy(Sender: TObject);
begin
RsltStream.Free;
TmpStream.Free;
end;

为控件Button1的OnClick事件添加代码:
procedure TServer.Button1Click(Sender: TObject);
var ReqCode:array[0..29] of char;ReqCodeStr:string;
begin
ReqCodeStr:='show'+Edit1.Text;
StrpCopy(ReqCode,ReqCodeStr);
TmpStream.Clear;
RsltStream.Clear;
SUDP.RemoteHost:=Edit2.Text;
SUDP.SendBuffer(ReqCode,30);
end;

为控件SUDP的OnDataReceived事件添加代码:
procedure TServer.SUDPDataReceived(Sender: TComponent;
NumberBytes: Integer;
FromIP: String);
var ReqCode:array[0..29] of char;ReqCodeStr:string;
begin
ReqCodeStr:='show'+Edit1.text;
StrpCopy(ReqCode,ReqCodeStr);
SUDP.ReadStream(TmpStream);
RsltStream.CopyFrom(TmpStream,NumberBytes);
if NumberBytes< BufSize then
{ 数据已读完 }
begin
RsltStream.Position:=0;
Image1.Picture.Bitmap.LoadFromStream(RsltStream);
TmpStream.Clear;
RsltStream.Clear;
end
else
begin
TmpStream.Clear;
ReqCode:='show';
SUDP.RemoteHost:=Edit2.Text;
SUDP.SendBuffer(ReqCode,30);
end;
end;

存为“C:/VServer/SvrUnit.pas”和
“C:/VServer/VServer.dpr”,并编译。
---- 四、测试。
---- 1、本地机测试:在本地机同时运行Vserver.exe和VClient.exe,利用程序
的默认设置,即可实现截屏。查看“控制面板”-“网络”-“TCP/IP”-“IP地
址”,将程序的“客户IP地址”设为该地址 ,同样正常运行。
---- 2、远程测试:选一台受控机,运行VClient.exe;另选一台主控机,运行
VServer.exe,将“受控机IP地址”即Edit2的内容设为受控机的IP地址,“截屏
”即可。以上简要介绍了远程屏幕抓取的实现方法,至于在主控机上一屏同时监
视多个受控机,读者可自行完善。以上程序,在Windows98对等网、Delphi 4.0
下调试通过。
 
还有吗?
 
有没有OCR的例程?
 
继续啊!
 
多人接受答案了。
 
马大虾的屏幕抓字解说让人耳目一新,可惜内容太抽象,能否将源代码公开,这样
才是真正的为中国程序员服务。
在这项技术中有两种hook技术,mousehook比较简单的,apihook很难的,请马大虾
不吝赐教;
另外,马大虾网页上的Worm.dcu好像我不会用的,也有限制的,我想向您购买源程
序,不知道应该如何联系。
 
各位好,我来晚了。不知道各位对这个问题还有没有兴趣。提供两个例子:
ftp://ftp.cs.pku.edu.cn/ProgramSource/DelphiCom/GUIhook95.zip
ftp://ftp.cs.pku.edu.cn/ProgramSource/DelphiCom/GUIhookNT.zip

Enjoy!
 
好像进不去也!
SNKoala 大侠帮忙看看,如能email 一份给我那就感激得 ........
ERROR
The requested URL could not be retrieved

An FTP authen
tication failure occurred while trying to retrieve the URL:
ftp://ftp.cs.pku.edu.cn/ProgramSource/DelphiCom/GUIhook95.zip
Squid sent the following FTP command:
PASS
and then
received this reply
Not logged in, access denied.
 
后退
顶部