EternalWindows
アクセスコントロール / セキュリティ記述子の形式

セキュリティ記述子の形式には、自己相対形式と絶対形式の2種類が存在します。 自己相対形式とは、セキュリティ記述子に格納されているデータ(所有者SIDやDACL)が連続してメモリに格納されている形式で、 実際のデータとデータへのオフセットを格納しています。 このため、ファイルなどに保存できる利点があります。 一方、絶対形式はデータへのポインタを格納する形式で、 セキュリティ記述子内のデータの書き換えをポインタで行える利点があります。 セキュリティ記述子を表すSECURITY_DESCRIPTOR型は、次のように定義されています。

typedef struct _SECURITY_DESCRIPTOR_RELATIVE {
    BYTE  Revision;
    BYTE  Sbz1;
    SECURITY_DESCRIPTOR_CONTROL Control;
    DWORD Owner;
    DWORD Group;
    DWORD Sacl;
    DWORD Dacl;
    } SECURITY_DESCRIPTOR_RELATIVE, *PISECURITY_DESCRIPTOR_RELATIVE;

typedef struct _SECURITY_DESCRIPTOR {
   BYTE  Revision;
   BYTE  Sbz1;
   SECURITY_DESCRIPTOR_CONTROL Control;
   PSID Owner;
   PSID Group;
   PACL Sacl;
   PACL Dacl;

   } SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;

SECURITY_DESCRIPTOR_RELATIVEが自己相対形式を表し、 SECURITY_DESCRIPTORが絶対形式を表します。 ただし、メンバの並び方は同じであるため、 自己相対形式を表す場合でも、SECURITY_DESCRIPTOR型を用いるのが一般的です。 これまで取り上げてきた関数では、 GetNamedSecurityInfoが自己相対形式のセキュリティ記述子を返し、 InitializeSecurityDescriptorで絶対形式のセキュリティ記述子を作成します。

自己相対形式であるセキュリティ記述子の中身を変更することは、それほど容易ではありません。 この形式ではデータが連続して格納されているため、 新しいデータを追加するためのスペースが存在しないのです。 よって、先に絶対形式に変換し、 データをポインタで指した後に自己相対形式に戻せば、 データはセキュリティ記述子の中に格納されることになります。 自己相対形式のセキュリティ記述子を絶対形式に変換するには、MakeAbsoluteSDを呼び出します。

BOOL WINAPI MakeAbsoluteSD(
  PSECURITY_DESCRIPTOR pSelfRelativeSD,
  PSECURITY_DESCRIPTOR pAbsoluteSD,
  LPDWORD lpdwAbsoluteSDSize,
  PACL pDacl,
  LPDWORD lpdwDaclSize,
  PACL pSacl,
  LPDWORD lpdwSaclSize,
  PSID pOwner,
  LPDWORD lpdwOwnerSize,
  PSID pPrimaryGroup,
  LPDWORD lpdwPrimaryGroupSize
);

pSelfRelativeSDは、自己相対形式のセキュリティ記述子を指定します。 pAbsoluteSDは、絶対形式のセキュリティ記述子を受け取るバッファを指定します。 lpdwAbsoluteSDSizeは、pAbsoluteSDのサイズを格納した変数のアドレスを指定します。 pDaclは、DACLを受け取るバッファを指定します。 lpdwDaclSizeは、pDaclのサイズを格納した変数のアドレスを指定します。 pSaclは、SACLを受け取るバッファを指定します。 lpdwSaclSizeは、pSaclのサイズを格納した変数のアドレスを指定します。 pOwnerは、所有者SIDを受け取るバッファを指定します。 lpdwOwnerSizeは、pOwnerのサイズを格納した変数のアドレスを指定します。 pPrimaryGroupは、プライマリグループSIDを受け取るバッファを指定します。 lpdwPrimaryGroupSizeは、pPrimaryGroupのサイズを格納した変数のアドレスを指定します。 なお、サイズを格納する全ての引数は、 そのサイズがバッファとして十分でない場合に関数によってバッファのサイズが格納されます。

絶対形式のセキュリティ記述子を自己相対形式に変換するには、MakeSelfRelativeSDを呼び出します。

