EternalWindows
セキュリティコンテキスト / トークンユーザー

降格したセキュリティコンテキストでプロセスを明示的に実行することは、重要な意味を持ちます。 確かにWindows Vistaから登場したUACにより、 多くのプロセスは既定で制限ユーザーとしてコードを実行していますが、 UACがオフになっている場合は、不必要に管理者としてコードを実行することになり、 好ましいことではありません。 このため、トークンの取り扱いや、降格の具体的な方法を知る必要があります。 プロセスに割り当てられたトークンは、OpenProcessTokenで取得することができます。

BOOL WINAPI OpenProcessToken(
  HANDLE ProcessHandle,
  DWORD DesiredAccess,
  PHANDLE TokenHandle
);

ProcessHandleは、トークンを取得したいプロセスのハンドルを指定します。 DesiredAccessは、トークンに要求するアクセス権を指定します。 TokenHandleは、トークンのハンドルを受け取る変数のアドレスを指定します。

トークンのハンドルを取得すれば、GetTokenInformationでトークンの情報を取得することができます。

BOOL WINAPI GetTokenInformation(
  HANDLE TokenHandle,
  TOKEN_INFORMATION_CLASS TokenInformationClass,
  LPVOID TokenInformation,
  DWORD TokenInformationLength,
  PDWORD ReturnLength
);

TokenHandleは、トークンのハンドルを指定します。 TokenInformationClassは、取得する情報の種類を表すTOKEN_INFORMATION_CLASS型の定数を指定します。 TokenInformationは、情報を受け取るバッファを指定します。 TokenInformationLengthは、TokenInformationに指定したサイズを取得します。 ReturnLengthは、バッファに必要なサイズを受け取る変数のアドレスを指定します。

TokenInformationClassに指定できる定数の一部を次に示します。

トークン情報 TOKEN_INFORMATION_CLASS 意味
トークンユーザー TokenUser システムによって認証されたユーザー。 これはプロセスやスレッドのセキュリティコンテキストを表す指標ともなり、アクセスチェックの成否に関わる。
トークングループ TokenGroups トークンユーザーがメンバとなるグループ。 このグループにはAdministratorsのようなビルトイングループやEveryoneのようなビルトインシステムグループも含まれる。 トークングループはアクセスチェックの成否に関わる。
特権リスト TokenPrivileges 特権はユーザーやグループに割り当てられ、それが有効であるかによって、 アクセスチェックや一部の関数の成否に影響する。
デフォルト所有者SID TokenOwner オブジェクトを作成したときに割り当てられるデフォルトのセキュリティ記述子の所有者SID。 この情報はSetTokenInformationで変更可能。
デフォルトDACL TokenDefaultDacl オブジェクトを作成したときに割り当てられるデフォルトのセキュリティ記述子のDACL。 このDACLはトークンユーザーとシステムアカウントに全てのアクセスを許可している。 この情報はSetTokenInformationで変更可能。
トークンソース TokenSource トークンの作成者を表す最大8文字の文字列。 通常、トークンを作成できるのはLSAだけであるが、 LsaLogonUserの第8引数で自ら指定することもできる。 この情報を取得するにはTOKEN_QUERY_SOURCEアクセス権が必要。
トークンタイプ TokenType そのトークンがプライマリトークンか偽装トークンかを識別する。 偽装トークンはそれを採用しているスレッドのセキュリティコンテキストしか表さないので、 このトークンを基に新しいプロセスを作成したい場合は、プライマリトークンに変換する必要がある。
偽装レベル TokenImpersonationLevel サーバーがクライアントのセキュリティコンテキストで偽装できるかどうかや、 リモートコンピュータ上での偽装ができるかを表す。
認証ID TokenStatistics TOKEN_STATISTICS.AuthenticationIdにログオンセッションを一意に識別する値が格納される。 この値はLsaLogonUserの第11引数で自ら指定することもできる。 TOKEN_STATISTICS.TokenIdは、SRMがトークンに割り当てるローカルで一意の識別子。 TOKEN_STATISTICS.ModifiedIdは、トークンの情報に変更が加えられるたびに更新される。
制限SIDのリスト TokenRestrictedSids 制限付きトークン内に含まれることがあり、 アクセスチェックを複数回行わせるために利用される。 これにより、オブジェクトへのアクセスをより厳密に制御できる。
昇格タイプ TokenElevationType トークンが昇格しているかどうかを取得する。
リンクトークン TokenLinkedToken 指定したトークンにリンクしているトークンを取得する。 UACが有効になっている必要がある。
仮想化の有無 TokenVirtualizationEnable UACにおける仮想化が有効になっているかどうかを取得する。 0でない値が返った場合は、仮想化が有効である。
整合性レベル TokenIntegrityLevel 整合性レベルを表すSIDを取得する。 トークンの整合性レベルがアクセス対象となるオブジェクトの整合性レベルより低い場合、 アクセスは制限されることになる。
UIアクセス TokenUIAccess UIPIの制限を受けるかどうかを取得する。 0でない値が返った場合は、UIPIの制限を受けない。 つまり、整合性レベルが低いプロセスが高いプロセスに対してUI操作を行える。
ログオンSID TokenLogonSid ウインドウステーションとデスクトップへのアクセスに利用されるSID。 ログオンSIDのテキスト形式はS-1-5-5-0-Xであり、 X(RID)は常にランダムに作成される。 ログオンSIDは、TokenGroupsで取得できるTOKEN_GROUPS構造体にも含まれており、 SE_GROUP_LOGON_IDフラグと論理積をとることにより判定することができる。

