自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

屏幕取詞的實(shí)現(xiàn)方法(Windows 9x)

開(kāi)發(fā) 后端
屏幕取詞現(xiàn)在已經(jīng)是廣泛應(yīng)用的一個(gè)功能,但其實(shí)這個(gè)功能在Windows中的實(shí)現(xiàn)還是非常復(fù)雜的。本文介紹屏幕取詞的實(shí)現(xiàn)方法。

有關(guān)屏幕取詞

"鼠標(biāo)屏幕取詞"技術(shù)是在電子字典中得到廣泛地應(yīng)用的,如四通利方和金山詞霸等軟件,這個(gè)技術(shù)看似簡(jiǎn)單,其實(shí)在windows系統(tǒng)中實(shí)現(xiàn)卻是非常復(fù)雜的,總的來(lái)說(shuō)有兩種實(shí)現(xiàn)方式:

***種:采用截獲對(duì)部分gdi的api調(diào)用來(lái)實(shí)現(xiàn),如textout,textouta等。

第二種:對(duì)每個(gè)設(shè)備上下文(dc)做一分copy,并跟蹤所有修改上下文(dc)的操作。

第二種方法更強(qiáng)大,但兼容性不好,而***種方法使用的截獲windowsapi的調(diào)用,這項(xiàng)技術(shù)的強(qiáng)大可能遠(yuǎn)遠(yuǎn)超出了您的想象,毫不夸張的說(shuō),利用 windowsapi攔截技術(shù),你可以改造整個(gè)操作系統(tǒng),事實(shí)上很多外掛式windows中文平臺(tái)就是這么實(shí)現(xiàn)的!而這項(xiàng)技術(shù)也正是這篇文章的主題。

截windowsapi的調(diào)用,具體的說(shuō)來(lái)也可以分為兩種方法:

***種方法通過(guò)直接改寫(xiě)winapi 在內(nèi)存中的映像,嵌入?yún)R編代碼,使之被調(diào)用時(shí)跳轉(zhuǎn)到指定的地址運(yùn)行來(lái)截獲;第二種方法則改寫(xiě)iat(import address table輸入地址表),重定向winapi函數(shù)的調(diào)用來(lái)實(shí)現(xiàn)對(duì)winapi的截獲。

***種方法的實(shí)現(xiàn)較為繁瑣,而且在win95、98下面更有難度,這是因?yàn)殡m然微軟說(shuō)win16的api只是為了兼容性才保留下來(lái),程序員應(yīng)該盡可能地調(diào)用 32位的api,實(shí)際上根本就不是這樣!win 9x內(nèi)部的大部分32位api經(jīng)過(guò)變換調(diào)用了同名的16位api,也就是說(shuō)我們需要在攔截的函數(shù)中嵌入16位匯編代碼!

我們將要介紹的是第二種攔截方法,這種方法在win95、98和nt下面運(yùn)行都比較穩(wěn)定,兼容性較好。由于需要用到關(guān)于windows虛擬內(nèi)存的管理、打破進(jìn)程邊界墻、向應(yīng)用程序的進(jìn)程空間中注入代碼、pe(portable executable)文件格式和iat(輸入地址表)等較底層的知識(shí),所以我們先對(duì)涉及到的這些知識(shí)大概地做一個(gè)介紹,***會(huì)給出攔截部分的關(guān)鍵代碼。

