轉貼:“金山詞霸”螢幕取詞技術揭密(討論稿) (17千字)

看雪資料發表於2001-11-01

“金山詞霸”螢幕取詞技術揭密(討論稿)



主題  螢幕取詞技術系列講座(一)
作者   亦東

很多人對這個問題感興趣。
原因是這項技術讓人感覺很神奇,也很有商業價值。
現在詞典市場金山詞霸佔了絕對優勢,所以再做字典也沒什麼前途了。我就是這麼認為的,所以我雖然掌握了這項技術,卻沒去做字典軟體。只做了一個和詞霸相似的軟體自己用,本來想拿出來做共享軟體,但我的詞庫是“偷”來的,而且詞彙不多,所以也就算了,詞庫太小,只能取詞有什麼用呢?而且詞霸有共享版的。
但既然很多人想了解這項技術,我也不會保留。我準備分多次講述這項技術的所有細節。
大約每週一兩次。想知道的人就常常來看看吧!

一.基礎知識
首先想編這種程式需要一些基礎知識。
會用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


Guest  1999-04-30 16:00:48 
請問用VC自己的DEBUGGER不行嗎?為什麼要用SOFTICE? 我沒用過SOFTICE,它有什麼特別之處嗎?


葫蘆  1999-04-30 19:15:03 
本人對這個問題也有興趣,以前研究過16位版本截獲TextOut和ExtTextOut的過程;

但對金山詞霸,用Softice跟蹤,發現SetWindowsHookEx在程式裝載時就安裝了滑鼠鉤子,暫停取字/恢復取字只是設定的內部變數,但本人發現金山詞霸並沒有象16位版本那樣修改TextOut和ExtTextOut。


蒼蠅 (555021552)  1999-05-02 08:56:57 
有哪位大蝦願意先介紹一下SOFTICE?


蟑螂  1999-05-04 13:58:22 
把金山詞霸的cjktl95.dll用tdump分析可以看到它根本用的不是hook,而是用了一些kernel32.dll中的Win32 SDK裡沒有包含的函式,使用這些函式來替換改寫原先的幾個GDI函式。看來是Win95未公開的一些東西。有些人知道,為什麼不肯說出來呢?未必見得多麼高深!



葫蘆  1999-05-05 23:28:07 
你可能沒有研究過它的目的碼,怎麼能斷言沒有使用hook呢? 其實金山詞霸一執行就安裝了滑鼠鉤子。


亦東  1999-05-07 09:52:42 
未必見得多麼高深?
你知道有些人是怎麼知道這些Win95未公開的東西的嗎?你不會是以為微軟偷偷告訴他們的吧?其實他們大多和我一樣是用softice自己跟蹤Win95跟出來的。

還有你從cjktl95.dll裡tdump出的未公開函式和hook無關,那是做別的用的。
setwindowshook在user裡


亦東  1999-05-10 16:16:14 
從cjktl95.dll裡tdump出的未公開函式和32位和16位之間的互調有關。

主題  螢幕取詞技術系列講座(二)
作者   亦東

很抱歉讓大家久等了!
我看了一些人的回帖,發現很多人對取詞的原理還是不太清楚。
首先我來解釋一下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...


葫蘆  1999-05-06 14:52:30 
你說的這個技術是16位的,至少金山詞霸III沒有采用16位的AllocCsToDsAlias函式,也根本沒有修改TextOut開始的語句為JMP XXXXXXXX。softice本人非常精通,是絕對不會錯的!


葫蘆  1999-05-06 15:38:40 
誰假冒吾名,壞吾名聲?!本人在此鄭重宣佈,以上貼子非本人所發!
經過跟蹤分析,金山詞霸III確實修改了TextOut的入口為轉跳到自身程式碼的JMP XXXXXXXX語句,只是修改沒有呼叫AllocCsToDsAlias,也不是在取字時才去修改,而是常常修改(估計是在Timer中進行的)。


亦東  1999-05-06 17:25:23 
我說過詞霸裡用AllocCsToDsAlias了嗎?
我是說AllocCsToDsAlias,的確可以用,但詞霸裡是用DPMI的Int 31做的,其實本質是一樣的,你用softice跟一下就知道了
bpint 31 設一箇中斷斷點。

實際上我至少有五種方法寫Windows程式碼段
我介紹使用AllocCsToDsAlias是因為這種方法最簡單,其他方法要麻煩得多。
詞霸用int 31來做是出於相容性的考慮,
AllocCsToDsAlias畢竟不是公開函式,但DPMI卻是標準。

詞霸在你滑鼠在某一點停留超過200ms時就會取詞,所以他有一個定時器,會經常修改API入口。

看來“假”葫蘆只是自以為非常精通softice.



mao  1999-05-06 19:29:28 
在微軟的MSDN中有一個程式,包含了全部的Source code, 名字好象叫"Inject" 或 stealth什麼的,忘了。提供了一個很完善的hook任何一個windows函式的功能。對win16完全適用,在win95下也有用,但NT下不行。

