Windows の互換性機能を使って 32bit のデスクトップアプリに DLL をインジェクションする方法

Windows には、古いプログラムを新しいバージョンの Windows で動かすための「互換性機能」があります。その中の「InjectDLL」という仕組みを使うと、32bit のデスクトップアプリに自作の DLL を注入できます。試してみましょう。

動作確認環境

  • Windows 11 Home 25H2
  • Visual Studio Community 2026 (Visual C++)

注入対象のプログラムを作る

まずはメッセージボックスを表示するだけの単純なプログラムを作ります。

#include <windows.h>

int main()
{
    MessageBox(0, "Hello!", "msg", MB_OK);
    return 0;
}

Visual Studio の「x86 Native Tools Command Prompt」を開き、cl コマンドでビルドします(もちろん IDE を使っても構いません)。

C:\msg> cl msg.c user32.lib
Microsoft (R) C/C++ Optimizing Compiler Version 19.51.36248 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

msg.c
Microsoft (R) Incremental Linker Version 14.51.36248.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:msg.exe
msg.obj
user32.lib

「msg.exe」が生成されました。

実行してみます。

c:\msg> msg.exe

「Hello!」と表示されました。

注入する DLL を作る

次に、「msg.exe」プロセスに注入する DLL を作ります。処理内容は、メッセージボックスの「Hello!」を「Modified by msgshim.dll!」に読み替えるものとしましょう。

プログラムは、次のプロンプトにより AI (Copilot) に書いてもらいます。

Windows の Application Compatibility - InjectDLL で使うための Custom Shim DLL を書いてください。
条件は次の通り。

・ OS は Windows 11 25H2
・ コンパイラは Visual C++ 2026
・ C++ を使わず C 言語で書くこと
・ 32bit プログラム前提
・ ファイル名は msgshim.c
・ 注入する機能は右の通り : MessageBoxA 関数を差し替え、
   テキストが "Hello!" だったら "Modified by msgshim.dll !" に書き換えて表示する。
   テキストがそれ以外だったら、そのまま表示する。
・ 日本語のコメントも適宜挿入する

以下のプログラムが生成されました(少しだけ修正しています)。

// msgshim.c - Application Compatibility Shim 用カスタム DLL
// 目的: MessageBoxA をフックし、特定の文字列を差し替えて表示する
// 対象: Windows 11 25H2 / 32bit / Visual C++ 2026
// 言語: C(C++ 機能は使用しない)

#include <windows.h>
#include <dbghelp.h>

// 元の MessageBoxA の関数ポインタ
static int (WINAPI *OriginalMessageBoxA)(
    HWND, LPCSTR, LPCSTR, UINT
) = NULL;

//------------------------------------------------------------------
// フック後の MessageBoxA
//------------------------------------------------------------------
int WINAPI HookedMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType)
{
    // "Hello!" の場合だけテキストを書き換える
    if (lpText && lstrcmpA(lpText, "Hello!") == 0) {
        return OriginalMessageBoxA(
            hWnd,
            "Modified by msgshim.dll!",
            lpCaption,
            uType
        );
    }

    // それ以外は元の関数をそのまま呼ぶ
    return OriginalMessageBoxA(hWnd, lpText, lpCaption, uType);
}

