C++ 黑客编程揭秘与防范(第3版)
上QQ阅读APP看书,第一时间看更新

1.3.2 通过API函数模拟鼠标键盘按键的操作

在开发程序时,总是依靠发送消息是非常辛苦的事情,因为消息的类型非常多,并且不同消息的附件参数也因不同的消息类型而异。Windows几乎为每个常用的消息都提供了相应的API函数。为了不必记忆过多的消息,使用API函数进行开发是相对比较直观的。

1.鼠标键盘按键模拟函数

在使用Windows的系统消息进行模拟鼠标或键盘按键操作时,可能显得不直观,也不方便。微软公司在进行设计时已经考虑到了这点,因此在Windows下的大部分消息都可以直接使用对应的等价API函数,不必直接通过发送消息。比如可以用WM_GETTEXT消息去获取文本的内容,对应的函数有GetWindowText()。试想一下,如果程序中一眼看去都是SendMessage()与PostMessage()之类的函数,岂不是很吓人。

本节介绍两个函数,分别用来模拟鼠标和键盘的输入,它们分别是keybd_event()和mouse_event(),定义如下:

        VOID keybd_event(
          BYTE bVk,
          BYTE bScan,
          DWORD dwFlags,
          ULONG_PTR dwExtraInfo
        );
        VOID mouse_event(
          DWORD dwFlags,
          DWORD dx,
          DWORD dy,
          DWORD dwData,
          ULONG_PTR dwExtraInfo
        );

从函数的名称就能看出,这两个API函数分别对应的是键盘事件和鼠标事件,在程序里使用时,对于阅读代码的人来说就比较直观了。下面将使用keybd_event()和mouse_event()两个函数来完成上一小节编写的刷新网页的小工具。

2.网页刷新工具

keybd_event()和mouse_event()这两个API函数,从函数的参数上来看,不需要给它们传递窗口句柄当作参数。那么这两个函数在进行鼠标和键盘的模拟时就必须将目标窗口激活并处于所有窗口的最前端。因此在程序中首先要完成的是将目标窗口设置到最前面,并且处于激活状态。先来看一下程序的界面部分,如图1-11所示。

图1-11 模拟鼠标键盘

这次的窗口相比上个程序的窗口要简单些。在界面上有两个按钮,第1个按钮“模拟键盘”是通过keybd_event()来模拟按F5键从而刷新网页,第2个按钮“模拟鼠标”是通过mouse_event()来模拟鼠标右键,从而弹出浏览器的快捷菜单,再通过keybd_event()模拟按R键来刷新网页。

知道了程序要实现的功能,先来完成将目标窗口设置到最前面并处于激活状态的部分,代码如下:

        VOID CSimInputDlg::FindAndFocus()
        {
            GetDlgItemText(IDC_EDIT_CAPTION, m_StrCaption);

            // 判断输入是否为空
            if ( m_StrCaption == "" )
            {
              return ;
            }

            m_hWnd = ::FindWindow(NULL, m_StrCaption.GetBuffer(0));

            // 该函数将创建指定窗口的线程设置到前台
            // 并且激活该窗口
            ::SetForegroundWindow(m_hWnd);
        }

这个自定义函数非常简单,分别调用了FindWindow()和SetForegroundWindow()两个API函数。FindWindow()函数在前面部分已经介绍过了。SetForegroundWindow()函数的使用比较简单,它会将指定的窗口设置到最前面并处于激活状态,该函数只有1个参数,是目标窗口的窗口句柄(这里的窗口句柄变量m_hWnd就是前面提到的由MFC提供的变量,该值也可以使用GetSafeHwnd()函数来进行获取,这点前面已经说过了,读者可以自行测试)。

