Import table shimming

20 Feb 2017

Previously I described how programs can depend on functions that don't exist in old versions of Windows, preventing them from running. When this occurs, the functions are often minor and inconsequential. So how can things that need functions work when the functions don't exist?

By providing the functions.

Consider the below shim code, for example, as how to implement the missing four functions that sdir needed on NT4 when compiled with Visual C++ 2012:

PVOID WINAPI
ShimDecodePointer( PVOID Ptr )
{
   return Ptr;
}

PVOID WINAPI
ShimEncodePointer( PVOID Ptr )
{
   return Ptr;
}

#define GET_MODULE_HANDLE_EX_FLAG_PIN                 (0x00000001)
#define GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT  (0x00000002)
#define GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS        (0x00000004)

BOOL WINAPI
ShimGetModuleHandleExW(DWORD dwFlags,
                       LPCWSTR lpModuleName,
                       HMODULE* phModule)
{
   WCHAR szFile[MAX_PATH];
   HMODULE hModule;

   switch(dwFlags) {
      case 0:
         hModule = GetModuleHandleW(lpModuleName);
         if (hModule == NULL) {
            *phModule = NULL;
            return FALSE;
         }
         if (!GetModuleFileNameW(hModule,szFile,MAX_PATH)) {
            *phModule = NULL;
            return FALSE;
         }
         if (!LoadLibraryW(szFile)) {
            *phModule = NULL;
            return FALSE;
         }
         *phModule = hModule;
         return TRUE;
      case GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT:
         hModule = GetModuleHandleW(lpModuleName);
         *phModule = hModule;
         if (hModule == NULL) {
            return FALSE;
         } else {
            return TRUE;
         }
      default:
         *phModule = NULL;
         return FALSE;
   }
}

BOOL WINAPI
ShimSetFilePointerEx(HANDLE hFile,
                     LARGE_INTEGER lpDistanceToMove,
                     PLARGE_INTEGER lpNewFilePointer,
                     DWORD dwMoveMethod)
{
   LARGE_INTEGER TempFilePointer;

   TempFilePointer.HighPart = lpDistanceToMove.HighPart;
   TempFilePointer.LowPart = SetFilePointer(hFile,
                                            lpDistanceToMove.LowPart,
                                            &TempFilePointer.HighPart,
                                            dwMoveMethod);
   if (TempFilePointer.LowPart == INVALID_SET_FILE_POINTER &&
       GetLastError() != NO_ERROR) {
      return FALSE;
   }

   if (lpNewFilePointer != NULL) {
      lpNewFilePointer->HighPart = TempFilePointer.HighPart;
      lpNewFilePointer->LowPart = TempFilePointer.LowPart;
   }
   return TRUE;
}

Clearly not the world's most sophisticated code. The next step is injecting these functions underneath an existing program. This can be done by compiling it as a DLL, exporting the functions with the correct names, and redirecting the functions not implemented back into the native system's corresponding DLL. Consider this module definition file:

LIBRARY _ERNEL32 BASE=0x700000 VERSION 5.1 EXPORTS DllMain=_DllMain@12 DecodePointer=_ShimDecodePointer@4 EncodePointer=_ShimEncodePointer@4 GetModuleHandleExW=_ShimGetModuleHandleExW@12 SetFilePointerEx=_ShimSetFilePointerEx@20 AddAtomA=Kernel32.AddAtomA AddAtomW=Kernel32.AddAtomW AddConsoleAliasA=Kernel32.AddConsoleAliasA AddConsoleAliasW=Kernel32.AddConsoleAliasW AllocConsole=Kernel32.AllocConsole ...

Now the import table of the program needs to be updated to point to this shim DLL. Note the name of "_ERNEL32" to make this an in-place update to any existing binary. Once that update is done, hey presto, it works:

Here's what this looks like under Dependency Walker. The shim DLL is there with some functions implemented itself, and some functions redirected back to the native version of the DLL. No red unresolved functions in sight:

This is a technique I've been using for a while. I'm sure I didn't invent it, but it appears some others are trying to go much further with it than I have so far.