Print Story Creating a shim DLL on Microsoft Windows
Technology
By Rogerborg (Sat Feb 18, 2006 at 12:55:21 PM EST) 1337, virginity, nerd (all tags)
Pip pip, chums.  Ever wanted to replace some functionality in a Windows system DLL with your own, for purposes noble or nefarious?   One way to do it is with a shim DLL, that looks and acts like the real thing but which you can hook your own functionality into on a function by function basis.  Enclosed is some C++ source that will create a shim DLL for any arbitrary DLL, replicating all of its exported functions, and (by default) calling straight through to the real system DLL.


The following code, when compiled and run, will take a specified Windows system DLL, FOO.dll and produce a FOO.cpp and FOO.def file which can then be used to compile a shim DLL (which is an exercise for the reader).

The intended use of the shim DLL is to drop it into the directory of a specific executable.  The executable will (in most cases) load the shim DLL instead of the real one.  By default, all functions in the shim DLL call (actually jmp) straight through to the function in the real DLL in the Windows system directory (system32).  You should do this first, to verify that the basic shim DLL works transparently.

Now you can get up to mischief by replacing individual _I_BAR functions with functions with the correct signatures, and call (rather than jmp) through to the real functions, for which you'll need to replace the _O_BAR() function pointer with one with the correct signature.  This allows you to view or modify both the parameters, and the return value.  The uses for this are practically endless; just for example, you can intercept all IP traffic from an application (which Ethereal already allows) but you can also modify the connections, and the incoming or outgoing data.

If you are feeling particularly confident, you could even replace the real system DLL with the shim one, so that all applications will use it.  I do not recommend that you do this, as if you pooch it, you might completely stuff your Windows install.

The code follows.  Save it out and compile it (modifying the CLONE_DLL and/or INSERT_DEBUG_OUTPUT defines first) and run the resulting console executable.  All being well, it will produce a .cpp and .def file.  Use them to build the actual shim DLL, modifying them as required.  In Microsoft Dev Studio, it's as simple as creating a new empty DLL project and including the FOO.cpp and FOO.def files.

I've tested this by shimming WS2_32.dll, which has a fairly hefty set of exports, and it works as expected.  Bug reports and improvements welcome.



#include <stdio.h>
#include <windows.h>
#include <list>
using namespace std;

// Change this to the name of the DLL that you want to clone
#define CLONE_DLL "WS2_32"

// Define this to insert basic fprintf debug into the cloned DLL
#define INSERT_DEBUG_OUTPUT 1

typedef struct
{
    int ordinal;
    char name[64];
} Symbol;

static list<Symbol> gs_symbols;

