EternalWindows
GINA / GINAの登録

GINAを作成したら、次はそのGINAをレジストリに登録しなければなりません。 Winlogonは、ログオンオプション等の一連のフラグを以下のレジストリキーで管理しています。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon

このレジストリキーにはGinaDLLというエントリを設定することができ、 そこに書かれているファイル名をWinlogonはGINAとしてロードします。 他にこのキーにはGINAを開発するにあたって重要なエントリを含んでいるため、 この機会にその一部を確認しておくことにします。

エントリ 説明
AllowMultipleTSSessions ユーザー切り替えを有効にするかどうか。 LogonTypeが1であり、さらにこの値が1である場合は、 ユーザー切り替えが可能になる。 ただし、ユーザー切り替えはmsgina.dllから提供されているものであるため、 オリジナルのGINAを開発するときにはこの値を確認する必要はない。
DisableCAD CAD(Ctrl + Alt + Del)を無効にするかどうか。 この値が1または、エントリが存在しない場合はCADが無効になる。 オリジナルのGINAを開発するときは、この値は必ず確認するべきである。
GinaDLL 登録するGINAのファイル名、もしくはフルパスを書き込む。 このエントリが存在しない場合はmsgina.dllがロードされるが、 エントリが存在していてファイル名が空白である場合は、 msgina.dllはロードされず、Winlogonは強制終了する。 このため、GINAの登録を削除するというのはファイル名ではなく、 エントリ自体を削除することを意味する。
LogonType ようこそ画面を表示するかどうか。 この値が1の場合はようこそ画面が表示され、 0の場合はログオンダイアログボックスを通じてログオンする。 ただし、ようこそ画面はmsgina.dllから提供されているものであるため、 オリジナルのGINAを開発するときにはこの値を確認する必要はない。
Shell シェルアプリケーションとして起動されるファイル名を書き込む。 デフォルトでは、Explorer.exeになっている。
Userinit ユーザープロファイルのロードや、ログオンスクリプトの実行、 Shellに書かれているアプリケーションを起動する実行ファイルを書き込む。 デフォルトでは、C:\WINDOWS\system32\userinit.exe,になっている。 GINAがエクスポートする関数の1つであるWlxActivateUserShellは、 このエントリに書かれているファイル名を起動をする義務があり、 カンマで区切られた全てのEXEを走査することになる。

Winlogonは、GinaDLLに書かれている文字列がファイル名だけである場合は、 そのファイルの検索対象としてsystem32フォルダを参照することになっています。 そのため、基本的にはファイルのフルパスではなく、ファイル名を書き込むだけで十分です。 ただし、この場合は作成したGINAをsystem32フォルダにコピーさせることになりますから、 レジストリにGINAを登録したものの、肝心のGINAをコピーし忘れるようなことがあっては、 GINAが見つからないがためにWinlogonは強制終了してしまいます。 手作業で登録やコピーを行っては、いつかこのようなミスを起こすことも考えられますから、 それらの処理を自動化するツールが存在すると大変便利です。 次に示すプログラムは、処理の自動化に加えGINAがエクスポートする関数もチェックするため、 よろしければ使ってみてください。

#include <windows.h>
#include <shlwapi.h>

#pragma comment (lib, "shlwapi.lib")

BOOL RegisterGina(LPTSTR lpszFileName);
BOOL CopyGina(LPTSTR lpszFileName);
BOOL WriteGina(LPTSTR lpszFileName);
BOOL IsValidGina(LPTSTR lpszFileName);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	BOOL            bFileExist = FALSE;
	TCHAR           szDirectory[MAX_PATH];
	HANDLE          hFindFile;
	WIN32_FIND_DATA findData;
	
	GetCurrentDirectory(sizeof(szDirectory) / sizeof(TCHAR), szDirectory);
	lstrcat(szDirectory, L"\\*");

	hFindFile = FindFirstFile(szDirectory, &findData);

	do {
		if (lstrcmp(findData.cFileName, TEXT("..")) != 0 && lstrcmp(findData.cFileName, TEXT(".")) != 0) {
			if (lstrcmp(PathFindExtension(findData.cFileName), TEXT(".dll")) == 0) {
				bFileExist = TRUE;
				RegisterGina(findData.cFileName);
				break;
			}
		}

	} while(FindNextFile(hFindFile, &findData));
	
	FindClose(hFindFile);

	if (!bFileExist)
		MessageBox(NULL, TEXT("カレントディレクトリにDLLが見つかりません。"), NULL, MB_ICONWARNING);

	return 0;
}

BOOL RegisterGina(LPTSTR lpszFileName)
{
	TCHAR szBuf[256];

	if (!IsValidGina(lpszFileName))
		return FALSE;
	
	if (!CopyGina(lpszFileName))
		return FALSE;

	if (!WriteGina(lpszFileName))
		return FALSE;
	
	wsprintf(szBuf, TEXT("%sを正常に登録しました。"), lpszFileName);
	MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);

	return TRUE;
}

