20. DLL Injection and Foreign Process Access

Page 340
  20. DLL Injection and Foreign Process Access  
   
  The global hook example in the previous chapter demonstrates the process of DLL injection. To recap, when a global hook is set, the system will inject (that is, map or load) the DLL containing the hook procedure into the address space of any process that receives a hooked message, thus enabling the hooked thread to call the hook procedure.  
   
  This has some interesting implications beyond its intended use. In particular, there is nothing that requires the injected DLL to contain only hook procedures we can put any code that we want into the DLL. Thus, we can force any process in the system to run code of our own choosing!  
   
  We are going to use this idea to create a very interesting application. Its current purpose is to allocate memory in a foreign process as well as to read to and write from that memory. However, its design is more general than this, for it allows us to force a foreign process to run any code we choose to include in the injected DLL, which is why I named the DLL rpiAccessProcess.dll.  
   
  After discussing the application, we will consider examples of the use of foreign memory allocation.  
 
  Accessing a Foreign Process: The Hooked Thread Graph  
   
  The rpiAccessProcess.dll library that accompanies this book currently exports the following functions:  
 
  rpiVirtualAlloc
Allocates the specified amount of memory in the foreign process.
 
Page 341
 
  rpiVirtualFree
Frees memory allocated by a call to rpiVirtualAlloc.
 
 
  rpiVirtualWrite
Writes a specified number of bytes to foreign memory.
 
 
  rpiVirtualRead
Reads a specified number of bytes from foreign memory.
 
 
  rpiSetForegroundWindow
Moves an application to the foreground. Designed to work just like Win32's SetForegroundWindow even under Windows 98 and Windows 2000. This function was discussed first in Chapter 11, Processes.
 
   
  To understand how these functions work and how the application is designed, it is helpful to think of each thread that is either making or receiving a memory allocation request as a node in a graph, which, for lack of a better name, I call the hooked-thread graph. As we will see, a thread is part of the hooked-thread graph; that is, a thread is represented by a node in the graph, if it has been hooked and the DLL is loaded in the address space of its owner process.  
   
  Figure 20-1 illustrates this idea. It shows three hooked threads (which may or may not be in different processes).  
   
  0341-01.gif  
   
  Figure 20-1.