“模拟键盘”按钮对应的代码如下:

        void CSimInputDlg::OnBtnSimkeybd()
        {
            // 在此处添加处理程序代码
            // 找到窗口
            // 将其设置到前台并激活
            FindAndFocus();
            Sleep(1000);

            // 模拟F5三次
            keybd_event(VK_F5, 0, 0, 0);
            Sleep(1000);
            keybd_event(VK_F5, 0, 0, 0);
            Sleep(1000);
            keybd_event(VK_F5, 0, 0, 0);
        }

在进行模拟键盘按键前,首先要调用自定义函数FindAndFocus()将浏览器设置到最前面并处于激活状态(在“模拟鼠标”按钮中同样要先调用FindAndFocus()自定义函数)。通过调用keybd_event()函数来模拟F5键进行了3次网页的刷新。

“模拟鼠标”按钮对应的代码如下:

        void CSimInputDlg::OnBtnSimmouse()
        {
            // 在此处添加处理程序代码
            FindAndFocus();

            // 得到窗口在屏幕的坐标(x, y)
            POINT pt = { 0 };
            ::ClientToScreen(m_hWnd, &pt);

            // 设置鼠标位置
            SetCursorPos(pt.x + 36, pt.y + 395);

            // 模拟单击鼠标右键
            // 单击鼠标右键后,浏览器会弹出快捷菜单
            mouse_event(MOUSEEVENTF_RIGHTDOWN, 0, 0, 0, 0);
            Sleep(100);
            mouse_event(MOUSEEVENTF_RIGHTUP, 0, 0, 0, 0);

            Sleep(1000);
            // 0x52 = R
            // 在弹出右键菜单后按下R键
            // 会刷新页面
            keybd_event(0x52, 0, 0, 0);
        }

代码中用到了两个陌生的API函数,分别是ClientToScreen ()和SetCursorPos()。它们的定义如下:

        BOOL ClientToScreen(
          HWND hWnd,         // handle to window
          LPPOINT lpPoint  // screen coordinates
        );

ClientToScreen()函数的作用是将窗口区域的坐标转换为屏幕的坐标。更直接的解释是,得到指定窗口在屏幕中的坐标位置。

        BOOL SetCursorPos(
          int X,  // horizontal position
          int Y    // vertical position
        );

SetCursorPos()函数的作用是将鼠标光标移动到指定的坐标位置。

在程序中为什么不使用mouse_event()来移动鼠标光标的位置,而是使用SetCursorPos()的位置呢?在API函数中,与SetCursorPos()对应的一个函数是GetCursorPos(),而SetCursorPos()函数往往会与GetCursorPos()函数一起使用。因为在很多情况下,程序设置鼠标光标位置进行一系列操作后,仍需要将鼠标光标的位置设置回原来的位置,那么在调用SetCursorPos()前,就需要调用GetCursorPos()得到鼠标光标的当前位置,这样才可以在操作完成后把鼠标光标设置为原来的位置。由此也可以看出,很多API函数是成对出现的,有Set也有Get,这样在记忆的时候非常的方便。

在程序中调用SetCursorPos()函数时,参数中的x坐标和y坐标分别加了两个整型的常量,这里可能比较费解。这两个整型常量的作用是通过ClientToScreen()函数得到的是浏览器左上角的xy坐标,而浏览器的鼠标右键菜单必须在浏览器的客户区中才能激活,因此需要在左上角坐标的基础上增加两个偏移,代码里的两个整型常量就是一个偏移(这里的偏移值可以自己随意修改,只要保证鼠标能够落在浏览器窗口中即可)。

3.小结

对于鼠标和键盘按键的模拟在很多地方都会使用,比如有的病毒用模拟鼠标单击杀毒软件的警告提示,比如游戏辅助工具通过模拟鼠标进行快速单击……对于鼠标和键盘按键的模拟并不简单。在常规的情况下,可以通过上面介绍的内容来进行鼠标和键盘按键的模拟操作。但是对于有些情况就不行了,比如有些游戏过滤了PostMessage()函数发送来的消息,有些游戏hook了keybd_event()和mouse_event()函数,有些游戏使用了DX来响应鼠标和键盘……