DLL のエクスポート転送で遊んでみる

エクスポート転送

 DLL にはエクスポート関数やエクスポート変数に実態をもたせないで、別の DLL に転送する機能があります。以下は dumpbin.exe で XP の KERNEL32.DLL の転送されているエクスポート一覧を表示したものです。

C:\>dumpbin.exe /exports c:\windows\system32\kernel32.dll | grep forwarded
          9    8          AddVectoredExceptionHandler (forwarded to NTDLL.RtlAddVectoredExceptionHandler)
        122   79          DecodePointer (forwarded to NTDLL.RtlDecodePointer)
        123   7A          DecodeSystemPointer (forwarded to NTDLL.RtlDecodeSystemPointer)
        128   7F          DeleteCriticalSection (forwarded to NTDLL.RtlDeleteCriticalSection)
        147   92          EncodePointer (forwarded to NTDLL.RtlEncodePointer)
        148   93          EncodeSystemPointer (forwarded to NTDLL.RtlEncodeSystemPointer)
        151   96          EnterCriticalSection (forwarded to NTDLL.RtlEnterCriticalSection)
        361  168          GetLastError (forwarded to NTDLL.RtlGetLastWin32Error)
        515  202          HeapAlloc (forwarded to NTDLL.RtlAllocateHeap)
        521  208          HeapFree (forwarded to NTDLL.RtlFreeHeap)
        525  20C          HeapReAlloc (forwarded to NTDLL.RtlReAllocateHeap)
        527  20E          HeapSize (forwarded to NTDLL.RtlSizeHeap)
        536  217          InitializeSListHead (forwarded to NTDLL.RtlInitializeSListHead)
        541  21C          InterlockedFlushSList (forwarded to NTDLL.RtlInterlockedFlushSList)
        543  21E          InterlockedPopEntrySList (forwarded to NTDLL.RtlInterlockedPopEntrySList)
        544  21F          InterlockedPushEntrySList (forwarded to NTDLL.RtlInterlockedPushEntrySList)
        577  240          LeaveCriticalSection (forwarded to NTDLL.RtlLeaveCriticalSection)
        653  28C          QueryDepthSList (forwarded to NTDLL.RtlQueryDepthSList)
        695  2B6          RemoveVectoredExceptionHandler (forwarded to NTDLL.RtlRemoveVectoredExceptionHandler)
        703  2BE          RestoreLastError (forwarded to NTDLL.RtlRestoreLastWin32Error)
        705  2C0          RtlCaptureContext (forwarded to NTDLL.RtlCaptureContext)
        706  2C1          RtlCaptureStackBackTrace (forwarded to NTDLL.RtlCaptureStackBackTrace)
        707  2C2          RtlFillMemory (forwarded to NTDLL.RtlFillMemory)
        708  2C3          RtlMoveMemory (forwarded to NTDLL.RtlMoveMemory)
        709  2C4          RtlUnwind (forwarded to NTDLL.RtlUnwind)
        710  2C5          RtlZeroMemory (forwarded to NTDLL.RtlZeroMemory)
        759  2F6          SetCriticalSectionSpinCount (forwarded to NTDLL.RtlSetCriticalSectionSpinCount)
        788  313          SetLastError (forwarded to NTDLL.RtlSetLastWin32Error)
        853  354          TryEnterCriticalSection (forwarded to NTDLL.RtlTryEnterCriticalSection)
        872  367          VerSetConditionMask (forwarded to NTDLL.VerSetConditionMask)

 これを見ると HeapAlloc() は NTDLL.DLL の RtlAllocateHeap() に転送されていることなどが分かります。
 転送先は [拡張子を除いた DLL 名].[エクスポート名] の形式となっており、転送先の DLL の拡張子は必ず dll でなければなりません。

エクスポート転送の作り方

 .def ファイルにエクスポート名と転送先を記述するだけで作れます。

forward.def
LIBRARY   FORWARD
EXPORTS
  MessageBoxW = USER32.MessageBoxW
forward.cpp
#include <windows.h>
#pragma comment(linker, "/nodefaultlib")

extern "C" BOOL WINAPI _DllMainCRTStartup(HMODULE, DWORD, void*)
{
  return TRUE;
}

