EternalWindows
GINA / ログオンユーザーリスト

今回は、GINAフックを利用した簡単なプログラムを作成します。 まず、次の実行結果を見てください。

ユーザー名を入力するエディットボックスでマウスの右ボタンを押した場合、 そのコンピュータにログオン可能なユーザー名がメニューで表示されます。 ここで選択したユーザー名は実際にエディットボックスに表示され、 後は適切なパスワードを入力するだけでログオンできることになります。

メニューの表示方法はともかく、ログオン可能なユーザーの取得というのは、 なかなか難しいところがあると思います。 NetUserEnumという関数は、コンピュータに存在する全てのユーザーを 列挙することができますが、そこにはログオン不可能なユーザーも含まれています。 この関数は取得するアカウントタイプのフィルタを指定することができるものの、 それらを指定してもログオン可能かどうかを判断することはできません。 実際には、ユーザー名を取得することさえできれば、後はそのユーザーに 対話ログオンを許可するユーザー権利が割り当てられているかどうかを調べることで ログオン可能かどうかの判断はできるのですが、この操作はLSA関数の複雑な呼び出しが 必要になることから、できれば避けて通りたい方法といえます。

そこで少し考え方を変えて、ログオン可能なユーザーの共通点を考えてみたいと思います。 実は、ログオン可能なユーザーにはそれに関するユーザー権利の割り当ての他に、 ユーザープロファイルを持つという特徴があります。 プロファイルはレジストリのHKEY_USERSからアクセス可能で、 一連のサブキーの名前はプロファイルを持つユーザーのSIDとなっています。 しかし、ユーザープロファイルのロードというのは基本的にログオンと共に 明示的に行うものであるため、HKEY_USERSを参照する限りでは、 ユーザープロファイルが現在ロードされているかどうかしかを確認することしかできません。 ここで本当に行いたいのは、ユーザープロファイルを持つかどうかの判定ですから、 HKEY_USERSではなく次のレジストリキーのサブキーを確認することになります。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList

このキーのサブキーには、ユーザープロファイルを持つユーザーのSIDが列挙されているため、 レジストリ関数でサブキー名(SID)を取得した後、それをユーザー名に変換すれば、 ユーザープロファイルを持つユーザー名を列挙できることになります。 このサブキーにはサービスアカウントのSIDも含まれていますが、 そのSIDがサービスアカウントのものかどうかは比較的容易に判断できます。

今回のプログラムは、前述の通りログオン可能なユーザーのリストをメニューで表示します。 フックするダイアログはログオンダイアログボックスとし、 ダイアログの識別子がWindowsのバージョンによって一定でないことから、 動作環境はWindowsXPのみとして作成されています。 なお、Guestアカウントは対話ログオンが許可されていないことがあるので、 そのようなLSAポリシーに関するログオンエラーが表示されるような場合は、 コントロールパネルでGuestアカウントをオンにするようにしてください。

#define UNICODE
#define _WIN32_WINNT 0x0501
#include <windows.h>
#include <winwlx.h>
#include <sddl.h>

#define IDD_WLXLOGGEDOUTSAS_DIALOG   1500
#define IDC_WLXLOGGEDOUTSAS_USERNAME 1502

typedef BOOL (WINAPI *LPFNWLXNEGOTIATE)(DWORD, PDWORD);
typedef BOOL (WINAPI *LPFNWLXINITIALIZE)(LPWSTR, HANDLE, PVOID, PVOID, PVOID *);
typedef VOID (WINAPI *LPFNWLXDISPLAYSASNOTICE)(PVOID);
typedef int  (WINAPI *LPFNWLXLOGGEDOUTSAS)(PVOID, DWORD, PLUID, PSID, PDWORD, PHANDLE, PWLX_MPR_NOTIFY_INFO, PVOID *);
typedef BOOL (WINAPI *LPFNWLXACTIVATEUSERSHELL)(PVOID, PWSTR, PWSTR, PVOID);
typedef int  (WINAPI *LPFNWLXLOGGEDONSAS)(PVOID, DWORD, PVOID);
typedef VOID (WINAPI *LPFNWLXDISPLAYLOCKEDNOTICE)(PVOID);
typedef int  (WINAPI *LPFNWLXWKSTALOCKEDSAS)(PVOID, DWORD);
typedef BOOL (WINAPI *LPFNWLXISLOCKOK)(PVOID);
typedef BOOL (WINAPI *LPFNWLXISLOGOFFOK)(PVOID);
typedef VOID (WINAPI *LPFNWLXLOGOFF)(PVOID);
typedef VOID (WINAPI *LPFNWLXSHUTDOWN)(PVOID, DWORD);

