EternalWindows
LSP / インストールの準備

今回は、実際にインストールを行うために必要なコードと、 Windows Vista以降で使用可能なWSCInstallProviderAndChainsについて説明します。 この関数の引数に注目すれば、インストールに必要な情報がより明確になります。

int WSPAPI WSCInstallProviderAndChains(
  const LPGUID lpProviderId,
  const LPWSTR lpszProviderDllPath,
  const LPWSTR lpszLspName,
  DWORD dwServiceFlags,
  const LPWSAPROTOCOL_INFO lpProtocolInfoList,
  DWORD dwNumberOfEntries,
  LPDWORD lpdwCatalogEntryId,
  LPINT lpErrno
);

lpProviderIdは、インストールしたいLSPのGUIDを指定します。 lpszProviderDllPathは、インストールしたいLSPのファイルパスを指定します。 lpszLspNameは、LSPに設定したい名前を指定します。 dwServiceFlagsは、原則としてXP1_IFS_HANDLESを指定します。 lpProtocolInfoListは、WSAPROTOCOL_INFO構造体の配列を指定します。 LSPは、これらのエントリの上にインストールされることになります。 dwNumberOfEntriesは、lpProtocolInfoListの要素数を指定します。 lpdwCatalogEntryIdは、作成されたダミーエントリのIDを受け取る変数のアドレスを指定します。 不要な場合は、NULLを指定して問題ありません。 lpErrnoは、エラー情報を受け取る変数のアドレスを指定します。 戻り値は、関数が成功した場合に0が返り、失敗した場合にSOCKET_ERRORが返ります。

次に示すInstallLspは、インストールを実行するための準備を行います。

BOOL InstallLsp(LPWSTR lpszFilePath, LPWSTR lpszProtocolName, LPDWORD lpdwEntryArray, int nEntryCount)
{
	int                 i, j;
	int                 nError;
	int                 nTotalEntryCount;
	BOOL                bResult;
	DWORD               dwSize;
	OSVERSIONINFO       versionInfo;
	GUID                guidProvider;
	HMODULE             hmod;
	LPFNGETLSPGUID      lpfnGetLspGuid;
	LPWSAPROTOCOL_INFOW lpEntryList;
	LPWSAPROTOCOL_INFOW lpBaseEntryList;
	
	hmod = LoadLibraryW(lpszFilePath);
	if (hmod == NULL)
		return FALSE;

	lpfnGetLspGuid = (LPFNGETLSPGUID)GetProcAddress(hmod, "GetLspGuid");
	if (lpfnGetLspGuid == NULL) {
		FreeLibrary(hmod);
		return FALSE;
	}
	
	lpfnGetLspGuid(&guidProvider);
	FreeLibrary(hmod);
	
	WSCEnumProtocols(NULL, NULL, &dwSize, &nError);
	lpEntryList = (LPWSAPROTOCOL_INFOW)HeapAlloc(GetProcessHeap(), 0, dwSize);
	nTotalEntryCount = WSCEnumProtocols(NULL, lpEntryList, &dwSize, &nError);

	for (i = 0; i < nTotalEntryCount; i++) {
		if (lstrcmpW(lpEntryList[i].szProtocol, lpszProtocolName) == 0) {
			MessageBox(NULL, TEXT("同一プロトコル名のエントリが既に存在します。"), NULL, MB_ICONWARNING);
			HeapFree(GetProcessHeap(), 0, lpEntryList);
			return FALSE;
		}
	}

	lpBaseEntryList = (LPWSAPROTOCOL_INFOW)HeapAlloc(GetProcessHeap(), 0, sizeof(WSAPROTOCOL_INFOW) * nEntryCount);

	for (i = 0; i < nEntryCount; i++) {
		for (j = 0; j < nTotalEntryCount; j++) {
			if (lpEntryList[j].dwCatalogEntryId == lpdwEntryArray[i]) {
				if (lpEntryList[j].ProtocolChain.ChainLen == 0) {
					MessageBox(NULL, TEXT("ダミーエントリが選択されています。"), NULL, MB_ICONWARNING);
					HeapFree(GetProcessHeap(), 0, lpBaseEntryList);
					HeapFree(GetProcessHeap(), 0, lpEntryList);
					return FALSE;
				}
				CopyMemory(&lpBaseEntryList[i], &lpEntryList[j], sizeof(WSAPROTOCOL_INFOW));
				break;
			}
		}
	}
	
	versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
	GetVersionEx(&versionInfo);

	if (versionInfo.dwMajorVersion >= 6)
		bResult = InstallLspVistaLater(guidProvider, lpszFilePath, lpszProtocolName, lpBaseEntryList, nEntryCount, &nError);
	else
		bResult = InstallLspLegacy(guidProvider, lpszFilePath, lpszProtocolName, lpBaseEntryList, nEntryCount, &nError);

	if (bResult)
		MessageBox(NULL, TEXT("LSPのインストールに成功しました。"), TEXT("OK"), MB_OK);
	else {
		MessageBox(NULL, TEXT("LSPのインストールに失敗しました。"), NULL, MB_ICONWARNING);
		if (nError == WSANO_RECOVERY)
			MessageBox(NULL, TEXT("管理者として実行してください。"), NULL, MB_ICONWARNING);
	}
	
	HeapFree(GetProcessHeap(), 0, lpBaseEntryList);
	HeapFree(GetProcessHeap(), 0, lpEntryList);

	return bResult;
}

