EternalWindows
GINA / ディスパッチテーブル

GINAがWinlogonの一部として動作する以上、 ときとしてWinlogonが提供する機能を利用したい場合があります。 たとえば、何らかのグラフィカルなセキュリティ操作を実行しようとした場合、 その操作はwinlogonデスクトップで行ったほうが安全ですから、 デスクトップの切り替えをWinlogonに依頼するような方法があれば大変便利です。 また、Winlogonとしても何らかの状態変更の通知をGINAから受信したいこともあるでしょう。 Winlogonは、このような希望を満たすためにディスパッチテーブルと呼ばれる関数群を用意し、 GINAはそれらの関数を呼び出すことでWinlogonの機能を利用したり通知したりします。

typedef struct _WLX_DISPATCH_VERSION_1_0 {
    PWLX_USE_CTRL_ALT_DEL           WlxUseCtrlAltDel;
    PWLX_SET_CONTEXT_POINTER        WlxSetContextPointer;
    PWLX_SAS_NOTIFY                 WlxSasNotify;
    PWLX_SET_TIMEOUT                WlxSetTimeout;
    PWLX_ASSIGN_SHELL_PROTECTION    WlxAssignShellProtection;
    PWLX_MESSAGE_BOX                WlxMessageBox;
    PWLX_DIALOG_BOX                 WlxDialogBox;
    PWLX_DIALOG_BOX_PARAM           WlxDialogBoxParam;
    PWLX_DIALOG_BOX_INDIRECT        WlxDialogBoxIndirect;
    PWLX_DIALOG_BOX_INDIRECT_PARAM  WlxDialogBoxIndirectParam;
    PWLX_SWITCH_DESKTOP_TO_USER     WlxSwitchDesktopToUser;
    PWLX_SWITCH_DESKTOP_TO_WINLOGON WlxSwitchDesktopToWinlogon;
    PWLX_CHANGE_PASSWORD_NOTIFY     WlxChangePasswordNotify;
} WLX_DISPATCH_VERSION_1_0, *PWLX_DISPATCH_VERSION_1_0;

ディスパッチテーブルは、GINAがエクスポートする関数と同様に1.4までのバージョンに 分かれており、1.0のテーブルはどのWindowsでも利用可能になっています。 また、関数のプレフィックスがWlxとなっているので、 エクスポート関数と誤解しないように注意しなければなりません。 関数の主な使い方は、スタブを使わないGINAを作成するときに説明しますが、 WlxMessageBoxに関してはスタブを利用するプログラムでも利用する場面があります。 この関数は、いわばuser32.dllがエクスポートするMessageBox関数のWinlogon版で、 GINAでメッセージボックスを表示する際は、この関数を使うことが推奨されています。

int WlxMessageBox(
  HANDLE hWlx,       
  HWND hwndOwner,    
  LPWSTR lpszText,    
  LPWSTR lpszTitle,  
  UINT fuStyle       
);

第2引数から第4引数までは、MessageBoxの引数ととほぼ同じ意味を持ちますが、 WlxMessageBoxでは、第1引数にWinlogonのハンドルを指定しなければなりません。 Winlogonのハンドルとは、ディスパッチテーブルの関数を呼び出すときに必要になるもので、 ディスパッチテーブルと共にWlxInitializeで取得することになります。

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

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

この関数の詳しい説明はここでは行いませんが、 現時点では第2引数にWinlogonのハンドルが格納されていることと、 第4引数がディスパッチテーブルのアドレスになっていることを覚えておいてください。 hwlxとpDispatchTableはグローバル変数で、この関数内で必ず初期化するようにします。

今回のプログラムは先に示したWlxMessageBoxを通じて、 GINAがエクスポートする関数の呼ばれるタイミングを確認します。 メッセージボックスのタイトルはそれを呼び出した関数名となっており、 OKボタンを押すことで順次処理が進んでいきます。

#define UNICODE
#include <windows.h>
#include <winwlx.h>

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;

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_pDispatchTable->WlxMessageBox(hWlx, NULL, NULL, L"WlxInitialize", MB_OK);

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

VOID WINAPI WlxDisplaySASNotice(PVOID pWlxContext)
{
	g_pDispatchTable->WlxMessageBox(g_hWlx, NULL, NULL, L"WlxDisplaySASNotice", MB_OK);
	
	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)
{
	g_pDispatchTable->WlxMessageBox(g_hWlx, NULL, NULL, L"WlxLoggedOutSAS", MB_OK);
	
	return g_lpfnWlxLoggedOutSAS(pWlxContext, dwSasType, pAuthenticationId, pLogonSid, pdwOptions, phToken, pMprNotifyInfo, pProfile);
}

BOOL WINAPI WlxActivateUserShell(PVOID pWlxContext, PWSTR pszDesktopName, PWSTR pszMprLogonScript, PVOID pEnvironment)
{
	g_pDispatchTable->WlxMessageBox(g_hWlx, NULL, NULL, L"WlxActivateUserShell", MB_OK);
	
	return g_lpfnWlxActivateUserShell(pWlxContext, pszDesktopName, pszMprLogonScript, pEnvironment);
}

