Get structure from memory in another process and unicode question

M

michelqa

Hello,

I can retrieve column text from a ListView in another process but I
cant figure out how to access to structure elements (LVCOLUMN)

<code>
//Handle variable is a valid ListView handle

LV_COLUMN ListViewItem = new LV_COLUMN();
IntPtr ListViewItemPointer = IntPtr.Zero;
byte[] ListViewItemBuffer = new byte[512];
IntPtr ListViewPointer_item = IntPtr.Zero;
IntPtr ListViewProcessPointer = IntPtr.Zero;

//open the process
int ProcessID;
Win32.GetWindowThreadProcessId(Handle, out ProcessID);
ListViewProcessPointer = Win32.OpenProcess(Win32.PROCESS_VM_OPERATION
| Win32.PROCESS_VM_READ | Win32.PROCESS_VM_WRITE |
Win32.PROCESS_QUERY_INFORMATION, false, (int)ProcessID);

//allocate memory
ListViewItemPointer = Win32.VirtualAllocEx(ListViewProcessPointer,
IntPtr.Zero, Marshal.SizeOf(typeof(LV_COLUMN)), Win32.MEM_COMMIT,
Win32.PAGE_READWRITE);
ListViewPointer_item = Win32.VirtualAllocEx(ListViewProcessPointer,
IntPtr.Zero, 512, Win32.MEM_COMMIT, Win32.PAGE_READWRITE);

//Get column Text (pszText) and width (cx)
int ColumnCnt=0;
bool GetColumnResult=true;
while (GetColumnResult)
{
ListViewItem.mask=(int)Win32.ListViewConstants.LVCF_TEXT|
(int)Win32.ListViewConstants.LVCF_WIDTH;
ListViewItem.cchTextMax = 512;
ListViewItem.pszText = ListViewPointer_item;
Win32.WriteProcessMemory(ListViewProcessPointer,
ListViewItemPointer,
ref ListViewItem, Marshal.SizeOf(typeof(LV_COLUMN)),
IntPtr.Zero);
GetColumnResult=Convert.ToBoolean((int)Win32.SendMessage(
Handle, (int)Win32.WindowsMessages.LVM_GETCOLUMN,
(IntPtr)ColumnCnt, ListViewItemPointer));
if (GetColumnResult)
{
IntPtr bytesReaded;
IntPtr buff = IntPtr.Zero;
Win32.ReadProcessMemory(ListViewProcessPointer,
ListViewPointer_item, ListViewItemBuffer, 512, out bytesReaded);

MessageBox.Show("ColumnText=["+Encoding.Unicode.GetString(ListViewItemBuffer)
+"]");
MessageBox.Show("ColumnWidth (not
working)="+ListViewItem.cx.ToString());
}
ColumnCnt++;
}
Win32.VirtualFreeEx(ListViewProcessPointer, ListViewItemPointer, 0,
Win32.MEM_RELEASE);
Win32.VirtualFreeEx(ListViewProcessPointer, ListViewPointer_item, 0,
Win32.MEM_RELEASE);
</code> :


QUESTIONS
***************
- How can I get my LVCOLUMN structure from memory to read the cv
member to get the column Width ?
- Unicode issue, When displaying the column name, look like there is
something to trim because the last "]" character is missing when
executing :
MessageBox.Show("ColumnText=["+Encoding.Unicode.GetString(ListViewItemBuffer)
+"]<-Text here is ignored why???"); Is anybody know the correct way
to trim the string?

I cant find an ugly workaround for the unicode issue but I really need
to get access to the LVCOLUMN elements....the width by example
LVCOLUMN.cx is suppose to contain the width of the column when using
the mask LVCF_WIDTH.

I'm working on this since a long time....please help me if you can.
Thanks.
 
J

