使用SendMessage()模拟鼠标操作

使用SendMessage()模拟鼠标操作

在样本分析工作中遇到一个样本,该样本先是打开Windows“运行”窗口,也就是常用的win + R的窗口,然后使用该窗口运行一个.chm文件,这个.chm文件的具体作用尚不清楚,不过杀软报毒,大概是实施二阶段攻击的恶意代码。

样本中使用SendMessage()来模拟鼠标操作,比较有意思,这里尝试复现一下。

具体思路是,先运行一个程序,打开该程序的窗口,然后使用FindWindows()找到那个窗口,找到窗口后使用SendMessage()向那个窗口发送消息,模拟鼠标的操作来修改该窗口的状态。

这里介绍一下几个可以用以创建进程的API。

WinExec(): 这个API是最好使用的,估计这个API的内部是通过ShellExecute()来实现的。

ShellExecute() & ShellExecuteEx(): 这两个API在MS文档上的描述都是对指定的文件执行一个操作。

但是这两个函数的区别在于:

  • ShellExecuteEx()的返回值是布尔类型,使用这个API创建进程后将会自动调用CloseHandle()关闭进程对应的句柄。
  • ShellExecute()的返回值则是新创建的进程的句柄

这里值得注意的是,ShellExecute()不仅能够对本地的文件执行操作,实际上也能够对URL执行操作。

CreateProcess(): 这个API就是ShellExecute() & ShellExecuteEx() 这两个API的底层实现。

另外,如果创建了进程句柄,就必须使用CloseHandle()关闭对应的句柄,因为即使进程终止,系统内核对象中的句柄以及已保存的状态都不会被清空。

虽然思路很清晰,但是具体实施还是比较麻烦的。

FindWindow

首先是需要找到要操作的窗口,使用FindWindow() & FindWindowEx()这两个API。

FindWindowA() 函数原型:

1
2
3
4
HWND FindWindowA(
[in, optional] LPCSTR lpClassName,
[in, optional] LPCSTR lpWindowName
);

参数:

lpClassName:一个字符串指针,用以指定要寻找的窗口的类名

lpWindowName:一个字符串指针,用以指定要寻找的窗口的名称,也即窗口的标题

返回值:

对应窗口的句柄。

The following table describes the system classes that are available for use by all processes.

Class Description
Button The class for a button.
ComboBox The class for a combo box.
Edit The class for an edit control.
ListBox The class for a list box.
MDIClient The class for an MDI client window.
ScrollBar The class for a scroll bar.
Static The class for a static control.

The following table describes the system classes that are available only for use by the system.

Class Description
ComboLBox The class for the list box contained in a combo box.
DDEMLEvent The class for Dynamic Data Exchange Management Library (DDEML) events.
Message The class for a message-only window.
#32768 The class for a menu.
#32769 The class for the desktop window.
#32770 The class for a dialog box.
#32771 The class for the task switch window.
#32772 The class for icon titles.

这里我们比较关心的窗口类名就是"#32770"也就是系统对话框。

使用FindWindowA()找到了对应窗口后,我们还需要找到要操作的窗口组件,FindWindowEx()能够完成这个任务。

FindWindowExA()函数原型:

1
2
3
4
5
6
HWND FindWindowExA(
[in, optional] HWND hWndParent,
[in, optional] HWND hWndChildAfter,
[in, optional] LPCSTR lpszClass,
[in, optional] LPCSTR lpszWindow
);

参数:

hWndParent:使用FindWindowA() 找到的窗口对应的句柄

hWndChildAfter:用以指定从第几个组件开始寻找

lpszClass:用以指定需要寻找的组件的类名

lpszWindow:用以指定需要寻找的组件的名称

返回值:

对应窗口的组件的句柄。

在这篇文章中,需要使用FindWindowEx()找到用以输入命令的ComboBox(ComboBox类的窗口是一种常见的控件,它是组合框控件的实现,通常用于显示一个可编辑的文本框和一个下拉列表框,允许用户从列表中选择项或手动输入文本)以及“确定”按钮。

SendMessage

找到对应窗口的组件后,可以使用SendMessage()来模拟对该组件的操作。

SendMessage函数原型:

1
2
3
4
5
6
LRESULT SendMessage(
[in] HWND hWnd,
[in] UINT Msg,
[in] WPARAM wParam,
[in] LPARAM lParam
);

参数:

hWnd:SendMessage()作用的窗口组件对应的句柄

Msg:要发送的消息,通常是系统预定义的消息。当然,程序也可以使用自定义的消息。

wParam:Word Parameter,附加于消息的参数

lParam:Long Parameter,附件于消息的参数

这里介绍一下常用的消息:

  • WM_ACTIVATE:用以修改窗口的激活状态,如果窗口处于后台运行,那么这个消息能够激活该窗口;反之,如果窗口目前处于前台,那么这个消息会让窗口转为后台运行。
  • WM_SETTEXT:用以设置窗口的文本内容,在参数lParam中指定要设置的文本内容
  • WM_LBUTTONDOWN:用以模拟鼠标左键Press效果,参数lParam设为 “MK_BUTTON”,参数wParam用以指定鼠标点击位置
  • WM_LBUTTONUP: 用以模拟鼠标左键Release效果,参数设置和WM_LBUTTONDOWN同理。

有了这些消息类型后,可以模拟出向指定ComboBox输入一条文本内容,然后鼠标点击运行的过程。

实例

这里将会运用同样的原理,使用“运行”窗口运行任务管理器(“taskmgr”)。

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <Windows.h>

int main()
{
SHELLEXECUTEINFOA pExecInfo;
HWND hWindow;
HWND hCombo;
HWND hButton;

pExecInfo.cbSize = sizeof(SHELLEXECUTEINFOA);
pExecInfo.fMask = SEE_MASK_FLAG_NO_UI;
pExecInfo.lpVerb = "open";
pExecInfo.lpFile = "shell:::{2559a1f3-21d7-11d4-bdaf-00c04f60b9f0}";
pExecInfo.nShow = 1;

if (ShellExecuteExA(&pExecInfo))
{
do {
hWindow = FindWindowA("#32770", "运行");
Sleep(0x5);
} while (!hWindow);

if ((hCombo = FindWindowExA(hWindow, NULL, "ComboBox", NULL)) &&
(hButton = FindWindowExA(hWindow, NULL, "Button", "确定")))
{
SendMessageA(hCombo, WM_ACTIVATE, NULL, (LPARAM)"taskmgr");
SendMessageA(hButton, WM_LBUTTONDOWN, MK_LBUTTON, NULL);
SendMessageA(hButton, WM_LBUTTONUP, MK_LBUTTON, NULL);
}
else
{
printf("Faild to find the combobox. error code %d.\n", GetLastError());
exit(EXIT_FAILURE);
}
}
else
{
printf("Failed to invoke the run window.\n");
exit(EXIT_FAILURE);
}
return 0;
}

程序运行结果如下:

mouse clicking simulation

注意上面的代码中,在使用ShellExecuteA()时,将fMask指定为"SEE_MASK_FLAG_NO_UI",意即不要显示UI界面,即使是错误对话框,使用这个掩码是为了提高隐蔽性。

但是使用这个掩码后,得到的窗口也是未激活状态的,所以需要使用MS_ACTIVATE来激活窗口。

另外,在创建进程后,FindWindow()并不能马上找到对应窗口,所以需要将寻找窗口的函数写在一个循环体中。


使用SendMessage()模拟鼠标操作
https://2hyan9.github.io/2023/08/06/使用SendMessage模拟鼠标操作/
作者
2hYan9
发布于
2023年8月6日
许可协议