自动发送QQ消息功能的原理及实现

一、QQ窗口分析

近来QQ尾巴病毒,在网络上很是流行,我也常常收到网友们发到来的带尾巴的消息。国庆节闲来无事,就拿此病毒来消遣一下——写一个类似的自动发送QQ消息的小程序。
先让我们分析一下QQ尾巴的发作情况:当用户打开一个QQ消息发送窗口时,病毒会自动往消息文本框里输入文本,然后不等用户反应过来就发出去了。如果要实现这些功能必须获得QQ窗口“发送”按钮的句柄和文本框的句柄。
在进行程序编写之前需要使用Spy++ 软件对QQ窗口进行分析,以了解QQ窗口各个子窗口之间的关系。SSpy++ (SPYXX.EXE) 是一个基于 Win32(最新版本支持Win64) 的实用工具,它提供系统的进程、线程、窗口和窗口消息的图形视图。使用 Spy++ 可以执行下列操作: 显示系统对象(包括进程、线程和窗口)之间关系的图形树。 搜索指定的窗口、线程、进程或消息。 查看选定的窗口、线程、进程或消息的属性。 直接从视图中选择窗口、线程、进程或消息。 通过鼠标定位,使用查找程序工具选择窗口。 使用复杂的消息日志选择参数设置消息选项。 提示使用 Spy++ 时,在许多实例中都可以单击鼠标右键显示常用命令的弹出式菜单。命令是否可用取决于指针的位置。例如,如果在指向窗口视图中的某项时单击并且选定的窗口可见,则弹出式“突出显示”菜单项将导致选定窗口的边框闪烁,从而可以轻松地在屏幕上找到该窗口。
启动Spy++,并打开一个QQ窗口。在Spy++的“监视”菜单中选择“查找窗口”(如图1),就弹出如图2的窗口,然后选择“查找程序工具”,拖拉到QQ窗口的输入文本框上,
image1
单击确定,就弹出如图3的窗口。
image2
在图3这个窗口中,我们可以对当前窗口的类别、父窗口和子窗口的继承关系等有关该窗口的信息。经分析,输入文本框是QQ窗口的第4个子窗口的第23个子窗口的第1个子窗口,这对我们程序的编写十分重要。采用同样的步骤,对“发送”按钮进行分析,得到:“发送”是QQ窗口的一个子窗口。在本文中为了示范作用,我们采用多种不同的方式获得子窗口。本程序是在VC++2003开发环境下编写的,可以正常运行。

二、QQ窗口句柄的获取

QQ窗口有两种,一种是消息模式,在这种情况下,窗口标题含有“发送消息”字样;一种是聊天模式,窗口标题含有“聊天中”字样。这些文字将成为我们寻找QQ窗口的线索。下面是获得QQ窗口的代码:

CWnd *hCurrentWindow;
CWnd *QQWnd;
CString WndText;
CString SendText;
hCurrentWindow=(CDialog* )GetWindow(GW_HWNDFIRST);
while(hCurrentWindow!=NULL)
{
	hCurrentWindow->GetWindowText(WndText);
	if((WndText.Find("聊天中")!=-1)||(WndText.Find("发送消息")!=-1))
	{
		QQWnd=hCurrentWindow;
		break;
	}
	else
	hCurrentWindow=hCurrentWindow->GetWindow(GW_HWNDNEXT);
}

