EternalWindows
シェル拡張 / ShellExecute フック
サンプルはこちら

ShellExecute フックは、ShellExecuteまたはShellExecuteExの呼び出しを検出したい場合に使用します。 基本的にこれらの関数は何らかのファイルを開く場合に呼ばれるため、 そうした瞬間を検出したい場合は便利な存在になるでしょう。 ShellExecute(Ex)を呼び出すアプリケーションは様々存在すると思われますが、 たとえばexplorer.exeなら、タスクバーの「タスク マネージャ」を選択した場合に呼び出します。 次に示す図は、その呼び出しを検出した瞬間を表しています。

ShellExecute フックのオブジェクトは、IShellExecuteHookを実装する必要があります。

class CShellExecuteHook : public IShellExecuteHook
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP Execute(LPSHELLEXECUTEINFO pei);
	
	CShellExecuteHook();
	~CShellExecuteHook();

private:
	LONG m_cRef;
};

IShellExecuteHookのメソッドはExecuteのみです。 ShellExecute(Ex)が呼ばれた場合はこのメソッドが呼ばれることになり、 S_OKを返すことで関数を失敗、S_FALSEを返すことで関数の処理を続行させることができます。 引数の中身を変更しても、関数の呼び出し側には影響がないことに注意してください。

今回のExecuteの処理は、MessageBoxを呼び出すだけの単純なものです。

STDMETHODIMP CShellExecuteHook::Execute(LPSHELLEXECUTEINFO pei)
{
	MessageBox(NULL, pei->lpFile, TEXT("ShellExecuteHook"), MB_OK);

	return S_FALSE;
}

第1引数のSHELLEXECUTEINFO構造体には、ShellExecute(Ex)の引数に関連した値が格納されています。 たとえば、ShellExecuteの第3引数はpei->lpFileに関連し、 第4引数はpei->lpParametersに関連します。 lpFileには実行したいファイルパスが格納されていることがあるため、 このパスが特定のパスと一致するかを調べ、それが一致する場合はS_OKを返すようにするのもよいでしょう。 これにより、lpFileで識別されるファイルの実行を拒否することができます。

ShellExecute フックを登録するには、次に示すレジストリキーを作成することになります。

HKEY_LOCAL_MACHINE
  SOFTWARE
    Microsoft
      CurrentVersion
        explorer
          ShellExecuteHooks オブジェクトのCLSID = 独自のハンドラ名

ShellExecute フックのDLLは、ShellExecute(Ex)が呼ばれた段階でシェルにロードされ、 自動でアンロードされることはありません。 登録したDLLを置き換える場合は、登録を解除してからシェルを再起動する必要があります。

IShellExecuteHookの実用性

ShellExecute(Ex)は主としてファイルを開く目的で使用されることから、 プロセスの起動を検出するという目的ではIShellExecuteHookは使いにくいと言えます。 たとえば、ShellExecuteが次のように呼び出されたとします。

ShellExecute(NULL, TEXT("open"), TEXT("notepad"), TEXT("c:\\sample.txt"), NULL, SW_SHOWNORMAL);

この場合、IShellExecuteHook::Executeのpei->lpFileにnotepadが格納されることから、 メモ帳の起動を検出できることになります。 ただし、notepad.exeと指定されることもフルパスで指定されることも考えられるため、 どのような場合でも対応できるコードを記述する必要があります。 では、次のようにShellExecuteが呼ばれた場合はどうなるでしょうか。

ShellExecute(NULL, TEXT("open"), TEXT("c:\\sample.txt"), NULL, NULL, SW_SHOWNORMAL);

この場合、pei->lpFileにはプロセス名ではなくファイルパスが格納されることになり、 最終的にどのプロセスが起動されるかが一目で判断できません。 結論から述べると、ファイルの拡張子に関連付けられたプロセスが起動されることになるため、 AssocQueryStringで関連するプロセス名を取得できるのですが、実際にはこれでも不完全です。 なぜなら、CreateProcessなどによるプロセスの起動では、 IShellExecuteHook::Execute自体が呼ばれないからです。

実は、IShellExecuteHookを使用することは、Windows Vistaから推奨されなくなっています。 これはどういった経緯からか分かりませんが、今後も問題なく使い続けられるとは限らない可能性があります。 事実、次に示すレジストリキーのEnableShellExecuteHooksエントリが0である場合、 ShellExecute フックはシェルにロードされません。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer

上記キーにEnableShellExecuteHooksエントリが存在しない場合、 または存在するけれども値が1の場合は、ShellExecute フックはロードされるはずです。



戻る