先說(shuō)windows虛擬內(nèi)存的管理。windows9x給每一個(gè)進(jìn)程分配了4gb的地址空間,對(duì)于nt來(lái)說(shuō),這個(gè)數(shù)字是2gb,系統(tǒng)保留了2gb 到 4gb之間的地址空間禁止進(jìn)程訪問(wèn),而在win9x中,2gb到4gb這部分虛擬地址空間實(shí)際上是由所有的win32進(jìn)程所共享的,這部分地址空間加載了共享win32 dll、內(nèi)存映射文件和vxd、內(nèi)存管理器和文件系統(tǒng)碼,win9x中這部分對(duì)于每一個(gè)進(jìn)程都是可見(jiàn)的,這也是win9x操作系統(tǒng)不夠健壯的原因。 win9x中為16位操作系統(tǒng)保留了0到4mb的地址空間,而在4mb到2gb之間也就是win32進(jìn)程私有的地址空間,由于每個(gè)進(jìn)程的地址空間都是相對(duì)獨(dú)立的,也就是說(shuō),如果程序想截獲其它進(jìn)程中的api調(diào)用,就必須打破進(jìn)程邊界墻,向其它的進(jìn)程中注入截獲api調(diào)用的代碼,這項(xiàng)工作我們交給鉤子函數(shù)(setwindowshookex)來(lái)完成,關(guān)于如何創(chuàng)建一個(gè)包含系統(tǒng)鉤子的動(dòng)態(tài)鏈接庫(kù),《電腦高手雜志》在第?期已經(jīng)有過(guò)專(zhuān)題介紹了,這里就不贅述了。所有系統(tǒng)鉤子的函數(shù)必須要在動(dòng)態(tài)庫(kù)里,這樣的話,當(dāng)進(jìn)程隱式或顯式調(diào)用一個(gè)動(dòng)態(tài)庫(kù)里的函數(shù)時(shí),系統(tǒng)會(huì)把這個(gè)動(dòng)態(tài)庫(kù)映射到這個(gè)進(jìn)程的虛擬地址空間里,這使得dll成為進(jìn)程的一部分,以這個(gè)進(jìn)程的身份執(zhí)行,使用這個(gè)進(jìn)程的堆棧,也就是說(shuō)動(dòng)態(tài)鏈接庫(kù)中的代碼被鉤子函數(shù)注入了其它gui 進(jìn)程的地址空間(非gui進(jìn)程,鉤子函數(shù)就無(wú)能為力了),當(dāng)包含鉤子的dll注入其它進(jìn)程后,就可以取得映射到這個(gè)進(jìn)程虛擬內(nèi)存里的各個(gè)模塊(exe和 dll)的基地址,如:hmodule hmodule=getmodulehandle("mypro.exe");在mfc程序中,我們可以用afxgetinstancehandle() 函數(shù)來(lái)得到模塊的基地址。exe和dll被映射到虛擬內(nèi)存空間的什么地方是由它們的基地址決定的。它們的基地址是在鏈接時(shí)由鏈接器決定的。當(dāng)你新建一個(gè) win32工程時(shí),vc++鏈接器使用缺省的基地址0x00400000??梢酝ㄟ^(guò)鏈接器的base選項(xiàng)改變模塊的基地址。exe通常被映射到虛擬內(nèi)存的 0x00400000處,dll也隨之有不同的基地址,通常被映射到不同進(jìn)程的相同的虛擬地址空間處。

系統(tǒng)將exe和dll原封不動(dòng)映射到虛擬內(nèi)存空間中,它們?cè)趦?nèi)存中的結(jié)構(gòu)與磁盤(pán)上的靜態(tài)文件結(jié)構(gòu)是一樣的。即pe (portable executable) 文件格式。我們得到了進(jìn)程模塊的基地址以后,就可以根據(jù)pe文件的格式窮舉這個(gè)模塊的image_import_descriptor數(shù)組,看看進(jìn)程空間中是否引入了我們需要截獲的函數(shù)所在的動(dòng)態(tài)鏈接庫(kù),比如需要截獲"textouta",就必須檢查"gdi32.dll"是否被引入了。說(shuō)到這里,我們有必要介紹一下pe文件的格式,如右圖,這是pe文件格式的大致框圖,最前面是文件頭,我們不必理會(huì),從pe file optional header后面開(kāi)始,就是文件中各個(gè)段的說(shuō)明,說(shuō)明后面才是真正的段數(shù)據(jù),而實(shí)際上我們關(guān)心的只有一個(gè)段,那就是".idata"段,這個(gè)段中包含了所有的引入函數(shù)信息,還有iat(import address table)的rva(relative virtual address)地址。