在对代码进行解释之间,先对GetWindow(UNIT nCmd)进行简单的介绍。该函数返回与窗口有特定关系(如Z序或所有者)的窗口句柄。参数nCmd:说明指定窗口与要获得句柄的窗口之间的关系。该参数值可以是下列之一:

  • GW_CHILD:如果指定窗口是父窗口,则获得的是在Z序顶端的子窗口的句柄,否则为NULL。函数仅检查指定父窗口的子窗口,不检查继承窗口。
  • GW_ENABLEDPOUP:(WindowsNT 5.0)返回的句柄标识了属于指定窗口的处于使能状态弹出式窗口(检索使用第一个由GW_HWNDNEXT 查找到的满足前述条件的窗口);如果无使能窗口,则获得的句柄与指定窗口相同。
  • GW_HWNDFIRST:返回的句柄标识了在Z序最高端的相同类型的窗口。如果指定窗口是最高端窗口,则该句柄标识了在Z序最高端的最高端窗口;如果指定窗口是顶层窗口,则该句柄标识了在Z序最高端的顶层窗口:如果指定窗口是子窗口,则句柄标识了在Z序最高端的同属窗口。
  • GW_HWNDLAST:返回的句柄标识了在Z序最低端的相同类型的窗口。如果指定窗口是最高端窗口,则该柄标识了在Z序最低端的最高端窗口:如果指定窗口是顶层窗口,则该句柄标识了在Z序最低端的顶层窗口;如果指定窗口是子窗口,则句柄标识了在Z序最低端的同属窗口。
  • GW_HWNDNEXT:返回的句柄标识了在Z序中指定窗口下的相同类型的窗口。如果指定窗口是最高端窗口,则该句柄标识了在指定窗口下的最高端窗口:如果指定窗口是顶层窗口,则该句柄标识了在指定窗口下的顶层窗口;如果指定窗口是子窗口,则句柄标识了在指定窗口下的同属窗口。  
    
  •  GW HWNDPREV:返回的句柄标识了在Z序中指定窗口上的相同类型的窗口。如果指定窗口是最高端窗口,则该句柄标识了在指定窗口上的最高端窗口;如果指定窗口是顶层窗口,则该句柄标识了在指定窗口上的顶层窗口;如果指定窗口是子窗口,则句柄标识了在指定窗口上的同属窗口。  
    
  •   GW_OWNER:返回的句柄标识了指定窗口的所有者窗口(如果存在)。  
    

如果函数成功,返回值为窗口句柄;如果与指定窗口有特定关系的窗口不存在,则返回值为NULL。

在本段代码中我们用到了GW_HWNDFIRST和GW_HWNDNEXT这两个参数。该代码的思路是:首先获得最高端的窗口,然后获得其文本,判断其文本中是否包含“聊天中”或者“发送消息”,若包含,则找到QQ窗口;否则,转到下一个窗口。

三、为文本框设置文本

在获得了QQ窗口的句柄之后,我们就可以依据前面的分析,进一步找到输入文本框的句柄,对文本进行设置。
以下为获得QQ窗口输入文本框句柄的代码:

HWND CQQTailDlg:: MyChildWnd(HWNDhwnd,int num)//查找第num个子窗口,hwnd
//为父窗口
{
		HWND ChildWnd=0;
	for(int i=0;i<num;i++)
		{
		ChildWnd = ::FindWindowEx(hwnd,ChildWnd,NULL,NULL);
	}
return ChildWnd;
}

void CQQTailDlg::FindQQTextWnd(HWND hwnd) //查找输入文本框句柄
{
	HWND TempWnd;
	TempWnd=MyChildWnd(hwnd,4);
	TempWnd=MyChildWnd(TempWnd,23);
	TempWnd=MyChildWnd(TempWnd,1);
	TextWnd=TempWnd;
}

在本段代码里,查找子窗口,没有用上面提到的GetWindow()函数,而是FindWindowEx(HWND hwndParent,HWND hwndChildAfter,LPCTSTR lpszClass,LPCTSTR lpszWindow)。该函数获得一个窗口的句柄,该窗口的类名和窗口名与给定的字符串相匹配。这个函数查找子窗口,从排在给定的子窗口后面的下一个子窗口开始,在查找时不区分大小写。下面对其参数进行介绍

  • hwndParent:要查找子窗口的父窗口句柄。如果hwnjParent为NULL,则函数以桌面窗口为父窗口,查找桌面窗口的所有子窗口。
  • hwndChildAfter:子窗口句柄。查找从在Z序中的下一个子窗口开始。子窗口必须为hwndPareRt窗口的直接子窗口而非后代窗口。如果HwndChildAfter为NULL,查找从hwndParent的第一个子窗口开始。如果hwndParent 和 hwndChildAfter同时为NULL,则函数查找所有的顶层窗口及消息窗口。
  • lpszClass:指向一个指定了类名的空结束字符串,或一个标识类名字符串的成员的指针。如果该参数为一个成员,则它必须为前次调用theGlobaIAddAtom函数产生的全局成员。该成员为16位,必须位于lpClassName的低16位,高位必须为0。
  • lpszWindow:指向一个指定了窗口名(窗口标题)的空结束字符串。如果该参数为 NULL,则为所有窗口全匹配。