LPFNWLXNEGOTIATE           g_lpfnWlxNegotiate = NULL;
LPFNWLXINITIALIZE          g_lpfnWlxInitialize = NULL;
LPFNWLXDISPLAYSASNOTICE    g_lpfnWlxDisplaySASNotice = NULL;
LPFNWLXLOGGEDOUTSAS        g_lpfnWlxLoggedOutSAS = NULL;
LPFNWLXACTIVATEUSERSHELL   g_lpfnWlxActivateUserShell = NULL;
LPFNWLXLOGGEDONSAS         g_lpfnWlxLoggedOnSAS = NULL;
LPFNWLXDISPLAYLOCKEDNOTICE g_lpfnWlxDisplayLockedNotice = NULL;
LPFNWLXWKSTALOCKEDSAS      g_lpfnWlxWkstaLockedSAS = NULL;
LPFNWLXISLOCKOK            g_lpfnWlxIsLockOk = NULL;
LPFNWLXISLOGOFFOK          g_lpfnWlxIsLogoffOk = NULL;
LPFNWLXLOGOFF              g_lpfnWlxLogoff = NULL;
LPFNWLXSHUTDOWN            g_lpfnWlxShutdown = NULL;

HANDLE                    g_hWlx = NULL;
PWLX_DISPATCH_VERSION_1_0 g_pDispatchTable = NULL;
DLGPROC                   g_lpfnDefWlxLoggedOutSASDlgProc = NULL;
PWLX_DIALOG_BOX_PARAM     g_lpfnDefWlxDialogBoxParam = NULL;

int WINAPI MyWlxDialogBoxParam(HANDLE hWlx, HANDLE hInst, LPWSTR lpszTemplate, HWND hwndOwner, DLGPROC dlgprc, LPARAM dwInitParam);
BOOL WINAPI MyWlxLoggedOutSASDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
BOOL InitializePopupMenu(HMENU hmenuPopup);
void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId);
BOOL ConvertSidToName(PSID pSid, LPTSTR lpszName, DWORD dwSizeName);
BOOL InitializeStub(void);
BOOL ReplaceDefaultGina(void);

BOOL WINAPI WlxNegotiate(DWORD dwWinLogonVersion, PDWORD pdwDllVersion)
{
	ReplaceDefaultGina();
	
	if (!InitializeStub())
		return FALSE;

	return g_lpfnWlxNegotiate(dwWinLogonVersion, pdwDllVersion);
}

BOOL WINAPI WlxInitialize(LPWSTR lpWinsta, HANDLE hWlx, PVOID pvReserved, PVOID pWinlogonFunctions, PVOID *pWlxContext)
{
	g_hWlx = hWlx;
	g_pDispatchTable = (PWLX_DISPATCH_VERSION_1_0)pWinlogonFunctions;

	g_lpfnDefWlxDialogBoxParam = g_pDispatchTable->WlxDialogBoxParam;
	g_pDispatchTable->WlxDialogBoxParam = MyWlxDialogBoxParam;

	return g_lpfnWlxInitialize(lpWinsta, hWlx, pvReserved, pWinlogonFunctions, pWlxContext);
}

VOID WINAPI WlxDisplaySASNotice(PVOID pWlxContext)
{
	g_lpfnWlxDisplaySASNotice(pWlxContext);
}