C:\>cl forward.cpp forward.def /link user32.lib
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 13.10.6030 for 80x86
Copyright (C) Microsoft Corporation 1984-2002. All rights reserved.

forward.cpp
Microsoft (R) Incremental Linker Version 7.10.6030
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:forward.exe
user32.lib
/def:forward.def
forward.obj
   ライブラリ forward.lib とオブジェクト forward.exp を作成中

C:\>dumpbin /exports forward.dll
Microsoft (R) COFF/PE Dumper Version 7.10.6030
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file forward.dll

File Type: DLL

  Section contains the following exports for FORWARD.dll

    00000000 characteristics
    4A4C612C time date stamp Thu Jul 02 16:26:36 2009
        0.00 version
           1 ordinal base
           1 number of functions
           1 number of names

    ordinal hint RVA      name

          1    0          MessageBoxW (forwarded to USER32.MessageBoxW)

  Summary

        1000 .rdata
        1000 .reloc
        1000 .text
 コンパイルは古めの VS 2003 でやってます。出来上がった forward.lib を user32.lib の代わりにリンクすると USER32.dll からではなく FORWARD.dll から MessageBoxW をインポートする実行可能ファイルの出来上がりです。

さらに拡張して・・・

 .def を以下のように修正します。
forward.def
LIBRARY   FORWARD
EXPORTS
  MessageBoxW                 = USER32.MessageBoxW                  @200 NONAME
  GetModuleHandleA            = KERNEL32.GetModuleHandleA           @202 NONAME
  GetStartupInfoA             = KERNEL32.GetStartupInfoA            @203 NONAME
  GetCommandLineA             = KERNEL32.GetCommandLineA            @204 NONAME
  GetVersionExA               = KERNEL32.GetVersionExA              @205 NONAME
  ExitProcess                 = KERNEL32.ExitProcess                @206 NONAME
  GetProcAddress              = KERNEL32.GetProcAddress             @207 NONAME
  TerminateProcess            = KERNEL32.TerminateProcess           @208 NONAME
  GetCurrentProcess           = KERNEL32.GetCurrentProcess          @209 NONAME
  WriteFile                   = KERNEL32.WriteFile                  @210 NONAME
  GetStdHandle                = KERNEL32.GetStdHandle               @211 NONAME
  GetModuleFileNameA          = KERNEL32.GetModuleFileNameA         @212 NONAME
  UnhandledExceptionFilter    = KERNEL32.UnhandledExceptionFilter   @213 NONAME
  FreeEnvironmentStringsA     = KERNEL32.FreeEnvironmentStringsA    @214 NONAME
  GetEnvironmentStrings       = KERNEL32.GetEnvironmentStrings      @215 NONAME
  FreeEnvironmentStringsW     = KERNEL32.FreeEnvironmentStringsW    @216 NONAME
  WideCharToMultiByte         = KERNEL32.WideCharToMultiByte        @217 NONAME
  GetLastError                = KERNEL32.GetLastError               @218 NONAME
  GetEnvironmentStringsW      = KERNEL32.GetEnvironmentStringsW     @219 NONAME
  SetHandleCount              = KERNEL32.SetHandleCount             @220 NONAME
  GetFileType                 = KERNEL32.GetFileType                @221 NONAME
  HeapDestroy                 = KERNEL32.HeapDestroy                @222 NONAME
  HeapCreate                  = KERNEL32.HeapCreate                 @223 NONAME
  VirtualFree                 = KERNEL32.VirtualFree                @224 NONAME
  HeapFree                    = KERNEL32.HeapFree                   @225 NONAME
  LoadLibraryA                = KERNEL32.LoadLibraryA               @226 NONAME
  GetACP                      = KERNEL32.GetACP                     @227 NONAME
  GetOEMCP                    = KERNEL32.GetOEMCP                   @228 NONAME
  GetCPInfo                   = KERNEL32.GetCPInfo                  @229 NONAME
  HeapAlloc                   = KERNEL32.HeapAlloc                  @230 NONAME
  VirtualAlloc                = KERNEL32.VirtualAlloc               @231 NONAME
  HeapReAlloc                 = KERNEL32.HeapReAlloc                @232 NONAME
  RtlUnwind                   = KERNEL32.RtlUnwind                  @233 NONAME
  InterlockedExchange         = KERNEL32.InterlockedExchange        @234 NONAME
  VirtualQuery                = KERNEL32.VirtualQuery               @235 NONAME
  HeapSize                    = KERNEL32.HeapSize                   @236 NONAME
  QueryPerformanceCounter     = KERNEL32.QueryPerformanceCounter    @237 NONAME
  GetTickCount                = KERNEL32.GetTickCount               @238 NONAME
  GetCurrentThreadId          = KERNEL32.GetCurrentThreadId         @239 NONAME
  GetCurrentProcessId         = KERNEL32.GetCurrentProcessId        @240 NONAME
  GetSystemTimeAsFileTime     = KERNEL32.GetSystemTimeAsFileTime    @241 NONAME
  LCMapStringA                = KERNEL32.LCMapStringA               @242 NONAME
  MultiByteToWideChar         = KERNEL32.MultiByteToWideChar        @243 NONAME
  LCMapStringW                = KERNEL32.LCMapStringW               @244 NONAME
  GetStringTypeA              = KERNEL32.GetStringTypeA             @245 NONAME
  GetStringTypeW              = KERNEL32.GetStringTypeW             @246 NONAME
  GetLocaleInfoA              = KERNEL32.GetLocaleInfoA             @247 NONAME
  VirtualProtect              = KERNEL32.VirtualProtect             @248 NONAME
  GetSystemInfo               = KERNEL32.GetSystemInfo              @249 NONAME
  IsBadWritePtr               = KERNEL32.IsBadWritePtr              @250 NONAME

 そして、KERNEL32.lib と USER32.LIB の代わりに FORWARD.lib をリンクする以下のようなソースをコンパイルしてみます。