今回は、この中のトークンユーザーを取得してみたいと思います。 プロセスのトークンからこの情報を取得すれば、 プロセスが現在どのユーザーとしてコードを実行しているかが分かります。 TokenUserを指定する場合は、TOKEN_USER構造体を使用することになります。

typedef struct _TOKEN_USER {
  SID_AND_ATTRIBUTES User;
} TOKEN_USER,  *PTOKEN_USER;

Userは、SID_AND_ATTRIBUTES構造体が格納されます。 この構造体は次のように定義されています。

typedef struct _SID_AND_ATTRIBUTES {
  PSID  Sid;
  DWORD Attributes;
} SID_AND_ATTRIBUTES,  *PSID_AND_ATTRIBUTES;

Sidは、SID構造体のアドレスが格納されます。 TOKEN_USER構造体に含まれるSID_AND_ATTRIBUTES.Sidは、トークンユーザーを表します。 Attributesは、SIDの属性を表す定数が格納されます。 SIDがトークングループを表す場合は、このメンバが重要な意味を持ちます。

今回のプログラムは、GetTokenInformationでトークンユーザーを取得します。

#include <windows.h>

BOOL ConvertSidToName(PSID pSid, LPTSTR lpszName, DWORD dwSizeName);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR       szUserName[256];
	DWORD       dwLength;
	HANDLE      hToken;
	PTOKEN_USER pTokenUser;

	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
		MessageBox(NULL, TEXT("トークンのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	GetTokenInformation(hToken, TokenUser, NULL, 0, &dwLength);
	pTokenUser = (PTOKEN_USER)LocalAlloc(LPTR, dwLength);
	GetTokenInformation(hToken, TokenUser, pTokenUser, dwLength, &dwLength);

	ConvertSidToName(pTokenUser->User.Sid, szUserName, sizeof(szUserName) / sizeof(TCHAR));

	MessageBox(NULL, szUserName, TEXT("トークンユーザー"), MB_OK);

	LocalFree(pTokenUser);
	CloseHandle(hToken);

	return 0;
}

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

プログラムを実行すると現在ログオンしているユーザー名が表示されると思われます。 しかし、このプログラムは現在ログオンしているユーザー名を取得するよう設計されているわけではありません。 あくまで、プロセスに割り当てられたトークンのトークンユーザーを取得しています。 このため、割り当てられたトークンが別ユーザーのものであれば、 そのユーザー名が表示されます。 OpenProcessTokenの呼び出しは、次のようになっています。

if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
	MessageBox(NULL, TEXT("トークンのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
	return 0;
}

現在のプロセスのトークンを取得したいため、 第1引数は現在のプロセスを表すハンドルを指定します。 これは、GetCurrentProcessで取得することができます。 第2引数は、GetTokenInformationを呼び出すだけであれば、TOKEN_QUERYで問題ありません。 トークンの複製や変更を行う場合は、別のアクセス権を指定することになります。 関数が成功した場合は、第3引数にトークンのハンドルが返ります。 トークンユーザーを取得するコードは、次のようになっています。

GetTokenInformation(hToken, TokenUser, NULL, 0, &dwLength);
pTokenUser = (PTOKEN_USER)LocalAlloc(LPTR, dwLength);
GetTokenInformation(hToken, TokenUser, pTokenUser, dwLength, &dwLength);

1回目のGetTokenInformationの呼び出しでは、第3引数にNULLを指定しています。 SIDのサイズが可変であり、データを格納できるだけのサイズが事前に分からないためです。 よって、データのサイズを指定する第4引数も0とします。 第5引数で必要なサイズを取得してから、もう一度GetTokenInformationを呼び出します。

pTokenUser->User.Sidから、トークンユーザーを表すSIDを参照することができます。 後はこれをLookupAccountSidに指定すれば、SIDからユーザー名に変換することができます。 ConvertSidToNameという自作関数は、この変換処理をラッピングしています。 実のところ、トークンユーザーを取得するだけであれば、 GetTokenInformationよりもGetUserNameを呼び出した方が効率的といえます。 この関数のリファレンスには「現在のスレッドのユーザー名を取得する」と記述されていますが、 このユーザー名がトークンユーザーのことです。 スレッドにトークンが割り当てられていない場合は、プロセスのトークンが参照されます。


戻る