//------------------------------------------------------------------
// API Hook を行う簡易的な IAT 書き換え関数
//------------------------------------------------------------------
static void HookMessageBoxA(void)
{
    HMODULE hModule = GetModuleHandleA(NULL);
    if (!hModule) return;

    // Import Directory を取得
    ULONG size;
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc =
        (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToDataEx(
            hModule, TRUE, IMAGE_DIRECTORY_ENTRY_IMPORT, &size, NULL);
    if (!pImportDesc) return;

    // 各 DLL の Import Table を走査
    for (; pImportDesc->Name; pImportDesc++) {
        LPCSTR dllName = (LPCSTR)((BYTE*)hModule + pImportDesc->Name);

        // user32.dll を探す
        if (lstrcmpiA(dllName, "user32.dll") == 0) {

            // IAT の FirstThunk を取得
            PIMAGE_THUNK_DATA pThunk =
                (PIMAGE_THUNK_DATA)((BYTE*)hModule + pImportDesc->FirstThunk);

            for (; pThunk->u1.Function; pThunk++) {

                PROC *ppfn = (PROC*)&pThunk->u1.Function;

                // MessageBoxA を探す
                if (*ppfn == (PROC)MessageBoxA) {

                    // 元の関数を保存し、IAT を書き換えてフックする
                    OriginalMessageBoxA = (int (WINAPI *)(HWND, LPCSTR, LPCSTR, UINT))MessageBoxA;

                    DWORD oldProtect;
                    VirtualProtect(ppfn, sizeof(PROC), PAGE_READWRITE, &oldProtect);
                    *ppfn = (PROC)HookedMessageBoxA;
                    VirtualProtect(ppfn, sizeof(PROC), oldProtect, &oldProtect);

                    return;
                }
            }
        }
    }
}

//------------------------------------------------------------------
// DLL エントリポイント
//------------------------------------------------------------------
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
    switch (fdwReason) {
    case DLL_PROCESS_ATTACH:
        // DLL がプロセスにロードされたタイミングでフックを仕掛ける
        HookMessageBoxA();
        break;

    case DLL_PROCESS_DETACH:
        // 必要なら後処理を書く
        break;
    }

    return TRUE;
}

「/LD」オプションを付けて DLL としてビルドします。

C:\msg> cl /LD msgshim.c user32.lib dbghelp.lib
Microsoft (R) C/C++ Optimizing Compiler Version 19.51.36248 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

msgshim.c
Microsoft (R) Incremental Linker Version 14.51.36248.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:msgshim.dll
/dll
/implib:msgshim.lib
msgshim.obj
user32.lib
dbghelp.lib

注入用の DLL である「msgshim.dll」が生成されました。

「アプリケーション互換性ツール」をインストール

続いて、「msg.exe」と「msgshim.dll」の関連付けに必要な「アプリケーション互換性ツール」をダウンロード、インストールします。マイクロソフト製の無償ツールです。

検索エンジンで Windows ADK (Windows Assessment and Deployment Kit) の Web サイトを探して開き、ADK をダウンロードします。記事執筆時点では、URL は「https://learn.microsoft.com/ja-jp/windows-hardware/get-started/adk-install」、リンクは「ADK 10.1.28000.1 をダウンロードする (2025 年 11 月)」、ファイル名は「adksetup.exe」でした。

セットアップログラム「adksetup.exe」を実行します。

最初に表示される[場所の指定]画面では、「このコンピューターに Windows アセスメント & デプロイメントキットをインストールします」を選択します。

[インストールを行う機能を選択してください]画面では、「アプリケーション互換性ツール」を選択します。