建議大家去找找這個例子看看會很有幫助。

此外有本清華出的微軟的Advanced Windows也介紹了具體的方法。

建議亦東干脆把source code給open出來,讓有興趣的朋友用起來更方便,把取詞技術可發展的應用發揚光大!


亦東  1999-05-07 09:40:56 
我不主張“把取詞技術可發展的應用發揚光大”,這也是我掌握了這項技術一年多才公開它的原因。我公開它並不是想讓大家都來做字典軟體,相反我希望不要再有人做字典了,現在做字典的人已經太多了。看到某種軟體有利可圖,大家就一哄而上,這種惡性競爭對中國軟體業的發展是極其有害的。我公開他的目的是希望提高大家的程式設計水平。你會發現在研究這項技術的過程中你的程式設計水平和對Windows的理解程度會有質的飛躍,我本人就從中獲益匪淺。

MSDN中是有這樣的程式碼。
甚至有一個叫ProcHook.Dll,提供SetProcAddress之類的函式,但是沒有原始碼。原始碼在1994年的 MSJ 上很難弄到的。
原始碼我會分幾次公開,每次會有詳細的說明。

想要原始碼的人,準備一份VC++1.52或Borland C++,最好還有softice,下次我會給出一段程式碼,教你如何修改Windows的程式碼。


老冒 (555036)  1999-05-07 11:56:46 
如果認為螢幕取詞的應用就是做字典,就大錯特錯了。其實關於攔劫windows api的東東早就在93年的Undocument Windows上公開過了。

其實Adobe的Adobe Type Manager在Windows 3.0的時代就透過這種辦法實現了漂亮的字型. (現在有TTF不需要ATM了)

MSDN上的那個東東是有全部source和sample, 我抓下來編譯過。是1996年夏天的一張MSDN Level 2光碟上的,現在也不知擱哪裡了,有興趣的朋友自己找去吧。

還是open 完整的source好,很多朋友其實只要用這項技術,並不太想知道細節,不是嗎.


亦東  1999-05-07 13:28:19 
這種技術不是做字典全屏漢化就是外掛語言平臺,自從王志東使用它以來,就沒用來編過其他軟體,也許有但我不知道。

原來有那麼多書和其他資料上都有這種技術的資料還有例子,到是我孤陋寡聞了,以為大家都不知道,在這裡給大家講一些眾所周知的東西。回去我要好好研究一下,看看Rasir Dex的詞霸是從那裡抄的。

我不知道某項技術中細節是不是重要,如果很多人只想用而不想自己編,那麼樓下那50多個回帖是怎麼回是?


老冒  1999-05-07 13:54:18 
呵呵,你可千萬要堅持把講座做下去,否則那50多個回應的哥們企不要把我給痛扁了...:)

俺已兩年多不碰底層技術的,這方面很落後啦...俺可應付不了這麼多熱切的求知朋友,亦東要頂住呵!

歡迎和俺多多交流探導!

P.S. 亦東大俠目前何方高就? 正在忙什麼專案?交流交流


亦東  1999-05-07 17:46:06 
沒什麼正經事做,到處瞎混呢!


葫蘆  1999-05-08 21:50:29 
我對此持否定觀點,不要自作聰明,以為AllocCsToDsAlias就是能用的,其實AllocCsToDsAlias只是16位的Windows用的函式,32位的Win95程式不能使用此函式,不信你在VC 5.0或6.0中可以試試。
另外,int 31h也不是說能用就能拿來用的,在Windows 3.x下使用是沒有問題的(本人還有這方面的文章發表),但在Win95下隨意使用會產生GP錯,主要原因是32位並不支援DPMI直接呼叫,不知亦東先生對此有沒有研究,就在此發表諸多理論!本人就先請問:32位程式如何呼叫16位函式或動態庫你懂不懂?


葫蘆  1999-05-09 02:39:10 
上面的這個帖子並沒有攻擊誰的意思,只是希望大家探討問題都要本著認真的態度,不要不懂裝懂,至少有一點大家要清楚:32位的程式根本不能使用 int 31h,呼叫16位的動態庫Kernel中的AllocCsToDsAlias也並不是件簡單的事。


nn_zdm (555031742)  1999-05-09 16:35:35 
使用hook函式,可用的功能並非只是做字典全屏漢化和外掛語言平臺。使用hook可以除錯程式,就象你們說的softice其本身也是使用了hook函式。


nn_zdm (555031742)  1999-05-09 16:42:05 
另外hook函式還可以使用在遊戲修改工具中,本人就開發過此類工具。《整人專家》估計也是使用這種方法。當然還有另外兩種方法。


亦東  1999-05-10 16:10:59 
你們說的都有道理。
但hook有兩種,一種是Windows標準鉤子,透過SetWindowshook掛。
另一種是非標準的,透過在API入口寫JMP XXXXXXXX來實現的。
softice的鉤子更高階,他都掛到VXD上了。
從32為程式碼呼叫16位DLL碰巧我會。

