border border Developer Documentation border border
Usualy, we can retreive information by sending messages (using the SendMessage API) to desired control.
The only trouble here is that we want to get information from other processes, and some messages can't be send (sometimes, they just make process to crash) because the other process don't have access to memory pointers given throught parameters.
So the "easy to do jobs" begins to become quite harder : to send these messages, a solution is to allocate memory in the remote process and pass these memory pointers as arguments of the SendMessage API. Next we just have to read the process memory of our allocated pointers and we get results.

Password fields are quite different, because the 2 important messages to show or get text have to be sent inside the remote process.
To solve this, we have to do code injection in the remote process.

More Information depending controls:
   - Edit, Static or controls we currently not support
   - Password
   - Headers
   - Listview
   - Treeview
   - ComboBox
   - ListBox

Edit, Static or controls we currently not support

We just try to get displayed text by sending the following messages with our process pointers
WM_GETTEXTLENGTH : to retreive text length and allocate enough memory
WM_GETTEXT : to retreive text

Password fields

To reveal password we first retreive the char hiding data by sending EM_GETPASSWORDCHAR, to restore it after user have seen it.
Warning : we use the SendMessageW because password char for comtrl32 v6 is defined as Unicode.

Next we inject code for sending message EM_SETPASSWORDCHAR inside the remote process. To do this we use the CCodeInject class (given with source code).
What this class do is writing remote process memory to inject our code and a structure containing all requiered parameters (and results).
This class next calls CreateRemoteThread func giving it our remote allocated code pointer as start address, and our remote allocated structure pointer as thread func parameter.
So our defined function will be executed in remote process receiving the struct as parameter.
Warning all functions (api or dll) called inside your injected code must be called with their address in the remote process.
That means you have to use the GetProcAddress API inside the remote process.

To hide password again we just send EM_SETPASSWORDCHAR without injecting nothing : Microsoft allows it as the password char is 0.

Retreiving password text is quite similar to sending EM_SETPASSWORDCHAR.
First we retreive password length be directly sending WM_GETTEXTLENGTH message.
Next we allocate enough memory in remote process for storing password, and by the same way of code injection of EM_SETPASSWORDCHAR we send the WM_GETTEXT message inside remote process.
As we have split Password content retreival and password showing, we have to do this. Another solution is to directly send the WM_GETTEXT message when the password is revealed.

