EternalWindows
GINA / シェル起動

WlxLoggedOutSASの各引数を適切に初期化してWLX_SAS_ACTION_LOGONを返したら、 次に呼ばれる関数はWlxActivateUserShellです。 この関数では、ログオンしたユーザーとして動作する最初のプロセスを起動します。

BOOL WlxActivateUserShell(
  PVOID pWlxContext,
  PWSTR pszDesktopName,
  PWSTR pszMprLogonScript,
  PVOID pEnvironment
);

pWlxContextは、コンテキストのアドレスです。 pszDesktopNameは、これから作成するプロセスを動作させるデスクトップ名です。 この名前はウインドウステーション名が付加された形となっており、 Winlogonは既にこのデスクトップを作成しています。 pszMprLogonScriptは、ネットワークプロバイダから返されたログオンスクリプトです。 pEnvironmentは、ログオンしたユーザーの環境変数へのアドレスです。 この引数は参照する必要がなくなったら、VirtualFreeで開放しなければなりません。 戻り値は、プロセスの作成に成功した場合はTRUE、失敗した場合はFALSEを返します。

WinlogonはWlxActivateUserShellがTRUEを返すと、 デスクトップをwinlogonデスクトップからdefaultデスクトップに切り替えます。 この時点では既にWlxActivateUserShellで作成したプロセスは起動されていますから、 恐らくデスクトップにはシェル(デフォルトでは、explorer.exe)が表示されていることでしょう。 シェルを起動しなければ、ユーザーはファイルやフォルダを操作できませんから、 WlxActivateUserShellでは必ずシェルを起動しなければなりません。 次に、WlxActivateUserShellのコード例を示します。

BOOL WINAPI WlxActivateUserShell(PVOID pWlxContext, PWSTR pszDesktopName, PWSTR pszMprLogonScript, PVOID pEnvironment)
{
	BOOL                bResult;
	STARTUPINFO         si;
	PROCESS_INFORMATION pi;
	LPGINACONTEXT       lpgc = (LPGINACONTEXT)pWlxContext;
	
	ZeroMemory(&si, sizeof(STARTUPINFO));
	si.cb          = sizeof(STARTUPINFO);
	si.lpTitle     = L"userinit";
	si.lpDesktop   = pszDesktopName;
	si.wShowWindow = SW_SHOW;

	bResult = CreateProcessAsUser(lpgc->hToken, L"C:\\WINDOWS\\system32\userinit.exe", NULL, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, pEnvironment, NULL, &si, &pi);
	if (bResult) {
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
	}
	
	VirtualFree(pEnvironment, 0, MEM_RELEASE);

	return bResult;
}

最初にこのコードを見たときには、恐らく思うところが2つほどあるでしょう。 CreateProcessAsUserは確かにプロセスを起動する関数ですが、 これはCreateProcessの呼び出しでも問題ないのではないでしょうか。 また、WlxActivateUserShellではシェルを起動しなければならないと述べたに関わらず、 起動対象のEXEがuserinit.exeになっているのは何故なのでしょうか。 ここには、C:\\WINDOWS\\explorer.exeを指定するのではないのでしょうか。 実のところ、これらの疑問を実際にコード化してもエクスプローラは正しく動作しますが、 相当たる潜在的問題を抱えてしまっているという事実があるため、 まずはその問題について深く考えていかなければなりません。

そもそも、GINAはWinlogonというプロセスにロードされているDLLであるため、 そのコードを実行するにあたって参照されるセキュリティコンテキストは、 Winlogonのセキュリティコンテキストとなります。 このセキュリティコンテキストはローカルシステムカウント(SYSYEM)となっているため、 GINAのコードもローカルシステムカウントとして実行されることになります。 この事実は、GINAのような高度なセキュリティ操作や特権を必要とするコードにとっては 重要なことですが、大半のプロセスはそのような操作は行いませんから、 ローカルシステムカウントでコードを実行する必要はどこにもありません。 しかし、CreateProcessでプロセスを作成すると、そのプロセスに割り当てられるトークンは、 呼び出し側のセキュリティコンテキストを表すトークンのコピーとなりますから、 たとえばエクスプローラを起動したならば、エクスプローラはローカルシステムカウントとして 実行されることになります。

多くのプロセスがエクスプローラから起動されるであろうことから、 それらのプロセスもまた、ローカルシステムカウントとして実行されることになります。 これにより、個々のプロセスが呼び出せる関数も必然的に増えることになり、 悪意のあるプロセスのコードの実行を助長する原因になりかねません。 したがって、CreateProcessAsUserを呼び出してログオンしたユーザーのトークンを指定し、 これから起動されていくプロセスの操作は、ログオンしたユーザーの セキュリティコンテキストの範疇に収まるようにするのです。 また、CreateProcessAsUserの第8引数ではpEnvironmentを指定していますが、 ここをNULLにするとローカルシステムアカウントの環境変数を引き継いでしまうので、 必ずpEnvironmentを指定しなければなりません。 pEnvironmentはUNICODE文字列として構成されているため、 第7引数はCREATE_UNICODE_ENVIRONMENTを指定します。

続いて、CreateProcessAsUserでexplorer.exeではなくuserinit.exeを起動している理由ですが、 そもそもシェルというのは、無条件にexplorer.exeのことを指すわけではありません。 Winlogonは、自身のレジストリに存在するShellというエントリのデータをシェルと 定義しているため、本来ならばここにあるファイルを起動しなければならないのです。 userinit.exeはそのファイルを起動するに加えて、ログオンしたユーザーのスクリプトの実行や、 グループポリシーの適応、及びフォントやスクリーン色のプロファイルの設定を確立するため、 userinit.exeを起動すれば安全かつ確実にシェルが起動されることになります。