BOOL WINAPI MakeSelfRelativeSD(
  PSECURITY_DESCRIPTOR pAbsoluteSD,
  PSECURITY_DESCRIPTOR pSelfRelativeSD,
  LPDWORD lpdwBufferLength
);

pAbsoluteSDは、絶対形式のセキュリティ記述子を指定します。 pSelfRelativeSDは、自己相対形式のセキュリティ記述子を受け取るバッファを指定します。 lpdwBufferLengthは、pSelfRelativeSDのサイズを格納した変数のアドレスを指定します。 サイズが十分でない場合は、関数によってバッファのサイズが格納されます。

自己相対形式のセキュリティ記述子に対するデータの設定は、これまで何度も行ってきました。 たとえば、SetNamedSecurityInfoはオブジェクトにDACLを設定しますが、 この関数はセキュリティ記述子を要求する引数がないため、 セキュリティ記述子の変換処理はアプリケーションから隠蔽されます。 つまり、通常ならば、データを設定するために変換処理が必要になることはありません。 では、どういう場合に変換処理が必要になるかですが、 これは主に外部からセキュリティ記述子を取得した場合です。 たとえば、サービスアプリケーションは、 インストール時に専用のレジストリキーを作成されることになっていますが、 このレジストリキーにはセキュリティ記述子が自己相対形式で格納されています。 自己相対形式ではデータが連続していますから、こうした場所に保存することができるわけです。 外部から取得したセキュリティ記述子にデータを設定する場合は、 セキュリティ記述子の型が関数によって隠蔽されるようなことはありませんから、 アプリケーションが明示的に変換処理を行う必要があります。

外部からセキュリティ記述子を取得する場合は、 それが形式として有効であるかをIsValidSecurityDescriptorで確認するべきといえます。

BOOL WINAPI IsValidSecurityDescriptor(
  PSECURITY_DESCRIPTOR pSecurityDescriptor
);

pSecurityDescriptorは、セキュリティ記述子を指定します。 戻り値がTRUEの場合は、正しいセキュリティ記述子の形式であることを意味します。

今回のプログラムは、外部からセキュリティ記述子を取得する例としてLSAシークレットを使用しています。 事前に、LSAの章のプログラムでLSAシークレットを作成し、 AdministratorsがSECURITYキーに書き込みアクセスできるようにしておきます。 また、プログラムは管理者として実行する必要があります。

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

#define SECRET_WRITE_DATA 0x0001
#define SECRET_READ_DATA  0x0002

