EternalWindows
GINA / SAS

これまで説明してきたWlxNegotiateやWlxInitializeは、 いわばWinlogonやGINA自身に情報を与えるための関数であり、 ユーザー認証に関わるような処理を行うことは基本的にありません。 WlxInitializeがTRUEを返すとWlxDisplaySASNoticeが呼ばれ、 この関数から認証に関わる具体的な処理が行われていくことになります。

VOID WlxDisplaySASNotice(
  PVOID pWlxContext
);

pWlxContextは、WlxInitializeで指定したコンテキストのアドレスです。 この関数に戻り値はありません。

WlxDisplaySASNoticeは、ユーザーにSASの入力を促すために存在します。 SAS(secure attention sequence)は、ログオンやログオフを安全に開始するために入力する情報であり、 デフォルトのSASはCtrl+Alt+Delの押下です。 この場合のデフォルトというのは、msgina.dllが採用しているSASのことであり、 msgina.dllはWlxDisplaySASNoticeでCtrl+Alt+Delの押下を促すダイアログを表示しています。

SASを実装するとは、GINAが独自に定義している情報の入力を検出し、 ディスパッチテーブルのWlxSasNotifyを呼ぶことに相当します。 これにより、WinlogonはSASが入力されたことを知ることができ、 SASの入力で動作を開始する関数を呼び出してくれることになります。

VOID WlxSasNotify(
  HANDLE hWlx,     
  DWORD dwSasType  
);

hWlxは、Winlogonのハンドルです。 dwSasTypeは、生じたSASタイプを指定します。 SASタイプとはSASに関連付ける数値のことで、 この値はSASの入力を必要とする関数の引数に指定されます。 0からWLX_SAS_TYPE_MAX_MSFT_VALUEまでの値はマイクロソフトによって定義されているため、 独自のSASのSASタイプは、WLX_SAS_TYPE_MAX_MSFT_VALUEより大きい値にしなければなりません。

多くのGINAの開発者は、独自のSASを実装することに疑問を感じていると思われます。 そもそも、ユーザーにSASの入力を促すのは、これから始まるログオンやログオフの開始を 他のプロセスから検出できないようにするためであって、 既にCtrl+Alt+Delという安全な入力メカニズムが存在するのであれば、 それに代わるものはないともいえるはずです。 Ctrl+Alt+Delのキーの組み合わせがデフォルトのSASと呼ばれるためか、 Winlogonはこの組み合わせをオリジナルのGINAからでも利用できるように、 ディスパッチテーブルにWlxUseCtrlAltDelという関数を用意しています。 この関数を呼び出せば、WinlogonがCtrl+Alt+Delの押下を検出してWlxSasNotifyを 呼び出してくれるため、独自のSASを実装する必要はなくなります。

VOID WlxUseCtrlAltDel(
  HANDLE hWlx  
);

hWlxは、Winlogonのハンドルです。 この関数に戻り値はありません。

実を言うと、WinlogonはオリジナルのGINAがmsgina.dllをロードしない設計であっても、 msgina.dllをロードすることになっているので、実際にCtrl+Alt+Delの押下を検出しているのは、 Winlogonではなくmsgina.dllである可能性が高いといえるでしょう。 当然ながらWlxUseCtrlAltDelは、SASの入力をユーザーを促す前に呼び出すべきですから、 前節のようにWlxInitializeで呼び出しておくべきといえます。 次に、WlxDisplaySASNoticeの実装例を示します。

VOID WINAPI WlxDisplaySASNotice(PVOID pWlxContext)
{
	LPGINACONTEXT lpgc = (LPGINACONTEXT)pWlxContext;

	lpgc->pDispatchTable->WlxDialogBoxParam(lpgc->hWlx, lpgc->hinstDll, MAKEINTRESOURCE(ID_CADDIALOG), NULL, CadProc, NULL);			
}

pWlxContextは、WlxInitializeで指定したコンテキストのアドレスが格納されているため、 LPGINACONTEXTでキャストして使うことができます。 WlxDialogBoxParamでダイアログを表示するのは、ユーザーにSASの入力を促すためです。 このダイアログは、ユーザーがSASを入力するまで表示し続け、 入力されたときにはそれを破棄しなければなりませんが、 この入力された瞬間というのはどうやって検出すればよいでしょうか。 WlxUseCtrlAltDelを呼び出している場合は、WinlogonがCtrl+Alt+Delの押下を検出しますから、 開発者のコードがそれを知ることは一見不可能のように思えても仕方ありません。 しかし、WinlogonはWlxSasNotifyが呼び出されると、WLX_WM_SASというメッセージを 現在表示しているダイアログに送ることになっているため、 ダイアログはこのメッセージを捕らえることでSASの入力を間接的に受信することができます。

switch (uMsg) {

case WLX_WM_SAS:
	if (wParam == WLX_SAS_TYPE_CTRL_ALT_DEL) { // Ctrl+Alt+Delが押下された
		// ここでTRUEを返すとダイアログは破棄されない
	}
	break;
}

WLX_WM_SASにはwParamにSASタイプが格納されていること以外に、 戻り値としてFALSEを返すと、ダイアログが破棄されるという特徴があります。 このとき、たとえば送られたSASタイプがWLX_SAS_TYPE_CTRL_ALT_DELならば、 WlxDialogBoxの戻り値はWLX_DLG_SASとなり、関数の呼び出し側は戻り値を通じて 表示したダイアログがSASメッセージで破棄されたのかどうかを確認することができます。 なお、WLX_WM_SASでTRUE返した場合、指定されたSASタイプは無視されます。

