rundll32 が関数に渡す引数は何なのか

概要

Windows 付属の rundll32.exe コマンドを利用して、指定した DLL 内の関数を呼び出すことができます。このとき rundll32 が関数に渡す引数について、やや詳しく説明します。

動作確認環境

  • Windows 10 Home 21H1, 64bit
    (ただし、今回は 32bit の C:\Windows\SysWOW64\rundll32.exe モジュールについて扱います)
  • Visual Studio Community 2019

rundll32 の使い方

rundll32.exe の使い方は次の通りです。オプションは省略可能です。

rundll32 DLL名,関数名 [オプション]

たとえば次のように入力すると、MyDll.dll がエクスポートしている MyFunc 関数がオプション Hello で呼び出されます。

rundll32 MyDll.dll,MyFunc Hello

コマンドラインを見ると MyFunc 関数が引数 Hello で呼び出される、言い換えると「MyFunc(“Hello”);」が実行されるように見えますが、違います。

引数 1. ウィンドウハンドル

関数の第 1 引数には「rundll32 が内部で生成したウィンドウのハンドル」が渡されます。ウィンドウハンドルですので固定値ではなく、たとえば 0x001805EA になったり 0x009E0558 になったりします。
このウィンドウは画面には表示されませんが、次のようにすると見ることができます。

  1. 第 1 引数で指定されたウィンドウを表示するプログラムを、DLL のエクスポート関数として書きます。
// MyDll.c
#include <windows.h>
int __stdcall MyFunc(HWND hwnd, HINSTANCE hinst, char *pcCmdLine, int nCmdShow)
{
    MoveWindow(hwnd, 0, 0, 300, 200, TRUE);
    ShowWindow(hwnd, SW_SHOW);
    MessageBox(0, "test", "yaya-Low-Layer Lab.", MB_OK);
    return 0;
}
// MyDll.def
EXPORTS
    MyFunc

  1. コンパイルして DLL を作ります。
cl /LD MyDll.c MyDll.def user32.lib

  1. rundll32 を利用して、この関数を呼び出します。

rundll32 MyDll.dll,MyFunc
  1. ほとんどの人が見たことのないであろう、rundll32 が内部で生成したウィンドウが表示されました。

引数 2. インスタンスハンドル

関数の第 2 引数には「rundll32 のインスタンスハンドル」、言い換えると「rundll32.exe モジュールがロードされたアドレス」が渡されます。
モジュールのロードアドレスは OS の ASLR 機能によりランダム化されるので、たとえば 0x00940000 になったり 0x00C20000 になったりします。

引数 3. オプションへのポインタ

関数の第 3 引数には「rundll32 の実行時に指定したオプションへのポインタ」が渡されます。
オプションとして次のように「Hello」を指定すると、文字列 “Hello” へのポインタが渡されます。

rundll32 MyDll.dll,MyFunc Hello

数字「123」を指定しても、数として解釈されることはなく、文字列 “123” へのポインタが渡されます。

rundll32 MyDll.dll,MyFunc 123

空白を含むオプション「Hello 123」を指定しても、空白で分離されることはなく、単に文字列 “Hello 123” へのポインタが渡されます。

undll32 MyDll.dll,MyFunc Hello 123

オプションを指定しなかった場合は、長さ 0 の文字列へのポインタが渡されます。NULL ポインタが渡されるわけではありません。

rundll32 MyDll.dll,MyFunc

これらのオプションは rundll32 が内部で確保したヒープメモリに格納され、アドレスは不定です。

引数 4. ウィンドウの表示状態

関数の第 4 引数には、「ウィンドウの表示状態」が渡されます。
rundll32 を普通に実行すると、10 (SW_SHOWDEFAULT) が渡されます。

rundll32 MyDll.dll,MyFunc

次のように start /min を指定して実行すると、7 (SW_SHOWMINNOACTIVE) が渡されます。

start /min c:\windows\syswow64\rundll32 MyDll.dll,MyFunc

(※ 「start /min rundll32 MyDll.dll,MyFunc」とすると、64bit の rundll32.exe 経由で 32bit の rundll32.exe が実行される関係で /min の指定が無視されるようです)

次のように start /max を指定して実行すると、3 (SW_SHOWMAXIMIZED) が渡されます。

start /max c:\windows\syswow64\rundll32 MyDll.dll,MyFunc

おわりに

rundll32 が関数に渡す引数をまとめると、次のようになります。

  • 第 1 引数 : ウィンドウハンドル(rundll32 が内部で生成したウィンドウのハンドル)
  • 第 2 引数 : インスタンスハンドル(rundll32.exe モジュールがロードされたアドレス)
  • 第 3 引数 : オプションへのポインタ(rundll32 のオプション文字列へのポインタ)
  • 第 4 引数 : ウィンドウの表示状態(通常は 10)

次のコマンドを実行すると、

rundll32 MyDll.dll,MyFunc Hello

「MyFunc(“Hello”);」ではなく、「MyFunc(<ウィンドウハンドル>, <インスタンスハンドル>, “Hello”, <ウィンドウの表示状態>);」が実行されることになります。

次のコマンドを実行しても、

rundll32 kernel32.dll,Beep 1500 500

「Beep(1500, 500);」が実行されて 1500Hz の音が 500 ミリ秒間鳴ることはありません。「Beep(<ウィンドウハンドル>, <インスタンスハンドル>);」が実行され、不定の周波数の音が不定の時間鳴るだけです。