InstallLspの第1引数はLSPのファイルパスであり、第2引数はプロトコル名です。 第3引数は、エントリIDの配列であり、 LSPはこのエントリIDで識別されるエントリの上にインストールすることになります。 第4引数は、第3引数の配列の要素数です。 各種処理を順に見ていきます。

hmod = LoadLibraryW(lpszFilePath);
if (hmod == NULL)
	return FALSE;

lpfnGetLspGuid = (LPFNGETLSPGUID)GetProcAddress(hmod, "GetLspGuid");
if (lpfnGetLspGuid == NULL) {
	FreeLibrary(hmod);
	return FALSE;
}

このコードは、LSPからGUIDを取得する部分です。 後の節で作成するLSPでは、内部で自分のGUIDを定義すると共にそのGUIDをGetLspGuidという関数で公開しているため、 この関数を呼び出すことでLSPのGUIDを取得しています。

WSCEnumProtocols(NULL, NULL, &dwSize, &nError);
lpEntryList = (LPWSAPROTOCOL_INFOW)HeapAlloc(GetProcessHeap(), 0, dwSize);
nTotalEntryCount = WSCEnumProtocols(NULL, lpEntryList, &dwSize, &nError);

for (i = 0; i < nTotalEntryCount; i++) {
	if (lstrcmpW(lpEntryList[i].szProtocol, lpszProtocolName) == 0) {
		MessageBox(NULL, TEXT("同一プロトコル名のエントリが既に存在します。"), NULL, MB_ICONWARNING);
		HeapFree(GetProcessHeap(), 0, lpEntryList);
		return FALSE;
	}
}

このコードは、現在システムに存在する全てのエントリを取得する部分です。 全てのエントリがあれば、その中からlpdwEntryArray[i]のエントリIDを持つエントリを発見できますから、この処理は必要となります。 また、新しくインストールするLSPのプロトコル名が既存のエントリによって使用されていないかも確認しておきます。

lpBaseEntryList = (LPWSAPROTOCOL_INFOW)HeapAlloc(GetProcessHeap(), 0, sizeof(WSAPROTOCOL_INFOW) * nEntryCount);

このコードは、インストール対象となるエントリのメモリを確保する部分です。 つまり、LSPは、lpBaseEntryListで識別されるエントリの上にインストールされることになります。

for (i = 0; i < nEntryCount; i++) {
	for (j = 0; j < nTotalEntryCount; j++) {
		if (lpEntryList[j].dwCatalogEntryId == lpdwEntryArray[i]) {
			if (lpEntryList[j].ProtocolChain.ChainLen == 0) {
				MessageBox(NULL, TEXT("ダミーエントリが選択されています。"), NULL, MB_ICONWARNING);
				HeapFree(GetProcessHeap(), 0, lpBaseEntryList);
				HeapFree(GetProcessHeap(), 0, lpEntryList);
				return FALSE;
			}
			CopyMemory(&lpBaseEntryList[i], &lpEntryList[j], sizeof(WSAPROTOCOL_INFOW));
			break;
		}
	}
}

