金山词霸的屏幕取词功能是怎样实现的?
解决方案 »
- 谁能给我详细的解释一下 局域网中如果将两台机器的mac及IP全部改成一样的以后访问上会有什么样的影响
- 为什么定义大写的类型,INT,BOOL等?
- 求助,关于MFC静态编译后出现的问题(动态编译Ok)
- 用回车键实现光标转移的怎么实现呢?
- 谁来说说消息是怎么一回事?
- 如何在数组中设定空值?
- 如果改变cpu的占用率?急~~~~
- 哪儿有问题,设置背景色没效果
- 关于MFC显示BMP图像的问题
- 在Windows2000Server 下运行出现“无法对‘DAO/Jet db’引擎进行初始化”
- 0x800A0E7D的错误如何解决啊?谢谢了
- 【FAQ下载】CSDN-VC/MFC 2006 FAQ整理 && 下载
http://www.csdn.net/expert/topic/1013/1013184.xml?temp=.3898737
Win32全局钩子在VC5中的实现
Windows系统是建立在事件驱动的机制上的,说穿了就是整个系统都是通过消息的传递来实现的。而钩子是Windows系统中非常重要的系统接口,用它可以截获并处理送给其他应用程序的消息,来完成普通应用程序难以实现的功能。钩子的种类很多,每种钩子可以截获并处理相应的消息,如键盘钩子可以截获键盘消息,外壳钩子可以截取、启动和关闭应用程序的消息等。本文在VC5编程环境下实现了一个简单的鼠标钩子程序,并对Win32全局钩子的运行机制、Win32 DLL的特点、VC5环境下的MFC DLL以及共享数据等相关知识进行了简单的阐述。
一.Win32全局钩子的运行机制 钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。对每种类型的钩子由系统来维护一个钩子链,最近安装的钩子放在链的开始,而最先安装的钩子放在最后,也就是后加入的先获得控制权。要实现Win32的系统钩子,必须调用SDK中的API函数SetWindowsHookEx来安装这个钩子函数,这个函数的原型是HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId);,其中,第一个参数是钩子的类型;第二个参数是钩子函数的地址;第三个参数是包含钩子函数的模块句柄;第四个参数指定监视的线程。如果指定确定的线程,即为线程专用钩子;如果指定为空,即为全局钩子。其中,全局钩子函数必须包含在DLL(动态链接库)中,而线程专用钩子还可以包含在可执行文件中。得到控制权的钩子函数在完成对消息的处理后,如果想要该消息继续传递,那么它必须调用另外一个SDK中的API函数CallNextHookEx来传递它。钩子函数也可以通过直接返回TRUE来丢弃该消息,并阻止该消息的传递。 二.Win32 DLL的特点 Win32 DLL与 Win16 DLL有很大的区别,这主要是由操作系统的设计思想决定的。一方面,在Win16 DLL中程序入口点函数和出口点函数(LibMain和WEP)是分别实现的;而在Win32 DLL中却由同一函数DLLMain来实现。无论何时,当一个进程或线程载入和卸载DLL时,都要调用该函数,它的原型是BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD fdwReason, LPVOID lpvReserved);,其中,第一个参数表示DLL的实例句柄;第三个参数系统保留;这里主要介绍一下第二个参数,它有四个可能的值:DLL_PROCESS_ATTACH(进程载入),DLL_THREAD_ATTACH(线程载入),DLL_THREAD_DETACH(线程卸载),DLL_PROCESS_DETACH(进程卸载),在DLLMain函数中可以对传递进来的这个参数的值进行判别,并根据不同的参数值对DLL进行必要的初始化或清理工作。举个例子来说,当有一个进程载入一个DLL时,系统分派给DLL的第二个参数为DLL_PROCESS_ATTACH,这时,你可以根据这个参数初始化特定的数据。另一方面,在Win16环境下,所有应用程序都在同一地址空间;而在Win32环境下,所有应用程序都有自己的私有空间,每个进程的空间都是相互独立的,这减少了应用程序间的相互影响,但同时也增加了编程的难度。大家知道,在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32环境中,情况却发生了变化,当进程在载入DLL时,系统自动把DLL地址映射到该进程的私有空间,而且也复制该DLL的全局数据的一份拷贝到该进程空间,也就是说每个进程所拥有的相同的DLL的全局数据其值却并不一定是相同的。因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。亦即把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。 三.VC5中MFC DLL的分类及特点 在VC5中有三种形式的MFC DLL(在该DLL中可以使用和继承已有的MFC类)可供选择,即Regular statically linked to MFC DLL(标准静态链接MFC DLL)和Regular using the shared MFC DLL(标准动态链接MFC DLL)以及Extension MFC DLL(扩展MFC DLL)。第一种DLL的特点是,在编译时把使用的MFC代码加入到DLL中,因此,在使用该程序时不需要其他MFC动态链接类库的存在,但占用磁盘空间比较大;第二种DLL的特点是,在运行时,动态链接到MFC类库,因此减少了空间的占用,但是在运行时却依赖于MFC动态链接类库;这两种DLL既可以被MFC程序使用也可以被Win32程序使用。第三种DLL的特点类似于第二种,做为MFC类库的扩展,只能被MFC程序使用。 四.在VC5中全局共享数据的实现 在主文件中,用#pragma data_seg建立一个新的数据段并定义共享数据,其具体格式为: #pragma data_seg ("shareddata") HWND sharedwnd=NULL;//共享数据 #pragma data_seg() 仅定义一个数据段还不能达到共享数据的目的,还要告诉编译器该段的属性,有两种方法可以实现该目的(其效果是相同的),一种方法是在.DEF文件中加入如下语句: SETCTIONS shareddata READ WRITE SHARED 另一种方法是在项目设置链接选项中加入如下语句: /SECTION:shareddata,rws 五.具体实现步骤 由于全局钩子函数必须包含在动态链接库中,所以本例由两个程序体来实现。 1.建立钩子Mousehook.DLL (1)选择MFC AppWizard(DLL)创建项目Mousehook; (2)选择MFC Extension DLL(共享MFC拷贝)类型; (3)由于VC5没有现成的钩子类,所以要在项目目录中创建Mousehook.h文件,在其中建立钩子类: class AFX_EXT_CLASS Cmousehook:public CObject { public: Cmousehook(); //钩子类的构造函数 ~Cmousehook(); //钩子类的析构函数 BOOL starthook(HWND hWnd); //安装钩子函数 BOOL stophook(); 卸载钩子函数 }; (4)在Mousehook.app文件的顶部加入#include"Mousehook.h"语句; (5)加入全局共享数据变量: #pragma data_seg("mydata") HWND glhPrevTarWnd=NULL; //上次鼠标所指的窗口句柄 HWND glhDisplayWnd=NULL; //显示目标窗口标题编辑框的句柄 HHOOK glhHook=NULL; //安装的鼠标勾子句柄 HINSTANCE glhInstance=NULL; //DLL实例句柄 #pragma data_seg() (6)在DEF文件中定义段属性: SECTIONS mydata READ WRITE SHARED (7)在主文件Mousehook.cpp的DllMain函数中加入保存DLL实例句柄的语句: DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved) { //如果使用lpReserved参数则删除下面这行 UNREFERENCED_PARAMETER(lpReserved); if (dwReason == DLL_PROCESS_ATTACH) { TRACE0("MOUSEHOOK.DLL Initializing!\n"); //扩展DLL仅初始化一次 if (!AfxInitExtensionModule(MousehookDLL, hInstance)) return 0; new CDynLinkLibrary(MousehookDLL); //把DLL加入动态MFC类库中 glhInstance=hInstance; //插入保存DLL实例句柄 } else if (dwReason == DLL_PROCESS_DETACH) { TRACE0("MOUSEHOOK.DLL Terminating!\n"); //终止这个链接库前调用它 AfxTermExtensionModule(MousehookDLL); } return 1; } (8)类Cmousehook的成员函数的具体实现: Cmousehook::Cmousehook() //类构造函数 { } Cmousehook::~Cmousehook() //类析构函数 { stophook(); } BOOL Cmousehook::starthook(HWND hWnd) //安装钩子并设定接收显示窗口句柄 { BOOL bResult=FALSE; glhHook=SetWindowsHookEx(WH_MOUSE,MouseProc,glhInstance,0); if(glhHook!=NULL) bResult=TRUE; glhDisplayWnd=hWnd; //设置显示目标窗口标题编辑框的句柄 return bResult; } BOOL Cmousehook::stophook() //卸载钩子 { BOOL bResult=FALSE; if(glhHook) { bResult= UnhookWindowsHookEx(glhHook); if(bResult) { glhPrevTarWnd=NULL; glhDisplayWnd=NULL;//清变量 glhHook=NULL; } } return bResult; } (9)钩子函数的实现: LRESULT WINAPI MouseProc(int nCode,WPARAM wparam,LPARAM lparam) { LPMOUSEHOOKSTRUCT pMouseHook=(MOUSEHOOKSTRUCT FAR *) lparam; if (nCode>=0) { HWND glhTargetWnd=pMouseHook->hwnd; //取目标窗口句柄 HWND ParentWnd=glhTargetWnd; while (ParentWnd !=NULL) { glhTargetWnd=ParentWnd; ParentWnd=GetParent(glhTargetWnd); //取应用程序主窗口句柄 } if(glhTargetWnd!=glhPrevTarWnd) { char szCaption[100]; GetWindowText(glhTargetWnd,szCaption,100); //取目标窗口标题 if(IsWindow(glhDisplayWnd)) SendMessage(glhDisplayWnd,WM_SETTEXT,0,(LPARAM)(LPCTSTR)szCaption); glhPrevTarWnd=glhTargetWnd; //保存目标窗口 } } return CallNextHookEx(glhHook,nCode,wparam
简要说明: “鼠标屏幕取词”技术是在电子字典中得到广泛地应用的,如四通利方和金山词霸等软件,这个技术看似简单,其实在windows系统中实现却是非常复杂的,总的来说有两种实现方式:
第一种:采用截获对部分gdi的api调用来实现,如textout,textouta等。
第二种:对每个设备上下文(dc)做一分copy,并跟踪所有修改上下文(dc)的操作。
第二种方法更强大,但兼容性不好,而第一种方法使用的截获windowsapi的调用,这项技术的强大可能远远超出了您的想象,毫不夸张的说,利用windowsapi拦截技术,你可以改造整个操作系统,事实上很多外挂式windows中文平台就是这么实现的!而这项技术也正是这篇文章的主题。
截windowsapi的调用,具体的说来也可以分为两种方法:
第一种方法通过直接改写winapi 在内存中的映像,嵌入汇编代码,使之被调用时跳转到指定的地址运行来截获;第二种方法则改写iat(import address table 输入地址表),重定向winapi函数的调用来实现对winapi的截获。
第一种方法的实现较为繁琐,而且在win95、98下面更有难度,这是因为虽然微软说win16的api只是为了兼容性才保留下来,程序员应该尽可能地调用32位的api,实际上根本就不是这样!win 9x内部的大部分32位api经过变换调用了同名的16位api,也就是说我们需要在拦截的函数中嵌入16位汇编代码!
我们将要介绍的是第二种拦截方法,这种方法在win95、98和nt下面运行都比较稳定,兼容性较好。由于需要用到关于windows虚拟内存的管理、打破进程边界墙、向应用程序的进程空间中注入代码、pe(portable executable)文件格式和iat(输入地址表)等较底层的知识,所以我们先对涉及到的这些知识大概地做一个介绍,最后会给出拦截部分的关键代码。
先说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进程,钩子函数就无能为力了),
当包含钩子的dll注入其它进程后,就可以取得映射到这个进程虚拟内存里的各个模块(exe和dll)的基地址,如:
hmodule hmodule=getmodulehandle(“mypro.exe”);
在mfc程序中,我们可以用afxgetinstancehandle()函数来得到模块的基地址。exe和dll被映射到虚拟内存空间的什么地方是由它们的基地址决定的。它们的基地址是在链接时由链接器决定的。当你新建一个win32工程时,vc++链接器使用缺省的基地址0x00400000。可以通过链接器的base选项改变模块的基地址。exe通常被映射到虚拟内存的0x00400000处,dll也随之有不同的基地址,通常被映射到不同进程
的相同的虚拟地址空间处。
系统将exe和dll原封不动映射到虚拟内存空间中,它们在内存中的结构与磁盘上的静态文件结构是一样的。即pe (portable executable) 文件格式。我们得到了进程模块的基地址以后,就可以根据pe文件的格式穷举这个模块的image_import_descriptor数组,看看进程空间中是否引入了我们需要截获的函数所在的动态链接库,比如需要截获“textouta”,就必须检查“gdi32.dll”是否被引入了。说到这里,我们有必要介绍一下pe文件的格式,如右图,这是pe文件格式的大致框图,最前面是文件头,我们不必理会,从pe file optional header后面开始,就是文件中各个段的说明,说明后面才是真正的段数据,而实际上我们关心的只有一个段,那就是“.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中被引入的每个函数的信息,找到我们需要截获的函数的跳转地址,然后改成我们自己的函数的地址……具体的做法在后面的关键代码中会有详细的讲解。
讲了这么多原理,现在让我们回到“鼠标屏幕取词”的专题上来。除了api函数的截获,要实现“鼠标屏幕取词”,还需要做一些其它的工作,简单的说来,可以把一个完整的取词过程归纳成以下几个步骤:
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);
}
/////////////////////////////////////////////// end //////////////////////////////////////////////////////////////////////不知道在这篇文章问世之前,有多少朋友尝试过去实现“鼠标屏幕取词”这项充满了挑战的技术,也只有尝试过的朋友才能体会到其间的不易,尤其在探索api函数的截获时,手头的几篇资料没有一篇是涉及到关键代码的,重要的地方都是一笔代过,msdn更是显得苍白而无力,也不知道除了image_import_descriptor和image_thunk_data,微软还隐藏了多少秘密,好在硬着头皮还是把它给攻克了,希望这篇文章对大家能有所帮助。
PROCESS_VM_OPERATION | //允许远程VM操作
PROCESS_VM_WRITE, //允许远程VM写
FALSE, dwRemoteProcessId )
由于我们后面需要写入远程进程的内存地址空间并建立远程线程,所以需要申请足够的权限(PROCESS_CREATE_THREAD、VM_OPERATION、VM_WRITE)。如果进程打不开,以后的操作就别想了。进程打开后,就可以建立远线程了,不过别急,先想想这个远线程的线程函数是什么?我们的目的是注入一个DLL。而且我们知道用LoadLibrary可以加载一个DLL到本进程的地址空间。于是,自然会想到如果可以在目标进程中调用LoadLibrary,不就可以把DLL加载到目标进程的地址空间了吗?对!就是这样。远线程就在这儿用了一次,建立的远线程的线程函数就是LoadLibrary,而参数就是要注入的DLL的文件名。(这里需要自己想一想,注意到了吗,线程函数ThreadProc和LoadLibrary函数非常相似,返回值,参数个数都一样) 还有一个问题,LoadLibrary这个函数的地址在哪儿?也许你会说,这个简单,GetProcAddress就可以得出。于是代码就出来了。char *pszLibFileRemote="my.dll";PTHREAD_START_ROUTINE pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32"), "LoadLibraryA");CreateRemoteThread( hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL); 但是不对!不要忘了,这是远线程,不是在你的进程里,而pszLibFileRemote指向的是你的进程里的数据,到了目标进程,这个指针都不知道指向哪儿去了,同样pfnStartAddr这个地址上的代码到了目标进程里也不知道是什么了,不知道是不是你想要的LoadLibraryA了。但是,问题总是可以解决的,Windows有些很强大的API函数,他们可以在目标进程里分配内存,可以将你的进程中的数据拷贝到目标进程中。因此pszLibFileRemote的问题可以解决了。char *pszLibFileName="my.dll";//注意,这个一定要是全路径文件名,除非它在系统目录里;原因大家自己想想。
//计算DLL路径名需要的内存空间
int cb = (1 + lstrlenA(pszLibFileName)) * sizeof(char);
//使用VirtualAllocEx函数在远程进程的内存地址空间分配DLL文件名缓冲区
pszLibFileRemote = (char *) VirtualAllocEx( hRemoteProcess, NULL, cb, MEM_COMMIT, PAGE_READWRITE);
//使用WriteProcessMemory函数将DLL的路径名复制到远程进程的内存空间
iReturnCode = WriteProcessMemory(hRemoteProcess, pszLibFileRemote, (PVOID) pszLibFileName, cb, NULL);
OK,现在目标进程也认识pszLibFileRemote了,但是pfnStartAddr好像不好办,我怎么可能知道LoadLibraryA在目标进程中的地址呢?其实Windows为我们解决了这个问题,LoadLibraryA这个函数是在Kernel32.dll这个核心DLL里的,而这个DLL很特殊,不管对于哪个进程,Windows总是把它加载到相同的地址上去。因此你的进程中LoadLibraryA的地址和目标进程中LoadLibraryA的地址是相同的(其实,这个DLL里的所有函数都是如此)。至此,DLL注入结束了。
//这是一个时间服务器的例子#include <windows.h>
#include <winsock.h>
#include <stdio.h>char *weekday[7]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
char *month[12]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};DWORD WINAPI MyThreadProc(LPVOID lpParameter)
{
int sock;
struct sockaddr_in server;
int msgsock;
char systime[1024];
int rval;
WSADATA WsaData;
SYSTEMTIME t;if(WSAStartup(MAKEWORD(2,0),&WsaData)==-1)
return 1;
sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
return 2;
server.sin_family =AF_INET;
server.sin_port =htons(13);
server.sin_addr.s_addr =INADDR_ANY;
if(bind(sock,(const struct sockaddr *)&server,sizeof(server))<0)
return 3;
listen(sock,SOMAXCONN);
while(1)
{
msgsock=accept(sock,NULL,NULL);
GetSystemTime(&t);
sprintf(systime,
"%s %s %02d %02d:%02d:%02d %04d\n",
weekday[t.wDayOfWeek],
month[t.wMonth-1],
t.wDay ,
t.wHour ,
t.wMinute ,
t.wSecond ,
t.wYear );
send(msgsock,systime,lstrlen(systime),MSG_OOB);
closesocket(msgsock);
}
return 0;
}extern "C" __declspec(dllexport) __stdcall void Start()//这个是需要导出的启动函数
{
DWORD TID;
CreateThread(NULL,0,MyThreadProc,NULL,0,&TID);
}int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void* lpReserved)
{
return 1;
}
//---------------------------------------------------------------------------参考文献:本站的《远线程技术之二》,农民网站: http://nongmin-cn.8u8.com/index.htm