Windows Process Injection: Multiple Provider Router (MPR) DLL and Shell Notifications

Introduction

Memory for MPR network providers can be modified to facilitate process injection by overwriting one of the function pointers and then invoking it via shell change notifications or window messages. While searching for a method of invocation, it was discovered injection could be acheived in explorer.exe without overwriting MPR functions at all. With that said, the structure that holds information about each provider is documented here, including how to find them in a remote process. This post is the result of playing around with the latest release of WinCheck by red plait which now includes dumping information about MPR providers. Worth a look if you’re interested in other vectors.

Network Providers

Figure 1. shows a list in the registry for a Windows 10, version 1903 VM.

Figure 1. List of Network Providers.

Figure 2. shows information for the VirtualBox provider used to implement shared folder functionality.

Figure 2. Information for VirtualBox Network Provider.

PROVIDER Structure

A legacy version was found on github and only required one additional field to be compatible with Windows 10. An array of these structures are stored in a global variable MPR!GlobalProviderInfo.

typedef struct _PROVIDER {
    NETRESOURCE                   Resource;
    DWORD                         Type;           // WNNC_NET_MSNet, WNNC_NET_LanMan, WNNC_NET_NetWare
    HMODULE                       Handle;         // Handle to the provider DLL.
    LPTSTR                        DllName;        // set to NULL after loaded.
    HMODULE                       AuthentHandle;  // Handle to authenticator DLL.
    LPTSTR                        AuthentDllName; // Authenticator Dll.
    DWORD                         InitClass;      // Network or Authentication provider.
    DWORD                         ConnectCaps;    // Cached result of GetCaps(WNNC_CONNECTION)
    DWORD                         Unknown;
    PF_NPAddConnection            AddConnection;
    PF_NPAddConnection3           AddConnection3;
    PF_NPGetReconnectFlags        GetReconnectFlags;
    PF_NPCancelConnection         CancelConnection;
    PF_NPGetConnection            GetConnection;
    PF_NPGetConnection3           GetConnection3;
    PF_NPGetUser                  GetUser;
    PF_NPOpenEnum                 OpenEnum;
    PF_NPEnumResource             EnumResource;
    PF_NPCloseEnum                CloseEnum;
    PF_NPGetCaps                  GetCaps;
    PF_NPGetDirectoryType         GetDirectoryType;
    PF_NPDirectoryNotify          DirectoryNotify;
    PF_NPPropertyDialog           PropertyDialog;
    PF_NPGetPropertyText          GetPropertyText;
    PF_NPSearchDialog             SearchDialog;
    PF_NPFormatNetworkName        FormatNetworkName;
    PF_NPLogonNotify              LogonNotify;
    PF_NPPasswordChangeNotify     PasswordChangeNotify;
    PF_NPGetCaps                  GetAuthentCaps;
    PF_NPFMXGetPermCaps           FMXGetPermCaps;
    PF_NPFMXEditPerm              FMXEditPerm;
    PF_NPFMXGetPermHelp           FMXGetPermHelp;
    PF_NPGetUniversalName         GetUniversalName;
    PF_NPGetResourceParent        GetResourceParent;
    PF_NPGetResourceInformation   GetResourceInformation;
    PF_NPGetConnectionPerformance GetConnectionPerformance;
}PROVIDER, *LPPROVIDER;

Global Provider Info

With exception to vboxtray.exe on my test machine, the only process that uses MPR!GlobalProviderInfo is explorer.exe. You might find it via debugging symbols using SymFromName. To avoid depending on symbols, searching through the .data segment of MPR.dll for pointers to heap memory is required. Each pointer is read and interpreted as a PROVIDER structure. The following function when provided with a process handle and pointer to heap is an example of how one might perform validation. The IsCodePtrEx and IsHeapPtrEx wrapper functions simply call VirtualQueryEx to obtain the attributes of the memory and return TRUE for heap/code pointers or FALSE if not.

