Tuesday, August 18, 2009

Archive – Hacking my way across the process boundary

This is another one of the posts that I am migrating from my previous blog. The post is based on an answer I provided on a news group post. A subsequent post provides a similar solution for .NET. This post was put up in early 2004.

About 2 years ago a C++/MFC question appeared on the news groups asking how to send windows messages to a ListView control in another process and receive the data returned in the LVITEM structure. At the time I came up with what I thought to be an imaginative solution and it worked for the op. Recently this question reappeared but this time for the .NET environment, a quick Google search and I pointed the OP to the original post, leaving the conversion to .NET as an exercise for the reader. Since the routine did the job again and I remember having some fun playing around with the routine so I thought I would revive it here. Now I just need to get around to converting it to .NET :)

In short you allocate a block of memory in the target process using VirtualAllocEx, this block must be big enough to hold both the LVITEM structure and any string data you expect back. You also declare an instance of the structure in your local process and setup all the necessary fields; the only field that is configured differently is the pszText member, which is defined as pointing to the block of memory in the target process offset by the sizeof the LVITEM structure. Then the local structure is moved to the target process using WriteProcessMemory. Once all the structures are in place and in the correct process all that remains is to send the message using SendMessage, and passing the address of the structure in the remote process as the lParam. On successful return of the SendMessage the remote process should contain the requested data and you can copy the data back to the local process using ReadProcessMemory. Once again the pszText pointer has to be fixed up in the local process to reference the local buffer offset again by the sizeof LVITEM.

The following C++ code provides an example of getting a item from a ListView hosted in another process.

const DWORD dwBufSize = 1024;

DWORD dwProcessID;
DWORD dwResult;
HANDLE hProcess;

BYTE *lpRemoteBuffer;

LVITEM lvItem = {0};

BYTE lpLocalBuffer[dwBufSize] = {0};

// Get the process id owning the window
::GetWindowThreadProcessId( hwndListView, &dwProcessID );

// Open the process wih all access (You may not have the rights to do this)
hProcess = ::OpenProcess( PROCESS_ALL_ACCESS, FALSE, dwProcessID );

// Allocate a buffer in the remote process
lpRemoteBuffer = (BYTE*)::VirtualAllocEx( hProcess, NULL, dwBufSize,
MEM_COMMIT, PAGE_READWRITE );

// Fill in the LVITEM struct, this is in your own process
// Set the pszText member to somewhere in the remote buffer,
// For the example I used the address imediately following the LVITEM stuct
lvItem.mask = LVIF_TEXT;
lvItem.iItem = 0;
lvItem.cchTextMax = 50;

// Point to after LVITEM in the remote buffer
lvItem.pszText = (LPTSTR)(lpRemoteBuffer + sizeof( LVITEM ));

// Copy the local LVITEM to the remote buffer
::WriteProcessMemory( hProcess, (LPVOID)lpRemoteBuffer,
&lvItem, sizeof(LVITEM), NULL );

// Send the message
::SendMessage( hwndListView, LVM_GETITEM, 0, (LPARAM)lpRemoteBuffer);

// Read the struct back from the remote process into local buffer
::ReadProcessMemory( hProcess, (LPVOID)lpRemoteBuffer, lpLocalBuffer,
dwBufSize, NULL );

//Fix pszText to point to same offset in local buffer
lvItem.pszText = (LPTSTR)(lpLocalBuffer + sizeof( LVITEM ));

// Clean-up
::VirtualFreeEx( hProcess, (LPVOID)lpRemoteBuffer, 0, MEM_RELEASE );
::CloseHandle( hProcess );

6 comments:

  1. Any ideas of what changes need to be made for this to work under a 64 bit operating system? I had this working under XP (32 bit)

    ReplyDelete
  2. I did a quick test of the code on 64 bit Windows 7, both processes compiled as 64 bit applications and it worked as is.

    ReplyDelete
  3. I just tried it with the exact code on Win7 x64, but the text was empty after "//Fix pszText to point to same offset in local buffer". I was trying it on the desktop's listview, and verified I have a valid handle. I tried using MessageBox(), but it displayed empty. Any ideas?

    ReplyDelete
  4. In addition, I am able to get the listview's count using ListView_GetItemCount(). I am using a 32bit compiler, I'm going to try it with a 64 and post if it works or not.

    ReplyDelete
  5. Yep, that was it! Don't use a 32bit compiler for 64bit shell control.

    ReplyDelete