Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using Pragmas to Create a Proxy DLL

0.00/5 (No votes)
20 Mar 2007 1  
An article on how to use MSVC pragmas to create a function forwarding DLL.

Screenshot - MechProxy.png

Introduction

This article explains a way to create a Proxy DLL employing the MSVC compiler '#pragma comment' feature. The source of inspiration for this was Ivo Ivanov's excellent article here in CodeProject [ref 1].

Background

Back in '96 or '97, I bought a set of games, MechWarrior 2 (MW2) plus Ghost Bear Legacy, and MW2:Mercenaries, on a business trip to Seattle. Unfortunately, I never had the time to finish Mercenaries. Some time ago, I decided to give it a try, only to find out that the game didn't work on Windows XP. As I like to see why/how things work, I fiddled around the game binaries with OllyDbg [ref 2], and found out that because BitBlt() nowadays only returns a Boolean flag for success/failure, the game didn't start.

Question: can someone confirm this: has the return value for BitBlt() changed from returning the number of lines blitted during the operation into true/false as it is now. I've tried to look for API specs on this, but everything I've found describes the current state.

After reading through web forums related to this series of games, I found out that I am not alone in trying to play it on XP. I thought about releasing the info of how my version (v1.05) of the game could be patched to be XP compatible. Then, I thought that a more elaborate method could make all three games of the MW2 series to run on XP. Therefore, I needed some way to make BitBlt() to return the number of lines blitted.

After some web browsing, I ended up at Ivo's page, and more specifically, the idea of proxy DLL. See Ivo's page on the explanation. I won't repeat it here.

Using the code

The provided sample code is built on the command line using Visual C++ 2005 Express Edition's compiler. I use Bakefile [ref 3] to create the makefiles I use. Bakefile and one tool in this distribution dictate the need for Python in the system. Note: When I was developing this, I was using Python v2.4, and since then I've upgraded Python into v2.5. Older versions can work, too.

Compiling the newly created proxy DLL can be done in the IDE, too. You just need to create a DLL project and include the generated/created files into it. I personally prefer the command-line for most jobs, because I feel that I'm somewhat in control then.

