Click here to Skip to main content
15,883,901 members
Articles / Desktop Programming / Win32

Spying Window Messages from the Inside

Rate me:
Please Sign up or sign in to vote.
4.94/5 (25 votes)
18 Feb 2009GPL36 min read 108.6K   13.9K   136   7
An article on Hooking and Monitoring Window messages
Image 1

Contents

Introduction

This article presents you with a different perspective of how to inspect window messages, to see how applications are communicating and managing their controls. We are not going to explain what window messages are or what they are used for in this article, so we suggest that you read these excellent articles to understand them: Handling Window Messages (Part 1, Part 2, Part 3). In this article we are going to monitor the Message API from the inside by hooking the target process.

So, What's the Good News?

As a first step when developing, to inspect Windows, we open the Spy++ application and start the tedious work of following messages as they are printed in their hundreds. This is helpful most of the time, as we usually want to know what our windows are seeing and receiving. Yet, what happens when we want to know exactly how an application is communicating with its controls (what calls it makes to the message API) or want to see if our messages are getting filtered by someone else? As you may know, Spy++ installs 3 global hooks to receive every Send, Post and Call to a window message handler. The information provided by these methods is not enough to know what messages are coming from our application or if any of them have been filtered by a hook installed earlier in the call chain.

Do not panic, Deviare comes to rescue. What we are going to do is intercept all the Message APIs from the process that the window belongs to and monitor its calls. From there, we can be sure of what messages are being sent from the application to its controls and if any of them are missing from the ones that Spy++ is reporting, then we will know if someone else is watching us...

What happens with the messages not known by Spy++? How are we going to see them? Look at what happens with many of the messages used by the standard ListView in Windows. Spy++ does not know anything about them if the window is subclassed (for example ATL:SysListView32), and cannot trace its content. Try following LVM_GETNEXTITEM in Outlook Express and you will only see unknown 0x100C messages. The same goes for custom user messages that you may know and want to follow. We need an application that can be customized to our needs!

Deviare Message Spy

To probe our theory, we have built this message spying application. We have added to it a way to lookup windows handlers, hook the process owning it, and correctly report the messages and structures.

Finding a Window: The Spy++ Style Window Finder

To pick the target window and the process, we wanted an interface like the one used in Process Explorer and Spy++. Thanks to Mark Belles, this was an easy task. He has a great article on how to implement a nice Window Finder, here on The Code Project.

Image 2

Image 3

Hooking

In order to install a hook, first we need to identify our target process. After obtaining a window handle from our Window Finder, we can use GetWindowThreadProcessId to identify which process owns the window. From there, we use the .NET API to access it and tell Deviare which process we wish to hook.

C#
Win32.GetWindowThreadProcessId(hWnd, out _processId);
_txtProc.Text = Process.GetProcessById(_processId).MainModule.ModuleName

For our monitoring, we have divided the API in 2 sets: the Dispatch group, and the Sent and Post group. Monitoring messages that arrive to the first group will provide us with a very similar view of what Spy++ sees. This is because these messages arrived to the application and have not been filtered by any hook. With our second group, we will identify direct and asynchronous calls to the Message API.

Let's see how we install the hook for one of these functions:

C#
procs = _mgr.get_Processes(0);
procs = _mgr.get _Processes(0)
proc = procs.get_Item(_processId)
IPEModuleInfo mod = proc.Modules.get_ModuleByName("user32.dll");
IExportedFunction fnc = mod.Functions.get_ItemByName("PostMessageW");
_hook = _mgr.CreateHook(fnc);
_hook.Attach(proc);
_hook.OnFunctionCalled += new Deviare.DHookEvents_OnFunctionCalledEventHandler
			(_hookPst_OnFunctionCalled);
_hook.Properties = (int)DeviareCommonLib.NktHookFlags._call_before;
_hook.Hook();

As you see, we easily pick our target process by Id and select its Module and Function by name. The module name is not important, as it is always going to be “user32.dll”. If you have doubts, you can use Spy Studio to watch the process modules and exported functions.

Once the hook gets installed, we will receive notifications on our handler. From there, we parse the function parameters transparently with the interface provided. (These parameters are actually in the target process, and Deviare copies them to our process on our demand and handles all the communication).

C#
int returnVal = callInfo.ReturnValue;
IParams pms = callInfo.Params;
IEnumParams enm = pms.Enumerator;
IParam pm = enm.First;
IParam recvMsgHndl = pms.get_Item(0);
IParam recvMsgParam = pms.get_Item(1);
IParam recvWParam = pms.get_Item(2);
IParam recvLParam = pms.get_Item(3);

After reading all the data we require from the call, we will use our generated XML to identify the message and properly cast it to its structure and show it properly.

The XML