int WINAPI WlxLoggedOutSAS(PVOID pWlxContext, DWORD dwSasType, PLUID pAuthenticationId, PSID pLogonSid, PDWORD pdwOptions, PHANDLE phToken, PWLX_MPR_NOTIFY_INFO pMprNotifyInfo, PVOID *pProfile)
{
	return g_lpfnWlxLoggedOutSAS(pWlxContext, dwSasType, pAuthenticationId, pLogonSid, pdwOptions, phToken, pMprNotifyInfo, pProfile);
}

BOOL WINAPI WlxActivateUserShell(PVOID pWlxContext, PWSTR pszDesktopName, PWSTR pszMprLogonScript, PVOID pEnvironment)
{
	return g_lpfnWlxActivateUserShell(pWlxContext, pszDesktopName, pszMprLogonScript, pEnvironment);
}

int WINAPI WlxLoggedOnSAS(PVOID pWlxContext, DWORD dwSasType, PVOID pReserved)
{
	return g_lpfnWlxLoggedOnSAS(pWlxContext, dwSasType, pReserved);
}

VOID WINAPI WlxDisplayLockedNotice(PVOID pWlxContext)
{
	g_lpfnWlxDisplayLockedNotice(pWlxContext);
}

int WINAPI WlxWkstaLockedSAS(PVOID pWlxContext, DWORD dwSasType)
{
	return g_lpfnWlxWkstaLockedSAS(pWlxContext, dwSasType);
}

BOOL WINAPI WlxIsLockOk(PVOID pWlxContext)
{
	return g_lpfnWlxIsLockOk(pWlxContext);
}

BOOL WINAPI WlxIsLogoffOk(PVOID pWlxContext)
{
	return g_lpfnWlxIsLogoffOk(pWlxContext);
}

VOID WINAPI WlxLogoff(PVOID pWlxContext)
{
	g_lpfnWlxLogoff(pWlxContext);
}

VOID WINAPI WlxShutdown(PVOID pWlxContext, DWORD dwShutdownType)
{	
	g_lpfnWlxShutdown(pWlxContext, dwShutdownType);
}

int WINAPI MyWlxDialogBoxParam(HANDLE hWlx, HANDLE hInst, LPWSTR lpszTemplate, HWND hwndOwner, DLGPROC dlgprc, LPARAM dwInitParam)
{
	int nDlgId = LOWORD(lpszTemplate);

	if (nDlgId == IDD_WLXLOGGEDOUTSAS_DIALOG) {
		g_lpfnDefWlxLoggedOutSASDlgProc = dlgprc;
		return g_lpfnDefWlxDialogBoxParam(hWlx, hInst, lpszTemplate, hwndOwner, MyWlxLoggedOutSASDlgProc, dwInitParam);
	}

	return g_lpfnDefWlxDialogBoxParam(hWlx, hInst, lpszTemplate, hwndOwner, dlgprc, dwInitParam);
}

BOOL WINAPI MyWlxLoggedOutSASDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HMENU hmenuPopup = NULL;

	switch (uMsg) {

	case WM_INITDIALOG:
		if (hmenuPopup == NULL) {
			hmenuPopup = CreatePopupMenu();
			InitializePopupMenu(hmenuPopup);
		}
		break;

	case WM_SETCURSOR: {
		int          nId;	
		HWND         hwndEditBox;
		POINT        pt;
		WCHAR        szAccountName[256];
		MENUITEMINFO mii;

		hwndEditBox = GetDlgItem(hwndDlg, IDC_WLXLOGGEDOUTSAS_USERNAME);
		
		if ((HWND)wParam == hwndEditBox && GetFocus() == hwndEditBox && HIWORD(lParam) == WM_RBUTTONUP) {
			GetCursorPos(&pt);
			nId = TrackPopupMenu(hmenuPopup, TPM_RETURNCMD | TPM_NONOTIFY, pt.x, pt.y, 0, hwndDlg, NULL);
			if (nId != 0) {	
				mii.cbSize     = sizeof(MENUITEMINFO);
				mii.fMask      = MIIM_ID | MIIM_TYPE;
				mii.fType      = MFT_STRING;
				mii.wID        = nId;
				mii.dwTypeData = szAccountName;
				mii.cch        = sizeof(szAccountName) / sizeof(WCHAR);
				
				GetMenuItemInfo(hmenuPopup, nId, FALSE, &mii);
				SetWindowText(hwndEditBox, szAccountName);
			}

			return TRUE;
		}

		break;
	}

	default:
		break;

	}

	return g_lpfnDefWlxLoggedOutSASDlgProc(hwndDlg, uMsg, wParam, lParam);
}

