AppInit_DLLs による DLL インジェクション

概要

ここでは AppInit_DLLs レジストリ値へ DLL 名を登録することによって、システムに自分の DLL を (いろんなプロセスに自動的に) ロードさせる方法を記載しています。

DLL インジェクション

API のフック、ウィンドウのサブクラス化、システムのモニタリングなどを行う目的で、自前の DLL を "よそ様" のプロセスにロードさせることを、DLL インジェクション (DLL injection, DLL の注入) と呼びます。

Windows は DLL インジェクションの方法を何種類か提供していますが、ここでは AppInit_DLLs というレジストリ値を用いる方法をご紹介します。

方法

AppInit_DLLs レジストリ値は以下の場所にあります。

HKEY_LOCAL_MACHINE
 \Software
  \Microsoft
   \Windows NT
    \CurrentVersion
     \Windows
      \AppInit_DLLs

こちらのレジストリに DLL を登録しておくと、user32.dll のロード時 (DllMain の DLL_PROCESS_ATACH 通知) に、user32.dll が登録された DLL をロードします。複数の DLL を登録する場合には、' ' (スペース) または ',' (カンマ) によって区切ります。

尚、user32.dll がロードするので user32.dll を使わない EXE にはロードされないことになります。

ここでは試しに自前の DLL (mydll.dll) を使って動作テストしてみます。mydll はロード時に、それをロードしたプロセス名をデバッグトレースに出力します

mydll.dll のコードは次の通りです。

#include <windows.h>

BOOL APIENTRY DllMain( HANDLE hinstDLL, DWORD  fdwReason, LPVOID lpvReserved) {

    TCHAR szProcessName [MAX_PATH + 2];
    int nLength;
 
    switch ( fdwReason ) {
    case DLL_PROCESS_ATTACH:
        if ( GetModuleFileName ( GetModuleHandle (0), szProcessName, MAX_PATH ) ) {
            nLength = lstrlen ( szProcessName );
            szProcessName [ nLength ] = '\r';
            szProcessName [ nLength + 1 ] = '\n';
            szProcessName [ nLength + 2 ] = '\0';
   
            OutputDebugString ( szProcessName );
        }
        else {
            OutputDebugString ( "GetModuleFileName failed.\n" );
        }  
        break;
    case DLL_PROCESS_DETACH:
        break;
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    }

    return TRUE;
}

mydll.dll のソースコードのダウンロード [mydll.zip, makefile]

この mydll.dll をビルドして C:\ 直下に配置し、AppInit_DLLs レジストリ値を C:\mydll.dll に設定します。さらに、この設定を反映させるために Windows を再起動します。

そこで電卓 (calc.exe)、メモ帳 (notepad.exe) 及び FireFox (firefox.exe) などを試しに起動してみました。確かに、プロセスの起動時、 DebugView にプロセス名が出力されていることが確認できます (左図, 画像をクリックすると拡大表示します)。

期待したとおりの動作になりました。


 

念のため、mydll.dll がロードされていることを確認しておきます。

>tasklist /M mydll.dll

Image Name                   PID Modules
========================= ====== =============================================
winlogon.exe                 664 mydll.dll
services.exe                 708 mydll.dll                                   
lsass.exe                    720 mydll.dll
svchost.exe                  888 mydll.dll
svchost.exe                  952 mydll.dll
svchost.exe                  992 mydll.dll
svchost.exe                 1068 mydll.dll
svchost.exe                 1100 mydll.dll
msdtc.exe                   1448 mydll.dll
...

確かに mydll.dll がロードされています。

また、 mydll.dll をロードしていないプロセスがあることも確認できます。

>tasklist /FI "MODULES ne mydll.dll"

Image Name                   PID Session Name     Session#    Mem Usage
========================= ====== ================ ======== ============
System Idle Process            0 Console                 0         16 K
System                         4 Console                 0        236 K
smss.exe                     536 Console                 0        388 K
csrss.exe                    584 Console                 0      8,032 K

そこで user32.dll がロードされていないプロセスを見ると次のようになります。

C:\Debuggers>tasklist /FI "MODULES ne user32.dll"
Image Name                   PID Session Name     Session#    Mem Usage
========================= ====== ================ ======== ============
System Idle Process            0 Console                 0         16 K
System                         4 Console                 0        236 K
smss.exe                     536 Console                 0        388 K

つまり、 csrss.exe だけは user32.dll をロードしているにもかかわらず mydll.dll をロードしていません (が、Csrss = Windows subsystem ですからユーザーのコードがロードされないようになっているのが健全に思われます。この辺りについて詳細をご存知の方、ぜひ教えてください。)。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説