Jon Skeet [C# MVP]

- Unicode issue, When displaying the column name, look like there is
something to trim because the last "]" character is missing when
executing :
MessageBox.Show("ColumnText=["+Encoding.Unicode.GetString(ListViewItemBuffer)
+"]<-Text here is ignored why???");  Is anybody know the correct way
to trim the string?

Encoding.GetString is converting *all* the data (because that's what
you've asked it to do). That means you'll end up with a load of
unicode character 0s at the end - and MesageBox.Show will stop when it
sees character 0, thinking it's the end of the string. You should
really only decode the correct number of bytes, but an alternative is
to trim the end of the result of Encoding.GetString with TrimEnd('\0')

Jon
 
M

michelqa

Thanks Jon it work to fix my unicode problem

I desperately still need help for my problem to retrive LV_COLUMN
structure.

Look like something is missing to get the struct with
ListViewItemPointer... I try Marshal.PtrToStruct and different other
things without success
 
P

Pavel Minaev

Thanks Jon it work to fix my unicode problem

I desperately still need help for my problem to retrive LV_COLUMN
structure.

Look like something is missing to get the struct with
ListViewItemPointer... I try Marshal.PtrToStruct and different other
things without success

If you're using the overload of PtrToStructure which takes a
destination Object as a second argument (rather than a Type), keep in
mind that it won't work if you pass it a value type. So, if you've
declared ListViewItem as a struct , you should rather use the
PtrToStructure(IntPtr, Type) overload - perhaps this is the problem?

If not, then can you be more specific as to what exactly goes wrong
for you?
 
M

michelqa

When I try to convert the pointer to structure I get a null
exception : "Object reference not set to an instance of an object"

I add the following code in the while loop

LV_COLUMN TmpLvCol; //or LV_COLUMN TmpLvCol=new LV_COLUMN();
TmpLvCol=(LV_COLUMN)Marshal.PtrToStructure(ListViewItemPointer,typeof(LV_COLUMN)); //
Return Object reference not set to an instance of an object.
MessageBox.Show(TmpLvCol.cx.ToString());


If I use the PtrToStructure without the return value i get " The
structure must not be a value class."

LV_COLUMN TmpLvCol=new LV_COLUMN();
Marshal.PtrToStructure(ListViewItemPointer,TmpLvCol);
MessageBox.Show(TmpLvCol.cx.ToString()); // return The structure must
not be a value class.

Is PtrToStructure is really the way to get my structure??
Is ListViewItemPointer need some additionnal memory manipulation to be
able to retreive the information?
 
P

Pavel Minaev

When I try to convert the pointer to structure I get a null
exception : "Object reference not set to an instance of an object"

I add the following code in the while loop

LV_COLUMN TmpLvCol;    //or LV_COLUMN TmpLvCol=new LV_COLUMN();
TmpLvCol=(LV_COLUMN)Marshal.PtrToStructure(ListViewItemPointer,typeof(LV_CO­LUMN));  //
Return Object reference not set to an instance of an object.
MessageBox.Show(TmpLvCol.cx.ToString());

Marshal won't handle the bit about the pointer pointing to a memory
block in another process by itself - you'll have to help it by copying
the struct into your own process memory space manually, just like you
do with the string, and then use PtrToStructure on the copy. Also, it
will probably try to follow the LPTSTR pointer in the copied struct,
so you'll need to marshal that between processes as well - copy the
string data (you already did that), and modify the pointer in your
copy of the struct to point to that data.
If I use the PtrToStructure without the return value i get " The
structure must not be a value class."

Yes, this is what I mentioned earlier. Just don't use that overload.
 
M

michelqa

copying the struct into your own process memory space manually, just like you
do with the string, and then use PtrToStructure on the copy.

I have to admin that I'm totally confused with memory allocation
between process...could you be just a little bit more explicit ? I
try the following in the loop but it fails :

IntPtr buff = IntPtr.Zero;
bool ReadProcResul=Win32.ReadProcessMemory(ListViewProcessPointer,
ListViewItemPointer, buff, (uint)Marshal.SizeOf(typeof(LV_COLUMN)),
out bytesReaded); //--Fail
if (!ReadProcResul)
MessageBox.Show("Read memory Fail");

as I understand it the steps are
- Allocate memory for the struct with virtualallocex(), //already
done in my initial code
- Read the struct (copy from remote to local memory) with
ReadProcessMemory()
- Convert the IntPtr to the struct with PtrToStructure()
- ??understand what to do with the LPSTR in the struct when retreiving
the struct...but I already have the text of the column

still continue to play with this...
 
M

michelqa

here is my latest code I only want to get the access to LV_COLUMN
returned infos for the first column.

LV_COLUMN lv_col = new LV_COLUMN();
/*** Open the process ***/
int ProcessID;
Win32.GetWindowThreadProcessId(Handle, out ProcessID);
IntPtr hProcess = Win32.OpenProcess(Win32.PROCESS_VM_OPERATION |
Win32.PROCESS_VM_READ | Win32.PROCESS_VM_WRITE |
Win32.PROCESS_QUERY_INFORMATION, false, (int)ProcessID);
/*** Allocate memory for lv_col struct and lv_col.pszText ***/
IntPtr lv_colPtr = Win32.VirtualAllocEx(hProcess, IntPtr.Zero,
Marshal.SizeOf(typeof(LV_COLUMN)), Win32.MEM_COMMIT,
Win32.PAGE_READWRITE);
IntPtr pszTextPtr= Win32.VirtualAllocEx(hProcess, IntPtr.Zero, 512,
Win32.MEM_COMMIT, Win32.PAGE_READWRITE);
/*** Fill lv_col ***/
lv_col.mask=(int)Win32.ListViewConstants.LVCF_TEXT|
(int)Win32.ListViewConstants.LVCF_WIDTH;
lv_col.cchTextMax = 512;
lv_col.pszText = pszTextPtr;
/*** Write structure to memory ***/
Win32.WriteProcessMemory(hProcess, lv_colPtr, ref lv_col,
Marshal.SizeOf(typeof(LV_COLUMN)), IntPtr.Zero);
/*** Send LVM_GETCOLUMN to retreive information about first column
***/
Win32.SendMessage(Handle, (int)Win32.WindowsMessages.LVM_GETCOLUMN,
(IntPtr)0, lv_colPtr));
/*** Read the first column text ***/
IntPtr bytesReaded;
byte[] ListViewItemBuffer = new byte[512];
Win32.ReadProcessMemory(hProcess, pszTextPtr, ListViewItemBuffer,
512, out bytesReaded);

MessageBox.Show("ColumnText=["+Encoding.Unicode.GetString(ListViewItemBuffer).TrimEnd('\0').Trim()
+"]");
/*** Nothing work from this point ReadProcessMemory fail ***/
if (!Win32.ReadProcessMemory(hProcess, lv_colPtr, BuffPtr,
(uint)Marshal.SizeOf(typeof(LV_COLUMN)), out bytesReaded)) // = fail
MessageBox.Show("read fail");
LV_COLUMN lv_colTmp;

lv_colTmp=(LV_COLUMN)Marshal.PtrToStructure(lv_colPtr,typeof(LV_COLUMN)); //
= fail
MessageBox.Show("Column width="+lv_colTmp.cx.ToString());
Win32.VirtualFreeEx(hProcess, lv_colPtr, 0, Win32.MEM_RELEASE);
Win32.VirtualFreeEx(hProcess, pszTextPtr, 0, Win32.MEM_RELEASE);

After hours of search in google, learn more about memory operation and
trying to understand what could be wrong I still have no clue about
this. Will continue to work on this today for the next 5 hours :(
 
P

Pavel Minaev

I have to admin that I'm totally confused with memory allocation
between process...could you be just a little bit more explicit ?  I
try the following in the loop but it fails :

IntPtr buff = IntPtr.Zero;
bool ReadProcResul=Win32.ReadProcessMemory(ListViewProcessPointer,
ListViewItemPointer, buff, (uint)Marshal.SizeOf(typeof(LV_COLUMN)),
out bytesReaded);   //--Fail
if (!ReadProcResul)
   MessageBox.Show("Read memory Fail");

as I understand it the steps are
- Allocate memory for the struct with virtualallocex(),   //already
done in my initial code
- Read the struct (copy from remote to local memory) with
ReadProcessMemory()
- Convert the IntPtr to the struct with PtrToStructure()

Well yes, that's where it fails, most likely. See, you've read the
struct from another process' memory into your own. However, the struct
itself has a pointer field - pszText - and the value of that field is
still the same as in the process you've copied it from. There, it
pointed to the actual string data; in your process, it's just an
invalid pointer. I think that PtrToStructure() tries to treat it as a
proper string for unmarshalling, and ends up dereferencing that
invalid pointer.

To do this "properly", after you've copied the raw bytes of the struct
into your own memory, you'd need to fix that pointer in there -
overwrite the value inside with pointer to string data in your memory
(which you earlier obtained by copying the string itself). However, in
this case, since you've already got the string data elsewhere, you
might want to just zero out those bytes, since it's much simpler - the
marshaller should be able to handle this, and unmarshall it as a null
string. Offset of pszText within LVITEM is 20 bytes, so you just need
to zero bytes 21-24 within the memory block.
 
P

Pavel Minaev

Finally.... It work now!!!

I found the solution with Pavel's precious advises and the following
example in VB :  http://files.codes-sources.com/fichier.aspx?id=36678&f=prjSaveIcons.N...

Pavel : Thanks again for your help :)

You're welcome. Could you please clarify what was the nature of the
problem in the end (for those who might stumble onto this thread in
the future looking for a solution to the same problem)? Was my last
guess correct?
 
M

michelqa

Dont even took the time really understand what was wrong
previously... I restart from scratch many times.

To close this thread... here is my last code (working) :

int ProcessID;
Win32.GetWindowThreadProcessId(Handle, out ProcessID);
IntPtr hProcess = Win32.OpenProcess(Win32.PROCESS_VM_OPERATION |
Win32.PROCESS_VM_READ | Win32.PROCESS_VM_WRITE |
Win32.PROCESS_QUERY_INFORMATION, false, (int)ProcessID);
LV_COLUMN lv_col = new LV_COLUMN();
IntPtr pszTextPtr= Win32.VirtualAllocEx(hProcess, IntPtr.Zero, 512,
Win32.MEM_COMMIT|Win32.MEM_RESERVE, Win32.PAGE_READWRITE);
IntPtr lv_colPtr = Win32.VirtualAllocEx(hProcess, IntPtr.Zero,
Marshal.SizeOf(lv_col), Win32.MEM_COMMIT|Win32.MEM_RESERVE,
Win32.PAGE_READWRITE);
lv_col.mask=(int)Win32.ListViewConstants.LVCF_TEXT|
(int)Win32.ListViewConstants.LVCF_WIDTH;
lv_col.cchTextMax = 512;
lv_col.pszText = pszTextPtr;
string L_buf=" ";
IntPtr TextPtr=Marshal.StringToHGlobalUni(L_buf);
IntPtr bytesReaded;
Win32.WriteProcessMemory(hProcess, pszTextPtr, TextPtr, 512,
IntPtr.Zero);
Marshal.FreeHGlobal(TextPtr);
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(lv_col));
Marshal.StructureToPtr(lv_col, ptr, true);
Win32.WriteProcessMemory(hProcess, lv_colPtr,ptr,
Marshal.SizeOf(lv_col),IntPtr.Zero);
Marshal.FreeHGlobal(ptr);
Win32.SendMessage(Handle, (int)Win32.WindowsMessages.LVM_GETCOLUMN,
(IntPtr)0, lv_colPtr);
ptr = Marshal.AllocHGlobal(512);
Win32.ReadProcessMemory(hProcess, pszTextPtr, ptr, 512, out
bytesReaded);
L_buf = Marshal.PtrToStringUni(ptr);
Marshal.FreeHGlobal(ptr);
ptr = Marshal.AllocHGlobal(Marshal.SizeOf(lv_col));
Win32.ReadProcessMemory(hProcess, lv_colPtr, ptr,(uint)
Marshal.SizeOf(lv_col), out bytesReaded);
lv_col = (LV_COLUMN) Marshal.PtrToStructure(ptr, lv_col.GetType());
Marshal.FreeHGlobal(ptr);
MessageBox.Show(lv_col.cx.ToString());
MessageBox.Show(L_buf);
Win32.VirtualFreeEx(hProcess, lv_colPtr, 0, Win32.MEM_RELEASE);
Win32.VirtualFreeEx(hProcess, pszTextPtr, 0, Win32.MEM_RELEASE);
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top