BOOL InitializePopupMenu(HMENU hmenuPopup)
{
	int      nItemId = 1;
	HKEY     hKey;
	PSID     pSid;
	LONG     lResult;
	DWORD    dwIndex = 0;
	DWORD    dwName;
	WCHAR    szName[512];
	WCHAR    szAccountName[256];
	FILETIME fileTime;

	lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList", 0, KEY_READ, &hKey);
	if (lResult != ERROR_SUCCESS)
		return 0;

	for (;;) {
		dwName = sizeof(szName);
		lResult = RegEnumKeyEx(hKey, dwIndex, szName, &dwName, NULL, NULL, NULL, &fileTime);
		if (lResult == ERROR_NO_MORE_ITEMS)
			break;
		
		ConvertStringSidToSid(szName, &pSid);
		if (!IsWellKnownSid(pSid, WinLocalSystemSid) && !IsWellKnownSid(pSid, WinLocalServiceSid) && !IsWellKnownSid(pSid, WinNetworkServiceSid)) {
			ConvertSidToName(pSid, szAccountName, sizeof(szAccountName) / sizeof(WCHAR));
			InitializeMenuItem(hmenuPopup, szAccountName, nItemId);
			nItemId++;
		}

		LocalFree(pSid);
	
		dwIndex++;
	}

	RegCloseKey(hKey);

	return TRUE;
}

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId)
{
	MENUITEMINFO mii;
	
	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_ID | MIIM_TYPE;
	mii.fType      = MFT_STRING;
	mii.wID        = nId;
	mii.dwTypeData = lpszItemName;
	
	InsertMenuItem(hmenu, nId, FALSE, &mii);
}

BOOL ConvertSidToName(PSID pSid, LPTSTR lpszName, DWORD dwSizeName)
{
	TCHAR        szDomainName[256];
	DWORD        dwSizeDomain = sizeof(szDomainName) / sizeof(TCHAR);
	SID_NAME_USE sidName;

	return LookupAccountSid(NULL, pSid, lpszName, &dwSizeName, szDomainName, &dwSizeDomain, &sidName);
}