userinit.exeは、ユーザーの環境を初期化するうえで非常に重要な役割を果たしていますが、 実はWinlogonのレジストリにはUserinitという専用のエントリが用意されています。 このエントリにはuserinit.exeがデフォルトで設定されていますが、 多くの場合、userinit.exeに取って代わるようなEXEファイルを指定することはないでしょう。 そのため、WlxActivateUserShellでUserinitエントリを参照せずに、 無条件にuserinit.exeを起動するようにしても基本的に問題はないのですが、 Userinitエントリには複数のEXEの起動に対応するという面白い特徴を持っています。

C:\WINDOWS\system32\userinit.exe,

この文字列はUserinitエントリのデータですが、最後にカンマが付いている点に注目してください。 これは、EXEのファイル名の終端を示すもので、各EXEはこのカンマで区切られることになります。

C:\WINDOWS\system32\userinit.exe,C:\WINDOWS\system32\calc.exe,

この例の場合、userinit.exeのcalc.exeの両方を起動するべきことを意味しています。 Winlogonのカレントディレクトリはsystem32フォルダになっているため、 同フォルダに存在するexeを起動する場合は、単純にファイル名だけを指定しても問題ありません。 次に、CreateProcessAsUserとUserinitエントリを考慮したWlxActivateUserShellのコードを示します。

BOOL WINAPI WlxActivateUserShell(PVOID pWlxContext, PWSTR pszDesktopName, PWSTR pszMprLogonScript, PVOID pEnvironment)
{
	int                 nLength;
	int                 nProcessCount = 0;
	BOOL                bResult;
	WCHAR               szExe[256];
	WCHAR               szUserinit[1024];
	LPWSTR              lpszBegin, lpszEnd;
	STARTUPINFO         si;
	PROCESS_INFORMATION pi;
	LPGINACONTEXT       lpgc = (LPGINACONTEXT)pWlxContext;
	
	GetWinlogonRegistryData(L"Userinit", szUserinit, sizeof(szUserinit));
	lpszEnd = szUserinit;

	do {
		for (lpszBegin = lpszEnd, nLength = 0; *lpszEnd != ','; lpszEnd++)
			nLength += 1;
		lstrcpyn(szExe, lpszBegin, nLength + 1);
		
		ZeroMemory(&si, sizeof(STARTUPINFO));
		si.cb          = sizeof(STARTUPINFO);
		si.lpTitle     = szExe;
		si.lpDesktop   = pszDesktopName;
		si.wShowWindow = SW_SHOW;

		bResult = CreateProcessAsUser(lpgc->hToken, szExe, NULL, NULL, NULL, FALSE, CREATE_UNICODE_ENVIRONMENT, pEnvironment, NULL, &si, &pi);
		if (bResult) {
			CloseHandle(pi.hThread);
			CloseHandle(pi.hProcess);
			nProcessCount++;
		}

		lpszEnd++;

	} while (*lpszEnd != '\0');

	VirtualFree(pEnvironment, 0, MEM_RELEASE);

	return nProcessCount > 0;
}

lpszEndをカンマが見つかるまで進めて行き、それに要した文字数をnLengthに加算していきます。 カンマが見つかった場合、走査対象となるEXEの先頭を指すlpBeginからnLength + 1分だけ、 szExeに文字列をコピーします(+1するのは、'\0'文字を含めるためです)。 nProcessCountは起動に成功したプロセスの数をカウントし、 1つでもプロセスを起動できたならば、WlxActivateUserShellはTRUEを返すことになります。

Volatile Environmentキーについて

ここでは、GINAにおける環境変数の設定について説明します。 環境変数には一般に、システム環境変数とよばれる種類とユーザー環境変数と 呼ばれる種類があり、これらはどちらにもレジストリに管理されていますが、 実はもう1つ、揮発性環境変数という種類があります。 この変数も同じようにレジストリにて管理されることになっているのですが、 ログオン時によって明示的に作成され、ログオフ時には削除されるという点において、 他の環境変数とは一線を画しています。 この環境変数は次のレジストリキーにて管理されることになっています。

HKEY_CURRENT_USER\Volatile Environment

Volatile Environmentキーに設定できるエントリには、次のようなものがあります。 説明の内容については、推測の域を出ません。

エントリ 説明
APPDATA ユーザープロファイルのアプリケーションデータ。
CLIENTNAME ターミナルサービスに接続してきたクライアント名。
HOMEDRIVE ユーザープロファイルが格納されているドライブ名。
HOMEPATH HOMEDRIVEと連結することによって、ユーザープロファイルのパスになる。
HOMESHARE ---
LOGONSERVER ログオンしたドメインのドメインコントローラ。
SESSIONNAME ターミナルサービスにおけるこのユーザーのセッション名。

恐らく、これらの情報がログオン時に明示的に作成される理由は、 ログオン時に常にこれらの情報が一定になるとは限らないからだと思われます。 そして、このログオンしたときのタイミングを知ることができるのはWinlogonのGINAですから、 Volatile Environmentキーへの書き込みはGINAが明示的に行わなければならないのです。 Volatile Environmentキーを作成しなかった場合、 GetEnvironmentStringsで得られる文字列には上記のエントリの環境変数は含まれず、 GetEnvironmentVariableで上記の環境変数からデータを求めるようなこともできなくなります。 また、一部のアプリケーションは、ユーザーのログオン時間を算出できないこともあるでしょう。 これはどういうことかいうと、Volatile Environmentキーがログオン時に作成されるという 点に注目して、RegQueryInfoKeyで最終書き込み時間を表すFILETIME構造体を取得し、 そこで得た時間をログオン時間と解釈してしまうという狙いです。



戻る