views:

234

answers:

3

I wrote a function which can yield the text of a tree view item, even if the tree view is in a remote process. The function allocates two chunks of memory in the remote process, populates a TVITEM structure (which is the copied into the remote process), sends a TVM_GETITEM message and finally reads the contents of the second remote memory chunk back into a local buffer. This is the code:

std::string getTreeViewItemText( HWND treeView, HTREEITEM item )
{
    DWORD pid;
    ::GetWindowThreadProcessId( treeView, &pid );

    HANDLE proc = ::OpenProcess( PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, pid );
    if ( !proc )
        // handle error

    TVITEM tvi;
    ZeroMemory( &tvi, sizeof(tvi) );

    LPVOID tvi_ = ::VirtualAllocEx( proc, NULL, sizeof(tvi), MEM_COMMIT, PAGE_READWRITE);
    if ( !tvi_ )
        // handle error

    TCHAR buffer[100] = { 'X' };

    LPVOID txt_ = ::VirtualAllocEx( proc, NULL, sizeof(buffer), MEM_COMMIT, PAGE_READWRITE );
    if ( !txt_ )
        // handle error

    tvi.mask = TVIF_TEXT | TVIF_HANDLE;
    tvi.pszText =  (LPTSTR)txt_;
    tvi.cchTextMax = sizeof(buffer) / sizeof(buffer[0] );
    tvi.hItem = item;

    if ( !::WriteProcessMemory( proc, tvi_, &tvi, sizeof(tvi), NULL ) )
        // handle error

    if ( !::SendMessage( treeView, TVM_GETITEM, 0, (LPARAM)tvi_ ) )
        // handle error

    if ( !::ReadProcessMemory( proc, (LPCVOID)txt_, buffer, sizeof( buffer ), NULL ) )
        // handle error

    ::VirtualFreeEx( proc, tvi_, 0, MEM_RELEASE );

    ::VirtualFreeEx( proc, txt_, 0, MEM_RELEASE );

    ::CloseHandle( proc );

    return buffer;
}

This code works very nicely with the plain tree views which you get when passing the WC_TREEVIEW class name to CreateWindow. However, I noticed that it does not work with the newer trees as provided by MS Common Controls v5 (comctl32.ocx) or MS Common Controls v6 (mscomctl.ocx). In those cases, the returned text is always empty (the buffer is all zeroes). I also noticed that the SendMessage call returns zero (hence the error handling denoted by the // handle error comments above kicks in). It's unclear to me whether this really indicates an error, in any case the buffer is filled with all zeroes.

All other tree view messages (like TVM_GETITEMRECT) seem to work perfectly well.

Does anybody know why that is? I tried playing around with the UNICODE flag (I noticed that TVM_GETITEM is either defined to TVM_GETITEMA or TVM_GETITEMW) but that didn't seem to help.

+1  A: 

Use Spy++ to see whether the treeview handles WM_NOTIFY messages with NM_CUSTOMDRAW notification flag. If it does, then, bad luck. The actual data is stored internally somehow and you've got little chance to pull it out.

This equally applies to previous versions of Windows BTW.

GSerg
Good hint! However, it seems that the Spy++ which comes with Visual Studio 2008 doesn't show any window messages for notepad. It works fine with e.g. Firefox, but for notepad I don't see anything. I double-checked that the logging is indeed enabled and all message types are activated. Pity :-/
Frerich Raabe
Then also check UAC settings. Spy++ should be allowed to run elevated. Just checked, all Notepad messages are there.
GSerg
Unfortunately, there doesn't seem to be a WM_NOTIFY involved.
Frerich Raabe
+2  A: 

Ok, let's give it another shot.

Newer TreeViews expect TVITEMEX instead of TVITEM, and since there's no usual cbSize field, the control is not able to tell which version it receives and assumes TVITEMEX. Maybe there's an issue with the treeview not being able to access last members of TVITEMEX because no memory is allocated. Try using TVITEMEX or allocating a bit more memory for TVITEM than actually required.

Also consider that

The returned text will not necessarily be stored in the original buffer passed by the application. It is possible that pszText will point to text in a new buffer rather than place it in the old buffer.

You therefore might need to read from a different piece of process memory.

And, the buffer is zeroed because VirtualAllocEx resets the memory.

And as a last and probably useless resort, try using MEM_RESERVE|MEM_COMMIT instead of just MEM_COMMIT.

GSerg
Thanks for your suggestions! I tried TVITEMEX, but it had no effect. In the end, it turned out that I had to explicitely check whether the remote side is a Unicode window or not, because the default SendMessage two-way translation between ANSI and Unicode processes does not apply. See my answer for details.
Frerich Raabe
+1  A: 

The code doesn't work as expected if it's compiled with UNICODE defined, but the remote process isn't (or the other way round). You should call IsWindowUnicode first on the treeView handle to check whether the remote side expects Unicode messages.

This is needed since the standard two-way marshalling which SendMessage does is not sufficient in this case: you have to send two entirely different window messages depending on whether the remote side is a Unicode window or not. If it's Unicode, use SendMessageW with TVM_GETITEMW. If it's ANSI, use SendMessageA with TVM_GETITEMA.

This applies to all common controls, but not to the basic set of controls (which uses window messages < 1024).

I also believe that the code will break if it's compiled into a 64bit binary, but the remote process is 32bit (or the other way round). This is because the code copies it's local (say: 64bit) TVITEM into the remote process and then expects the remote process toread it as expected while dealing with the TVM_GETITEM(A|W) message. However, the size of the structure might be different (due to different pointer sizes).

Frerich Raabe
Strange, it works all fine here when I send TVM_GETITEMA to Windows 7 treeviews which are unicode (but the other way round isn't good, that's true). The pointer issue is a very good point though.
GSerg