說(shuō)到這里,截獲windowsapi的整個(gè)原理就要真相大白了。實(shí)際上所有進(jìn)程對(duì)給定的api函數(shù)的調(diào)用總是通過(guò)pe文件的一個(gè)地方來(lái)轉(zhuǎn)移的,這就是一個(gè)該模塊(可以是exe或dll)的".idata"段中的iat輸入地址表(import address table)。在那里有所有本模塊調(diào)用的其它dll的函數(shù)名及地址。對(duì)其它dll的函數(shù)調(diào)用實(shí)際上只是跳轉(zhuǎn)到輸入地址表,由輸入地址表再跳轉(zhuǎn)到dll真正的函數(shù)入口。

具體來(lái)說(shuō),我們將通過(guò)image_import_descriptor數(shù)組來(lái)訪問(wèn)".idata"段中引入的dll的信息,然后通過(guò)image_thunk_data數(shù)組來(lái)針對(duì)一個(gè)被引入的dll訪問(wèn)該dll中被引入的每個(gè)函數(shù)的信息,找到我們需要截獲的函數(shù)的跳轉(zhuǎn)地址,然后改成我們自己的函數(shù)的地址……

廢話不說(shuō)了,下面提供一個(gè)屏幕取詞具體的實(shí)現(xiàn)方法。

1. 安裝鼠標(biāo)鉤子,通過(guò)鉤子函數(shù)獲得鼠標(biāo)消息。

使用到的api函數(shù):setwindowshookex

2. 得到鼠標(biāo)的當(dāng)前位置,向鼠標(biāo)下的窗口發(fā)重畫(huà)消息,讓它調(diào)用系統(tǒng)函數(shù)重畫(huà)窗口。

使用到的api函數(shù):windowfrompoint,screentoclient,invalidaterect

3. 截獲對(duì)系統(tǒng)函數(shù)的調(diào)用,取得參數(shù),也就是我們要取的詞。

對(duì)于大多數(shù)的windows應(yīng)用程序來(lái)說(shuō),如果要取詞,我們需要截獲的是"gdi32.dll"中的"textouta"函數(shù)。

我們先仿照textouta函數(shù)寫(xiě)一個(gè)自己的mytextouta函數(shù),如:

  1. bool winapi mytextouta(hdc hdc, int nxstart, int nystart, lpcstr lpszstring,int cbstring)   
  2. {   
  3. // 這里進(jìn)行輸出lpszstring的處理   
  4. // 然后調(diào)用正版的textouta函數(shù)   
  5. }   

把這個(gè)函數(shù)放在安裝了鉤子的動(dòng)態(tài)連接庫(kù)中,然后調(diào)用我們***給出的hookimportfunction函數(shù)來(lái)截獲進(jìn)程對(duì)textouta函數(shù)的調(diào)用,跳轉(zhuǎn)到我們的mytextouta函數(shù),完成對(duì)輸出字符串的捕捉。hookimportfunction的用法:

  1. hookfuncdesc hd;   
  2. proc porigfuns;   
  3. hd.szfunc="textouta";   
  4. hd.pproc=(proc)mytextouta;   
  5. hookimportfunction (afxgetinstancehandle(),"gdi32.dll",&hd,porigfuns);  