BOOL GetLsaSecretSD(LPTSTR lpszSubKey, PSECURITY_DESCRIPTOR *ppSecurityDescriptor);
BOOL SetLsaSecretSD(LPTSTR lpszSubKey, PSECURITY_DESCRIPTOR pSecurityDescriptor, DWORD dwSDSize);
void GetNewSelfRelativSD(PSECURITY_DESCRIPTOR pSelfRelativeSD, PACL pDaclNew, PSECURITY_DESCRIPTOR *ppSelfRelativeSDNew, LPDWORD lpdwSdSize);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR                szSubKey[] = TEXT("SECURITY\\Policy\\Secrets\\L$MySecret\\SecDesc");
	PACL                 pDaclNew;
	EXPLICIT_ACCESS      explicitAccess;
	DWORD                dwSDSize;
	PSECURITY_DESCRIPTOR pSecurityDescriptor;
	PSECURITY_DESCRIPTOR pSecurityDescriptorNew;

	if (!GetLsaSecretSD(szSubKey, &pSecurityDescriptor)) {
		MessageBox(NULL, TEXT("LSAシークレットのセキュリティ記述子の取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}

	if (!IsValidSecurityDescriptor(pSecurityDescriptor)) {
		MessageBox(NULL, TEXT("セキュリティ記述子は有効ではありません。"), NULL, MB_ICONWARNING);
		LocalFree(pSecurityDescriptor);
		return 0;
	}

	BuildExplicitAccessWithName(&explicitAccess, TEXT("Administrators"), SECRET_READ_DATA, GRANT_ACCESS, 0);	
	SetEntriesInAcl(1, &explicitAccess, NULL, &pDaclNew);

	GetNewSelfRelativSD(pSecurityDescriptor, pDaclNew, &pSecurityDescriptorNew, &dwSDSize);
	
	if (!SetLsaSecretSD(szSubKey, pSecurityDescriptorNew, dwSDSize)) {
		MessageBox(NULL, TEXT("LSAシークレットのセキュリティ記述子の設定に失敗しました。"), NULL, MB_ICONWARNING);
		LocalFree(pSecurityDescriptor);
		LocalFree(pSecurityDescriptorNew);
		LocalFree(pDaclNew);
		return 0;
	}
	
	MessageBox(NULL, TEXT("LSAシークレットのセキュリティ記述子を変更しました。"), TEXT("OK"), MB_OK);

	LocalFree(pSecurityDescriptor);
	LocalFree(pSecurityDescriptorNew);
	LocalFree(pDaclNew);

	return 0;
}

void GetNewSelfRelativSD(PSECURITY_DESCRIPTOR pSelfRelativeSD, PACL pDaclNew, PSECURITY_DESCRIPTOR *ppSelfRelativeSDNew, LPDWORD lpdwSDSize)
{
	PSID                 pSidOwner;
	PSID                 pSidGroup;
	PACL                 pDacl;
	PACL                 pSacl;
	DWORD                dwOwnerSize = 0;
	DWORD                dwGroupSize = 0;
	DWORD                dwDaclSize = 0;
	DWORD                dwSaclSize = 0;
	DWORD                dwSDSize = 0;
	PSECURITY_DESCRIPTOR pAbsoluteSD;

	MakeAbsoluteSD(pSelfRelativeSD, NULL, &dwSDSize, NULL, &dwDaclSize, NULL, &dwSaclSize, NULL, &dwOwnerSize, NULL, &dwGroupSize);
	pSidOwner = (PSID)LocalAlloc(LPTR, dwOwnerSize);
	pSidGroup = (PSID)LocalAlloc(LPTR, dwGroupSize);
	pDacl = (PACL)LocalAlloc(LPTR, dwDaclSize);
	pSacl = (PACL)LocalAlloc(LPTR, dwSaclSize);
	pAbsoluteSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, dwSDSize);
	MakeAbsoluteSD(pSelfRelativeSD, pAbsoluteSD, &dwSDSize, pDacl, &dwDaclSize, pSacl, &dwSaclSize, pSidOwner, &dwOwnerSize, pSidGroup, &dwGroupSize); 

	SetSecurityDescriptorDacl(pAbsoluteSD, TRUE, pDaclNew, FALSE);

	dwSDSize = 0;
	MakeSelfRelativeSD(pAbsoluteSD, NULL, &dwSDSize);
	*ppSelfRelativeSDNew = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, dwSDSize);
	MakeSelfRelativeSD(pAbsoluteSD, *ppSelfRelativeSDNew, &dwSDSize);
	*lpdwSDSize = dwSDSize;

	LocalFree(pSidOwner);
	LocalFree(pSidGroup);
	LocalFree(pDacl);
	LocalFree(pSacl);
	LocalFree(pAbsoluteSD);
}

BOOL GetLsaSecretSD(LPTSTR lpszSubKey, PSECURITY_DESCRIPTOR *ppSecurityDescriptor)
{
	HKEY   hKey;
	LONG   lResult;
	LPVOID lpData;
	DWORD  dwSize;

	lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpszSubKey, 0, KEY_QUERY_VALUE, &hKey);
	if (lResult == ERROR_SUCCESS) {
		RegQueryValueEx(hKey, NULL, NULL, NULL, NULL, &dwSize);
		lpData = LocalAlloc(LPTR, dwSize);
		RegQueryValueEx(hKey, NULL, NULL, NULL, (LPBYTE)lpData, &dwSize);
		*ppSecurityDescriptor = lpData;
		RegCloseKey(hKey);
		return TRUE;
	}
	else
		return FALSE;
}

BOOL SetLsaSecretSD(LPTSTR lpszSubKey, PSECURITY_DESCRIPTOR pSecurityDescriptor, DWORD dwSdSize)
{
	HKEY hKey;
	LONG lResult;

	lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, lpszSubKey, 0, KEY_SET_VALUE, &hKey);
	if (lResult == ERROR_SUCCESS) {
		RegSetValueEx(hKey, NULL, 0, REG_BINARY, (LPBYTE)pSecurityDescriptor, dwSdSize);
		RegCloseKey(hKey);
		return TRUE;
	}
	else
		return FALSE;
}

