Windows 9x屏幕文字检索的实现方法是什么?针对这个问题,本文详细介绍了相应的分析和解答,希望能帮助更多想要解决这个问题的朋友找到更简单易行的方法。
有关屏幕取词
“鼠标屏幕单词检索”技术广泛应用于电子词典中,例如像思痛坊和金山网络安全这样的软件。这项技术看似简单,但实际上在windows系统中实现起来非常复杂。一般来说,有两种实现方式:
* * *物种:通过拦截对一些gdi的api调用来实现,如textout、textouta等。
其次,复制每个设备上下文(dc),并跟踪修改上下文(dc)的所有操作。
第二种方法比较强大,但是兼容性不好。* * *的方法拦截windowsapi的调用。这项技术可能比你想象的要强大得多。毫不夸张地说,你可以利用windowsapi的拦截技术来改造整个操作系统。其实很多插件windows中文平台都是这样实现的!这项技术也是本文的主题。
截取windowsapi的调用,具体来说,也可以分为两种方法:
* * *方法直接在内存中重写winapi的镜像,嵌入汇编代码,并使其在被调用拦截时跳转到指定地址;第二种方法重写iat(导入地址表)并重定向winapi函数的调用以拦截winapi。
* * *方法的实现比较繁琐,在win95和WIN98下更难。这是因为虽然微软说win16的api只是为了兼容而保留,但是程序员应该尽可能的调用32位的api,实际上完全不是这样!win 9x中的32位API大多在转换后调用同名的16位API,这意味着我们需要在截取的函数中嵌入16位汇编代码!
我们将介绍第二种拦截方法,它在win95、98和nt下运行稳定,兼容性好。因为需要使用关于windows虚拟内存管理、打破进程边界墙、向应用程序的进程空间注入代码、pe(可移植可执行)文件格式和iat (Input Address Table)等底层知识。我们先简单介绍一下涉及到的知识,而* * *会给出截取部分的关键代码。
先说windows虚拟内存的管理。Windows9x为每个进程分配4gb的地址空间。对于nt,这个数字是2gb。系统保留2gb到4gb之间的地址空间来禁止进程访问。在win9x中,2gb到4gb之间的虚拟地址空间实际上由所有win32进程共享。这部分地址空间加载了共享win32 dll、内存映射文件和vxd、内存管理器和文件系统代码。win9x的这一部分对每个进程都是可见的,这也是win9x操作系统不够健壮的原因。Win9x为16位操作系统预留了0-4mb的地址空间,而4mb到2gb之间的地址空间是win32进程的私有地址空间。因为每个进程的地址空间是相对独立的,也就是说,如果程序想要拦截其他进程中的api调用,就必须打破进程边界墙,将拦截api调用的代码注入到其他进程中。这项工作由setwindowshookex完成,以及如何创建一个包含系统。这一期有专门的介绍,这里就不赘述了。所有系统钩子函数必须在动态库中。这种情况下,当一个进程隐式或显式调用动态库中的函数时,系统会将动态库映射到这个进程的虚拟地址空间,这就使得dll成为进程的一部分,作为这个进程执行,并使用这个进程的栈,也就是说,将动态链接库中的代码注入到其他gui进程(非gui进程,Hook函数无能为力)的地址空间中, 当包含钩子的dll被注入到其他进程中时,可以获得映射到这个进程的虚拟内存的每个模块(exe和dll)的基址,比如:hmodule hmodule=getmodulehandle(' mypro . exe '); 在mfc程序中,我们可以使用afxgetinstancehandle()函数来获取模块的基址。exe和dll映射到虚拟内存空间的位置由它们的基址决定。它们的基地址由链接器在链接时确定。构建新的win32项目时,VC链接器使用默认基址0x00400000。您可以通过链接器的base选项更改模块的基址。Exe通常映射到0x00400000的虚拟内存,dll也有不同的基址,通常映射到不同进程的同一个虚拟地址空间。
系统exe和dll原封不动地映射到虚拟内存空间,它们在内存中的结构与磁盘上的静态文件相同。也就是pe(可移植可执行)文件格式。在得到进程模块的基址后,我们可以根据pe文件的格式对这个模块的image_import_descriptor数组进行排气,看看我们需要截取的函数所在的动态链接库是否引入到进程空间中。比如我们需要截取‘text outa’,就要检查‘GDI 32 . dll’是否已经引入。说到这里,我们有必要介绍一下pe文件的格式,如右图所示,这是pe文件格式的通用框图。前面是文件头,不用我们关注。从pe文件可选头的后面开始,是文件中每个段的描述,描述是真实的段数据。事实上,我们只关心一个部分,那就是。idata '段。
这个段中包含了所有的引入函数信息,还有iat(import address table)的rva(relative virtual address)地址。
说到这里,截获windowsapi的整个原理就要真相大白了。实际上所有进程对给定的api函数的调用总是通过pe文件的一个地方来转移的,这就是一个该模块(可以是exe或dll)的".idata"段中的iat输入地址表(import address table)。在那里有所有本模块调用的其它dll的函数名及地址。对其它dll的函数调用实际上只是跳转到输入地址表,由输入地址表再跳转到dll真正的函数入口。
具体来说,我们将通过image_import_descriptor数组来访问".idata"段中引入的dll的信息,然后通过image_thunk_data数组来针对一个被引入的dll访问该dll中被引入的每个函数的信息,找到我们需要截获的函数的跳转地址,然后改成我们自己的函数的地址……
废话不说了,下面提供一个屏幕取词具体的实现方法。
1. 安装鼠标钩子,通过钩子函数获得鼠标消息。
使用到的api函数:setwindowshookex
2. 得到鼠标的当前位置,向鼠标下的窗口发重画消息,让它调用系统函数重画窗口。
使用到的api函数:windowfrompoint,screentoclient,invalidaterect
3. 截获对系统函数的调用,取得参数,也就是我们要取的词。
对于大多数的windows应用程序来说,如果要取词,我们需要截获的是"gdi32.dll"中的"textouta"函数。
我们先仿照textouta函数写一个自己的mytextouta函数,如:
bool winapi mytextouta(hdc hdc, int nxstart, int nystart, lpcstr lpszstring,int cbstring) { // 这里进行输出lpszstring的处理 // 然后调用正版的textouta函数 }
把这个函数放在安装了钩子的动态连接库中,然后调用我们***给出的hookimportfunction函数来截获进程对textouta函数的调用,跳转到我们的mytextouta函数,完成对输出字符串的捕捉。hookimportfunction的用法:
hookfuncdesc hd; proc porigfuns; hd.szfunc="textouta"; hd.pproc=(proc)mytextouta; hookimportfunction (afxgetinstancehandle(),"gdi32.dll",&hd,porigfuns);
下面给出了hookimportfunction的源代码,相信详尽的注释一定不会让您觉得理解截获到底是怎么实现的很难,ok,let s go:
begin #include <crtdbg.h> // 这里定义了一个产生指针的宏 #define makeptr(cast, ptr, addvalue) (cast)((dword)(ptr)+(dword)(addvalue)) // 定义了hookfuncdesc结构,我们用这个结构作为参数传给hookimportfunction函数 typedef struct tag_hookfuncdesc { lpcstr szfunc; // the name of the function to hook. proc pproc; // the procedure to blast in. } hookfuncdesc , * lphookfuncdesc; // 这个函数监测当前系统是否是windownt bool isnt(); // 这个函数得到hmodule -- 即我们需要截获的函数所在的dll模块的引入描述符(import descriptor) pimage_import_descriptor getnamedimportdescriptor(hmodule hmodule, lpcstr szimportmodule); // 我们的主函数 bool hookimportfunction(hmodule hmodule, lpcstr szimportmodule, lphookfuncdesc pahookfunc, proc* paorigfuncs) { //下面的代码检测参数的有效性 // _assert(szimportmodule); _assert(!isbadreadptr(pahookfunc, sizeof(hookfuncdesc))); #ifdef _debug if (paorigfuncs) _assert(!isbadwriteptr(paorigfuncs, sizeof(proc))); _assert(pahookfunc.szfunc); _assert(*pahookfunc.szfunc != \0 ); _assert(!isbadcodeptr(pahookfunc.pproc)); #endif if ((szimportmodule == null) || (isbadreadptr(pahookfunc, sizeof(hookfuncdesc)))) { _assert(false); setlasterrorex(error_invalid_parameter, sle_error); return false; } // 监测当前模块是否是在2gb虚拟内存空间之上 // 这部分的地址内存是属于win32进程共享的 if (!isnt() && ((dword)hmodule >= 0x80000000)) { _assert(false); setlasterrorex(error_invalid_handle, sle_error); return false; } // 清零 if (paorigfuncs) memset(paorigfuncs, null, sizeof(proc)); // 调用getnamedimportdescriptor()函数,来得到hmodule -- 即我们需要 // 截获的函数所在的dll模块的引入描述符(import descriptor) pimage_import_descriptor pimportdesc = getnamedimportdescriptor(hmodule, szimportmodule); if (pimportdesc == null) return false; // 若为空,则模块未被当前进程所引入 // 从dll模块中得到原始的thunk信息,因为pimportdesc->firstthunk数组中的原始信息已经 // 在应用程序引入该dll时覆盖上了所有的引入信息,所以我们需要通过取得pimportdesc->originalfirstthunk // 指针来访问引入函数名等信息 pimage_thunk_data porigthunk = makeptr(pimage_thunk_data, hmodule, pimportdesc->originalfirstthunk); // 从pimportdesc->firstthunk得到image_thunk_data数组的指针,由于这里在dll被引入时已经填充了 // 所有的引入信息,所以真正的截获实际上正是在这里进行的 pimage_thunk_data prealthunk = makeptr(pimage_thunk_data, hmodule, pimportdesc->firstthunk); // 穷举image_thunk_data数组,寻找我们需要截获的函数,这是最关键的部分! while (porigthunk->u1.function) { // 只寻找那些按函数名而不是序号引入的函数 if (image_ordinal_flag != (porigthunk->u1.ordinal & image_ordinal_flag)) { // 得到引入函数的函数名 pimage_import_by_name pbyname = makeptr(pimage_import_by_name, hmodule, porigthunk->u1.addressofdata); // 如果函数名以null开始,跳过,继续下一个函数 if ( \0 == pbyname->name[0]) continue; // bdohook用来检查是否截获成功 bool bdohook = false; // 检查是否当前函数是我们需要截获的函数 if ((pahookfunc.szfunc[0] == pbyname->name[0]) && (strcmpi(pahookfunc.szfunc, (char*)pbyname->name) == 0)) { // 找到了! if (pahookfunc.pproc) bdohook = true; } if (bdohook) { // 我们已经找到了所要截获的函数,那么就开始动手吧 // 首先要做的是改变这一块虚拟内存的内存保护状态,让我们可以自由存取 memory_basic_information mbi_thunk; virtualquery(prealthunk, &mbi_thunk, sizeof(memory_basic_information)); _assert(virtualprotect(mbi_thunk.baseaddress, mbi_thunk.regionsize, page_readwrite, &mbi_thunk.protect)); // 保存我们所要截获的函数的正确跳转地址 if (paorigfuncs) paorigfuncs = (proc)prealthunk->u1.function; // 将image_thunk_data数组中的函数跳转地址改写为我们自己的函数地址! // 以后所有进程对这个系统函数的所有调用都将成为对我们自己编写的函数的调用 prealthunk->u1.function = (pdword)pahookfunc.pproc; // 操作完毕!将这一块虚拟内存改回原来的保护状态 dword dwoldprotect; _assert(virtualprotect(mbi_thunk.baseaddress, mbi_thunk.regionsize, mbi_thunk.protect, &dwoldprotect)); setlasterror(error_success); return true; } } // 访问image_thunk_data数组中的下一个元素 porigthunk++; prealthunk++; } return true; } // getnamedimportdescriptor函数的实现 pimage_import_descriptor getnamedimportdescriptor(hmodule hmodule, lpcstr szimportmodule) { // 检测参数 _assert(szimportmodule); _assert(hmodule); if ((szimportmodule == null) || (hmodule == null)) { _assert(false); setlasterrorex(error_invalid_parameter, sle_error); return null; } // 得到dos文件头 pimage_dos_header pdosheader = (pimage_dos_header) hmodule; // 检测是否mz文件头 if (isbadreadptr(pdosheader, sizeof(image_dos_header)) || (pdosheader->e_magic != image_dos_signature)) { _assert(false); setlasterrorex(error_invalid_parameter, sle_error); return null; } // 取得pe文件头 pimage_nt_headers pntheader = makeptr(pimage_nt_headers, pdosheader, pdosheader->e_lfanew); // 检测是否pe映像文件 if (isbadreadptr(pntheader, sizeof(image_nt_headers)) || (pntheader->signature != image_nt_signature)) { _assert(false); setlasterrorex(error_invalid_parameter, sle_error); return null; } // 检查pe文件的引入段(即 .idata section) if (pntheader->optionalheader.datadirectory[image_directory_entry_import].virtualaddress == 0) return null; // 得到引入段(即 .idata section)的指针 pimage_import_descriptor pimportdesc = makeptr(pimage_import_descriptor, pdosheader, pntheader->optionalheader.datadirectory[image_directory_entry_import].virtualaddress); // 穷举pimage_import_descriptor数组寻找我们需要截获的函数所在的模块 while (pimportdesc->name) { pstr szcurrmod = makeptr(pstr, pdosheader, pimportdesc->name); if (stricmp(szcurrmod, szimportmodule) == 0) break; // 找到!中断循环 // 下一个元素 pimportdesc++; } // 如果没有找到,说明我们寻找的模块没有被当前的进程所引入! if (pimportdesc->name == null) return null; // 返回函数所找到的模块描述符(import descriptor) return pimportdesc; } // isnt()函数的实现 bool isnt() { osversioninfo stosvi; memset(&stosvi, null, sizeof(osversioninfo)); stosvi.dwosversioninfosize = sizeof(osversioninfo); bool bret = getversionex(&stosvi); _assert(true == bret); if (false == bret) return false; return (ver_platform_win32_nt == stosvi.dwplatformid); }
关于Windows 9x屏幕取词的实现方法是什么问题的解答就分享到这里了,希望
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/113748.html