EternalWindows
LSA / 特権の追加

プロセスのスレッドが特権を必要とする関数を呼び出すには、2つの条件を満たしておく必要があります。 1つはアカウントに特権を割り当てておくことであり、 もう1つはその特権をスレッドのトークン内で有効にしておくことです。 アカウントに特権を割り当てておけば、ログオン時に作成されるトークンに特権が割り当てられますから、 スレッドは特権を有効にすることができます。 アカウントに特権またはユーザー権利を割り当てるには、LsaAddAccountRightsを呼び出します。

NTSTATUS LsaAddAccountRights(
  LSA_HANDLE PolicyHandle,
  PSID AccountSid,
  PLSA_UNICODE_STRING UserRights,
  ULONG CountOfRights
);

PolicyHandleは、ポリシーオブジェクトのハンドルを指定します。 このハンドルにはPOLICY_LOOKUP_NAMESアクセス権が割り当てられている必要があります。 AccountSidは、特権またはユーザー権利を割り当てたいアカウントのSIDを指定します。 UserRightsは、割り当てたい特権及びユーザー権利を格納したLSA_UNICODE_STRING構造体のアドレスを指定します。 CountOfRightsは、割り当てたい特権及びユーザー権利の数を指定します。

LsaAddAccountRightsを呼び出せば、アカウントに特権を割り当てられるわけですが、 その時点で直ちに、このアカウントで動作するスレッドが特権を必要とする関数を呼び出せるわけではありません。 トークンに特権が割り当てられるのはログオン時ですから、 現在のトークンの特権リストに変更が生じるようなことはありません。 よって、特権を必要とする関数を呼び出すには、一度ログオフする必要があります。

アカウントに割り当てられた特権またはユーザー権利を削除するには、 LsaRemoveAccountRightsを呼び出します。

NTSTATUS LsaRemoveAccountRights(
  LSA_HANDLE PolicyHandle,
  PSID AccountSid,
  BOOLEAN AllRights,
  PLSA_UNICODE_STRING UserRights,
  ULONG CountOfRights
);

PolicyHandleは、ポリシーオブジェクトのハンドルを指定します。 このハンドルにはPOLICY_LOOKUP_NAMESアクセス権が割り当てられている必要があります。 AccountSidは、特権またはユーザー権利を削除したいアカウントのSIDを指定します。 AllRightsは、全ての特権及びユーザー権利を削除し、アカウント自体を削除するかどうかを指定します。 通常は、任意の特権またはユーザー権利を削除するため、この引数はFALSEを指定することになるでしょう。 UserRightsは、削除したい特権及びユーザー権利を格納したLSA_UNICODE_STRING構造体のアドレスを指定します。 CountOfRightsは、割り当てたい特権及びユーザー権利の数を指定します。

今回のプログラムは、AdministratorsグループにSE_ASSIGNPRIMARYTOKEN_NAME特権を追加します。 この特権は、CreateProcessAsUserを呼び出すために必要となります。

#include <windows.h>
#include <ntsecapi.h>

BOOL ConvertNameToSid(LPTSTR lpszName, PSID *ppSid);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	PSID                  pSid;
	NTSTATUS              ns;
	LSA_HANDLE            hPolicy;
	LSA_UNICODE_STRING    lsaString;
	LSA_OBJECT_ATTRIBUTES objectAttributes;
	BOOL                  bAddRight = TRUE;

	ZeroMemory(&objectAttributes, sizeof(LSA_OBJECT_ATTRIBUTES));
	objectAttributes.Length = sizeof(LSA_OBJECT_ATTRIBUTES);
	
	ns = LsaOpenPolicy(NULL, &objectAttributes, POLICY_LOOKUP_NAMES, &hPolicy);
	if (LsaNtStatusToWinError(ns) != ERROR_SUCCESS) {
		MessageBox(NULL, TEXT("ポリシーオブジェクトのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}

	ConvertNameToSid(TEXT("Administrators"), &pSid);
	
	lsaString.Buffer        = L"SeAssignPrimaryTokenPrivilege";
	lsaString.Length        = (USHORT)(lstrlenW(lsaString.Buffer) * sizeof(WCHAR));
	lsaString.MaximumLength = lsaString.Length + sizeof(WCHAR);

	if (bAddRight) {
		ns = LsaAddAccountRights(hPolicy, pSid, &lsaString, 1);
		if (LsaNtStatusToWinError(ns) == ERROR_SUCCESS)
			MessageBox(NULL, TEXT("特権を追加しました。"), TEXT("OK"), MB_OK);
		else
			MessageBox(NULL, TEXT("特権の追加に失敗しました。"), NULL, MB_ICONWARNING);
	}
	else {
		ns = LsaRemoveAccountRights(hPolicy, pSid, FALSE, &lsaString, 1);
		if (LsaNtStatusToWinError(ns) == ERROR_SUCCESS)
			MessageBox(NULL, TEXT("特権を削除しました。"), TEXT("OK"), MB_OK);
		else
			MessageBox(NULL, TEXT("特権の削除に失敗しました。"), NULL, MB_ICONWARNING);
	}

	LocalFree(pSid);
	LsaClose(hPolicy);

	return 0;
}

BOOL ConvertNameToSid(LPTSTR lpszName, PSID *ppSid)
{
	TCHAR        szDomainName[256];
	DWORD        dwSizeDomain = sizeof(szDomainName) / sizeof(TCHAR);
	DWORD        dwSizeSid = 0;
	SID_NAME_USE sidName;

	LookupAccountName(NULL, lpszName, NULL, &dwSizeSid, szDomainName, &dwSizeDomain, &sidName);
	
	*ppSid = (PSID)LocalAlloc(LPTR, dwSizeSid);

	return LookupAccountName(NULL, lpszName, *ppSid, &dwSizeSid, szDomainName, &dwSizeDomain, &sidName);
}

LsaAddAccountRightsの第2引数に指定するのは、アカウント名ではなくSIDであるため、 自作関数のConvertNameToSidでアカウントを表すSIDを取得しています。 LSA_UNICODE_STRIN.Bufferの初期化では、SE_ASSIGNPRIMARYTOKEN_NAMEという定数を使用してもよいのですが、 Bufferメンバの型がLPWSTRであるため、明示的にUNICODE文字列を指定するようにしています。

今回のプログラムではアカウントに特権を割り当てていますが、当然ながらユーザー権利を割り当てることもできます。 たとえば、コントロールパネルから起動できる「ユーザーアカウント」には、 Guestアカウントのオンとオフを切り替えることができますが、 この機能にはユーザー権利が絡んでいます。 Guestアカウントをオフにした場合は、SE_DENY_INTERACTIVE_LOGON_NAMEがGuestアカウントに割り当てられ、 これにより、システムへの対話ログオンが許可されなくなるのです。


戻る