The thread graph
 
   
  Each node is just a structure defined in the DLL as:  
 
  struct rpiNODE {
   DWORD dwThreadID;
 

Page 342
 
     HHOOK hHook;
   HWND hDialog;
   int iOutDeg;
   int iInDeg;
}
 
   
  Of course, the value of dwThreadID the thread ID of the thread represented by the node uniquely identifies the node. The member hHook is the handle of the hook. As we will see, each node has an associated hidden dialog box, whose handle is hDialog. The iOutDeg member is the number of arcs (explained later) leaving the node and iInDeg is the number of arcs entering the node. (Don't worry, this is not going to be a lesson in data structures.)  
   
  Note that the nodes in the hooked-thread graph are kept in an array, called the nodes table, in a shared section of the DLL. This means that all threads using the DLL have access to this common table. (As we have discussed before, a variable in a DLL can have one of three types of scope: local means accessible to a single procedure, global means accessible to a single instance of the DLL, and shared means accessible to all instances of the DLL. For a local or global variable, each instance of the DLL gets a separate copy of the variable, so that if one instance changes the variable, other instances are not affected.)  
   
  As we can see from Figure 20-1, the hooked-thread graph may also have directed edges (arrows) between nodes. An edge from Node1 to Node2 represents a successful request for a memory allocation made by Node1 (Thread1) on Node2 (Thread2). The edge is labeled with the size of the memory allocation and the base address of that allocation. In the DLL, an edge is just a structure:  
 
  struct rpiEDGE {
   DWORD dwOutThreadID;    // ThreadID of node from which edge leaves
   DWORD dwInThreadID;     // ThreadID of node into which edge enters
   DWORD dwSize;           // Size of allocation
   LPVOID lpAddress;       // Base address of allocation
}
 
   
  The number of edges leaving a node is thus the number of times the thread has successfully requested memory from any other threads in the system. This number is called the out degree of the node. The number of edges coming into a node is the number of successful requests honored by that thread. This is called the in degree of the node.  
   
  As with the nodes, the edges in the hooked-thread graph are kept in an array, called the edges table, which is also in a shared section of the DLL.  
   
  The hooked-thread graph model of (successful) memory allocation requests makes the job of designing the application much easier. With this model in mind, we can describe how the application works.  
Page 343
   
  The rpiVirtualAlloc Function  
   
  The rpiVirtualAlloc function is defined as:  
 
  LPVOID WINAPI rpiVirtualAlloc(
   DWORD dwThreadID,
   DWORD dwSize,
   int *piResultCode
);
 
   
  or, in VB,  
 
  Public Declare Function rpiVirtualAlloc Lib "rpiAccessProcess.DLL" ( _
   ByVal dwThreadID As Long, _
   ByVal dwSize As Long, _
   Optional ByRef lResultCode As Long = 0 _
) As Long
 
   
  The final parameter is optional. If we do not want to examine the result code (which indicates the type of error, if any), then we can just omit this argument.  
   
  Now suppose that a VB application calls rpiVirtualAlloc. The thread ID would probably have been obtained from a window handle using FindWindow and GetWindowThreadProcessID:  
 
  Dim lResultCode As Long
Dim hWindow As Long
Dim ThreadID As Long
Dim lAddress As Long

hWindow = FindWindow(vbNullString, WindowTitle)
ThreadID = GetWindowThreadProcessId(hWindow, 0&)
lAddress = rpiVirtualAlloc(ThreadID, 4096&, lResultCode)
 
   
  Here is what happens:  
   
  1. If a node for the target thread does not exist, it is created. This is done by calling an internal DLL procedure named CreateNode, which works as follows:  
 
  a. An empty location in the nodes table is found. (The nodes table is not packed, and so may have unused entries in various locations. Also, the table's size is hardcoded into the DLL at 100 nodes. This should be plenty.)  
 
  b. A WH_CALLWNDPROC hook is installed on the target thread.  
 
  c. All of the members of the rpiNODE structure except the hDialog member can now be filled in.  
 
  d. The CreateNode function sends a harmless WM_NULL message to the target thread. This forces Windows to inject the DLL into the target process. The CallWndProc hook procedure is then called to process the WM_NULL message. This hook procedure checks to make sure that the hDialog value is 0, indicating that the dialog has not yet been created. It then calls the API function CreateDialog to create a dialog box. Because this is done in the  
Page 344
 
  hook procedure, the dialog box is created in the target process. The bottom portion of Figure 20-2 shows three such dialog boxes. These dialogs are intended to be hidden and so do not need to contain a text box, but for the sake of illustration, the application shows the dialogs and fills a text box with node data.  
 
  e. When the WM_NULL message returns, the CreateNode function checks the shared node table to make sure that the node has a nonzero hDialog value. This signals that the dialog has been created successfully. Then CreateNode returns to rpiVirtualAlloc.  
   
  0344-01.gif  
   
  Figure 20-2.
Illustrating the rpiAccessProcess application
 
   
  2. This procedure is repeated for the calling thread. That is, if a node for the calling thread does not exist, it is created, also by calling CreateNode.  
   
  3. At this point, there is a node for the calling thread and a node for the target thread. The next step is to create the edge, which is done as follows:  
 
  a. The dwInThreadID and dwOutThreadID values and the allocation size dwSize are set.  
 
  b. A memory allocation request is made by sending a MSG_VIRTUAL_ALLOC (defined to be WM_APP + 1) message to the target dialog box, whose handle is the hDialog value of the target node. Note that Windows reserves.  
Page 345
 
  the values WM_APP through &HBFFF for use by applications, so this value will not interfere with built-in Windows messages sent to the dialog.  
 
  c. The dialog box's dialog procedure (a dialog procedure is a special form of window procedure for processing dialog box messages) processes this message by calling the API function VirtualAlloc and then filling in the shared lpAddress member of the edge with the return value of this API function. The important point is that the dialog procedure is running in the target space, so its call to VirtualAlloc actually allocates memory in the target process.  
   
  4. Once the edge is created, the appropriate iInDeg and iOutDeg values are incremented, completing the edge.  
   
  Figure 20-1 shows the picture for three nodes and five edges.  
   
  The rpiVirtualFree Function  
   
  The rpiVirtualFree function is simpler than rpiVirtualAlloc. It is declared as follows:  
 
  int WINAPI rpiVirtualFree(
   DWORD dwThreadID,
   LPVOID lpAddress
);
 
   
  or, in VB:  
 
  Public Declare Function rpiVirtualFree Lib "rpiAccessProcess.DLL" ( _
   ByVal dwThreadID As Long, _
   ByVal lpAddress As Long) As Long
 
   
  The thread ID and address are sufficient (and necessary) to uniquely identify an edge of the hooked-thread graph, that is, a successful memory allocation request, because no two such allocations can use the same address in the same process. (The application cannot create two edges with the same lpAddress but different threads in the same process, because the second call to VirtualAlloc will fail.)  
   
  The rpiVirtualFree function works as follows:  
   
  1. It gets the index of the in node and out node for the edge.  
   
  2. It sends a MSG_VIRTUAL_FREE message to the target dialog box. The dialog's window procedure processes the message by calling the API function VirtualFree (from the target process space) and then clearing the shared lpAddress member of the edge.  
   
  3. The rpiVirtualFree function can then delete the edge (that is, clear the rpi- EDGE structure).  
   
  4. It then decrements the appropriate in degree and out degree.  
Page 346
   
  5. If either of the nodes has in degree 0 and out degree 0, the node is destroyed. This is done by first closing the dialog box and then unhooking the thread by calling UnhookWindowsHookEx.  
   
  This describes the main functionality of the hooked graph model. Once the graph is created, the edges act as a kind of conduit between processes. To force a target thread to run some code at the request of a calling thread, all we need to do is create a new message and add the desired code as the message-processing code in the dialog procedure. Sending the message will trigger the processing code. Voil .  
   
  Testing the Allocation Functions  
   
  Figure 20-2 shows the result of a VB application that can be used to test the rpiVirtualAlloc and rpiVirtualFree functions. The source code for this application is on the CD. Basically, this application provides a way to call the DLL functions rpiVirtualAlloc and rpiVirtualFree on two other instances of itself, modified slightly to change the window title.  
   
  To try this out, just run the three variations rpiHookApp1.exe, rpiHookApp2.exe, and rpiHookApp3.exe located on the CD. Figure 20-2 shows these executables and the corresponding node dialog boxes created by the DLL in response to several calls to rpiVirtualAlloc. This reflects the layout in Figure 20-1.  
   
  Keep in mind that you need to free all allocations before closing any of these applications, unless you like General Protection Faults.  
   
  Let me know if you find some interesting extensions for this model, beyond allocating foreign memory (and fixing SetForegroundWindow), which we consider next.  
 
  Allocating Foreign Memory  
   
  As mentioned, the rpiAccessProcess DLL is currently set up to allocate foreign memory. This is done through the rpiVirtualRead and rpiVirtualWrite functions. These functions are pretty straightforward, especially compared to the allocation functions.  
   
  The rpiVirtualWrite Function  
   
  The rpiVirtualWrite function is:  
 
  int WINAPI rpiVirtualWrite(
   DWORD dwThreadID,
   LPVOID lpSourceAddress
   LPVOID lpTargetAddress,
   DWORD nSize
);
 
Page 347
   
  or, in VB:  
 
  Public Declare Function rpiVirtualWrite Lib "rpiAccessProcess.DLL" ( _
   ByVal dwThreadID As Long, _
   ByVal lpSourceAddress As Long, _
   ByVal lpTargetAddress As Long, _
   ByVal dwSize As Long) As Long
 
   
  This function first checks to make sure that the edge identified by dwThreadID and lpAddress actually emanates from the calling thread's node, since we cannot allow a strange thread to write to memory allocated by another thread! (In a sense, writing memory is like pushing data along the edge, and it better be the correct edge.)  
   
  The rpiVirtualWrite function then calls the API function WriteProcessMemory, discussed in Chapter 16, Windows Messages. To do so, it calls GetWindowThreadProcessId, to get the process ID from the target dialog handle, and then it calls OpenHandle to get a process handle, which is required by WriteProcessMemory.  
   
  The rpiVirtualRead Function  
   
  The rpiVirtualRead function is:  
 
  int WINAPI rpiVirtualRead(
   DWORD dwThreadID,
   LPVOID lpSourceAddress
   LPVOID lpTargetAddress,
   DWORD nSize
);
 
   
  or, in VB:  
 
  Public Declare Function rpiVirtualRead Lib "rpiAccessProcess.DLL" ( _
   ByVal dwThreadID As Long, _
   ByVal lpSourceAddress As Long, _
   ByVal lpTargetAddress As Long, _
   ByVal dwSize As Long) As Long
 
   
  As with the rpiVirtualWrite function, this function first checks that the edge emanates from the calling thread's node. (In a sense, reading is like pulling data along the edge backwards.) It then calls the API function ReadProcessMemory.  
 
  Example: Foreign Control Extraction  
   
  We have seen that it is not difficult to extract data from a foreign listbox or combo box, because Windows automatically marshalls the necessary data across process boundaries. However, the same does not apply to the 32-bit controls such as the ListView control. In this case, we must do the marshalling ourselves.  
   
  To get the rpiControlExtractor application, first discussed in Chapter 16, to work on a ListView control, we can use the rpiAccessProcess allocation function to allocate a  
Page 348
   
  buffer in the foreign process. But first we need to discuss the ListView control itself.  
   
  Each item in a ListView control is represented by an LVITEM structure:  
 
  typedef struct LVITEM {
   UINT mask;
   int iItem;
   int iSubItem;
   UINT state;
   UINT stateMask;
   LPTSTR pszText;
   int cchTextMax;
   int iImage;
   LPARAM 1Param;
   #if (_WIN32_IE >= 0 0300)
      int iIndent;
   #endif
};
 
   
  Here are the members we need to consider:  
   
  The mask member is a set of flags that indicate which members of the structure either contain valid data or need to be set (depending upon the function being invoked). For instance, the value LVIF_TEXT specifies that the pszText member is either valid or needs to be filled in.  
   
  The item member is the 0-based index of the ListView item of interest.  
   
  The subitem member is the 1-based index of the subitem of interest. To refer to an item (rather than a subitem), set this to 0. (We will explain subitems in a moment.)  
   
  The pszText member is the address of a buffer containing the item or subitem text (a null-terminated string).  
   
  The cchTextMax member is the size of the buffer pointed to by the pszText member.  
   
  As is typical, the documentation on ListView controls, especially with respect to items and subitems, is confusing. However, the following appears to be the case.  
   
  When a ListView control is in report mode, it can display one or more columns. Each column after the first column corresponds to a ListView subitem. The first column corresponds to the ListView item.  
   
  Thus, the first column contains the text (also called the label) or icon for the item itself, and subsequent columns contain the text for the subitems. It follows that the number of subitems is one less than the number of columns. Subitems are not added or deleted by name. Instead, columns are added or deleted.  
   
  To retrieve the text of an item or subitem, we need to fill in the appropriate members of an LVITEM structure and send the control an LVM_GETITEMA message.  
Page 349
   
  Here is the code:  
 
  uLVItem.mask = LVIF_TEXT      ' Specifies that text is requested
uLVItem.iItem = lItem         ' Index of item
uLVItem.iSubItem = lsubitem   ' Index of subitem or 0
uLVItem.cchTextMax = 255      ' size of buffer
uLVItem.pszText = lpAddress   ' Address of text buffer

' Send the message
lResp = SendMessage (hListView, LVM_GETITEMA, lItem, lpAddress)
 
   
  The problem, of course, is that this structure must be placed in the foreign address space. Note that this includes the text buffer as well as the structure itself.  
   
  We can accomplish this as follows:  
   
  1. First, we set up two buffers in the foreign process space a 40-byte buffer for the LVITEM variable:  
 
  ' Allocate foreign buffer for 40-byte LVItem
lForeignLVItemAddr = rpiVirtualAlloc (lThreadID, 40&)
 
 
  and a 256-byte buffer for the item (or subitem) text:  
 
  ' Allocate foreign buffer for text of item
lForeignTextBufferAddr = rpiVirtualAlloc (lThreadID, 256&)
 
   
  2. Next, we fill a local LVITEM variable with the required members, using the address of the foreign text buffer:  
 
  uLocalLVItem.mask = LVIF_TEXT
uLocalLVItem.iItem = lItem
uLocalLVItem.iSubItem = lSubItem
uLocalLVItem.cchTextMax = 255
uLocalLVItem.pszText = 1ForeignTextBufferAddr
 
   
  3. The local LVITEM variable is copied to the foreign space:  
 
  ' Copy local uLocalLVItem to foreign space
lResp = rpiVirtualWrite(1ThreadID, VarPtr(uLocalLVItem.mask), _
        lForeignLVItemAddr, 40&)
 
   
  4. Now we can send the LVM_GETITEMA message to the foreign control:  
 
  ' Send the message
lResp = SendMessageByNum (lhwnd, LVM_GETITEMA, lItem, lForeignLVItemAddr)
 
   
  5. This fills the foreign LVITEM structure and the foreign text buffer. The text buffer can be copied back to the local process:  
 
  ' Copy the data back from foreign space
lResp = rpiVirtualRead(lThreadID, lForeignTextBufferAddr, _
   VarPtr(bLocalTextBuffer(0)), 256)
 
   
  6. Finally, we must not forget to free the foreign buffers:  
 
  ' Free foreign memory
lret = rpiVirtualFree(lThreadID, lForeignTextBufferAddr)
lret = rpiVirtualFree(lThreadID, lForeignLVItemAddr)
 
   
  That's basically all there is to it. The complete code is on the CD.  
Page 350
 
  Example: Fixing the VB6 Help System  
   
  Frankly, I think that, at the moment, Microsoft's new HTML help system, introduced in Visual Studio 6, leaves much to be desired. It is definitely a step backwards in functionality. Fortunately, these shortcomings can be addressed if Microsoft has the will to do so.  
   
  I will hasten to add that there is one very important advantage to the new system for serious VB programmers we now have access to the documentation for all of Visual Studio. Frankly, I use the VC++ and SDK documentation far more often than the VB6 documentation, even when programming in VB. Nevertheless, Microsoft should have done a better job of implementing this help system before releasing it. It has even prevented me from installing the latest versions of MSDN Library!  
   
  Let me share with you two of the help system's most inexcusable problems one of which is so amazing that I could not belive my eyes when I first encountered it. It also involves one of the strangest mysteries I have encountered in using PCs.  
   
  Figure 20-3 shows the problems. First, as you can see, the Topics Found dialog occupies only a small portion of the screen. Why on earth doesn't Microsoft implement dialog boxes that adjust their size based on screen resolution? I can't think of any excuse for not displaying a full screen height list here.  
   
  The second problem is that the list of topics is not alphabetized! At least, it wasn't alphabetized when I decided to write about the problem. Let me suggest that you start your VB6 help system and select the listbox control item in the index tab. Then click on the Properties link and check the Topics Found list to see whether it is alphabetized on your system.  
   
  From the time that I installed VB6 until I began writing the words you are now reading, my topic lists were not alphabetized. So I wrote an application to deal with this. Figure 20-4 shows the result.  
   
  Here is how the rpiFixVB6Help application works. Suppose that a Topics Found dialog is showing. I use the key combination Ctrl-Alt-Shift-X (an easy keystroke combination on my keyboard) to start the rpiFixVB6Help application. The application immediately searches the current windows in the system to find a top-level window named Topics Found, using the FindWindow function:  
 
  hTopics = FindWindow(vbNullString, "Topics Found")  
   
  The application then gets the handle of the child ListView control, which is named List1:  
 
  ' Search child windows for "List1"
hListView = FindWindowEx (hTopics, 0, vbNullString, "List1")
 
Page 351
   
  0351-01.gif  
   
  Figure 20-3.
The VB6 help system
 
   
  0351-02.gif  
   
  Figure 20-4.
The rpiFixVB6Help application
 
Page 352
   
  Now, just as in the rpiControlExtractor program, the application fills its listbox (whose Sorted property is set to True) with the topics. Now, when I double-click on an item in this listbox, the application searches the ListView control for that item, using the same cross-process tricks as before, and sending the message:  
 
  lResp = SendMessageByNum(hListView, LVM_FINDITEMA, -1&, lForeignLVFindInfoAddr)  
   
  Then the item in the ListView control is selected and made visible by sending the appropriate messages:  
 
  ' Send the message to select the item by setting its state
lResp = SendMessageByNum(hListView, LVM_SETITEMSTATE, lItem, lForeignLVItem)

' Ensure item is visible
lResp = SendMessageByNum(hListView, LVM_ENSUREVISIBLE, lItem, 0&)
 
   
  Finally, the application closes down.  
   
  There is one more tale to tell with respect to this situation. Just as I cranked up the VB6 help system to get the screen shot in Figure 20-4. the Topics list appeared alphabetized! I don't know how this happened, but the problem is fixed on the computer I am using to write this book. The only guess I can make is that I recently installed a non-Microsoft application. Perhaps it overwrote some system files with either newer files that fixed the problem or older files that didn't have the problem? Who knows.  
   
  In any case, at first I thought that I had been imagining things, but I was much relieved to see that the problem still exists on my laptop! Just to be sure the laptop wasn't the anomaly, and to see if Microsoft was aware of this problem, I called Microsoft technical support. After walking the technical support person through the steps to get the Topics Found list in Figure 20-4, she said that her list was also not alphabetized, but didn't seem too concerned and suggested that I call the Microsoft wish list line!  
   
  In any case, if your Topics Found lists are not alphabetized, or if you would like to have full screen lists, then you can use the rpiFixVBHelp application.  


WIN32 API Programming with Visual Basic
Win32 API Programming with Visual Basic
ISBN: 1565926315
EAN: 2147483647
Year: 1999
Pages: 31
Authors: Steven Roman

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net