Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Sending windows messages from a remote machine

0.00/5 (No votes)
6 Sep 2003 1  
A server utility that attaches itself to an existing process and processes client requests

Introduction

This article is inspired by two other articles on the code project website. The first one is Brian Friesen's PasswordSpy - Retrieving lost passwords using Windows hooks , the second one is Robert Kuster's Three Ways to Inject Your Code into Another Process . I would like to thank the two authors for their excellent work. Basically, I combine the techniques described in their articles to make some simple utility programs that allow you to send windows messages (not just stealing password, :-) to almost any running program on a windows machine from a different machine.

The SendMessage API

As you may have already known, if you have a windows handle, you can use the win32 api SendMessage to call that window's predefined routine to process the message you specified. For example, if you use SendMessage specifying the window's handle, the WM_GETTEXT message, a size of a character buffer, and a pointer to the buffer, you will be able to copy the text in the target window into the buffer in your program. The interesting point is, the target window may belong to a different process. Similarly, you can do the same with the WM_SETTEXT message to make a window display whatever text you wish.

However, this api apparently does not work if your program is running on a different machine. We need a process on the target machine to act as the server in order to process requests from our client program that runs on a different machine. Another problem is, some security sensitive programs may choose to ignore or return error when a windows message is sent from a different process. If we can somehow make our sever run within the target process, then the problem will be solved.

My solution

My solution consists of three small components, Loader.exe, Worker.dll , and MsgSender.dll. XYNTService described in one of my other articles can provide some more features. Let me first explain how these three components work together.

Using the Loader.exe program, you can load the dynamic library Worker.dll into another process's address space. For example, the following command will load Worker.dll into the address space of the notepad application that is already running on your machine.

Loader.exe /notepad.exe

If notepad is not running, the command will not work, of course. If it is running, Worker.dll will start a new thread in the notepad process and waiting to process incoming requests from client programs. The MsgSender.dll is a com dll that can be used in client programs to send requests to the server (i.e. the Worker.dll running inside the notepad process). The SendMsg method in the com dll will send a request to the instance of the Worker.dll attached to the notepad process and get back an output string which is specific to the request being sent. The MsgSender.dll can be used by any program (VB, VC++, .NET, etc.) that can create a com object and the program can be run on a different machine.

Now I will explain the details under the cover.

The Loader.exe program simply uses some win32 api calls to load Worker.dll into the target process. The main functions being used are VirtualAllocEx, LoadLibrary, and CreateRemoteThread. Please read Robert Kuster's article or the source code of this article if you want to know more implementation detail. As you can see, the target process is identified by executable file name therefore if multiple instances of the same program are running, only one of them will be "hacked".

The dynamic library Worker.dll has to be located in the same folder as the Loader.exe program. When it is loaded into the target process's address space, its DllMain routine will be invoked. Within this routine, a new thread will be started. The new thread will create a server (tcp) socket and listens to client requests on that socket. When a client request comes in, the same thread will process it and send back the output string. Which port to use for the server socket is defined in the Worker.ini file located in the same folder as Worker.dll. Here is an example of the Worker.ini file.

[Settings] 

PortNumber=51290 

MaxDataSize=10240 

If the Worker.ini file does not exist or the PortNumber parameter is missing, Worker.dll will use the default port 51299. The MaxDataSize parameter is used to limit the size of user request so that no client can send arbitrarily large amount of data. Please note that the value of the PortNumber parameter has to be a port not currently used by other programs, otherwise the new thread will fail. Error messages will be written to file Worker.log located in the same folder as the dll.

The component MsgSender.dll contains a com object whose prog id is MsgSender.1. This com object has only two methods, SendMsg and GetErrorMessage.

HRESULT SendMsg(BSTR sServer, long nPort, BSTR sMsg, 
    [out, retval] BSTR* pOutput); 

HRESULT GetErrorMessage([out, retval] BSTR* pOutput); 