如果函数成功,返回值为具有指定类名和窗口名的窗口句柄。如果函数失败,返回值为NULL。
有前面的分析可知:输入文本框是QQ窗口的第4个子窗口的第23个子窗口的第1个子窗口,所以在FindQQTextWnd函数里出现了以下代码:

TempWnd=MyChildWnd(hwnd,4);
TempWnd=MyChildWnd(TempWnd,23);
TempWnd=MyChildWnd(TempWnd,1);

在获得了输入文本框句柄之后,就可以在文本框里设置你想发送的文本了。一般情况下可以向文本框发送WM_SETTEXT消息来实现,但是腾讯公司使用了一些技术,对文本框屏蔽了WM_SETTEXT消息。所以我们必须寻找其他的途径。功夫不负有心人,经过反复测试发现,WM_CHAR消息没有被腾讯公司屏蔽。因此,可以使用这个消息把字符发送到文本框。以下为设置文本代码:

void CQQTailDlg::SetTextWndText(HWNDhwnd,LPSTR pstr)//设置文本
{
	int len=::strlen(pstr);
	for(int i=0;i<len;i++)
	{
		::PostMessage(hwnd,WM_CHAR,pstr[i],0);
	}
}

这里用到了一个十分关键的函数PostMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM lParam)。该函数将一个消息放入(寄送)到与指定窗口创建的线程相联系消息队列里,不等待线程处理消息就返回。下面对其参数进行解释:

  • hWnd:其窗口程序接收消息的窗口的句柄。
  • Msg:指定被寄送的消息。
  • wParam:指定附加的消息特定的信息。
  • IParam:指定附加的消息特定的信息。

如果函数调用成功,返回非零值;如果函数调用失败,返回值是零。
在此我们不得不提到另外一个类似的函数SendMessage,在后面我们对其有详尽的介绍。PostMessage 和SendMessage的区别主要在于是否等待其他程序消息处理。PostMessage只是把消息放入队列,不等待线程处理消息完成就返回,然后继续执行;SendMessage把消息直接发送到窗口,并调用此窗口的相应消息处理函数,等消息处理函数结束后才返回。这两个函数的返回值也不同,PostMessage的返回值表示PostMessage函数执行是否正确,而SendMessage的返回值表示其他程序处理消息后的返回值。

至此我们就完成了向输入文本框添加文本的工作,下一步就是如何将文本自动发送出去。

四、文本的自动发送

要想实现文本的自动发送,首先必须获得“发送”按钮的句柄,然后向其发送左单击消息和弹起消息就可以了。

下面是获得“发送”按钮句柄的代码:

EnumChildWindows(QQWnd->m_hWnd,(WNDENUMPROC)EnumChildProc,0);

以及该函数调用的回调函数EnumChildProc:

BOOL CALLBACKEnumChildProc(HWND hwnd, LPARAM lParam)
{
	LPTSTR pstr;
	pstr= new TCHAR[100];
	int len=GetWindowTextLength(hwnd);
	::GetWindowText(hwnd,pstr,len+1);
	char *p;
	p=strstr(pstr,"发送");
	if(p&&len>1)
	{
		SendWnd=hwnd;
	}
	return TRUE;
}

在此段代码中我们使用了另外一种方式获得子窗口的句柄——枚举子窗口,使用了EnumChildWindows(HWND hWndParent,WNDENUMPROC lpEnumFunc,LPARAMlParam)函数。该函数可以枚举一个父窗口的所有子窗口。下面对其参数进行解释:

  • hWndParent:父窗口句柄
  • lpEnumFunc: 回调函数的地址
  • lParam:自已定义的参数