BOOL CopyGina(LPTSTR lpszFileName)
{
	TCHAR szBuf[256];
	TCHAR szFileCopy[MAX_PATH];

	GetSystemDirectory(szFileCopy, sizeof(szFileCopy) / sizeof(TCHAR));
	PathAppend(szFileCopy, lpszFileName);

	if (!CopyFile(lpszFileName, szFileCopy, TRUE)) {
		wsprintf(szBuf, TEXT("%sは既に存在します。"), lpszFileName);
		MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
		return FALSE;
	}

	return TRUE;
}

BOOL WriteGina(LPTSTR lpszFileName)
{
	HKEY hKey;
	LONG lResult;

	lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon"), 0, KEY_SET_VALUE, &hKey);
	if (lResult == ERROR_SUCCESS) {
		lResult = RegSetValueEx(hKey, TEXT("GinaDLL"), 0, REG_SZ, (LPBYTE)lpszFileName, (lstrlen(lpszFileName) + 1) * sizeof(TCHAR));
		RegCloseKey(hKey);
		if (lResult == ERROR_SUCCESS)
			return TRUE;
	}
	
	MessageBox(NULL, TEXT("レジストリ書き込みに失敗しました。"), NULL, MB_ICONWARNING);
	
	return FALSE;
}

BOOL IsValidGina(LPTSTR lpszFileName)
{
	int       i;
	int       nFunctions = 12;
	char      szBuf[256];
	HINSTANCE hinstDll;
	LPSTR     lpszFunctions[] = {
		"WlxNegotiate", "WlxInitialize", "WlxDisplaySASNotice", "WlxLoggedOutSAS",
		"WlxActivateUserShell", "WlxLoggedOnSAS", "WlxDisplayLockedNotice", "WlxWkstaLockedSAS",
		"WlxIsLockOk", "WlxIsLogoffOk", "WlxLogoff", "WlxShutdown"
	};

	hinstDll = LoadLibrary(lpszFileName);
	if (hinstDll == NULL)
		return FALSE;

	for (i = 0; i < nFunctions; i++) {
		if (GetProcAddress(hinstDll, lpszFunctions[i]) == NULL) {
			wsprintfA(szBuf, "%sがエクスポートされていません。", lpszFunctions[i]);
			MessageBoxA(NULL, szBuf, NULL, MB_ICONWARNING);
			FreeLibrary(hinstDll);
			return FALSE;
		}
	}

	FreeLibrary(hinstDll);

	return TRUE;
}

このプログラムは、カレントディレクトリに存在するDLLをGINAとして登録します。 RegisterGinaが呼び出しているIsValidGinaは、GINAがver1.0の関数をエクスポート しているかを調べ、CopyGinaはsystem32フォルダにGINAをコピーし、 WriteGinaはGINAのファイル名をレジストリに書き込みます。 このWriteGinaは、既にGINAとして書かれているファイル名を無条件に上書きするため、 サードパーティー製のGINAが登録されているよう場合は注意してください。 これらの関数が全て成功したら、GINAが正常に登録されたことを知らせるメッセージボックスが 表示され、次回起動からは登録したGINAがロードされることになります。 次に、ディレクトリ構成の例を示します。

RegisterGinaというEXEが今回のプログラムとします。 このプログラムはディレクトリ内にあるDLLをGINAとしますから、 上図の場合、gina1.dllがGINAとして登録されることになります。 この1という数字には少し狙いがあるようにも思えますが、 これは次にGINAを登録するときの伏線です。 たとえば、gina.dllを登録すれば次回起動時にはgina.dllがロードされるわけですが、 その起動の回では新しい修正版のgina.dllを登録することはできません。 これは、既にWinlogonがgina.dllという名前のDLLをロードしているために、 同名のファイルで上書きしようにも失敗してしまうからです。 このため、GINAのファイル名は完成するまで一定でなく、 また、相当の数のGINAがsystem32フォルダにコピーされることから、 筆者はファイル名に数字を付けることでGINAの修正バージョンを管理しています。

今回のプログラムでGINAを登録したら、システムを再起動してみてください。 そして、その起動でシステムが通常通り動作していれば成功ということになります。 前節のGINAにメッセージボックスを表示するコードを加えていれば、 少なからずオリジナルのGINAがロードされていると実感できるところですが、 取り敢えず最初はGINAを正常に登録することに専念したほうがよいでしょう。 もし、システムを起動する度にエラーが発生するような場合、 つまり、ReplaceDefaultGinaが実行される前にエラーが発生するような場合は、 F8キーを押してシステムをセーフモードで起動してみるとよいかもしれません。 どうやら、WindowsXP Home(他の環境は未確認)をセーフモードで起動しているときには、 必ずmsgina.dllがロードされることになっているようなので、 このときにしかるべき対策を取れば次回起動は正常に動作することと思われます。


戻る