Here are the steps for creating a proxy DLL. I'll use GDI32 as an example as that was needed in my MechWarrior foolery project.

  1. Run the make_pragmas-tool provided in the source package.
  2. This script runs the tool DUMPBIN and parses its output to generate a header. Here's its usage print displayed when it is being run without any arguments:

    Usage:
        make_pragmas.py -o [output dir] [source DLL name]
    
    Where:
        source DLL name is the name of the DLL
        where the exports are extracted from
    
    
    Example:
        C:\>python make_pragmas.py gdi32
    
        Generates a 'gdi32_fwd.h' in the current directory.
    
    
    Options:
      --version             show program's version number and exit
      -h, --help            show this help message and exit
      -o DIR, --output-dir=DIR
                            Specify output directory.

    For this example, the command is like follows:

    C:>python make_pragmas.py -o inc \winnt\system32\gdi32.dll
    05-Mar-07 12:13:26 INFO     Processing '\winnt\system32\gdi32.dll'.
    05-Mar-07 12:13:26 INFO     Generating 'inc/gdi32_fwd.h'.
    05-Mar-07 12:13:27 INFO     All done, TTFN.
    C:>

    Looking at the generated header file, we see this:

    C:>more inc\gdi32_fwd.h
    #pragma comment(linker, "/export:AbortDoc=gdi32.AbortDoc")
    #pragma comment(linker, "/export:AbortPath=gdi32.AbortPath")
    #pragma comment(linker, "/export:AddFontMemResourceEx=gdi32.AddFontMemResourceEx")
    #pragma comment(linker, "/export:AddFontResourceA=gdi32.AddFontResourceA")
    #pragma comment(linker, "/export:AddFontResourceExA=gdi32.AddFontResourceExA")
    #pragma comment(linker, "/export:AddFontResourceExW=gdi32.AddFontResourceExW")
    #pragma comment(linker, "/export:AddFontResourceTracking=
                            gdi32.AddFontResourceTracking")
    #pragma comment(linker, "/export:AddFontResourceW=gdi32.AddFontResourceW")
    #pragma comment(linker, "/export:AngleArc=gdi32.AngleArc")
    #pragma comment(linker, "/export:AnimatePalette=gdi32.AnimatePalette")
    #pragma comment(linker, "/export:AnyLinkedFonts=gdi32.AnyLinkedFonts")
    #pragma comment(linker, "/export:Arc=gdi32.Arc")
    #pragma comment(linker, "/export:ArcTo=gdi32.ArcTo")
    #pragma comment(linker, "/export:BRUSHOBJ_hGetColorTransform=
                            gdi32.BRUSHOBJ_hGetColorTransform")
    #pragma comment(linker, "/export:BRUSHOBJ_pvAllocRbrush=
                            gdi32.BRUSHOBJ_pvAllocRbrush")
    #pragma comment(linker, "/export:BRUSHOBJ_pvGetRbrush=gdi32.BRUSHOBJ_pvGetRbrush")
    #pragma comment(linker, "/export:BRUSHOBJ_ulGetBrushColor=
                            gdi32.BRUSHOBJ_ulGetBrushColor")
    #pragma comment(linker, "/export:BeginPath=gdi32.BeginPath")
    // Below is the original BitBlt, pointing to GDI32.
    
    #pragma comment(linker, "/export:BitBlt=gdi32.BitBlt")
    #pragma comment(linker, "/export:CLIPOBJ_bEnum=gdi32.CLIPOBJ_bEnum")
    #pragma comment(linker, "/export:CLIPOBJ_cEnumStart=gdi32.CLIPOBJ_cEnumStart")
  3. Create a CPP-file for our own function implementation.
  4. As one can see, all exports are function forwarders into the real DLL's functions. The next part is to create a code file containing the DLLMain(), and routines that are to needed to be called instead of the real DLL's functions. In this case, its name is GDI42.CPP.

    When creating the CPP-file, please note the following points. First off, we need to define a function pointer that will hold the real GDI32's BitBlt routine.

    //! The real BitBlt.
    
    static BOOL (WINAPI *GDI32_BitBlt)(
        HDC hdcDest, // handle to destination DC
    
        int nXDest,  // x-coord of destination upper-left corner
    
        int nYDest,  // y-coord of destination upper-left corner
    
        int nWidth,  // width of destination rectangle
    
        int nHeight, // height of destination rectangle
    
        HDC hdcSrc,  // handle to source DC
    
        int nXSrc,   // x-coordinate of source upper-left corner
    
        int nYSrc,   // y-coordinate of source upper-left corner
    
        DWORD dwRop  // raster operation code
    
        );

    Next, we need to implement our own routine for blitting:

    //! Hooked BitBlt().
    
    extern "C" int WINAPI HookedBitBlt(
      HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight,
      HDC hdcSrc, int nXSrc, int nYSrc, DWORD dwRop
    )
    {
        // Call the real blitter, and store its return value.
    
        BOOL retVal = GDI32_BitBlt( hdcDest,
                                    nXDest,
                                    nYDest,
                                    nWidth,
                                    nHeight,
                                    hdcSrc,
                                    nXSrc,
                                    nYSrc,
                                    dwRop );
        if( retVal )
        {
            // Success, return the input number of lines to blit.
    
            return nHeight;
        }
        else
        {
            // Failed, return 0 lines.
    
            return 0;
        }
    }

    And, of course, the proxy attach/detach code, implemented in DLLMain:

    //! Attach or detach this proxy.
    
    BOOL WINAPI DllMain( HINSTANCE hInst,DWORD reason,LPVOID )
    {
        if( reason == DLL_PROCESS_ATTACH )
        {
            OutputDebugString( "GDI32 Proxy DLL starting.\n" );
            hLThis = hInst;
            // Load the real library, in this case GDI32.
    
            hGDI32 = LoadLibrary( "gdi32" );
            if( !hGDI32 )
            {
                return FALSE;
            }
            // Store the real BitBlt's address for hooked function to call.
    
            *(void **)&GDI32_BitBlt = (void *)GetProcAddress( hGDI32, "BitBlt" );
        }
        else if( reason == DLL_PROCESS_DETACH )
        {
            // Unload GDI32.
    
            FreeLibrary( hGDI32 );
            OutputDebugString( "GDI32 Proxy DLL signing off.\n" );
        }
        return TRUE;
    }
  5. Compile the code.
  6. After compilation is done, use DUMPBIN to find out the mangled, or decorated, name given to the proxy function. It will be needed when declaring the function to export:

    C:>dumpbin -symbols bin\gdi42_gdi42.obj
    <lots of output, and within that output:>
    00C 00000000 SECT4  notype ()    External     | _HookedBitBlt@36
  7. Modify the header generated in step 1.
  8. Open the generated header, gdi32_fwd.h, and change the line that reads:

    #pragma comment(linker, "/export:BitBlt=gdi32.BitBlt")

    into using the mangled name as the source for the BitBlt export:

    #pragma comment(linker, "/export:BitBlt=_HookedBitBlt@36")
  9. Recompile and verify.
  10. After recompiling, check with DUMPBIN that everything is as it should be, i.e., the fake GDI32, in this case, GDI42, exports BitBlt from within the DLL.

    C:>dumpbin -exports bin\gdi42.dll | grep -C 1 BitBlt
             18   11          BeginPath (forwarded to gdi32.BeginPath)
             19   12 00001000 BitBlt
             20   13          CLIPOBJ_bEnum (forwarded to gdi32.CLIPOBJ_bEnum)
  11. Modify the executable to be fooled.
  12. Next, launch the hex editor of your choice, and open the executable file to be fooled into it. Early in the file, there will be a string, GDI32. After that is changed into GDI42, the application will use the fresh proxy DLL, instead of the system32's GDI32, when calling the BitBlt routine.

Shortcomings

This approach won't work if the application to fool is protected in such a way that its import table is obfuscated. But for a game from the previous millennium, it works.

References

  1. Ivo Ivanov's 'API hooking revealed'
  2. OllyDbg by Oleh Yuschuk
  3. Bakefile by Vaclav Slavik

History

  • 20-Mar-07: Updated the Python-tool so that Python v2.3 won't choke on basicConfig() having arguments. Added some mention of Python versions into the 'Using the Code' section's first paragraph.
  • 06-Mar-07: Updated the Python tool within the source package. The old version didn't cope well with DLLs that contained function forwarders. Kernel32 is such a DLL. The updated script should now handle them properly.
  • 05-Mar-07: Doc created.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here