For the SendMsg method, the sServer parameter is the server ip address or the server name (if the server is running on the same machine, this parameter can be the empty string), the nPort parameter is the port number defined in the Worker.ini file, the sMsg parameter is the client request string which will be described in the next section in detail. The return value is the output string from Worker.dll . In case of error, the SendMsg method will return the empty string. The GetErrorMessage method returns a description string for the error that occurred during the previous call to SendMsg. The code below is a VB script that uses MsgSender.dll to retrieve text in an instance of the notepad program.

dim clt 

set clt = CreateObject("MsgSender.1") 

dim output 

output = clt.SendMsg("", 51290, ";Notepad;13;15;1000") 

if output = "" then 

    wscript.echo clt.GetErrorMessage 

else 

    wscript.echo output 

end if 

set clt = Nothing 

The code will be explained at the end of the next section.

Format of the client request string

Client request sent from MsgSender.dll has the following format

WindowCaption;WindowClass;WindowMessage;WParam;LParam 

or

;WindowHandle;WindowMessage;WParam;LParam 

Note that the request fields are delimited by semi-colon. WindowCaption is the title string of the target window (that you want to send a windows message to), WindowClass is the windows class string of the target window, WindowMessage is the numerical value of a windows message (for example, 13 is the WM_GETTEXT message), WParam and LParam are parameters for the windows message. Either the WindowCaption parameter or the WindowClass parameter can be left empty if it is not known.

If the handle of the target window is known, then the second format can be used and the decimal value of the handle is placed in the client request string. Please note that a semi-colon has to be in front of the handle value in the client request string.

For all but four special windows messages, here is how these parameters will be used by Worker.dll. After receiving a client request over the socket connection, Worker.dll will use the combination of WindowCaption and WindowClass to retrieve a handle of the target window. If you are familiar with the win32 api FindWindow or FindWindowEx, you will know what I am talking about here. The difference is, the window whose handle we are going to retrieve does not have to be a top-level window (it can be the great grand child of a top-level window, for example). In case the handle of the target window is known and sent in the client request string, Worker.dll will retrieve the handle value from the client request string.

Once the handle of the target window is successfully retrieved, Worker.dll will call the SendMessage api using the handle and the other fields WindowMessage , WParam , and LParam (all of them are converted into numerical values before calling SendMessage). The return value of the SendMessage api will be sent back to the client program, the socket connection to the client will be closed, and Worker.dll will continue to process new requests. Here is what the output string looks like

Message 16 returned 0 

The above should be interpreted as "the SendMessage api returned 0 for windows message 16 (WM_CLOSE)". Please check MSDN on what value will be returned by the SendMessage api for a specific windows message. On error, Worker.dll will return the empty string to the client and close the socket connection.

The four special messages that are treated differently are, WM_ENABLE (=10 ), WM_SETTEXT (= 12), WM_GETTEXT (=13), WM_GETTEXTLENGTH (=14 ). The WParam field in the client request is interpreted as the id of a child control on the target window. If the control id is not zero, the windows message in the client request will be sent to the child control instead of to the target window itself. Otherwise, the message will be sent to the target window (when child control id is zero or missing).

  • LParam is 1 or 0 for the WM_ENABLE message, indicating whether you want to enable or disable the target window (or the child control).
  • LParam is the actual text string for the WM_SETTEXT message. This message changes the text on the target window (or the child control).
  • LParam is the maximal number of characters you want to retrieve for the WM_GETTEXT message. This message retrieves the text on the target window (or the child control).
  • LParam is ignored for the WM_GETTEXTLENGTH message.
The return string for the WM_GETTEXT message is the actual text retrieved from the target window or the child control. For the other three messages, the return string is in the same format as the one described above. Please note there are other windows messages (not jut the four mentioned above) that should be processed differently. All you have to do is modify Worker.dll if you want to add some special code for a particular windows message.

Now I can explain the call to SendMsg in the above VB script code. The com object is connecting to port 51290 on the local machine since the sServer parameter is the empty string. The WindowCaption field in the request string is empty and the WindowClass field is Notepad, which means the target window will be the main window of an instance of the notepad program currently running. The WindowMessage field is 13 , which is the numerical value of WM_GETTEXT . The WParam field is 15 , which is the control id of the edit control on the notepad window. The LParam field is 1000 , which means this client wants to retrieve at most 1000 characters from the edit control of a notepad process on the local machine.