BOOL InitializeStub(void)
{
	HINSTANCE hinstDll;

	hinstDll = LoadLibrary(L"msgina.dll");
	if (hinstDll == NULL)
		return FALSE;
	
	g_lpfnWlxNegotiate = (LPFNWLXNEGOTIATE)GetProcAddress(hinstDll, "WlxNegotiate");
	if (g_lpfnWlxNegotiate == NULL)
		return FALSE;

	g_lpfnWlxInitialize = (LPFNWLXINITIALIZE)GetProcAddress(hinstDll, "WlxInitialize");
	if (g_lpfnWlxInitialize == NULL)
		return FALSE;

	g_lpfnWlxDisplaySASNotice = (LPFNWLXDISPLAYSASNOTICE)GetProcAddress(hinstDll, "WlxDisplaySASNotice");
	if (g_lpfnWlxDisplaySASNotice == NULL)
		return FALSE;
	
	g_lpfnWlxLoggedOutSAS = (LPFNWLXLOGGEDOUTSAS)GetProcAddress(hinstDll, "WlxLoggedOutSAS");
	if (g_lpfnWlxLoggedOutSAS == NULL)
		return FALSE;
	
	g_lpfnWlxActivateUserShell = (LPFNWLXACTIVATEUSERSHELL)GetProcAddress(hinstDll, "WlxActivateUserShell");
	if (g_lpfnWlxActivateUserShell == NULL)
		return FALSE;
	
	g_lpfnWlxLoggedOnSAS = (LPFNWLXLOGGEDONSAS)GetProcAddress(hinstDll, "WlxLoggedOnSAS");
	if (g_lpfnWlxLoggedOnSAS == NULL)
		return FALSE;
	
	g_lpfnWlxDisplayLockedNotice = (LPFNWLXDISPLAYLOCKEDNOTICE)GetProcAddress(hinstDll, "WlxDisplayLockedNotice");
	if (g_lpfnWlxDisplayLockedNotice == NULL)
		return FALSE;
	
	g_lpfnWlxWkstaLockedSAS = (LPFNWLXWKSTALOCKEDSAS)GetProcAddress(hinstDll, "WlxWkstaLockedSAS");
	if (g_lpfnWlxWkstaLockedSAS == NULL)
		return FALSE;
	
	g_lpfnWlxIsLockOk = (LPFNWLXISLOCKOK)GetProcAddress(hinstDll, "WlxIsLockOk");
	if (g_lpfnWlxIsLockOk == NULL)
		return FALSE;
	
	g_lpfnWlxIsLogoffOk = (LPFNWLXISLOGOFFOK)GetProcAddress(hinstDll, "WlxIsLogoffOk");
	if (g_lpfnWlxIsLogoffOk == NULL)
		return FALSE;
	
	g_lpfnWlxLogoff = (LPFNWLXLOGOFF)GetProcAddress(hinstDll, "WlxLogoff");
	if (g_lpfnWlxLogoff == NULL)
		return FALSE;
	
	g_lpfnWlxShutdown = (LPFNWLXSHUTDOWN)GetProcAddress(hinstDll, "WlxShutdown");
	if (g_lpfnWlxShutdown == NULL)
		return FALSE;

	return TRUE;
}

BOOL ReplaceDefaultGina(void)
{
	HKEY hKey;
	LONG lResult;

	lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon", 0, KEY_SET_VALUE, &hKey);
	if (lResult == ERROR_SUCCESS) {
		RegDeleteValue(hKey, L"GinaDLL");
		RegCloseKey(hKey);
		return TRUE;
	}
	else
		return FALSE;
}

まず、ソースコード冒頭にあるいくつかの追加された箇所を確認します。 _WIN32_WINNTは、IsWellKnownSidを呼び出すために必要です。 この関数は、WindowsXPから追加されたため、0x0501として定義します。 sddl.hは、ConvertStringSidToSidを呼び出すのに必要です。 IDD_WLXLOGGEDOUTSAS_DIALOGとIDC_WLXLOGGEDOUTSAS_USERNAMEは、 それぞれダイアログとユーザー名を入力するエディットボックスの識別子を定義しています。 このエディットボックスの識別子が1502と分かったのは、勿論調べたからです。 たとえば、フックプロシージャのWM_INITDLGでEnumChildWindowsを呼び出せば、 コールバック関数から得た子ウインドウハンドルからGetDlgCtrlIDやGetClassName、 GetWindowTextなどを呼び出して、子ウインドウの情報を取得することができるはずです。

スタブはログオンダイアログボックスをWlxLoggedOutSASで表示しているため、 その名に因んでフックプロシージャの名前はMyWlxLoggedOutSASDlgProcとしています。 このプロシージャでは、WM_INITDIALOGとWM_SETCURSORの2つのメッセージを処理しています。

case WM_INITDIALOG:
	if (hmenuPopup == NULL) {
		hmenuPopup = CreatePopupMenu();
		InitPopupMenu(hmenuPopup);
	}
	break;

hmenuPopupは、ポップアップメニューのハンドルです。 ログオンダイアログボックスはユーザーがログオフした場合などでは、 複数回表示されることがあるため、値がNULLかどうかのチェックを行い、 毎回メニューを初期化しないようにする必要があります。 InitializePopupMenuの内部は、次のようになっています。

