科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网安全频道如何获取其它进程中窗口的窗口过程

如何获取其它进程中窗口的窗口过程

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

获取本进程内窗口的窗口过程确实很简单,直接调用GetWindowLong(hWnd,GWL_WNDPROC)就可以了。

作者:巧巧读书 来源:巧巧读书 2008年9月2日

关键字: 进程 系统进程 进程管理

  • 评论
  • 分享微博
  • 分享邮件

  开发环境: Windows XP + VC6+Platform SDK 或者 VS.NET 2003+

  测试环境: Windows XP

  曾经以为获取一个窗口的窗口过程很简单,不就是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)函数调用的汇编代码大概就是这个样子(右边是响应的机器码):

  //参数入栈的顺序是从右向左,所以先pushnIndex,然后是pushhWnd

  push0xFC        //6AFC       //GWL_WNDPROC的值是-4,写成16进制就是0xFC

  pushhWndTarget     //5878563412   //假设hWndTarget=0x12345678

  callGetWindowLongA   //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

  pushlParam         //pusheax    //50 

  pushwParam        //pushhWndTarget //5878563412      //假设hWndTarget=0x12345678

  pushMsg                   //5834120000      //假设Msg=0x1234

  pushdwThreadId               //5878560000      //假设dwThreadId=0x5678

  callPostThreadMessageA           //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

  0x002d0012pusheax        //50       1byte

  0x002d0013pushhWndTarget    //5878563412  5bytes

  0x002d0018pushMsg        //5834120000  5bytes

  0x002d0023pushdwThreadId    //5878560000  5bytes

  0x002d0028callPostThreadMessageA //E87DEBA677  5bytes

  0x002d0033ret          //C3       1bytes

  0x002d0034xxxx

  有了以上的准备工作,下边就可以正式进行工作了。假如给定了要获取 hWndTarget 窗口的窗口过程

  调用 GetWindowThreadProcessId ,得到该窗口所属进程的Id,存放在 dwProcess中;

  调用 OpenProcess,打开该进程(如果打开失败,可能是权限不够,需要调用AdjustTokenPrivileges提升一下当前进程的权限),得到一个该进程的句柄,存放在hProcess中 ;

  调用 IsWindowUnicode,判断下一步应该调用 GetWindowLongA 还是 GetWindowLongW函数;

  调用 VirtualAllocEx,在目标进程中分配一些内存,供我们写入线程函数使用。函数返回的就是分配的内存的起始地址,就是我们的线程函数的起始地址,假设叫fnStartAddr;根据我们上边分析的结果,需要33个字节,另外,线程函数最后要有一个 ret指令,占用一个字节,共需34个字节;

  转载保留:http://www.qqread.com/process-info/s349814.html进入讨论组讨论。

  把以上分析的结果写入一个临时的缓冲区里;

  调用 WriteProcessMemory,把刚才的结果写入远程进程fnStartAddr的地址处 ;

  调用 CreateRemoteThread,指定线程函数地址为 fnStartAddr;

  进行一个小的消息循环,等待我们的返回结果;MSG msg;

  while(GetMessage(&msg,NULL,0,0))

  {

  if(msg.message == uMsgSendBack)

  {

  procRet = msg.lParam;

  break;

  }

  }

  进行一些善后工作,关闭打开的线程句柄、进程句柄,释放分配的远程内存;

  具体细节请参考示例代码。

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章