Retrieving information for all windows from a remote machine

As we have explained above, you can use windows caption/class string to identify the target window on a machine (so that you can send various windows messages to it). You can also use the handle of the target window directly if it is known. The handle value uniquely determines the target window (more than one windows can have the same caption and class strings).

But how do you get the handle of the target window in the first place? If you run a tool like Spy++ on the target machine, you can find out the handle value plus some other information for all windows. But, can you do this remotely? That is, retrieving windows caption, windows class, and windows handle for all windows from a remote machine?

I recently added a new feature to Worker.dll to accomplish this. If Worker.dll is already loaded into a process on the target machine by Loader.exe, you can send a special client request to retrieve the information you need. For example, here is a sample client request string

;;;1024000;102400 

The above request string contains two numbers, the first number is the maximal data size (1000k in this case) that you want to retrieve, the second number is the maximal data size (100k in this case) for each window. If you call SendMsg from a remote machine sending the above client request string to an instance of Worker.dll running on the target machine, you will get output data like the following.

...  
0_(MozillaWindowClass, 983464):   
    1_(MozillaWindowClass, 1180144):   
0_(MSCTFIME UI, 196678): M  
0_(IME, 196676): Default IME  
0_(tooltips_class32, 393444):   
0_(DV2ControlHost, 458888): Start Menu  
    1_(Desktop User Pane, 458892):   
        2_(Static, 524382): USER  
    1_(DesktopSFTBarHost, 524390):   
        2_(SysListView32, 589922):   
            3_(SysHeader32, 524392):   
    1_(Desktop More Programs Pane, 524384):   
        2_(Button, 655452): All &Programs  
    1_(DesktopSFTBarHost, 458896):   
        2_(SysListView32, 524388):   
            3_(SysHeader32, 458916):   
    1_(DesktopLogoffPane, 458914):   
        2_(ToolbarWindow32, 458912):   
0_(tooltips_class32, 65654):   
0_(tooltips_class32, 65670):   
0_(tooltips_class32, 65652):   
0_(Shell_TrayWnd, 196672):   
    1_(Button, 196680): start  
    1_(TrayNotifyWnd, 196682):   
        2_(TrayClockWClass, 196684): 4:34 PM  
        2_(SysPager, 262250):   
            3_(ToolbarWindow32, 65644): Notification Area  
        2_(Button, 65646):   
    1_(ReBarWindow32, 65656):   
        2_(MSTaskSwWClass, 65662): Running Applications  
            3_(ToolbarWindow32, 65666): Running Applications  
        2_(ToolbarWindow32, 65660): Quick Launch  
0_(tooltips_class32, 458894):   
0_(tooltips_class32, 458910):   
0_(ComboLBox, 328314):   
0_(SysFader, 131342): SysFader  
0_(tooltips_class32, 65686):   
0_(MozillaWindowClass, 1049066): 
untitled [file:/.../MessageAttachment.htm] - Composer  
    1_(MozillaWindowClass, 1114538):   
        2_(MozillaWindowClass, 852408):   
        2_(MozillaWindowClass, 983404):   
            3_(MozillaWindowClass, 983524):   
                4_(MozillaWindowClass, 1114534):   
                    5_(MozillaWindowClass, 1114584):   
0_(MSCTFIME UI, 328218): M  
0_(IME, 393420): Default IME  
0_(MSCTFIME UI, 197672): M  
0_(IME, 66282):   
0_(tooltips_class32, 66580):   
0_(DDEMLMom, 196698):   
0_(IME, 65828): Default IME  
0_(_AOL_TrayWindow, 65826):   
0_(tooltips_class32, 65688):   
0_(MSCTFIME UI, 65692): M  
0_(IME, 262200): Default IME  
0_(SysFader, 1180538): SysFader  
0_(Progman, 65674): Program Manager  
    1_(SHELLDLL_DefView, 65682):   
        2_(SysListView32, 65684): FolderView  
...

Each line in the output represents information for a window on the target machine, it is in the following format

Level_(Class, Handle): Caption 

