I want to talk a little bit today about shims, specifically addressing how they work to address compatibility issues, and what the security ramifications are when you use a shim to address a compatibility issue.

Merriam Webster defines a shim as:

"a thin often tapered piece of material (as wood, metal, or stone) used to fill in space between things (as for support, leveling, or adjustment of fit)"

(Wow, I feel like an 8th grader beginning a term paper, using a definition like that!)

But the name shim comes exactly from this definition - we jam a thin piece of code between things - specifically, between the application's code and Windows code.

This works specifically because we implement an Import Address Table (IAT) to link to DLLs - specifically, to Windows DLLs.

Rather than talk about this, I figured I would just show this to you in a debugger. I began with the following source code:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)
{
    MessageBox(NULL, TEXT("Hello world."), TEXT("Hello"), MB_OK);
    return 0;
}

Nothing too fancy. In fact, it's pretty much identical to what you'll find at the beginning of Charles Petzold's Programming Windows book. Let's see how this looks in assembler:

0:000> uf ShimsAndIat!WinMain
ShimsAndIat!WinMain [c:\...\shimsandiat\shim.c @ 5]:
    5 00401000 6a00                push    0
    6 00401002 68f4204000      push    offset ShimsAndIat!`string' (004020f4)
    6 00401007 68fc204000      push    offset ShimsAndIat!`string' (004020fc)
    6 0040100c 6a00                push    0
    6 0040100e ff15ac204000   call    dword ptr [ShimsAndIat!_imp__MessageBoxA (004020ac)]
    7 00401014 33c0                xor     eax,eax
    8 00401016 c21000             ret     10h

When we call into Windows to invoke the MessageBoxA function, we don't jump there directly. We jump to a local symbol: _imp__MessageBoxA. If we then take a look at what we find at this address, we'll find our pointer to the function of interest:

0:000> dds 4020ac
004020ac  76ff56df USER32!MessageBoxA

Because we are jumping to the location pointed to by this pointer, we can see how easy it is to inject some new code: we simply need to replace the contents of the pointer (at 0x004020ac) with a new value - one that points to our shim code! The application we are trying to fix thinks it is calling in to Windows, but the shim code runs instead, and this shim code may call into Windows.

Let's take a look at a shimmed up application.

In this application, we are making a call to GetVersionEx. The application has a bug - it is checking for equality of the version of Windows to 5.1 (Windows XP). Here is the portion of the unassembled function doing this comparison:

0:000> uf DWM_Compositing_Rendering_Demo!WinMain
DWM_Compositing_Rendering_Demo!WinMain:

... (disassembly elided for clarity)


   10 0040103d ff1528204000    call    dword ptr [DWM_Compositing_Rendering_Demo!_imp__GetVersionExW (00402028)]
   11 00401043 837c240805       cmp     dword ptr [esp+8],5
   11 00401048 753e                 jne     DWM_Compositing_Rendering_Demo!WinMain+0x88 (00401088) 
   11 0040104a 837c240c01      cmp     dword ptr [esp+0Ch],1
   11 0040104f 7537                 jne     DWM_Compositing_Rendering_Demo!WinMain+0x88 (00401088)

... (disassembly elided for clarity)

But this application is shimmed. If we look at what _imp_GetVersionExW is pointing to, it's not GetVersionEx. It's:

0:000> dds 00402028
00402028  6cd54c66 AcLayers!NS_WinXPSP2VersionLie::APIHook_GetVersionExW

Yep - it's been redirected to a version lie, which causes this check to succeed. (If only every developer's keyboard included a > symbol...)

The shim itself doesn't need to call into Windows, but most do. They have to in many cases to do anything interesting. Because, as you can see, from the perspective of Windows, shim code is no different than application code. It's just arbitrary code that happens to be calling in to Windows to do some work for it. It can't circumvent any protections, it can't do anything less securely - shim code can only do what your application's code could do. No more, no less.

That's why there are no shims for "don't give me a UAC prompt for this application" - because your application can't do it either.

It also means that anything we can fix using a shim you could fix using code. After all, we did it with some code sitting between your application and Windows, you could just write similar code directly in your application and not have to use a shim at all.