test.cpp
#include <windows.h>
#pragma comment(linker, "/nodefaultlib:kernel32.lib")
#pragma comment(lib, "forward.lib")

int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
  return MessageBoxW(NULL, L"world!", L"Hello", MB_OK);
}

 dumpbin でインポートを列挙してみます。
C:\>dumpbin /imports test.exe
Microsoft (R) COFF/PE Dumper Version 7.10.6030
Copyright (C) Microsoft Corporation.  All rights reserved.


Dump of file test.exe

File Type: EXECUTABLE IMAGE

  Section contains the following imports:

    FORWARD.dll
                405000 Import Address Table
                405D50 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                      Ordinal   200
                      Ordinal   202
                      Ordinal   203
                      Ordinal   204
                      Ordinal   205
                      Ordinal   206
                      Ordinal   207
                      Ordinal   208
                      Ordinal   209
                      Ordinal   210
                      Ordinal   211
                      Ordinal   212
                      Ordinal   213
                      Ordinal   214
                      Ordinal   215
                      Ordinal   216
                      Ordinal   217
                      Ordinal   218
                      Ordinal   219
                      Ordinal   220
                      Ordinal   221
                      Ordinal   222
                      Ordinal   223
                      Ordinal   224
                      Ordinal   225
                      Ordinal   226
                      Ordinal   227
                      Ordinal   228
                      Ordinal   229
                      Ordinal   230
                      Ordinal   231
                      Ordinal   232
                      Ordinal   233
                      Ordinal   234
                      Ordinal   235
                      Ordinal   236
                      Ordinal   237
                      Ordinal   238
                      Ordinal   239
                      Ordinal   240
                      Ordinal   241
                      Ordinal   242
                      Ordinal   243
                      Ordinal   244
                      Ordinal   245
                      Ordinal   246
                      Ordinal   247
                      Ordinal   248
                      Ordinal   249

  Summary

        1000 .data
        1000 .rdata
        4000 .text
 何をインポートしているか分からない EXE の出来上がり。逆アセンブラでも転送先をおっかけてまで表示するようなものはなかなかないようです。OllyDbg では IAT やコードから判断するのでばれちゃいますけどね。あとは forward.dll のエクスポートを列挙して一つ一つ序数とかけあわせれば何をインポートしているかは分かりますが面倒です。
 他には、「user32.dll の MessageBoxW をフックする」というようなやり方をしているツールからは API フックを防止できるでしょう。

 まぁ、そういった目的で使うぐらいなら素直にプロテクターを使ったほうが手っ取り早いです。タイトルどおりお遊びってことで。