For top level windows, the level is 0, child window of a top level window will have a level equals 1 (and so on). The output is indented so that you can see child-parent relationships between different windows. For example, the All Programs window has handle 655452, which is a child of the window with handle 524384 and a grand child of the top level Start Menu window.

It is not hard to modify the implementation of Worker.dll so that the above information will be returned in XML format.

Some potential problems

I don't mean any security related problems here. If you are going to run such a tool without reading my disclaimer at the end of the article or understanding the security risks involved, then you have no one to blame but yourself :-). What I meant is, sometimes the components don't work as you expected. For example, if you run the following command hoping that you can load Worker.dll into the IIS process, you will be disappointed.

Loader.exe /inetinfo.exe

This is because the IIS process is implemented as a windows service which is started by the operating system. The Loader.exe process you started from the command line does not have permission to open the IIS process and therefore cannot create a new thread from the IIS process. However, the above command can be placed into the XYNTService.ini file and be executed by XYNTService , then you can successfully load Worker.dll into the IIS process. Please read about XYNTService for more details on how to execute a command from a windows service.

Now you know how to load Worker.dll into the IIS process (assume IIS is running on your machine), you may think that you can send windows messages to any process on that machine day and night (and do some of the good or evil things you have always dreamed of doing). Wrong again. Worker.dll cannot send any windows message from the IIS process to a program (say, notepad) running on the desktop because the IIS process does not have permission to interact with windows on the desktop. However if you modify Worker.dll , you can do some other cool and dangerous things by loading it into the IIS process.

To be able to send windows messages to other programs on the desktop, you can either load Worker.dll into the target program directly or load it into windows explorer instead (explorer.exe is always running if a user is logged on).

How to install and use this tool

Here are the steps involved to install and run the utility described above.

  1. Unzip the downloaded file to the target machine (preserving the folder structure).
  2. Register the MsgSender.dll. You can also copy and register it on a different machine.
  3. Run the command Loader.exe /target_program.exe from the corresponding folder to load Worker.dll into the corresponding process.
  4. You can modify the Worker.ini file so that the server socket will use a different port.
  5. If you copy these programs and .ini files into multiple folders, you can load Worker.dll into multiple processes (after you modified the .ini files).
As you will see, the XYNTService.exe and the XYNTService.ini files are also included in this package. If you run the following commands, Worker.dll will be loaded into the windows explorer process (you may need to modify the XYNTService.ini file so that the command line for Loader.exe reflects the correct file path on your machine).

XYNTService.exe -i 

XYNTService.exe -r MessageAttachment 

If you reboot the machine or manually shutdown windows explorer, you will find out that you can no longer send client request to windows explorer using MsgSender.dll. This is because windows explorer has been terminated and restarted (no Worker.dll has been loaded into it yet). But after about an hour, MsgSender.dll will start working again. The trick is, XYNTService has automatically reloaded Worker.dll into the new windows explorer process. If you change the line CheckProcess=60 in the XYNTService.ini file to CheckProcess=1 , then you need only to wait for at most 1 minute instead of up to 60 minutes. Error messages from Worker.dll will be written to file Worker.log in the same folder. Error messages from MsgSender.dll can be seen by calling the GetErrorMessage method.

When you use the included source code to build and debug the programs yourself, remember that you should not mix the unicode version and the non-unicode version (the unicode version of Worker.dll only works with the unicode version of MsgSender.dll ).

This tool is implemented using win32 api calls and winsock api calls, there is no dependency on other components or libraries. It is not hard to write a java client program so that you can control your windows programs from a non windows platform. You can also modify the code to do a lot of other things or to process messages differently.

Disclaimer

There is no new idea or new technology introduced in this article. All I did is build a tool that (I think) is relatively easier to use. There could be other tools that do the same things better than mine.

Obviously there are security risks when using such tools in a un-protected environment. So use the code at your own risk. I will not be responsible for anything you do with the code. I hope you can find some good (non evil) uses of this tool.

Thanks.

Update history

  • September 7, 2003: Modified Worker.dll so that windows message can be sent to the target window using the windows handle (if it is known). Added a feature to retrieve information (class, caption, and handle) for all windows on the target machine.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here