Sunday, June 27, 2010

Crossing the process boundary with .NET

 

Every so often my post Hacking my way across the process boundary gets some attention. Mostly in the form of requests for a .NET version of this technique. Now out of laziness more than anything else I have not actually taken the time or effort to do the conversion until now. So for those that need to access ListView or TreeView data from another process here is a simple example of one possible way to do it. Since I used C# for the example, I could very well have used unsafe code blocks to do some of the work, however I decided to avoid this making this example applicable to VB.NET developers as well. If you would like to see a version using unsafe code blocks, drop me a note and I will get round to it. To keep the sample short I have removed anything but the most rudimentary error checking. For an explanation of this code, please refer to the original post sighted above.

 

using System;
using System.Runtime.InteropServices;
using System.Text;
public class CrossProcessMemory
{
const int LVM_GETITEM = 0x1005;
const int LVM_SETITEM = 0x1006;
const int LVIF_TEXT = 0x0001;
const uint PROCESS_ALL_ACCESS = (uint)(0x000F0000L | 0x00100000L | 0xFFF);
const uint MEM_COMMIT = 0x1000;
const uint MEM_RELEASE = 0x8000;
const uint PAGE_READWRITE = 0x04;

[DllImport("user32.dll")]
static extern bool SendMessage(IntPtr hWnd, Int32 msg, Int32 wParam, IntPtr lParam);

[DllImport("user32")]
static extern IntPtr GetWindowThreadProcessId( IntPtr hWnd, out int lpwdProcessID );

[DllImport("kernel32")]
static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle,
int dwProcessId);

[DllImport("kernel32")]
static extern IntPtr VirtualAllocEx( IntPtr hProcess, IntPtr lpAddress,
int dwSize, uint flAllocationType, uint flProtect);

[DllImport("kernel32")]
static extern bool VirtualFreeEx( IntPtr hProcess, IntPtr lpAddress, int dwSize,
uint dwFreeType );

[DllImport("kernel32")]
static extern bool WriteProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress,
ref LV_ITEM buffer, int dwSize, IntPtr lpNumberOfBytesWritten );

[DllImport("kernel32")]
static extern bool ReadProcessMemory( IntPtr hProcess, IntPtr lpBaseAddress,
IntPtr lpBuffer, int dwSize, IntPtr lpNumberOfBytesRead );

[DllImport("kernel32")]
static extern bool CloseHandle( IntPtr hObject );

[StructLayout(LayoutKind.Sequential)]
public struct LV_ITEM
{
public uint mask;
public int iItem;
public int iSubItem;
public uint state;
public uint stateMask;
public IntPtr pszText;
public int cchTextMax;
public int iImage;
}

public static string ReadListViewItem( IntPtr hWnd, int item )
{
const int dwBufferSize = 1024;

int dwProcessID;
LV_ITEM lvItem;
string retval;
bool bSuccess;
IntPtr hProcess = IntPtr.Zero;
IntPtr lpRemoteBuffer = IntPtr.Zero;
IntPtr lpLocalBuffer = IntPtr.Zero;
IntPtr threadId = IntPtr.Zero;

try
{
lvItem = new LV_ITEM();
lpLocalBuffer = Marshal.AllocHGlobal(dwBufferSize);
// Get the process id owning the window
threadId = GetWindowThreadProcessId( hWnd, out dwProcessID );
if ( (threadId == IntPtr.Zero) || (dwProcessID == 0) )
throw new ArgumentException( "hWnd" );

// Open the process with all access
hProcess = OpenProcess( PROCESS_ALL_ACCESS, false, dwProcessID );
if ( hProcess == IntPtr.Zero )
throw new ApplicationException( "Failed to access process" );

// Allocate a buffer in the remote process
lpRemoteBuffer = VirtualAllocEx( hProcess, IntPtr.Zero, dwBufferSize, MEM_COMMIT,
PAGE_READWRITE );
if ( lpRemoteBuffer == IntPtr.Zero )
throw new SystemException( "Failed to allocate memory in remote process" );

// 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 = item;
lvItem.pszText = (IntPtr)(lpRemoteBuffer.ToInt32() + Marshal.SizeOf(typeof(LV_ITEM)));
lvItem.cchTextMax = 50;

// Copy the local LVITEM to the remote buffer
bSuccess = WriteProcessMemory( hProcess, lpRemoteBuffer, ref lvItem,
Marshal.SizeOf(typeof(LV_ITEM)), IntPtr.Zero );
if ( !bSuccess )
throw new SystemException( "Failed to write to process memory" );

// Send the message to the remote window with the address of the remote buffer
SendMessage( hWnd, LVM_GETITEM, 0, lpRemoteBuffer);

// Read the struct back from the remote process into local buffer
bSuccess = ReadProcessMemory( hProcess, lpRemoteBuffer, lpLocalBuffer, dwBufferSize,
IntPtr.Zero );
if ( !bSuccess )
throw new SystemException( "Failed to read from process memory" );

// At this point the lpLocalBuffer contains the returned LV_ITEM structure
// the next line extracts the text from the buffer into a managed string
retval = Marshal.PtrToStringAnsi((IntPtr)(lpLocalBuffer.ToInt32() +
Marshal.SizeOf(typeof(LV_ITEM))));
}
finally
{
if ( lpLocalBuffer != IntPtr.Zero )
Marshal.FreeHGlobal( lpLocalBuffer );
if ( lpRemoteBuffer != IntPtr.Zero )
VirtualFreeEx( hProcess, lpRemoteBuffer, 0, MEM_RELEASE );
if ( hProcess != IntPtr.Zero )
CloseHandle( hProcess );
}
return retval;
}
}



Sunday, June 6, 2010

Visual Studio 2010 – Box Selection

This has been blogged about and promoted everywhere and it is true, it is a super cool feature that even the pre-.NET IDE supported, but VS2010 breaths new life into the feature with some great enhancements. For a very cool intro to the functionality take a look here where a member of the IDE team demonstrates the enhancements.

So, why on earth would I be blogging about “old” news. Well one thing that I found “missing” with this feature until now was the ability to use the keyboard to perform a block selection. Now with VS 2010 you can use Shift+Alt+[Up, Down, Left, Right] to perform a block selection. This is a great enhancement, and worth knowing about if you are a keyboard junkie.