BOOL InitializePopupMenu(HMENU hmenuPopup)
{
	int      nItemId = 1;
	HKEY     hKey;
	PSID     pSid;
	LONG     lResult;
	DWORD    dwIndex = 0;
	DWORD    dwName;
	WCHAR    szName[512];
	WCHAR    szAccountName[256];
	FILETIME fileTime;

	lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList", 0, KEY_READ, &hKey);
	if (lResult != ERROR_SUCCESS)
		return 0;

	for (;;) {
		dwName = sizeof(szName);
		lResult = RegEnumKeyEx(hKey, dwIndex, szName, &dwName, NULL, NULL, NULL, &fileTime);
		if (lResult == ERROR_NO_MORE_ITEMS)
			break;
		
		ConvertStringSidToSid(szName, &pSid);
		if (!IsWellKnownSid(pSid, WinLocalSystemSid) && !IsWellKnownSid(pSid, WinLocalServiceSid) && !IsWellKnownSid(pSid, WinNetworkServiceSid)) {
			ConvertSidToName(pSid, szAccountName, sizeof(szAccountName) / sizeof(WCHAR));
			InitializeMenuItem(hmenuPopup, szAccountName, nItemId);
			nItemId++;
		}

		LocalFree(pSid);
	
		dwIndex++;
	}

	RegCloseKey(hKey);

	return TRUE;
}

まず、ProfileListキーをオープンし、RegEnumKeyExでサブキー名を順次取得していきます。 このサブキー名は文字列化したSIDになっていますから、 ConvertStringSidToSidを呼び出せば簡単にバイナリのSIDに変換できます。 IsWellKnownSidが定める既知のSIDには、WinLocalSystemSid(SYSTEM)や WinLocalServiceSid(LOCAL SERVICE)、WinNetworkServiceSid(NETWORK SERVICE)などがあり、 これらのサービスアカウントは通常ログオンには使用できないようになっていることから、 メニューの項目に名前を追加するわけにはいきません。 ConvertSidToNameは、SIDから関連するアカウント名を取得する自作関数で、 InitMenuItemはそれと識別子を受け取って項目を追加します。

最後に、WM_SETCURSORの処理を見てみます。

case WM_SETCURSOR: {
	int          nId;	
	HWND         hwndEditBox;
	POINT        pt;
	WCHAR        szAccountName[256];
	MENUITEMINFO mii;

	hwndEditBox = GetDlgItem(hwndDlg, IDC_WLXLOGGEDOUTSAS_USERNAME);
	
	if ((HWND)wParam == hwndEditBox && GetFocus() == hwndEditBox && HIWORD(lParam) == WM_RBUTTONUP) {
		GetCursorPos(&pt);
		nId = TrackPopupMenu(hmenuPopup, TPM_RETURNCMD | TPM_NONOTIFY, pt.x, pt.y, 0, hwndDlg, NULL);
		if (nId != 0) {	
			mii.cbSize     = sizeof(MENUITEMINFO);
			mii.fMask      = MIIM_ID | MIIM_TYPE;
			mii.fType      = MFT_STRING;
			mii.wID        = nId;
			mii.dwTypeData = szAccountName;
			mii.cch        = sizeof(szAccountName) / sizeof(WCHAR);
			
			GetMenuItemInfo(hmenuPopup, nId, FALSE, &mii);
			SetWindowText(hwndEditBox, szAccountName);
		}

		return TRUE;
	}

	break;
}

エディットボックス上でのマウスの右ボタンの押下を検出するのであれば、 ウインドウのサブクラス化などを用いるのでないかと思われるかもしれませんが、 親ウインドウは子ウインドウのマウスメッセージをWM_SETCURSORで取得できるようになっています。 パラメータの意味は条件式が示すとおりですが、エディットボックスがフォーカスを持っているか どうかの判定に関しては、特に必須の処理というわけでもありません。 TrackPopupMenuでは、第2引数にフラグを指定することによってWM_COMMANDの発行を 防いでいますが、これは効率性を重視してのことです。 WM_COMMANDは、ログオンダイアログボックス上の子ウインドウの押下などでも送られますから、 得られた識別子がメニュー項目のものかどうかの判定が非常に厄介になります。 TrackPopupMenuの戻り値が0である場合は、項目が選択されなかったことを意味するため、 メニュー項目の識別子には0を指定するようなことがあってはいけせん。 InitPopupMenuのnItemIdが1から始まっているのはそのためです。


戻る