The XML document in this application was created specifically to link together the message names, values and parameters. As messages like WM_LBUTTONDOWN are predefined as 0x201, we can place this in an XML file containing information on the parameters WPARAM and LPARAM.

XML
<message value="0x201">
 <name>WM_LBUTTONDOWN</name>
 <return value="">
  <returninfo></returninfo>
  <returnmisc></returnmisc>
 </return>
 <wparam value="">
  <wname>wParam</wname>
  <wmisc>wParam Indicates whether various virtual keys are down. 
	This parameter can be one or more of the following values.
MK_CONTROL
    The CTRL key is down.
MK_LBUTTON
    The left mouse button is down.
MK_MBUTTON
    The middle mouse button is down.
MK_RBUTTON
    The right mouse button is down.
MK_SHIFT
    The SHIFT key is down.
MK_XBUTTON1
    Windows 2000/XP: The first X button is down.
MK_XBUTTON2
    Windows 2000/XP: The second X button is down.</wmisc>
  </wparam>
  <lparam value="">
   <lname>lParam</lname>
   <lmisc>lParam 
    The low-order word specifies the x-coordinate of the cursor. 
	The coordinate is relative to the upper-left corner of the client area.
    The high-order word specifies the y-coordinate of the cursor. 
	The coordinate is relative to the upper-left corner of the client area.
   </lmisc>
  </lparam>
  <misc></misc>

We could not find any database with this information, so we generated an XML document with the messages that we were interested in knowing about. As you can see, it is easy to simply add any message you want. In the process of building this XML, we used a very nice tool called ApiViewer from ActiveVB.de. Just search for the message names you want and you can evaluate the message values from the names.

The Cast

Now that we can identify the structures used on messages, we need to tell Deviare. Basically we are telling it to interpret our parameter, not as a simple LPARARM or WPARAM type, but as the complex structure we know is there. This is the case for messages like WM_DRAWITEM. So, to read its structure contained within the LPARAM, we need to cast it as follows:

C#
IParam pm = pms.get_Item(2); //LPARAM
pm = pm.CastTo("LPDRAWITEMSTRUCT"); 	//Now our IParam is read as a pointer 
				//to DRAWITEMSTRUCT
pm = pm.Evaluated; //Resolve the pointer indirection
//Ready to use IParam as the structure sent by the OS.

It is possible to do this with all of the structures you can find defined in the windows headers. So, you should be able to cast and read any of them that are used within these messages.

Using Deviare Message Spy

DevMsgSpy.png

Above we have our Deviare Message Spy in action. We selected the contacts list window from Outlook Express (at the bottom left) to spy on. You can see all the message values that were sent via Post and Send Message APIs. LVM_HITTEST has been expanded to show the full values received. As LPARAM is a pointer to the LVHITTESTINFO structure, we can find all relevant information contained within.

Hope you enjoyed this article, and found it useful. Let us know what you think!

Known Issues

Many messages have the same Hex Address, such as TB_GETITEMRECT and TTM_UPDATE. Both of these messages have the value of 0x41d but are very different messages.

The TTM_UPDATE Message forces the current tool to be redrawn. It does not use the wParam and lParam whereas TB_GETITEMRECT message retrieves the bounding rectangle of a button in a toolbar.

TB is a Toolbar message and TTM is a Tooltip message. As our Spy++ style window finder already finds the window class, such as SysListView32 and ToolbarWindow32, it would be easy to use the class name to tell the program with XML message is the correct one.

Resources

History

  • February 2009: Article posted

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Software Developer
Argentina Argentina
C/C++ developer interested on operating systems, reverse engineering, general system programming, CPU architecture, etc.
Now I'm working creating plugins Outlook Plugin Development.

Comments and Discussions

 
Questiontooltip text Pin
Member 1205449425-Mar-17 10:41
Member 1205449425-Mar-17 10:41 
SuggestionRequires /32BIT flag Pin
foxyshadis25-Sep-11 14:11
foxyshadis25-Sep-11 14:11 
GeneralRe: Requires /32BIT flag Pin
Member 123415361-Nov-16 1:54
Member 123415361-Nov-16 1:54 
GeneralDidn't work with me Pin
Bibo197813-Sep-09 22:35
Bibo197813-Sep-09 22:35 
The code throws unknown exception when I call hook command from deviare tool !!!
GeneralRe: Didn't work with me Pin
sprice8621-Nov-09 22:36
professionalsprice8621-Nov-09 22:36 
GeneralRe: Didn't work with me for me too people Pin
Priya_Sundar14-Oct-10 1:41
Priya_Sundar14-Oct-10 1:41 
GeneralRe: Didn't work with me Pin
wasimoooo23-Feb-12 1:03
wasimoooo23-Feb-12 1:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.