このコードは、全てのエントリの中からlpdwEntryArray[i]と一致するエントリを抜き出し、 それをlpBaseEntryListにコピーする部分です。 ただし、lpdwEntryArray[i]で識別されるエントリがレイヤードプロトコルである場合は、 このエントリをインストール対象として許可しないようにしています。

versionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&versionInfo);

if (versionInfo.dwMajorVersion >= 6)
	bResult = InstallLspVistaLater(guidProvider, lpszFilePath, lpszProtocolName, lpBaseEntryList, nEntryCount, &nError);
else
	bResult = InstallLspLegacy(guidProvider, lpszFilePath, lpszProtocolName, lpBaseEntryList, nEntryCount, &nError);

このコードは、Windowsのバージョンによって呼び出すべき変化させる部分です。 GetVersionExで初期化したOSVERSIONINFO構造体のdwMajorVersionが6以上である場合、 それはWindows Vista以降であることを意味しているため、 InstallLspVistaLaterという関数を呼び出すことになります。 一方、6より低い場合は、従来のインストールを行うためにInstallLspLegacyという関数を呼び出します。 この関数については、次節で取り上げます。 バージョンの確認には、VerifyVersionInfoを呼び出すのが推奨されていますが、 LSPがWindows9xから利用できる点を考慮して、GetVersionExを呼び出しています。

if (bResult)
	MessageBox(NULL, TEXT("LSPのインストールに成功しました。"), TEXT("OK"), MB_OK);
else {
	MessageBox(NULL, TEXT("LSPのインストールに失敗しました。"), NULL, MB_ICONWARNING);
	if (nError == WSANO_RECOVERY)
		MessageBox(NULL, TEXT("管理者として実行してください。"), NULL, MB_ICONWARNING);
}

このコードは、インストールが成功したかを確認する部分です。 インストールに失敗した場合は、nErrorにエラー情報が格納されているためこれを確認することになります。 LSPのインストールは、システムの管理者でなければ実行することができず、 これが原因で失敗した場合はWSANO_RECOVERYが返されることになっています。

InstallLspVistaLaterの内部は、次のようになっています。

BOOL InstallLspVistaLater(GUID guidProvider, LPWSTR lpszProviderPath, LPWSTR lpszProtocolName, LPWSAPROTOCOL_INFOW lpBaseEntryList, int nEntryCount, LPINT lpnError)
{
	HMODULE                         hmod;
	LPFNWSCINSTALLPROVIDERANDCHAINS lpfnWSCInstallProviderAndChains;

	hmod = LoadLibrary(TEXT("ws2_32.dll"));
	if (hmod == NULL)
		return FALSE;
	
	lpfnWSCInstallProviderAndChains = (LPFNWSCINSTALLPROVIDERANDCHAINS)GetProcAddress(hmod, "WSCInstallProviderAndChains");
	if (lpfnWSCInstallProviderAndChains == NULL) {
		FreeLibrary(hmod);
		return FALSE;
	}

	if (lpfnWSCInstallProviderAndChains(&guidProvider, lpszProviderPath, lpszProtocolName, XP1_IFS_HANDLES, lpBaseEntryList, nEntryCount, NULL, lpnError) == SOCKET_ERROR) {
		FreeLibrary(hmod);
		return FALSE;
	}

	FreeLibrary(hmod);

	return TRUE;
}

WSCInstallProviderAndChainsは、Windows Vista以降で使用可能な関数であるため、 LoadLibraryとGetProcAddressを使用して動的にリンクすることになります。

LSPとカテゴリ

LSPをプロセスのアドレス空間にロードするというのは、ある意味でセキュリティ的な問題を抱えているといえます。 たとえば、ロードしたLSPに不具合があった場合にはプロセスが強制終了することも考えられ、 特にこのプロセスがシステムプロセスである場合は事態が深刻になります。 こうしたことから、アプリケーションは、特定のカテゴリが設定されているLSPのみをロードするようなことが可能になっています。 次に、定義されているカテゴリの種類を示します。