int WINAPI WlxLoggedOnSAS(PVOID pWlxContext, DWORD dwSasType, PVOID pReserved)
{
	g_pDispatchTable->WlxMessageBox(g_hWlx, NULL, NULL, L"WlxLoggedOnSAS", MB_OK);
	
	return g_lpfnWlxLoggedOnSAS(pWlxContext, dwSasType, pReserved);
}

VOID WINAPI WlxDisplayLockedNotice(PVOID pWlxContext)
{
	g_pDispatchTable->WlxMessageBox(g_hWlx, NULL, NULL, L"WlxDisplayLockedNotice", MB_OK);
	
	g_lpfnWlxDisplayLockedNotice(pWlxContext);
}

int WINAPI WlxWkstaLockedSAS(PVOID pWlxContext, DWORD dwSasType)
{
	g_pDispatchTable->WlxMessageBox(g_hWlx, NULL, NULL, L"WlxWkstaLockedSAS", MB_OK);
	
	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_pDispatchTable->WlxMessageBox(g_hWlx, NULL, NULL, L"WlxLogoff", MB_OK);
	
	g_lpfnWlxLogoff(pWlxContext);
}

VOID WINAPI WlxShutdown(PVOID pWlxContext, DWORD dwShutdownType)
{
	g_pDispatchTable->WlxMessageBox(g_hWlx, NULL, NULL, L"WlxShutdown", MB_OK);
	
	g_lpfnWlxShutdown(pWlxContext, dwShutdownType);
}

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;
}

基本的に追加された処理は、WlxMessageBoxの呼び出しだけであるため、 それほど難しい箇所はないと思われます。 ログオンしている状態でSASを押下するとWlxLoggedOnSASが呼ばれ、 「コンピュータのロック」を選択すればWlxDisplayLockedNotice及び、 WlxWkstaLockedSASの呼び出しを確認することができます。 WlxNegotiateはWlxInitializeの前に呼ばれるため、 この関数でWlxMessageBoxを呼ぶことはできませんが、 代わりとしてMessageBoxを呼び出しても問題ないようです。 WlxIsLockOkとWlxIsLogoffOkではWlxMessageBoxを呼び出していませんが、 これに関しては後の節で詳しく説明します。

基本的にスタブを利用する限りでは、関数の呼ばれるタイミングを知るぐらいしかできませんが、 スタブに処理させているという点に注目して、スタブの動作を追跡するという使い方もあります。 GINAがエクスポートする関数は、単純に内部で適切な処理をして値を返すだけでなく、 内部で処理したデータを引数に書き込む義務が生じることがあるため、 その引数からスタブの処理結果を確認することができます。 たとえば、WlxLoggedOutSASのコードを次のコードに書き換えてみてください。

int WINAPI WlxLoggedOutSAS(PVOID pWlxContext, DWORD dwSasType, PLUID pAuthenticationId, PSID pLogonSid, PDWORD pdwOptions, PHANDLE phToken, PWLX_MPR_NOTIFY_INFO pMprNotifyInfo, PVOID *pProfile)
{
	int   nResult;
	WCHAR szBuf[256];

	nResult = g_lpfnWlxLoggedOutSAS(pWlxContext, dwSasType, pAuthenticationId, pLogonSid, pdwOptions, phToken, pMprNotifyInfo, pProfile);
	if (nResult == WLX_SAS_ACTION_LOGON) {
		wsprintf(szBuf, L"UserName %s Password %s", pMprNotifyInfo->pszUserName, pMprNotifyInfo->pszPassword);
		g_pDispatchTable->WlxMessageBox(g_hWlx, NULL, szBuf, L"認証情報", MB_OK);
	}

	return nResult;
}

WlxLoggedOutSASのpMprNotifyInfoには、関数側がログオンさせたユーザーの名前やパスワードを 格納することになっているため、当然ながらスタブはこの構造体を初期化することになります。 勿論、この初期化はlpfnWlxLoggedOutSASから制御が返った時点で終了しているわけですから、 構造体のメンバを参照すれば、ユーザー名やパスワードを取得することができてしまいます。 WLX_SAS_ACTION_LOGONは、ログオンが成功した場合にWlxLoggedOutSASが返す値であるため、 ここで得られるパスワードというのは正真正銘、本物のパスワードです。 したがって、ここでWlxMessageBoxを呼び出さずにユーザー名とパスワードをファイルに 書き込むような処理を行えば、簡単なパスワード補足プログラムが完成することになります。 一説では、このようなGINAのことをフェイクGINAと呼ぶようですが、 それを使う是非も含めて、これ以上は言及しないことにします。 本章が掲げる目的はあくまでGINAを完全に自作するフルGINAの開発ですから、 やはり、そこを重点的に説明していきたいと思います。


戻る