szSubKeyは、LSAシークレットのセキュリティ記述子が格納されている場所を表しています。 L$MySecretが自作のシークレットの名前で、 その下のSecDescというサブキーに自己相対形式のセキュリティ記述子が格納されています。 プログラムの流れは、まずGetLsaSecretSDでセキュリティ記述子を取得し、 独自のDACLを作成してから、GetNewSelfRelativSDを呼び出します。 この関数は、指定されたセキュリティ記述子に指定されたDACLが設定された新しいセキュリティ記述子を返します。 そして、最後にSetLsaSecretSDで新しいセキュリティ記述子を書き込みます。

BuildExplicitAccessWithNameとSetEntriesInAclの呼び出しによって、 Administratorsに読み取りを許可するACEがDACLに設定されます。 これにより、LSAシークレットの内容は書き換えることができなくなります。 読み取りに必要なアクセス権が0x0002で、書き込みに必要なアクセス権を0x0001として定義していますが、 これらはセキュリティ記述子に格納されているDACLのACEを走査すれば、特定することができます。 セキュリティ記述子とDACLを用意できたら、GetNewSelfRelativSDを呼び出します。

void GetNewSelfRelativSD(PSECURITY_DESCRIPTOR pSelfRelativeSD, PACL pDaclNew, PSECURITY_DESCRIPTOR *ppSelfRelativeSDNew, LPDWORD lpdwSDSize)
{
	PSID                 pSidOwner;
	PSID                 pSidGroup;
	PACL                 pDacl;
	PACL                 pSacl;
	DWORD                dwOwnerSize = 0;
	DWORD                dwGroupSize = 0;
	DWORD                dwDaclSize = 0;
	DWORD                dwSaclSize = 0;
	DWORD                dwSDSize = 0;
	PSECURITY_DESCRIPTOR pAbsoluteSD;

	MakeAbsoluteSD(pSelfRelativeSD, NULL, &dwSDSize, NULL, &dwDaclSize, NULL, &dwSaclSize, NULL, &dwOwnerSize, NULL, &dwGroupSize);
	pSidOwner = (PSID)LocalAlloc(LPTR, dwOwnerSize);
	pSidGroup = (PSID)LocalAlloc(LPTR, dwGroupSize);
	pDacl = (PACL)LocalAlloc(LPTR, dwDaclSize);
	pSacl = (PACL)LocalAlloc(LPTR, dwSaclSize);
	pAbsoluteSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, dwSDSize);
	MakeAbsoluteSD(pSelfRelativeSD, pAbsoluteSD, &dwSDSize, pDacl, &dwDaclSize, pSacl, &dwSaclSize, pSidOwner, &dwOwnerSize, pSidGroup, &dwGroupSize); 

	SetSecurityDescriptorDacl(pAbsoluteSD, TRUE, pDaclNew, FALSE);

	dwSDSize = 0;
	MakeSelfRelativeSD(pAbsoluteSD, NULL, &dwSDSize);
	*ppSelfRelativeSDNew = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, dwSDSize);
	MakeSelfRelativeSD(pAbsoluteSD, *ppSelfRelativeSDNew, &dwSDSize);
	*lpdwSDSize = dwSDSize;

	LocalFree(pSidOwner);
	LocalFree(pSidGroup);
	LocalFree(pDacl);
	LocalFree(pSacl);
	LocalFree(pAbsoluteSD);
}

既に述べたように、自己相対形式のセキュリティ記述子には、新たにDACLを設定するためのスペースが存在しないため、 まずはMakeAbsoluteSDで絶対形式に変換します。 1回目の呼び出しではサイズを取得することに専念し、 バッファを確保してから、2回目の呼び出しでデータを取得します。 次に、SetSecurityDescriptorDaclを呼び出して、絶対形式のセキュリティ記述子に独自のDACLを設定します。 これで具体的に何が起きるのかというと、pDaclのアドレスを維持しているpAbsoluteSDが、 pDaclではなくpDaclNewのアドレスを維持するようになります。 この状態でMakeSelfRelativeSDを呼び出せば、 pDaclNewを含んだ自己相対形式のセキュリティ記述子を取得できるようになります。


戻る