下面給出了hookimportfunction的源代碼,相信詳盡的注釋一定不會(huì)讓您覺(jué)得理解截獲到底是怎么實(shí)現(xiàn)的很難,ok,let s go:

  1. ///////////////////////////////////////////// begin ///////////////////////////////////////////////////////////////   
  2. #include <crtdbg.h>   
  3. // 這里定義了一個(gè)產(chǎn)生指針的宏   
  4. #define makeptr(cast, ptr, addvalue) (cast)((dword)(ptr)+(dword)(addvalue))   
  5. // 定義了hookfuncdesc結(jié)構(gòu),我們用這個(gè)結(jié)構(gòu)作為參數(shù)傳給hookimportfunction函數(shù)   
  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. // 這個(gè)函數(shù)監(jiān)測(cè)當(dāng)前系統(tǒng)是否是windownt   
  12. bool isnt();   
  13. // 這個(gè)函數(shù)得到hmodule -- 即我們需要截獲的函數(shù)所在的dll模塊的引入描述符(import descriptor)   
  14. pimage_import_descriptor getnamedimportdescriptor(hmodule hmodule, lpcstr szimportmodule);   
  15. // 我們的主函數(shù)   
  16. bool hookimportfunction(hmodule hmodule, lpcstr szimportmodule,   
  17. lphookfuncdesc pahookfunc, proc* paorigfuncs)   
  18. {   
  19. /////////////////////// 下面的代碼檢測(cè)參數(shù)的有效性 ////////////////////////////   
  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. // 監(jiān)測(cè)當(dāng)前模塊是否是在2gb虛擬內(nèi)存空間之上   
  36. // 這部分的地址內(nèi)存是屬于win32進(jìn)程共享的   
  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. // 調(diào)用getnamedimportdescriptor()函數(shù),來(lái)得到hmodule -- 即我們需要   
  46. // 截獲的函數(shù)所在的dll模塊的引入描述符(import descriptor)   
  47. pimage_import_descriptor pimportdesc = getnamedimportdescriptor(hmodule, szimportmodule);   
  48. if (pimportdesc == null)   
  49. return false// 若為空,則模塊未被當(dāng)前進(jìn)程所引入   
  50. // 從dll模塊中得到原始的thunk信息,因?yàn)閜importdesc->firstthunk數(shù)組中的原始信息已經(jīng)   
  51. // 在應(yīng)用程序引入該dll時(shí)覆蓋上了所有的引入信息,所以我們需要通過(guò)取得pimportdesc->originalfirstthunk   
  52. // 指針來(lái)訪問(wèn)引入函數(shù)名等信息   
  53. pimage_thunk_data porigthunk = makeptr(pimage_thunk_data, hmodule,   
  54. pimportdesc->originalfirstthunk);   
  55. // 從pimportdesc->firstthunk得到image_thunk_data數(shù)組的指針,由于這里在dll被引入時(shí)已經(jīng)填充了   
  56. // 所有的引入信息,所以真正的截獲實(shí)際上正是在這里進(jìn)行的   
  57. pimage_thunk_data prealthunk = makeptr(pimage_thunk_data, hmodule, pimportdesc->firstthunk);   
  58. // 窮舉image_thunk_data數(shù)組,尋找我們需要截獲的函數(shù),這是最關(guān)鍵的部分!   
  59. while (porigthunk->u1.function)   
  60. {   
  61. // 只尋找那些按函數(shù)名而不是序號(hào)引入的函數(shù)   
  62. if (image_ordinal_flag != (porigthunk->u1.ordinal & image_ordinal_flag))   
  63. {   
  64. // 得到引入函數(shù)的函數(shù)名   
  65. pimage_import_by_name pbyname = makeptr(pimage_import_by_name, hmodule,   
  66. porigthunk->u1.addressofdata);   
  67. // 如果函數(shù)名以null開(kāi)始,跳過(guò),繼續(xù)下一個(gè)函數(shù)   
  68. if ( \0 == pbyname->name[0])   
  69. continue;   
  70. // bdohook用來(lái)檢查是否截獲成功   
  71. bool bdohook = false;   
  72. // 檢查是否當(dāng)前函數(shù)是我們需要截獲的函數(shù)   
  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. // 我們已經(jīng)找到了所要截獲的函數(shù),那么就開(kāi)始動(dòng)手吧   
  83. // 首先要做的是改變這一塊虛擬內(nèi)存的內(nèi)存保護(hù)狀態(tài),讓我們可以自由存取   
  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. // 保存我們所要截獲的函數(shù)的正確跳轉(zhuǎn)地址   
  89. if (paorigfuncs)   
  90. paorigfuncs = (proc)prealthunk->u1.function;   
  91. // 將image_thunk_data數(shù)組中的函數(shù)跳轉(zhuǎn)地址改寫(xiě)為我們自己的函數(shù)地址!   
  92. // 以后所有進(jìn)程對(duì)這個(gè)系統(tǒng)函數(shù)的所有調(diào)用都將成為對(duì)我們自己編寫(xiě)的函數(shù)的調(diào)用   
  93. prealthunk->u1.function = (pdword)pahookfunc.pproc;   
  94. // 操作完畢!將這一塊虛擬內(nèi)存改回原來(lái)的保護(hù)狀態(tài)   
  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. // 訪問(wèn)image_thunk_data數(shù)組中的下一個(gè)元素   
  103. porigthunk++;   
  104. prealthunk++;   
  105. }   
  106. return true;   
  107. }   
  108. // getnamedimportdescriptor函數(shù)的實(shí)現(xiàn)   
  109. pimage_import_descriptor getnamedimportdescriptor(hmodule hmodule, lpcstr szimportmodule)   
  110. {   
  111. // 檢測(cè)參數(shù)   
  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. // 檢測(cè)是否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. // 檢測(cè)是否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數(shù)組尋找我們需要截獲的函數(shù)所在的模塊   
  147. while (pimportdesc->name)   
  148. {   
  149. pstr szcurrmod = makeptr(pstr, pdosheader, pimportdesc->name);   
  150. if (stricmp(szcurrmod, szimportmodule) == 0)   
  151. break// 找到!中斷循環(huán)   
  152. // 下一個(gè)元素   
  153. pimportdesc++;   
  154. }   
  155. // 如果沒(méi)有找到,說(shuō)明我們尋找的模塊沒(méi)有被當(dāng)前的進(jìn)程所引入!   
  156. if (pimportdesc->name == null)   
  157. return null;   
  158. // 返回函數(shù)所找到的模塊描述符(import descriptor)   
  159. return pimportdesc;   
  160. }   
  161. // isnt()函數(shù)的實(shí)現(xiàn)   
  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. }   