BOOL ValidateMPR(HANDLE hp, LPVOID cs) {
    PROVIDER prov;
    SIZE_T   rd;
    
    // read provider
    if(!ReadProcessMemory(hp, cs, &prov, 
      sizeof(prov), &rd)) return FALSE;
    
    // valid scope?
    switch(prov.Resource.dwScope) {
      case RESOURCE_CONNECTED :
      case RESOURCE_GLOBALNET :
      case RESOURCE_CONTEXT   :
        break;
      default:
        return FALSE;
    }
    
    /// valid type?
    switch(prov.Resource.dwType) {
      case RESOURCETYPE_DISK  :
      case RESOURCETYPE_PRINT :
      case RESOURCETYPE_ANY   :
        break;
      default:
        return FALSE;
    }    

    // valid display type?
    switch(prov.Resource.dwDisplayType) {
      case RESOURCEDISPLAYTYPE_NETWORK   :
      case RESOURCEDISPLAYTYPE_DOMAIN    :
      case RESOURCEDISPLAYTYPE_SERVER    :
      case RESOURCEDISPLAYTYPE_SHARE     :
      case RESOURCEDISPLAYTYPE_DIRECTORY :
      case RESOURCEDISPLAYTYPE_GENERIC   :
        break;
      default:
        return FALSE;
    }
    
    // if not empty, make sure it's the heap
    if(prov.Resource.lpLocalName != NULL) {
      if(!IsHeapPtrEx(hp, prov.Resource.lpLocalName)) 
        return FALSE;
    }
    
    if(prov.Resource.lpRemoteName != NULL) {
      if(!IsHeapPtrEx(hp, prov.Resource.lpRemoteName)) 
        return FALSE;
    }
    
    if(prov.Resource.lpComment != NULL) {
      if(!IsHeapPtrEx(hp, prov.Resource.lpComment)) 
        return FALSE;
    }
    
    if(prov.Resource.lpProvider != NULL) {
      if(!IsHeapPtrEx(hp, prov.Resource.lpProvider)) 
        return FALSE;
    }
    
    // ensure at least one function points to code
    if(!IsCodePtrEx(hp, prov.AddConnection)) 
      return FALSE;
    
    return TRUE;
}

Using the Windows Debugger attached to explorer.exe, an execution path to MPR functions from an external process was found by first setting a memory breakpoint…

ba r 8 MPR!GlobalProviderInfo

…then sending various signals to I/O ports owned by explorer.exe which eventually resulted in a breakpoint hit. Window messages are usually successful in getting a reaction and on this occasion, shell notifications provided a path to the MPR functions.

Shell Notifications

While searching for an invocation method, the SHGetFolderLocation API with FOLDERID_NetworkFolder (My Network Places) and FOLDERID_NetHood (Network Shortcuts) was tried and proved to be ineffective. Searching online led me to a book by Dino Esposito called Visual C++ Windows Shell Programming. He discusses the SHChangeNotify API and how it can be used to refresh the desktop. When explorer.exe starts, it creates and registers a “shell change notify” window that allows external applications to directly send it notifications about certain events like the addition or removal of files, folders and network shares. In some ways, it’s a less powerful version of the Windows Notification Facility discussed in another post. Internally, the CTrayNotify class handles notifications sent to the notify window. Sending SHCNE_ASSOCCHANGED results in a refresh of the User Interface and additionally calls MPR functions. If you want to find this window easily, call the the undocumented user32!GetShellChangeNotifyWindow API which in turn retrieves it from NtCurrentTeb()->Win32ClientInfo.

Figure 3. Shell Change Notify Window Properties.

As you can see from the window properties in Figure 3, index zero of the window bytes has a value set. This is a heap pointer to the CTrayNotify class inside explorer.exe.

Injection Steps

At the moment, I’ve not provided a PoC, but will upload one later to the injection repository under the mpr folder. In the meantime, here’s some rough steps on how the injection works.

  1. Load a provider DLL and find the Relative Virtual Address (RVA) of GlobalProviderInfo in the .data segment of mpr.dll.
  2. Read the base address of mpr.dll in explorer.exe and add to the GlobalProviderInfo RVA.
  3. Copy/validate a PROVIDER structure from the virtual address of GlobalProviderInfo in explorer.exe.
  4. Open explorer.exe, allocate RWX memory and write a payload there.
  5. Overwrite a specific function pointer in remote PROVIDER structure with pointer to payload.
  6. Call SHChangeNotify(SHCNE_ASSOCCHANGED) to invoke the payload.
  7. Restore original function pointer in remote PROVIDER structure, deallocate memory and exit.

Summary

The CTrayNotify class and methods that handle shell notifications are more interesting as a vector than the MPR providers because notifications can still be used without MPR providers. A pointer to the class is stored on the heap and accessible via the GetWindowLongPtr API. One might also be able to retrieve the pointer from the Win32ClientInfo structures in the Thread Environment Block. Process injection is possible, but hijacking pointers to code is more interesting IMHO. Currently, that would be difficult to identify because a single process can have thousands of function pointers and any one one of them has the potential to redirect code!

This entry was posted in programming, security, windows and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s