インストールが完了すると、使いにくさにますます磨きがかかったスタートメニューに、2 個の「Compatibility Administrator (…」が現れます。後ろが切れており、マウスをホバーしても何も表示されないので区別できませんが、どちらかが 32bit 用でどちらかが 64bit 用です。起動してみるとタイトルバーに「64-bit」または「32-bit」と表示されるので分かります。

.sdb ファイルを作る

インストールしたツールを使って、「msg.exe」と「msgshim.dll」を関連付けるための設定ファイル(.sdb ファイル, Shim Database, AppCompat Database, 互換性修正データベース)を作りましょう。

今回は 32bit のプログラムをターゲットにしていますので、「Compatibility Administrator (32-bit)」のほうを起動します。

左側のツリーから「New Database(1) [Untitiled_1]」を右クリックし、[Create New]-[Application Fix…] を選択します。

[Program information] 画面で、[Name of the program to be fixed]にプログラムの名前を、[Program file location]にフルパス名を入力します。今回はそれぞれ「msg」「c:\msg\msg.exe」と入力します。

[Compatibility Modes] 画面では、特に何もチェックせず次に進みます。

[Compatibility Fixes] 画面で、[InjectDll]をチェックします。

[Parameters] ボタンを押して [Options for InjectDll] 画面を開き、[Command line]の欄に、注入する DLL のフルパス名を入力します。今回は「c:\msg\msgshim.dll」と入力します。プログラム実行時、注入する DLL はこの場所に置いておかなければならないので要注意です。

[Matching Information] 画面では、特に何もチェックせず[完了]ボタンを押します。

[Save] ボタンを押し、任意のデータベース名を入力します、今回は「msg」とします。

「c:\msg\msg.sdb」として設定ファイルが保存されました。

.sdb ファイルの中には、「msg.exe の起動時に c:\msg\msgshim.dll を DLL インジェクションせよ」といった指示が書いてあります。本ツールは .sdb 作成用であり、.exe の実行時には不要です。

.sdb ファイルを OS に登録する

続いて、.sdb ファイルを OS に登録します。
登録には、OS 付属の sdbinst.exe コマンドを使います。書式は次の通りです

使用法: sdbinst [-?] [-q] [-u] [-g] [-p] [-n[:WIN32|WIN64]] myfile.sdb | {GUID} | "名前"

    -? - このヘルプ テキストを出力します。
    -p - 修正プログラムを含む SDB を許可します。
    -q - Quiet モード: プロンプトは自動的に許可されます。
    -u - アンインストールします。
    -g {GUID} - ファイルの GUID です (アンインストールのみ)。
    -n "名前" - ファイルの内部名です (アンインストールのみ)。

管理者権限のコマンドプロンプトを開き、sdbinst に続けて .sdb ファイルの名前を指定します。

C:\msg> sdbinst msg.sdb
msg のインストールが完了しました。

「インストールが完了しました」と表示されたら成功です。.sdb は OS への情報登録用であり、.exe の実行時には不要です。

ちなみに sdbinst は内部的に次の処理を行っています。

  • 指定の .sdb ファイルを、「C:¥WINDOWS\APPATCH\CUSTOMSDB」配下に、{GUID}.sdb という名前でコピーする。
  • レジストリ「HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags」の「Custom」や「InstalledSDB」配下に、コピーした {GUID}.sdb ファイルのフルパス名などを書き込む。
  • [コントロールパネル]-[プログラムと機能]の一覧([設定]-[アプリ]-[インストールされているアプリ]の一覧)に、このインジェクションの情報を追加する。

[プログラムと機能]の一覧に表示することで、不正な登録の心理的防止を図っていると思われます。また、この一覧からインジェクションをアンインストールできます。

プログラムを実行

では、いよいよプログラムを実行します。と言っても、普通に「msg.exe」を実行するだけです。

c:\msg> msg.exe

“Hello!” が “Modified by msgshim.dll !” に書き換わりました。オリジナルの .exe を変えることなく、DLL インジェクションによって動作を変更できました。

うまくいかない場合は、.sdb 作成時に指定した絶対パスと同じ場所に .dll を置いているか、.sdb 作成・登録後にプログラムを変更していないか、ご確認ください。

注入状態の確認

もう少し詳しく、「msg.exe」プロセス内に「msgshim.dll」がロードされる様子も見てみましょう。
次の引数を指定して WinDbg 経由で「msg.exe」を起動します。

C:\msg> windbg.exe -c "sxe ld msgshim.dll" msg.exe

g コマンドを一回発行すると「msgshim.dll」のロードのタイミングで停止します。

0:000> g
ModLoad: 71300000 71317000   c:\msg\msgshim.dll
eax=00000000 ebx=004feed0 ecx=00000000 edx=00000000 esi=005eed60 edi=005eed78
eip=77019c3c esp=004fee4c ebp=004feec8 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!NtMapViewOfSection+0xc:
77019c3c c22800          ret     28h

kv コマンドを発行してコールスタックを表示します。

0:000> kv
 # ChildEBP RetAddr      Args to Child              
00 004fee48 76fe7985     000002f4 ffffffff 005eed78 ntdll!NtMapViewOfSection+0xc (FPO: [10,0,0])
01 004feed0 76fe7585     005eeca8 00000000 7ffe0385 ntdll!LdrpMinimalMapModule+0x175 (FPO: [Non-Fpo])
02 004ff118 76ffa911     005eeca8 00000000 005eed8c ntdll!LdrpMapDllWithSectionHandle+0x26 (FPO: [0,141,4])
03 004ff180 76ffcf23     004ff390 005eeca8 00000000 ntdll!LdrpMapDllNtFileName+0x147 (FPO: [Non-Fpo])
04 004ff2b4 76ff64c7     50a3914f 004ff390 004ff384 ntdll!LdrpMapDllFullPath+0xb8 (FPO: [0,71,4])
05 004ff2fc 7704fbce     50a390fb 00000001 004ff528 ntdll!LdrpProcessWork+0x7e (FPO: [Non-Fpo])
06 004ff348 76fe4647     00000600 00000004 00000000 ntdll!LdrpLoadDllInternal+0x206 (FPO: [Non-Fpo])
07 004ff4f0 76b5f483     00000001 004ff518 004ff528 ntdll!LdrLoadDll+0x167 (FPO: [4,93,4])
08 004ff534 76b7e5a1     005eec0c 00000000 00000000 KERNELBASE!LoadLibraryExW+0x153 (FPO: [Non-Fpo])
09 004ff548 71a2a556     005eec0c 837af58a 00000000 KERNELBASE!LoadLibraryW+0x11 (FPO: [Non-Fpo])
0a 004ff584 71a2a443     00000000 71a2a3a0 00000000 AcGenral!NS_InjectDll::NotifyFn+0xb7 (FPO: [Non-Fpo])
0b 004ff598 71a04777     00000064 00000000 004ff5b0 AcGenral!NS_InjectDll::InitializeHooksMulti+0xa3 (FPO: [Non-Fpo])
0c 004ff5b4 71a03932     00000000 004ff5e8 00000000 AcGenral!MultiShimEntry+0x57 (FPO: [Non-Fpo])
0d 004ff5ec 71a01a18     00000000 00000000 71a01ad9 AcGenral!ShimLib::InitializeHooksEx+0x1d82 (FPO: [Non-Fpo])
0e 004ff5f8 71a01ad9     004ff634 71d69f21 00000001 AcGenral!ShimLib::NotifyShimsEx+0xa8 (FPO: [0,0,0])
0f 004ff600 71d69f21     00000001 00000000 002ac000 AcGenral!ShimLib::NotifyShims+0x29 (FPO: [Non-Fpo])
10 004ff634 71d753aa     00000000 00120000 7706a366 apphelp!SeShimManagerNotifyShims+0x66 (FPO: [Non-Fpo])
11 004ff640 7706a366     004ff758 00120000 50a39be3 apphelp!SE_InstallAfterInit+0x2a (FPO: [2,0,4])
12 004ff850 76fd4174     50a39b13 004ff8c8 00000000 ntdll!LdrpInitializeProcess+0x1966 (FPO: [Non-Fpo])
13 004ff8a0 77050543     00000000 00000000 002ac000 ntdll!_LdrpInitialize+0xd4 (FPO: [Non-Fpo])
14 004ff8cc 76fd4041     00000000 00000000 00000000 ntdll!LdrpInitializeInternal+0xaf (FPO: [0,5,4])
15 004ff8d8 00000000     004ff8ec 76fa0000 00000000 ntdll!LdrInitializeThunk+0x11 (FPO: [Non-Fpo])

「apphelp.dll」(Application Help) や「AcGeneral.dll」(Application Compatibility General) という OS の DLL を経由して、LoadLibraryW 関数が呼び出されています。

du コマンドを発行して、LoadLibraryW 関数の第一引数の内容を見てみます。

0:000> du 005eec0c
005eec0c  "c:\msg\msgshim.dll"

「msgshim.dll」へのフルパス名が入っていることが確認できました。

おわりに

マイクロソフト公認の機能で DLL インジェクションを行ってみました。何らかの理由で exe を変更できないが動作を微妙に変えたい場合に利用できるかもしれません。
ただし、32bit のデスクトップアプリ限定です。64bit アプリ、UWP アプリ、MSIX パッケージ化されたアプリ、また、マイクロソフトが保護対象と定めたアプリには効かないのでご注意ください。

参考文献

[1] Persistence – Application Shimming, Penetration Testing Lab, 2019
https://pentestlab.blog/2019/12/16/persistence-application-shimming/