EternalWindows
GINA / GINAスタブ

GINAは、いわばWinlogonが認識する関数をエクスポートするDLLですから、 基本的には普通のDLLと作成の仕方が変わることはありません。 しかし、GINAはWinlogonにロードされる、つまり他のプロセスにロードされるDLLですから、 エクスポートした関数が呼ばれるタイミングを非常に特定できにくいという問題があります。 また、呼ばれるタイミングが分かっていなければ関数の引数や実装を正しく理解できませんから、 GINAの関数の詳細は多くの場合、スタブを通じて学習していくとになります。

スタブとは主に、何らかのデータにアクセスするための具体的なコードを持っており、 それを呼び出す側の要求を透過的に満足させるような実装のことを指しています。 あらゆるGINAにとってスタブと成り得るのは、msgina.dllです。 このDLLはGINAが登録されていないときにロードされるデフォルトのGINAで、 全てのエクスポート関数の適切な実装を持っています。 このため、オリジナルのGINAの関数から対応するmsgina.dllの関数を呼び出せば、 その関数の目的に準じた動作を実現できることが可能になります。

スタブを利用することにより得られる利点の1つとして、 関数が呼ばれるタイミングを知ることに専念できることが挙げられます。 たとえば、関数が呼ばれたときにメッセージボックスを表示すれば、 そのタイトルを関数名をすることで、関数が呼ばれたタイミングを特定できます。 もう1つの利点として、GINAを安全に作成できることが挙げられます。 実は、GINAをスタブに頼らずに作成するのは非常に困難なことで、 いきなり完全にオリジナルなGINAを作ろうとすると、まず間違いなく失敗します。 よって、まずはスタブを通して半オリジナルのGINAを作っておき、 関数の呼ばれるタイミングを知ることに専念するわけです。 さらに、この半オリジナルのGINAは実際の処理をmsgina.dllが行うわけですから、 極めてエラーが発生する確率が低いという利点も併せ持っています。

それでは、実際にコードを見ていきましょう。 まず、GINAはDLLですから関数をエクスポートしなければなりません。 GINAによる関数のエクスポートはdefファイルにて行うという決まりがあり、 また、ver1.0の関数は必ずエクスポートしなければならないことから、 次のようにコードになるかと思われます。

EXPORTS
		WlxNegotiate
		WlxInitialize
		WlxDisplaySASNotice
		WlxLoggedOutSAS
		WlxActivateUserShell
		WlxLoggedOnSAS
		WlxDisplayLockedNotice
		WlxWkstaLockedSAS
		WlxIsLockOk
		WlxIsLogoffOk
		WlxLogoff
		WlxShutdown

ここに書かれている関数が、全てver1.0の関数だということを意識してください。 決して、1つでも関数が欠けることがあってはいけません。 次に、スタブを利用したGINAのコードを見てみます。

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

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

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

コードの冒頭にある、大量のtypedefと変数の宣言は非常に奇怪に思えるかもしれませんが、 それは、このプログラムがスタブを利用しているからに他なりません。 スタブを利用するとは、msgina.dllをロードして各関数のアドレスを取得し、 その関数を実際の処理の代わりとして呼び出すことを意味しています。 次のコードを見てください。

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

WlxLoggedOnSASという関数はエクスポートすべき関数の1つですが、 その実装はlpfnWlxLoggedOnSASに引数を指定するだけのコードになっています。 このlpfnWlxLoggedOnSASというのは、msgina.dllのWlxLoggedOnSASのアドレスを 指す関数ポインタで、正にこの呼び出しで実際の処理がスタブに渡ります。 WlxLoggedOnSASに限らず、全ての関数は処理をスタブに委ねますから、 必然的に対応する関数ポインタも増えることになり、型の定義も必要になってきます。

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

typedef BOOL (WINAPI *LPFNWLXNEGOTIATE)(DWORD, PDWORD);
// 途中省略
LPFNWLXSHUTDOWN  g_lpfnWlxShutdown  = NULL;

現時点では全ての処理をスタブに任していることから、 エクスポートすべき関数の引数を理解しておく必要はありません。 ただ、型の定義や変数の宣言の順番は、それが示す関数が呼ばれる順番に なるよう書いていますから、この順番は早いうちに覚えておくとよいでしょう。 また、UNICODEの定義、及びwinwlx.hのインクルードは必ず行ってください。 これは、GINAがエクスポートする関数が文字列をUNICODEとして要求しており、 さらにwinwlx.hにて定義されている専用の型を利用するよう設計されているからです。

次に、スタブを初期化するInitializeStubという自作関数を見てみましょう。

BOOL InitializeStub(void)
{
	HINSTANCE hinstDll;

	hinstDll = LoadLibrary(L"msgina.dll");
	if (hinstDll == NULL)
		return FALSE;

	// 途中省略

	g_lpfnWlxShutdown = (LPFNWLXSHUTDOWN)GetProcAddress(hinstDll, "WlxShutdown");
	if (g_lpfnWlxShutdown == NULL)
		return FALSE;

	return TRUE;
}

スタブをロードして、ver1.0の関数のアドレスを全て取得します。 ロードの対象がmsgina.dllであることから、ロードに失敗したり、 関数のアドレスを取得できなかたっりすることはまず考えられませんが、 念には念を入れて戻り値を調べるようにしています。 なお、msgina.dllはWlxのプレフィックスで始まる関数以外にShellShutdownDialog という関数をエクスポートしていますが、 この関数の呼び出しは独自のGINAで検出することはできません。

今回のようにスタブを利用している場合は滅多に起こることではありませんが、 プログラムを開発している以上、エラーというのは避けては通れないものです。 GINAが起こすエラーというのはWinlogonのエラーに直結しますから、 Winlogonは強制終了することになり、システムはクラッシュしてしまいます。 そして、この強制終了とクラッシュは無限に繰り返されます。 何故なら、エラーを持っているGINAを修正しようにも、 WinlogonがそのGINAをロードするのに変わりはありませんから、 いつものように同じ箇所でエラーを発生させることになってしまうのです。 次の自作関数は、この問題の対策を施しています。

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

ReplaceDefaultGinaは、GINAの登録をレジストリから削除します。 このようにすれば、たとえ現在ロードしているGINAにエラーがあっても、 次にWinlogonがGINAをロードするときにはGINAが登録されていないためにmsgina.dllが ロードされますから、次回起動は保障されることになります。 ReplaceDefaultGinaはエラーが発生する確率のあるコードより 前に呼び出すべきですから、WlxNegotiateという関数で呼ばれています。 この関数は、GINAがエクスポートする関数の中で最も早く呼ばれるため、 InitializeStubの呼び出しもこの関数で行われることになっています。 なお、GINAがDLLであることからDllMainでDLL_PROCESS_ATTACHを捕まえて、 そこで上記の関数を呼び出すという方法も考えられると思います。


戻る