WlxDisplaySASNoticeで本当に行うべきことは、 WlxSasNotifyの呼び出しであることを忘れないでください。 たとえば、WlxUseCtrlAltDelを呼び出すGINAは、先の例のようにダイアログを表示して、 WinlogonにSASの検出とWlxSasNotifyの呼び出しを任せることになるでしょうが、 WinlogonのレジストリにはDisableCADというエントリがあります。 このエントリの値が1である場合、ユーザーはCtrl+Alt+Delの押下を無効にすることを 望んでいますから、このようなときにはダイアログを表示するにわけにはいきません。 ただ、これは何もしなくてよいというわけではなく、たとえCtrl+Alt+Delが押下されていなくても、 WinlogonにはSASが入力されたことを通知しなければ後の関数が呼ばれないことになりますから、 WlxSasNotifyはSASの入力をスルーするという意味で呼ばれることもあります。

VOID WINAPI WlxDisplaySASNotice(PVOID pWlxContext)
{
	BOOL          bDisableCAD = TRUE;
	LPGINACONTEXT lpgc = (LPGINACONTEXT)pWlxContext;

	GetWinlogonRegistryData(L"DisableCAD", &bDisableCAD, sizeof(BOOL));
	if (bDisableCAD)
		lpgc->pDispatchTable->WlxSasNotify(lpgc->hWlx, WLX_SAS_TYPE_CTRL_ALT_DEL);
	else
		lpgc->pDispatchTable->WlxDialogBoxParam(lpgc->hWlx, lpgc->hinstDll, MAKEINTRESOURCE(ID_CADDIALOG), NULL, CadProc, NULL);			
}

BOOL GetWinlogonRegistryData(LPWSTR lpszValue, LPVOID lpData, DWORD dwSize)
{
	HKEY hKey;
	LONG lResult;

	lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", 0, KEY_QUERY_VALUE, &hKey);
	if (lResult == ERROR_SUCCESS) {
		lResult = RegQueryValueEx(hKey, lpszValue, NULL, NULL, (LPBYTE)lpData, &dwSize);
		RegCloseKey(hKey);
		return TRUE;
	}
	else
		return FALSE;
}

GetWinlogonRegistryDataという自作関数は、第1引数で指定したエントリのデータを取得します。 DisableCADというエントリは場合によっては存在しないこともあり、 そのようなときはCtrl+Alt+Delの押下は無効になることになっているため、 bDisableCADは予めFALSEで初期化しておきます。 WLX_SAS_TYPE_CTRL_ALT_DELはSASタイプとして定義されている定数で、 WinlogonがWlxSasNotifyを呼び出すときにはこのSASタイプが使われています。 上記のWlxSasNotifyでは、SASを押下したことにしているだけですから、 WLX_SAS_TYPE_CTRL_ALT_DELを指定するのも少々不可解な気もしますが、 どのような値にせよそれは後の関数に指定されるだけであって、 それ以外に深い意味があるわけではありません。 また、最初からSASの入力をユーザーに促すつもりがない場合は、 単純にWlxSasNotifyだけを呼び出しても問題ありません。

オリジナルSASの実装を考える

SASの入力をCtrl+Alt+Delとしたくないような場合、 即ちWlxUseCtrlAltDelを呼び出さないような場合は、SASを独自に実装することになります。 このときにまず考えるべきことは、SASの入力を何とするのかを定め、 それを検出するコードを考察することです。 SASというのは、いわばその入力がGINA以外に検出されなければよいわけですから、 キーの押下に限定されるものでもありません。 SASを検出した後に行う動作については、次の3つの状態によって違いが生じると思われます。

状態 考えられる実装
WlxDisplaySASNotice SASの入力を促すダイアログや画像を表示している場合は、それを破棄する。 ダイアログの場合であれば、破棄方法としてWLX_WM_SASを送ることもできる。 破棄を終えたら、WlxSasNotifyに独自に定義したSASタイプを指定する。 この結果、WlxLoggedOutSASが呼ばれる。
defaultデスクトップ 独自に定義したSASタイプを指定してWlxSasNotifyを呼び出す。 この結果、WlxLoggedOnSASが呼ばれる。
WlxDisplayLockedNotice SASの入力を促すダイアログや画像を表示している場合は、それを破棄する。 WlxSasNotifyの呼び出しは不要。

最後に、WinlogonにおけるCtrl+Alt+Delの検出方法について説明します。 結論から述べてしまうと、WinlogonはCtrl+Alt+Delというキーの組み合わせをRegisterHotKeyにて 自身が作成したウインドウに関連付けているだけです。 これにより、そのウインドウにCtrl+Alt+Delの押下時にWM_HOTKEYが呼ばれるようになります。 通常のWindowsアプリケーションは、SetWindowsHookExによるグローバルフックで キーボードプロシージャをインストールすることが可能となっています。 このような場合、システムはキーの押下時にそのプロシージャを呼び出すことになっていますが、 Ctrl+Alt+Delの組み合わせに関しては、これは無効となっています。 よって、他のプロセスがCtrl+Alt+Delの押下を検出することはできません。



戻る