Extracts of source code:
  2. typedef LRESULT (WINAPI *pfSendMessage)(HWND,UINT,WPARAM,LPARAM);
  4. // First we define the structure that will be transmitted by the CreateRemoteThread func as parameter
  5. typedef struct tagDataSendMessage
  6. {
  7. // params we want to transmit (SendMessage parameters)
  8. HWND Hwnd;
  9. UINT Msg;
  10. WPARAM Wparam;
  11. LPARAM Lparam;
  12. LRESULT Lresult;
  13. pfSendMessage pSendMessage;// fonction pointer in remote process memory
  17. // next write the code that will be injected
  18. // warning all functions must use functions address of remote process
  19. // that means you have to use GetProcAddress
  21. //-----------------------------------------------------------------------------
  22. // Name: SendMessageThreadProc
  23. // Object: proc that will be injected in remote process (just will call SendMessageW)
  24. // Parameters :
  25. // in : CRemoteCtrlContentSaver::PDATASENDMESSAGE lpParameter, containing
  26. // SendMessageW address and SendMessageW parameters
  27. // out :
  28. // return :
  29. //-----------------------------------------------------------------------------
  30. static DWORD WINAPI SendMessageThreadProc (CRemoteCtrlContentSaver::PDATASENDMESSAGE lpParameter)
  31. {
  32. lpParameter->Lresult=lpParameter->pSendMessage(lpParameter->Hwnd,lpParameter->Msg,lpParameter->Wparam,lpParameter->Lparam);
  33. return 0;
  34. }
  35. // This function marks the memory address after ThreadProc.
  36. static DWORD WINAPI SendMessageAfterThreadProc (void) {return 0;}
  39. BOOL GetPasswordContent()
  40. {
  41. // allocate a remote buffer
  42. psz=(WCHAR*)VirtualAllocEx(hProcess, NULL, (dwSize+1)*sizeof(WCHAR), MEM_COMMIT, PAGE_READWRITE);
  44. pCodeInject=new CCodeInject(hProcess);
  45. // comput code size
  46. dwThreadProcCodeSize = ((LPBYTE) SendMessageAfterThreadProc - (LPBYTE) SendMessageThreadProc);
  48. // fill injdata struct infos
  50. data.Hwnd=this->pSelectWindow->WindowHandle;
  51. data.pSendMessage=this->pSendMessage;
  52. data.Msg=WM_GETTEXT;
  53. data.Wparam=dwSize+1;
  54. data.Lparam=(LPARAM)psz;
  55. try
  56. {
  57. if (!pCodeInject->SetParameter(&data,sizeof(data)))
  58. {
  59. this->ShowError("Error injecting parameter in remote process");
  60. bRet=FALSE;
  61. throw;
  62. }
  63. if (!pCodeInject->SetCode((FARPROC)SendMessageThreadProc,dwThreadProcCodeSize))
  64. {
  65. this->ShowError("Error injecting code in remote process");
  66. bRet=FALSE;
  67. throw;
  68. }
  69. if (!pCodeInject->Execute())
  70. {
  71. this->ShowError("Error creating remote thread");
  72. bRet=FALSE;
  73. throw;
  74. }
  75. }
  76. catch(...)
  77. {
  78. }
  80. delete pCodeInject;
  82. if (bRet)
  83. {
  84. pszLocalText=new WCHAR[dwSize+1];
  85. bRet=ReadProcessMemory(hProcess,psz,pszLocalText,(dwSize+1)*sizeof(WCHAR),&ExchangedSize);
  86. }
  88. // free allocated memory
  89. VirtualFreeEx(hProcess,psz,0,MEM_RELEASE);
  91. CloseHandle(hProcess);
  93. if (pszLocalText)
  94. delete[] pszLocalText;
  95. }

First we directly send the HDM_GETITEMCOUNT message to get number of items.
Next we allocate memory in remote process for an HDITEM struct and for the associated header text (HDITEM.pszText).
After, we have to make a loop calling
   - SendMessage(hHeader,(UINT)HDM_GETITEM,(WPARAM)Index,(LPARAM)pHDITEM)
    where Index is the column number, and pHDITEM our remote allocated pointer on HDITEM struct
for filling the remote allocated memory.
   - ReadProcessMemory with pHDITEM->pszText as base pointer
for copying result into local memory.

Extracts of source code:
  1. pHDITEM=(HDITEM*)VirtualAllocEx(hProcess, NULL, sizeof(HDITEM), MEM_COMMIT, PAGE_READWRITE);
  2. pszText=(char*)VirtualAllocEx(hProcess, NULL, MaxTextSize, MEM_COMMIT, PAGE_READWRITE);
  4. ///////////////////////////////////////////////////////////////////
  5. // Fill struct info in remote memory
  6. ///////////////////////////////////////////////////////////////////
  8. // memset(pHDITEM,0,sizeof(HDITEM))
  9. pb=new BYTE[sizeof(HDITEM)];
  10. memset(pb,0,sizeof(HDITEM));
  11. ExchangedSize=0;
  12. WriteProcessMemory(hProcess, pHDITEM,pb, sizeof(HDITEM), &ExchangedSize);
  13. delete[] pb;
  15. // pHDITEM->cchTextMax=Size;
  16. WriteProcessMemory(hProcess, &(pHDITEM->cchTextMax),(LPCVOID)&MaxTextSize, sizeof(int), &ExchangedSize);
  18. // pHDITEM->pszText=pszText;
  19. WriteProcessMemory(hProcess, &(pHDITEM->pszText),(LPCVOID)&pszText, sizeof(char*), &ExchangedSize);
  21. // pHDITEM->Mask HDI_TEXT
  22. UINT uiMask=HDI_TEXT;
  23. WriteProcessMemory(hProcess, &(pHDITEM->mask),(LPCVOID)&uiMask, sizeof(UINT), &ExchangedSize);
  26. // for each item index of header (Index)
  27. // send HDM_GETITEM message with remote pointer pHDITEM
  28. SendMessage(hHeader,(UINT)HDM_GETITEM,(WPARAM)Index,(LPARAM)pHDITEM)
  30. // copy data from the remote allocated text pointer to a local pointer (pcLocalText)
  31. ReadProcessMemory(hProcess,pszText,pcLocalText,MaxTextSize,&ExchangedSize)
  33. // now we can use pcLocalText to save data


First we try to get headers by sending LVM_GETHEADER message.
If headers exist, we retreive them by the previous explained way (see Headers). Next we get the number of items with LVM_GETITEMCOUNT message, and do a loop
If user only wants selected items, we send the LVM_GETITEMSTATE message to know if we have to get item informations.
Next we have to send the LVM_GETITEMTEXT message. As for headers, we allocate memory in remote process for an LVITEM struct and for its associated text (LVITEM.pszText) to give the SendMessage function remote pointers as parameters.

Extracts of source code:
  1. ///////////////////////////////////////////////////////////////////
  2. // allocate pointer requiered by LVM_GETITEMTEXT in other process memory
  3. ///////////////////////////////////////////////////////////////////
  4. pLVITEM=(LVITEM*)VirtualAllocEx(hProcess, NULL, sizeof(LVITEM), MEM_COMMIT, PAGE_READWRITE);
  5. pszText=(char*)VirtualAllocEx(hProcess, NULL, MaxTextSize, MEM_COMMIT, PAGE_READWRITE);
  7. ///////////////////////////////////////////////////////////////////
  8. // Fill struct info in remote memory
  9. ///////////////////////////////////////////////////////////////////
  10. // memset(pLVITEM,0,sizeof(LVITEM))
  11. pb=new BYTE[sizeof(LVITEM)];
  12. memset(pb,0,sizeof(LVITEM));
  13. ExchangedSize=0;
  14. WriteProcessMemory(hProcess, pLVITEM,pb, sizeof(LVITEM), &ExchangedSize);
  15. // pLVITEM->cchTextMax=Size;
  16. WriteProcessMemory(hProcess, &(pLVITEM->cchTextMax),(LPCVOID)&MaxTextSize, sizeof(int), &ExchangedSize);
  17. // pLVITEM->pszText=pszText;
  18. WriteProcessMemory(hProcess, &(pLVITEM->pszText),(LPCVOID)&pszText, sizeof(char*), &ExchangedSize);
  20. // for each Item (ItemIndex)
  21. // for each subitem (SubItemIndex)
  23. ///////////////////////////////////////////////////////////////////
  24. // Fill struct info (subitem index) in remote memory
  25. ///////////////////////////////////////////////////////////////////
  26. // pLVITEM->iSubItem=SubItemIndex;
  27. WriteProcessMemory(hProcess, &(pLVITEM->iSubItem),(LPCVOID)&SubItemIndex, sizeof(int), &ExchangedSize);
  29. ///////////////////////////////////////////////////////////////////
  30. // Send LVM_GETITEMTEXT message lResult will contain the size of char copied
  31. ///////////////////////////////////////////////////////////////////
  32. lResult = SendMessage((HWND) this->pSelectWindow->WindowHandle, // handle to destination control
  33. (UINT) LVM_GETITEMTEXT, // message ID
  34. (WPARAM) ItemIndex, // = (WPARAM) (int) iItem;
  35. (LPARAM) pLVITEM // = (LPARAM) (LPLVITEM) pitem;
  36. );
  38. // copy data from the remote allocated text pointer to a local pointer (pcLocalText)
  39. ReadProcessMemory(hProcess,pszText,pcLocalText,lResult,&ExchangedSize)
  41. // now we can use pcLocalText to save data


For Treeview, we use the same way as Listview. We first allocate memory in remote process for a TV_ITEM struct, and for its associated text (TV_ITEM.pszText). Next we use classical Treeview macro (TreeView_GetRoot, TreeView_GetNextSibling, TreeView_GetChild) to parse the TreeView.


For ComboBox, we first have to check if parent window is an ComboBoxEx control (as only ComboBoxEx control can be selected graphicaly). That's important because if we send a CB_GETLBTEXT to the ComboBox child of a ComboBoxEx control, only the first letter of the text will be retreived.
The second trouble with ComboBox is that some application fill ComboBox content only on user click or when ComboBox list is going to be shown.
So if CB_GETCOUNT message returns is 0 or 1, to force application filling the combo, we simulate a mouse click and ask the list of Combobox to be shown by the following messages :
  1. // send messages to the combobox to assume items are filled because in some app,
  2. // items are inserted only at the first left mouse boutton down
  3. SendMessage(this->pSelectWindow->WindowHandle,WM_LBUTTONDOWN,MK_LBUTTON,0);
  4. SendMessage(this->pSelectWindow->WindowHandle,WM_LBUTTONUP,0,0);
  6. // send messages to the combobox to assume items are filled because in some app,
  7. // items are inserted only at the first Drop down list showing
  8. SendMessage(this->pSelectWindow->WindowHandle,CB_SHOWDROPDOWN,TRUE,0);
  9. SendMessage(this->pSelectWindow->WindowHandle,CB_SHOWDROPDOWN,FALSE,0);
Next we just have to retreive text by sending messages CB_GETLBTEXTLEN and CB_GETLBTEXT


Listboxes don't give us any troubles, we just have to send the following messages:
LB_GETSEL if user only wants to retreive selected items
LB_GETTEXTLEN and LB_GETTEXT to retreive text
border border border border