【編輯推薦】

  1. Winform中C#線程控制的四種常見(jiàn)情況分析
  2. C#子線程的控件操作問(wèn)題解析
  3. C#線程相關(guān)問(wèn)題總結(jié):基本操作及UI控件交互
  4. 學(xué)習(xí)C#多線程:lock的用法
  5. 總結(jié)C#多線程的點(diǎn)點(diǎn)滴滴
責(zé)任編輯:yangsai 來(lái)源: CSDN論壇
相關(guān)推薦

2011-08-02 10:34:25

ActiveDirec

2017-06-12 10:16:57

2022-05-03 10:32:57

微軟Windows 11

2009-08-27 16:24:36

C#屏幕取詞

2014-01-03 17:18:45

Windows 9開(kāi)始屏幕

2013-12-06 16:11:49

Windows 9概念圖

2018-05-25 14:40:17

Windows 10Windows屏幕亮度

2013-11-25 14:33:17

Windows 9概念圖

2014-02-11 11:41:45

Windows 9Windows 8

2016-12-22 09:11:24

Windows 10Windows 8Windows 7

2014-01-23 14:42:34

Windows 9

2014-08-25 15:25:04

Windows 9

2014-06-12 11:20:23

Windows 9

2023-10-30 08:22:58

Android常亮Activity

2013-09-29 09:06:38

Windows 9WindowsWindows Pho

2013-04-17 10:12:38

ws Phone開(kāi)發(fā)處理屏幕方向改變

2011-03-16 14:42:11

屏幕監(jiān)視

2012-08-13 10:51:02

Windows 9

2013-03-28 14:17:50

Windows BluWindows 8

2013-12-09 10:42:31

Windows 9
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)