概述:在Windows操作系统下,窗口上的字都是由TextOutA、TextOutW、ExtTextOutA、ExtTextOutw四个Api函数输出的,这四个函数都存在于Windows的核心库Gdi32.dll中。
由此看来,我们要实现屏幕取词就要从这四个函数入手,首先得到鼠标下方的程序名称,然后使鼠标下方区域的屏幕实效,此时系统就会调用这四个函数进行重画,我们就可以截获函数的参数,得到失效区域的文字,将文字进行处理。
在此过程中用导的主要函数:
得到窗口名:WindowFromPoint、ScreenToClient、InvalidateRect。
截获Api函数:SetWindowsHookEx、UnhookWindowsHookEx、CallNextHookEx、VirtualQuery、GetProcAddress、VirtualProtect。
下面我们将对实现的重要环节及相关知识做进一步的讲解:
-
由鼠标位置得到窗口名
由鼠标位置得到鼠标下方窗口的句柄可使用Api函数WindowFromPoint来实现:
HWND HWnd; //定义句柄变量
POINT MousePoint; //定义存放鼠标位置的变量
…
//由鼠标回调函数的参数得到鼠标位置
…
HWnd=WindowFormPoint(MousePoint);//由鼠标位置得到窗口的句柄
-
调用系统Api进行文字输出
在Windows系统中,所有的窗口都是通过一些系统函数画出来的,文字也是。在窗口区域无效时,系统就要调用相应函数进行重画。我们可以借助于Api函数使鼠标所在区域无效,系统将重新调用输出文字函数进行文字的再次输出。
InvalidateRect(hwnd,&rect,FALSE); //调用函数InvalidateRect使rect包含的矩形区域无效
-
鼠标钩子的应用
钩子是Windows系统消息处理机制的一个要点,主要分鼠标钩子、键盘钩子、外壳钩子、日志钩子等几种,通过在子例程里设置钩子函数,用来监视和截获Windows消息,从而在消息到达目的以前进行截获根据用户的要求做出相应的处理。
鼠标钩子用来截获鼠标消息,键盘钩子用来截获键盘消息,外壳钩子用来截获启动和关闭应用程序的消息,日志钩子用来监视和记录输入事件。
钩子也可分为线程专用钩子和全局钩子两种,线程专用钩子用来监视当前线程的消息,全局钩子用来监视系统全部线程的消息。要监视所有线程的消息,钩子函数必须被包含在动态连接库(Dll)中,这样才能被所有的应用程序所调用。
Windows提供Api函数SetWindowsHookEx来建立一个Hook,通过这个函数可以将一个程序放入Hook链中来监视系统的消息。
SetwindowsHookEx (idHook:Integer, //钩子类型
lpfn:TFNHookProc, //回调函数
hmod: HINST, //包含钩子函数的动态库地址
dwThreadId:DWORD //钩子的监控线程
)
HHOOK hMouseHook; //定义鼠标钩子变量
hMouseHook =SetWindowsHookEx(WH_MOUSE,(HOOKPROC)MouseProc, \
GetModuleHandle("hookdll.dll"),0); //设置鼠标钩子
-
替换系统Api函数
1.仿照系统Api函数TextOutA、TextOutW、ExtTextOutA、ExtTextOutW做四个自定义的函数MyTextOutA、MyTextOutW、MyExtTextOutA、MyExtTextOutW,和鼠标钩子放在同一个动态连接库中,参数和原函数保持一致。
//仿照TextOutA定义自己的函数
BOOL WINAPI MyTextOutA(HDC hdc, int nXStart, int nYStart, LPCSTR lpszString,
int cbString)
//仿照TextOutW定义自己的函数
BOOL WINAPI MyTextOutW(HDC hdc, int nXStart, int nYStart, LPCSTR lpszString,
int cbString)
//仿照ExtTextOutA定义自己的函数
BOOL WINAPI MyExtTextOutA(HDC hdc, int nXStart, int nYStart, UINT fuOptions,
const RECT FAR *lprc, LPCSTR lpszString,
UINT cbString,int FAR *lpDx)
//仿照ExtTextOutW定义自己的函数
BOOL WINAPI MyExtTextOutW(HDC hdc, int nXStart, int nYStart, UINT fuOptions,
const RECT FAR *lprc, LPCSTR lpszString,
UINT cbString,int FAR *lpDx)
2.当包含函数的动态连接库注入到其他进程时,寻找映射该进程的虚拟内存地址中各模块的基地址。
EXE和DLL被映射到虚拟内存空间的什么地方是由它们的基地址决定的。它们的基地址是在链接时由链接器决定的。当你新建一个Win32工程时,VC++链接器使用缺省的基地址0x00400000。你如果跟踪进WinMain的时候,hInstance值总是0x00400000就是上面的原因。当然也可以通过链接器的/BASE选项改变模块的基地址。
现在我们知道了,EXE通常被映射到虚拟内存的0x00400000处。DLL由于它们也有各自不同的基地址,通常情况下也被映射到不同进程的相同的虚拟地址空间处。那么我们怎么才能知道EXE和DLL被映射到哪里了呢?
在win32中,HMODULE和HINSTANCE是相同的。它们就是相应模块被装入进程的虚拟内存空间的基地址。比如:
HMODULE hmodule=GetModuleHandle(“gdi32.dll”);
返回的模块句柄强制转换为指针后,就是gdi32.dll被装入的基地址。
关于如何找到虚拟内存空间映射了哪些DLL?用如下方式实现:
while(VirtualQuery (base, &mbi, sizeof (mbi))>0) //穷举每一块内存区域
{
if(mbi.Type==MEM_IMAGE) //是EXE或DLL的映射
…
//进行地址转换,将原Api函数地址传给自定义函数
…
base=(DWORD)mbi.BaseAddress+mbi.RegionSize; //继续
}
3.得到基地址后,根据PE文件的格式穷举这个模块的IMAGE_IMPORT_DESCRIPTOR数组,
看是否引入了Gdi32.Dll,如是,穷举IMAGE_THUNK_DATA数组,看是否引入了
TextOuA、TextOutW、ExtTextOutA、ExtTextOutW等4个函数,如果找到其中之一,将其替换为相应的自己的函数。
|