该函数直到最个一个子窗口被枚举或回调函数返回一个false,否则将一直枚举下去。

EnumChildProc是一个回调函数,负责对每一个子窗口的操作。注意:此回调函数要么是类的静态函数,要么就是一个全局的函数。在本程序中将其设为全局函数。通过代码我们可以知道,在回调函数中,获取每一个子窗口的文本,判断其是否包含“发送”,若包含,则认为该子窗口为“发送”按钮。
获取按钮句柄之后,就可以很方便的向该窗口发送消息,代码如下:

::SendMessage(SendWnd,WM_LBUTTONDOWN,MK_LBUTTON,0);
::SendMessage(SendWnd,WM_LBUTTONUP,0,0);

这里我们用到了我们前面提到的SendMessage(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam),该函数将指定的消息发送到一个或多个窗口。下面对其参数进行解释:

  • hWnd:其窗口程序将接收消息的窗口的句柄。
  • Msg:指定被发送的消息。
  • wParam:指定附加的消息指定信息。
  • IParam:指定附加的消息指定信息。

该函数的返回值是指定消息处理的结果,依赖于所发送的消息。

至此我们就完成了文本的自动发送。

五、程序实现步骤

本程序是在VC++2005开发平台上开发而成的,是一个基于对话框的MFC应用程序。程序实现的具体步骤如下:

(1)在VC++ 2005下新建一个名为QQTail的解决方案,在应用程序类型中,选择基于对话框。

(2)使用对话框编辑器,将对话框设计为如图4所示的窗口。

(3)为“开始”、“停止”和“关闭”按钮添加消息处理函数,为两个文本框分别添加变量。

(4)将上面所述的各种代码分别写进相应的函数。下面将有详尽的代码。

image3

详尽代码如下:

voidCQQTailDlg::OnBnClickedOk()//“开始”按钮代码
{
	this->UpdateData(true);
	pstr= new TCHAR[100];
	m_text.GetWindowText(pstr,100);
	m_time=m_time*1000;
	this->SetTimer(1,m_time,NULL);//设置计时器
}

voidCQQTailDlg::OnBnClickedPause()//“停止”按钮代码
{
	this->KillTimer(1);//删除计时器
}

voidCQQTailDlg::QQTailStart()//该程序的核心函数,用于启动各个相关函数
{
	CString WndText;
	CString SendText;
	hCurrentWindow=(CDialog*)GetWindow(GW_HWNDFIRST);
	while(hCurrentWindow!=NULL)
	{
		hCurrentWindow->GetWindowText(WndText);
		if((WndText.Find("聊天中")!=-1)||(WndText.Find("发送消息")!=-1))
		{
			QQWnd=hCurrentWindow;
			break;
		}
		else
		hCurrentWindow=hCurrentWindow->GetWindow(GW_HWNDNEXT);
	}

	EnumChildWindows(QQWnd->m_hWnd,(WNDENUMPROC)EnumChildProc,0);
	FindQQTextWnd(QQWnd->m_hWnd);//获得文本框句柄

	SetTextWndText(TextWnd,pstr);//设置文本
	::SendMessage(SendWnd,WM_LBUTTONDOWN,MK_LBUTTON,0);
	::SendMessage(SendWnd,WM_LBUTTONUP,0,0);
}

voidCQQTailDlg::OnTimer(UINT nIDEvent)//计时器处理函数
{
	if(nIDEvent==1)
	this->QQTailStart();
	CDialog::OnTimer(nIDEvent);
}

其它相关函数已在前面进行了说明,在此不在赘述。

下面为程序的运行结果:
image4
图5:运行结果

图5的(1)为程序的运行界面,设置间隔时间为1秒,发送文本为star;(2)为对当前打开的QQ窗口的作用效果,我们可以看出,QQ确实每隔1秒就发送star一次,达到了预定的目标。

六、总结

本文以实现一个自动发送QQ消息的程序为主线,重点论述了三种子窗口获取的方法和两种发送消息的方法,希望给读者一点帮助。

posted @ 2015-07-13 10:31  GeekRai  阅读(13249)  评论(1编辑  收藏  举报