打倒米D國主義!!!


瓜果  1999-05-10 17:07:00 
誰知道在哪能搞到SOFTICE,我以前從未用過它!


葫蘆  1999-05-10 21:39:46 
願繼續拜讀你以後的講座。

SOFTICE嗎?光碟上很多,有for DOS, for Windows95, for Windows NT 各個版本。


孫瑋 (555031339)  1999-05-11 11:08:35 
能否將 si for NT 上傳到 10.82.46.33
(使用 ftp) user: haotao
pass: haotao123


tommy  1999-05-11 11:34:11 
http://www.swww.com.cn/htm/down/others/main.html 可以下載


亦東  1999-05-11 14:01:19 
最近忙於反美,暫時沒時間再寫了,過些時間才行,下次我會給出原始碼。
最新訊息,美國海軍被黑了。
http://www.nctsw.navy.mil/

打倒米D國主義!!!


黃金獅子  1999-05-12 13:19:32 
我對各位大蝦的討論深感興趣。

有幾個問題想請教:
1.AllocCsToDsAlias 在32-bit下呼叫是否採用Thunk?

2.32-bit 下是否有類似function?

3.Jeffrey Richter的"Advanced Windows"裡Remote Thread 的Thread Stack來遠端注入DLL函式,因此不需上述Function.

4.我有MSJ 1994-1的ProcHook.dll的source code,不知用於WinNT需如何改動.

5.總而言之,有無WinNT下hook API的source code,請告知.

(我還有Softice 3.24 for Win95, 3.25 for WinNT.)

--這個主題很好


鼴鼠  1999-05-14 09:41:20 
請大蝦給我發一份MSJ 1994-1的ProcHool.dll的source code, 我現在急需這方面的資料。謝謝!!

Email Address: yanshg@263.net


下面是一個Australia人的API hook軟體, 它是基於VxD技術。
Molten Home Page:
http://ourworld.compuserve.com/homepages/molten


黃金獅子  1999-05-14 14:34:54 
在Win95和NT上,可透過WriteProcessMemory()直接寫Code Segment.(原來以為"advanced Windows" 調CreateRemoteThread(),是因為WriteProcessMemory()只能寫程式碼段和堆疊段)


阿濤  1999-05-14 19:45:05 
如何亦東老兄要分步公佈是PROCHOOK的程式碼就不必了,這個程式的程式碼很容易搞到,只須到MSJ的站點上查一下就可找到。


亦東  1999-05-14 21:44:39 
大家到msj的大海里去撈針好了。

在95你WriteProcessMemory 寫kernel user gdi試試,一定失敗。

調CreateRemoteThread並不是為了寫程式碼段,有別的用途,是為了在其他程式裡分配記憶體。回去再好好看看“advanced Windows" 最好用一個程式試試,你就明白了。

其實在NT4.0調CreateRemoteThread是沒必要的,這是為了相容NT3.51.


nn_zdm (555031742)  1999-05-18 13:57:48 
利用CreateRemoteThread()函式,在WinNT4.0中使用很有用,它是在winNT中闖過程式邊界的三種辦法之一,在WinNT中,使用它可以進行遠端除錯,及修改他人程式碼.


nn_zdm (555031742)  1999-05-18 14:07:17 
在winNT中用WriteProcessMemory()寫code
程式碼是可以的,win95沒試過,但MSDN上說是可以的.不過,可能沒什麼用,因為CreateRemoteThread()函式只在winNT中有用.
如:
WriteProcessMemory(...,"LoadLibrary(...,"mydll.dll",..).
CreateRemoteThread(...)


nn_zdm (555031742)  1999-05-18 14:14:19 
上面寫漏了,應是
WriteProcessMemory(...,"LoadLibrary(...,"mydll.dll",..). ");
CreateRemoteThread(...) ;

主題  關於螢幕取詞的討論(三)
作者   亦東


讓大家久等,很抱歉,前些時候工作忙硬碟又壞了,太不幸了。

這回來點真格的。

我們們以擷取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=0;i<5;i++){
Address[i]=NewValue[i];
}
bHookAlready=TRUE;
}
}

BOOL HookOff()
{
if(bHookAlready){
for(int i=0;i<5;i++){
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


Guest  1999-05-21 22:20:47 
你這是16為的地址存取模式吧
你看這個MK_FP,win32不用了,
而且GetProcAddress(hKernel,"AllocCsToDsAlias")
這個API有用嗎?
sorry


Guest  1999-05-21 22:31:47 
亦東,想請教一個問題,
win32下,每個process有
自己的地址空間,process
A得到process B 的一個視窗C的handle,這個
handle的值 等於process B自己得到的window C 的handle值 嗎?
我想應該不相等,但系統是如何轉換的呢?(比如process A 向
window C 發訊息,系統如何
知道process A 裡的handle 和process B 裡的
handle 都是指的window C)
. 是不是用duplicatehandle()?
(宣告,我是真的不知道

相關文章