相邻肢液的两个单词单元之间没有间隔(这样使文件尽可能的小)。
打开方式:
用16位或者32位文件编辑器打开这类文神乎档
Windows *** 作系统的记事本(16位)可以打开,一些词典软件(如金山词霸)可以打开 “开心字游饥悉典”、“中文化精灵”、“Resource Modifier”等软件的“对照文本格式”支持这种.dic文件 560+等NP系列的专业词典的格式,只能在机型上使用;
打开词典软件,里边看有无添加词库选项。
屏幕取词技术揭密(讨论稿)?
主题 屏幕取词技术系列讲座(一)
作者 亦东
很多人对这个问题感兴趣。
原因是这项技术让人感觉很神奇,也很有商业价值。
现在词典市场金山词霸占了绝对优势,所以再做字典也没什么前途了。我就是这么认为的,所以我虽然掌握了这项技术,却没去做字典软件。只做了一个和词霸相似的软件自己用,本来想拿出来做共享软件,但我的词库是“偷”来的,而且词汇不多,所以也就算了,词库太小,只能取词有什么用呢?而且词霸有共享版的。
但既然很多人想了解这项技术,我也不会保留。我准备分多次讲述这项技术的所有细节。
大约每周一两次。想知道的人就常常来看看吧!
一.基础知识
首先想编这种程序需要一些基础知识。
会用Vc++,包括16/32位。
精通Windows API特别是GDI,KERNEL部分。
懂汇编语言,会用softice调试程序,因为这种程序最好用softice调试。
二.基本原理
在Window 3.x时代,windows系统提供的字符输出函数只有很少的几肆肢个。
TextOut
ExtTextOut
DrawText
......
其中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.net
主题 屏幕取词技术系列讲座(二)
作者 亦东
很抱歉让大家久等了!
我看了一些人的回帖,发现很多人对取词的原理还是不太清楚。
首先我来解释一下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...
?
主题 关于屏幕取词的讨论(三)
作者 亦东
让大家久等,很抱歉,前些时候工作忙硬盘又坏了,太不幸了。
这回来点真格的。
咱们以截取TextOut为例。
下面是代码:
//截取TextOut
typedef 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=0i<5i++){
Address[i]=NewValue[i]
}
bHookAlready=TRUE
}
}
BOOL HookOff()
{
if(bHookAlready){
for(int i=0i<5i++){
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.",切记,否则会系统崩溃。
有什么不明白的可以给我写信
yeedong@163.net
屏幕取词核心内幕屏幕取词核心内幕
本文只对与几个关键性技术的实现细节进行讨论,其它的编程细节,请参考源程序。
32位到16位的形式替换
32位代码与16位代码的数据交换
动态修改Windows内核
1. 32bit到16bit的形式替换(Thunk)
形式替换是指那些允许从16位代码调用32位代码或从32位代码调用16位代码的技术。形式替换用于解决试图在同一 *** 作系统或同一可执行程序上使16位代码与32位代码同时并存的问题,即16位代码与32位代码的混合编程技术。早期的DOS程序及Window3.x上的应用程序均为16位程序,Windows95及Windows NT虽然也可运行旧的16位程序,但它们的主流发展方向是32位应用程序。与Windows NT不同的是,Windows95不是一个“纯”32位 *** 作系统,为了兼有令人满意的速度和与旧的16位程序的良好兼容性,其内核本身就是一个16位与32位的混合体,因此也为编程者使用形式替换提供了便利。Microsoft为编写形式替换程序提供了通用的接口及工具,但因LTW32中的形式替换并不复杂,所以使用了一些编程技巧,而避免了使用Microsoft复杂的开发工具。
形式替换中最主要的问题有两点:①16位与32位数据类型尺寸的变化,如16位代码到32位代码的一个重要变化是整型数int的长度加倍了 ;②是堆栈 *** 作时,16位模式使用SS:SP堆栈指针控制栈顶,而在32位模式中使用ESP寄存器作堆栈指针控制栈顶。
在LTW32中,屏幕抓词功能由16位DLL实现,因而只需实现32位到16位的形式替换,为32位代码提供16位DLL的调用接口。CALL FWORD PTR是32位汇编代码的一种调用方法,它可让32位代码调用到16位代码。作为实现的关键,控制16位侧与32位侧各自的堆栈是编程的要点。
32位侧的调用代码:
_asm{
pusha
call fword ptr [func16bit] /* func16bit是16位被调用者的48位地址 */
popa
}
16位侧的调用代码:
int dummy
static char stack[8192] /* 16位代码的临时堆栈 */
static WORD stack_seg
static WORD prev_seg
static DWORD prev_ofs
static WORD prev_ds
_asm{
push ax
push bx
mov ax, ds
mov bx, seg dummy
mov ds, bx
mov stack_seg, bx
mov prev_ds, ax
pop bx
pop ax
mov prev_seg, ss
mov dword ptr prev_ofs, esp/* 保存32位堆栈指针 */
mov ss, stack_seg
mov sp, offset stack /* 设置16位堆栈指针 */
add sp, 8192
}
/* 此处加入16位代码要实现的功能 */
_asm{
mov ss, prev_seg
mov esp, dword ptr prev_ofs/* 恢复32位堆栈指针 */
mov ds, prev_ds
lea sp, word ptr [bp-2]
pop ds
pop bp
dec bp
db 66h
retf
}
由于调用中传递的参数有限,所以涉及的代码并不多,唯一比较复杂之处在16位侧,它临时转向一个16位堆栈,服务于来自32位的调用者的请求。在16位侧入口处,必须存放好32位调用者的32位堆栈的指针,并且在16位侧返回时恢复它。由代码可见,这个过程是不可重入的,即一次只支持一个调用者,由于调用者只有LTW32的32位侧代码,所以此限制可以满足。另外,在16位代码返回时,必须恢复32位调用者的48位堆栈指针(SS:ESP)而不是32位的CS:SP,而且必须用一个48位地址(16:32)远返回(66h RETF)到它的32位调用者。
处理好这些细节,实现32位到16位的形式替换实际上是很简单的。
2. 32位代码与16位代码的数据交换
32位代码使用16位段地址加32位线性地址(16:32)的地址形式,16位代码使用16位段选择符(selector)加16位偏移(16:16)的地址形式。在Windows 95的32位侧,段址28h是系统段,即通过CS=28h或DS=28h可以访问系统的4G空间,其它应用程序的地址空间都是映射到28h段的4G空间中。Windows95中所有32位进程的地址空间(共4G)的高2G(80000000H~FFFFFFFFH)全部映射到28:80000000~28:FFFFFFFFH。即这块区域是所有32位程序共享的。这里一般存放系统DLL、虚拟设备驱动程序(VxD)、内存映射文件、16位应用程序和16位全局堆等。最后一项很重要,这为32位代码与16位代码交换数据提供了一个简便的方法。因为16位程序的段选择符的基址即是其所映射的系统段中的线性地址,这样,只要能够得到这个线性地址,32位代码就可以轻易地访问到16位程序的数据(LTW32 的32位侧使用此方法从16位侧获得屏幕截获的信息)。而16位段选择符的线性基址可以通过使用系统调用GetSelectorBase()得到,具体实现可参考源程序。线性地址计算的例子如下:
16位地址:07F2:1234H
段选择符 07F2H的线性基址为:82F41300H
段选择符 07F2H的尺寸为:4000H
∵ 82F41300H + 1234H = 82F42534
∴ 对应的32位线性地址为28:82F42534H
3. 动态修改Windows内核
如前所述,Windows95不是一个“纯”32位 *** 作系统,其内核模块中的USER和GDI均是用16位代码实现的。USER32.DLL和GDI32.DLL只是16位的USER.EXE和GDI.EXE的32位调用接口。因此,如果屏幕截获程序用32位代码实现,则只能截获32位应用程序对USER32.DLL和GDI32.DLL的调用,无法截获16位应用程序对USER.EXE和GDI.EXE的调用,所以如果想截获所有应用程序(包括Windows95的桌面程序Explorer)中有关屏幕输出的系统调用,则应该用16位代码实现屏幕截获功能。这就是LTW32为什么不是“纯”32位应用程序的原因。LTW32主要截获两个系统调用TextOut()和ExtTextOut(),方法很简单,把这两个函数的头五个字节修改为一个JMP FAR 指令,使得对这两个函数的调用均转向屏幕截获程序。这就涉及到一个关键问题:动态修改Windows的代码。
在传统的DOS程序中,动态修改程序代码无任何困难,但在Windows中则不然,因为在Windows中,代码可被同一程序的多个实例(进程)共享,所以系统不允许应用程序动态的修改代码。在16位侧,内存的可读、写属性是与段选择符联系在一起的。段选择符基本上可分为两类:数据段选择符和代码段选择符。前者可读、可写、不可执行;后者可读、可执行、不可写。Windows提供了这两类段选择符相转换的系统调用。未公开的16位系统调用AllocCStoDSAlias()为给定的代码段选择符分配一个具有相同线性基址和尺寸的数据段别名(DS Alias)。通过DS别名可以对给定的代码段进行修改。AllocCStoDSAlias()的使用方法如下:
WORD (FAR PASCAL *AllocCStoDSAlias)(WORD)
AllocCStoDSAlias = GetProcAddress(
GetModuleHandle(“KERNEL”), ”ALLOCCSTODSALIAS”)
调用参数为给定的代码选择符,调用成功时返回一个线性基址和尺寸均与原代码选择符相同的DS别名。当不再使用此DS别名时,要用系统调用FreeSelector()把DS别名释放掉。
使用上述技术,就可实现动态修改Windows代码,从而改变GDI的系统调用TextOut()和ExtTextOut()的执行动作,实时地截获屏幕输出,为实现鼠标随动翻译提供可能。
把上述的32位到16位的形式替换、32位代码与16位代码的数据交换、动态修改Windows内核等技术综合应用在一起,配合单词查找算法和词组分析算法就可以实现鼠标随动翻译功能。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)