カテゴリ 意味
LSP_CRYPTO_COMPRESS データを暗号化または圧縮する。
LSP_FIREWALL ファイアウォールとして機能する。
LSP_LOCAL_CACHE データをキャッシュする。
LSP_INBOUND_MODIFY 外部から送られてきたデータを修正する。
LSP_INSPECTOR データを解析またはフィルタする。
LSP_OUTBOUND_MODIFY 外部へ送信するデータを修正する。
LSP_PROXY プロキシとして機能する。
LSP_REDIRECTOR リダイレクタとして機能する。
LSP_SYSTEM サービスやシステムプロセス内で機能する。

たとえば、アプリケーションにLSP_FIREWALLというカテゴリが設定されている場合、 このアプリケーションのプロセスにLSPをロードするためには、 LSPのカテゴリがLSP_FIREWALLでなければなりません。 つまり、カテゴリが何も設定されていなければ失敗し、 LSP_FIREWALL以外のカテゴリが設定されている場合も失敗します。 また、LSPのカテゴリがLSP_FIREWALL | LSP_PROXYとなっていても、 LSP_PROXYという、アプリケーションに設定されていないカテゴリを含んでいる場合は、 失敗することになります。 逆に、アプリケーションのカテゴリがLSP_FIREWALL | LSP_PROXYとなっている場合、 LSP_FIREWALLだけのLSPもロードされますし、 LSP_FIREWALL | LSP_PROXYのLSPもロードされます。 つまり、LSPをロードするための条件とは、 アプリケーションに設定されているカテゴリを1つでもLSPが含むことと、 アプリケーションに設定されていないカテゴリを一切LSPが含まないことになります。

LSPにカテゴリを設定するには、管理者として実行してWSCSetProviderInfoを呼び出すことになります。 次に、カテゴリをLSP_FIREWALLに設定する例を示します。

DWORD dwCategory = LSP_FIREWALL;
WSCSetProviderInfo(&guidProvider, ProviderInfoLspCategories, (PBYTE)&dwCategory, sizeof(DWORD), 0, &nError);

第1引数は、LSPのGUIDを指定します。 これは、ダミーエントリに設定されたGUIDでもあり、既にダミーエントリがインストールされている必要があります。 第2引数は、ProviderInfoLspCategoriesを指定します。 第3引数は、カテゴリを格納した変数のアドレスを指定します。 第4引数は第3引数のサイズを指定し、第5引数は0、第6引数はエラー情報を受け取る変数のアドレスを指定します。 上記コードが成功した場合、このLSPをロードできるアプリケーションは、 LSP_FIREWALLが設定されているアプリケーションと、 何もカテゴリが設定されていないアプリケーションになります。

アプリケーションにカテゴリを設定するには、管理者として実行してWSCSetApplicationCategoryを呼び出すことになります。 次に、カテゴリをLSP_FIREWALLに設定する例を示します。

WCHAR szFilePath[] = L"C:\\sample.exe";
WSCSetApplicationCategory(szFilePath, sizeof(szFilePath) / sizeof(WCHAR), NULL, 0, LSP_FIREWALL, NULL, &nError);

第1引数は、アプリケーションのパスを指定します。 この関数の呼び出しの時点で、ファイルが存在している必要はありません。 第2引数は、第1引数のサイズを指定します。 第3引数と第4引数は、NULLと0で問題ありません。 第5引数は、設定したいカテゴリを指定します。 第6引数は、NULLで問題ありません。 第7引数は、エラー情報を受け取る変数のアドレスを指定します。 上記コードが成功した場合、このアプリケーションがロードするLSPは、 LSP_FIREWALLが設定されているものに限られることになります。 ちなみに、カテゴリが設定されているアプリケーションは、次のレジストリキー以下に列挙されます。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WinSock2\Parameters\AppId_Catalog

このレジストリキーのサブキーのAppFullPathを確認すれば、アプリケーションのパスを特定することができます。 また、設定されているカテゴリは、PermittedLspCategoriesに格納されています。



戻る