main()
{
    char originalDLL[256];
    (void)GetSystemDirectory(originalDLL, sizeof(originalDLL) - 1);
    strcat(originalDLL, "\\" CLONE_DLL ".dll");

    char exportFile[256];
    (void)GetTempPath(sizeof(exportFile), exportFile);
    strcat(exportFile, "exports.txt");

    char dumpbin[512];
    sprintf(dumpbin, "dumpbin /exports %s > %s", originalDLL, exportFile);
    (void)system(dumpbin);

    FILE * exports = fopen(exportFile, "r");
    FILE * cpp = fopen(CLONE_DLL ".cpp", "w");
    FILE * def = fopen(CLONE_DLL ".def", "w");

    while(!feof(exports))
    {
        char line[256];
        Symbol newSymbol;

        if(fgets(line, sizeof(line) - 1, exports))
            if(2 == sscanf(line, "%d %*x %*x %s",
                &newSymbol.ordinal,
                newSymbol.name))
                gs_symbols.push_back(newSymbol);
    }

    fprintf(def, "LIBRARY " CLONE_DLL "\n\nEXPORTS\n");

    for(list<Symbol>::iterator symbol = gs_symbols.begin();
        symbol != gs_symbols.end();
        ++symbol)
        fprintf(def, "\t%s = _I_%s @%d\n",
                symbol->name,
                symbol->name,
                symbol->ordinal);

    fprintf(cpp, "#include <stdio.h>\n");
    fprintf(cpp, "#include <windows.h>\n");
    fprintf(cpp, "static HINSTANCE gs_hDLL = 0;\n\n");

#if INSERT_DEBUG_OUTPUT
    fprintf(cpp, "#if defined (_DEBUG)\n");
    fprintf(cpp, "static FILE * debug;\n");
    fprintf(cpp, "#define DEBUG(x) fprintf x\n");
    fprintf(cpp, "#else // DEBUG\n");
    fprintf(cpp, "#define DEBUG(x)\n");
    fprintf(cpp, "#endif // _DEBUG\n\n");
#endif // INSERT_DEBUG_OUTPUT

    for(symbol = gs_symbols.begin();
        symbol != gs_symbols.end();
        ++symbol)
    {
        fprintf(cpp, "int (__stdcall * _O_%s)();\n", symbol->name);
        fprintf(cpp, "extern \"C\" void __declspec(naked) __stdcall _I_%s()\n", symbol->name);       
        fprintf(cpp, "{\n");
#if INSERT_DEBUG_OUTPUT
        fprintf(cpp, "\tDEBUG((debug, \"%s\\n\"));\n", symbol->name);
#endif // INSERT_DEBUG_OUTPUT
        fprintf(cpp, "\t__asm { jmp _O_%s }\n", symbol->name);
        fprintf(cpp, "}\n\n");
    }

    fprintf(cpp, "BOOL WINAPI DllMain(HINSTANCE hI, DWORD reason, LPVOID notUsed)\n");
    fprintf(cpp, "{\n");
    fprintf(cpp, "    if (reason == DLL_PROCESS_ATTACH)\n");
    fprintf(cpp, "    {\n");

#if INSERT_DEBUG_OUTPUT   
    fprintf(cpp, "#if defined(_DEBUG)\n");
    fprintf(cpp, "        if(!debug)\n");
    fprintf(cpp, "            debug = fopen(\"debug.txt\", \"w\");\n");
    fprintf(cpp, "#endif // DEBUG\n");
    fprintf(cpp, "        DEBUG((debug, \"DllMain\\n\"));\n\n");
#endif // INSERT_DEBUG_OUTPUT

    fprintf(cpp, "        char realDLL[MAX_PATH];\n");
    fprintf(cpp, "        (void)GetSystemDirectory(realDLL, sizeof(realDLL) - 1);\n");
    fprintf(cpp, "        strcat(realDLL, \"\\\\" CLONE_DLL ".dll\");\n");
    fprintf(cpp, "        gs_hDLL = LoadLibrary(realDLL);\n");
    fprintf(cpp, "        if (!gs_hDLL)\n");
    fprintf(cpp, "            return FALSE;\n\n");

    for(symbol = gs_symbols.begin();
        symbol != gs_symbols.end();
        ++symbol)
    {
        fprintf(cpp, "        _O_%s = GetProcAddress(gs_hDLL, \"%s\");\n",
                symbol->name, symbol->name);
    }

    fprintf(cpp, "\n        return TRUE;\n");
    fprintf(cpp, "    }\n");
    fprintf(cpp, "    else if (reason == DLL_PROCESS_DETACH)\n");
    fprintf(cpp, "    {\n");

#if INSERT_DEBUG_OUTPUT
    fprintf(cpp, "#if defined(_DEBUG)\n");
    fprintf(cpp, "        if(debug)\n");
    fprintf(cpp, "            fclose(debug);\n");
    fprintf(cpp, "#endif // DEBUG\n\n");
#endif // INSERT_DEBUG_OUTPUT

    fprintf(cpp, "        FreeLibrary(gs_hDLL);\n\n");
    fprintf(cpp, "    }\n");
    fprintf(cpp, "    return TRUE;\n");
    fprintf(cpp, "}\n\n");

    fclose(exports);
    fclose(cpp);
    fclose(def);

    return 0;
}

Full discussion: http://www.hulver.com/scoop/story/2006/2/18/125521/185