2008年(884)
分类: C/C++
2008-08-06 10:04:08
曾经以为获取一个窗口的窗口过程很简单,不就是GetWindowLong一下吗,看spyxx获取的多么顺利。后来才发现原来不是这么简单。获取本进程内窗口的窗口过程确实很简单
,直接调用GetWindowLong(hWnd,GWL_WNDPROC)就可以了(注意,根据窗口是否是Unicode的,你要判断是调用GetWindowLongA,还是GetWindowLongW,可以用IsWindowUnicode来判断),
但是GetWindowLong这个函数内部会检查调用进程和该窗口句柄是否属于同一进程,如果不是,就简单的返回0了。
既然这样,我们又不能去修改GetWindowLong,那就只有想办法让它认为我们和那个窗口是在一个进程里了。回想起Windows核心编程里讲过,通过创建远程线程的方式
,可以在其它进程内创建一个新的线程,并且可以指定这个线程的线程函数
。因为Windows的进程之间的地址相互之间是不可见的,所以我们不能指定本地的线程函数,而要在远程分配内存,把我们要做的事情以机器码的形式写进去。
(spyxx是怎么做的呢?它启动时安装了一个全局的钩子WH_GETMESSAGE,这样,几乎所有有消息循环的程序都会加载它的hook dll,从而可以在其它进程的地址空间里调用GetWindowLong,如果我们仅仅为了获取一下窗口过程装一个全局钩子,未免有点儿太浪费了:))
先来分析一下我们的线程函数都需要做什么。
首先,需要调用GetWindowLong获取这个窗口的窗口过程,然后要告诉我们。可以用PostMessage或者PostThreadMessage的方式通知我们的程序 。具体如下所示:
//hWndTarget是我们要获取其窗口过程的窗口句柄, 假设 hWndTarget = 0x12345678 //dwThreadId是我们的线程Id ,假设 dwThreadId = 0x5678 LONG wndProc = GetWindowLong(hWndTarget,GWL_WNDPROC); PostThreadMessage(dwThreadId,WM_MYMESSAGE,(WPARAM)hWndTarget,(LPARAM)wndProc);
因为这个时候函数的参数我们都已经知道了,所以可以直接硬编码到程序里。先看一下GetWindowLongA的函数原型:
WINUSERAPI LONG WINAPI GetWindowLongA( HWND hWnd, int nIndex)
一共有两个参数,我们的 GetWindowLongA(hWnd,GWL_WNDPROC)函数调用的汇编代码大概就是这个样子(右边是响应的机器码):
//参数入栈的顺序是从右向左,所以先push nIndex,然后是push hWnd push 0xFC //6A FC //GWL_WNDPROC的值是-4,写成16进制就是0xFC push hWndTarget //58 78 56 34 12 //假设 hWndTarget = 0x12345678 call GetWindowLongA //E8 (GetWindowLongA-下一条指令的地址)
GetWindowLongA函数的返回值在eax寄存器里,我们的PostThreadMessageA函数里用wParam参数发送该窗口句柄,用lParam参数发送该窗口的窗口过程 ,所以push lParam 就是 push eax
再来看看PostThreadMessageA的函数原型:
WINUSERAPI BOOL WINAPI PostThreadMessageA( DWORD idThread, UINT Msg, WPARAM wParam, LPARAM lParam)
PostThreadMessageA函数调用的汇编代码如下:
//Msg是我们自定义的消息,假设 Msg = 0x1234 push lParam //push eax //50 push wParam //push hWndTarget //58 78 56 34 12 //假设 hWndTarget = 0x12345678 push Msg //58 34 12 00 00 //假设 Msg = 0x1234 push dwThreadId //58 78 56 00 00 //假设 dwThreadId = 0x5678 call PostThreadMessageA //E8 (PostThreadMessageA-下一条指令的地址)
解释一下call PostThreadMessageA指令:
第一个机器码是E8,后边跟的不是PostThreadMessageA这个函数的地址,而是从当前指令的下一条指令的地址到那个函数的距离。例如,当前指令的地址是 0x2d0028,call指令长度为5个字节 ,那么下一条指令的地址就是0x2d0028 5 = 0x2d0033,假如 PostThreadMessageA函数的起始地址是0x77d3ebb0,那么我们call后边的数就是 0x77d3ebb0 - 0x2d0033 = 77A6EB7D,那么 call PostThreadMessageA的实际的机器码就是 E8 7D EB A6 77
0x002d0012 push eax //50 1 byte 0x002d0013 push hWndTarget //58 78 56 34 12 5 bytes 0x002d0018 push Msg //58 34 12 00 00 5 bytes 0x002d0023 push dwThreadId //58 78 56 00 00 5 bytes 0x002d0028 call PostThreadMessageA //E8 7D EB A6 77 5 bytes 0x002d0033 ret //C3 1 bytes 0x002d0034 xxxx
有了以上的准备工作,下边就可以正式进行工作了。假如给定了要获取 hWndTarget 窗口的窗口过程
MSG msg; while(GetMessage(&msg,NULL,0,0)) { if(msg.message == uMsgSendBack) { procRet = msg.lParam; break; } }
具体细节请参考示例代码。
下载本文示例代码