EternalWindows
LSA / カテゴリとサブカテゴリ

監査ポリシーにはいくつかの種類がありますが、これらはカテゴリと呼ばれることがあります。 1つのカテゴリは関連する1つのGUIDを持っており、 Windows Vistaから登場したAudit関数は監査ポリシーをGUIDで表しています。 たとえば、次に示すAuditEnumerateCategoriesは、 システムに存在する全てのカテゴリのGUIDを列挙します。

BOOLEAN WINAPI AuditEnumerateCategories(
  GUID **ppAuditCategoriesArray,
  PULONG pCountReturned
);

ppAuditCategoriesArrayは、列挙されたカテゴリを受け取る変数のアドレスを指定します。 pCountReturnedは、カテゴリの数を受け取る変数のアドレスを指定します。

カテゴリのGUIDから関連する名前を取得するには、 AuditLookupCategoryNameを呼び出します。

BOOLEAN WINAPI AuditLookupCategoryName(
  const GUID *pAuditCategoryGuid,
  PTSTR *ppszCategoryName
);

pAuditCategoryGuidは、カテゴリのGUIDを指定します。 ppszCategoryNameは、関連する名前を受け取る変数のアドレスを指定します。

1つのカテゴリは、詳細な情報をサブカテゴリとして定義しています。 たとえば、オブジェクトアクセスのカテゴリには、 ファイルシステムというサブカテゴリやレジストリというサブカテゴリがあります。 サブカテゴリを列挙するには、AuditEnumerateSubCategoriesを呼び出します。

BOOLEAN WINAPI AuditEnumerateSubCategories(
  const GUID *pAuditCategoryGuid,
  BOOLEAN bRetrieveAllSubCategories,
  GUID **ppAuditSubCategoriesArray,
  PULONG pCountReturned
);

pAuditCategoryGuidは、サブカテゴリを列挙したいカテゴリのGUIDを指定します。 bRetrieveAllSubCategoriesは、全てのサブカテゴリを列挙するどうかを示す値を指定します。 TRUEを指定した場合は、全てのサブカテゴリを列挙されるため、 pAuditCategoryGuidはNULLで問題ありません。 ppAuditSubCategoriesArrayは、列挙されたサブカテゴリを受け取る変数のアドレスを指定します。 pCountReturnedは、サブカテゴリの数を受け取る変数のアドレスを指定します。

サブカテゴリのGUIDから関連する名前を取得するには、 AuditLookupSubCategoryNameを呼び出します。

BOOLEAN WINAPI AuditLookupSubCategoryName(
  const GUID *pAuditSubCategoryGuid,
  PTSTR *ppszSubCategoryName
);

pAuditSubCategoryGuidは、サブカテゴリのGUIDを指定します。 ppszSubCategoryNameは、関連する名前を受け取る変数のアドレスを指定します。

カテゴリは監査ポリシーですから、これがどのように設定されているかは、 LsaQueryInformationPolicyで取得することができます。 サブカテゴリがどのように設定されているかは、AuditQuerySystemPolicyで取得することができます。

BOOLEAN WINAPI AuditQuerySystemPolicy(
  const GUID *pSubCategoryGuids,
  ULONG PolicyCount,
  PAUDIT_POLICY_INFORMATION *ppAuditPolicy
);

pSubCategoryGuidsは、取得したいサブカテゴリのGUIDを指定します。 PolicyCountは、pSubCategoryGuidsの要素数を指定します。 ppAuditPolicyは、作成されたAUDIT_POLICY_INFORMATION構造体を受け取る変数のアドレスを指定します。 この構造体は、次のように定義されています。

typedef struct _AUDIT_POLICY_INFORMATION {
  GUID  AuditSubCategoryGuid;
  ULONG AuditingInformation;
  GUID  AuditCategoryGuid;
} AUDIT_POLICY_INFORMATION,  *PAUDIT_POLICY_INFORMATION;

AuditSubCategoryGuidは、サブカテゴリのGUIDが格納されます。 AuditingInformationは、POLICY_AUDIT_EVENT_SUCCESSなどの定数が格納されます。 AuditCategoryGuidは、カテゴリのGUIDが格納されます。

Audit関数によって確保されたメモリは、AuditFreeで開放することができます。

VOID WINAPI AuditFree(
  PVOID Buffer
);

Bufferは、開放したいメモリを指定します。

今回のプログラムは、存在する全てのカテゴリをコンボボックスに追加します。 そして、選択されたカテゴリのサブカテゴリをリストボックスに列挙します。

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

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndComboBox = NULL;
	static HWND hwndListBox = NULL;
	static GUID *pGuidCategory = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		ULONG  i;
		ULONG  uCategorCount;
		LPTSTR lpszCategoryName;

		hwndComboBox = CreateWindowEx(0, TEXT("COMBOBOX"), TEXT(""), WS_CHILD | WS_VISIBLE | CBS_DROPDOWN, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), TEXT(""), WS_CHILD | WS_VISIBLE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)2, ((LPCREATESTRUCT)lParam)->hInstance, NULL);
		
		AuditEnumerateCategories(&pGuidCategory, &uCategorCount);
		for (i = 0; i < uCategorCount; i++) {
			AuditLookupCategoryName(&pGuidCategory[i], &lpszCategoryName);
			SendMessage(hwndComboBox, CB_ADDSTRING, 0, (LPARAM)lpszCategoryName);
			AuditFree(lpszCategoryName);
		}

		SendMessage(hwndComboBox, CB_SETCURSEL, 0, 0);

		return 0;
	}

	case WM_COMMAND: {
		BOOLEAN                   bConfirmUserPolicy = FALSE;
		BOOLEAN                   bQueryAuditPolicy;
		ULONG                     i;
		DWORD                     dwIndex;
		GUID                      *pGuidSubCategory;
		ULONG                     uSubCategoryCount;
		LPTSTR                    lpszSubCategoryName;
		TCHAR                     szEventType[256];
		TCHAR                     szBuf[256];
		PAUDIT_POLICY_INFORMATION pAuditPolicy;

		if (HIWORD(wParam) != CBN_SELCHANGE || (HWND)lParam != hwndComboBox)
			return 0;

		SendMessage(hwndListBox, LB_RESETCONTENT, 0, 0);

		dwIndex = (DWORD)SendMessage(hwndComboBox, CB_GETCURSEL, 0, 0);

		AuditEnumerateSubCategories(&pGuidCategory[dwIndex], FALSE, &pGuidSubCategory, &uSubCategoryCount);

		if (bConfirmUserPolicy) {
			HANDLE      hToken;
			PTOKEN_USER pTokenUser;
			DWORD       dwLength;
			
			OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);
			GetTokenInformation(hToken, TokenUser, NULL, 0, &dwLength);
			pTokenUser = (PTOKEN_USER)LocalAlloc(LPTR, dwLength);
			GetTokenInformation(hToken, TokenUser, pTokenUser, dwLength, &dwLength);

			bQueryAuditPolicy = AuditQueryPerUserPolicy(pTokenUser->User.Sid, pGuidSubCategory, uSubCategoryCount, &pAuditPolicy);

			CloseHandle(hToken);
			LocalFree(pTokenUser);
		}
		else
			bQueryAuditPolicy = AuditQuerySystemPolicy(pGuidSubCategory, uSubCategoryCount, &pAuditPolicy);

		for (i = 0; i < uSubCategoryCount; i++) {
			AuditLookupSubCategoryName(&pGuidSubCategory[i], &lpszSubCategoryName);
			if (bQueryAuditPolicy) {
				if (pAuditPolicy[i].AuditingInformation == (POLICY_AUDIT_EVENT_SUCCESS | POLICY_AUDIT_EVENT_FAILURE))
					lstrcpy(szEventType, TEXT("○×"));
				else if (pAuditPolicy[i].AuditingInformation == POLICY_AUDIT_EVENT_SUCCESS)
					lstrcpy(szEventType, TEXT("○"));
				else if (pAuditPolicy[i].AuditingInformation == POLICY_AUDIT_EVENT_FAILURE)
					lstrcpy(szEventType, TEXT("×"));
				else if (pAuditPolicy[i].AuditingInformation == POLICY_AUDIT_EVENT_UNCHANGED)
					lstrcpy(szEventType, TEXT("---"));
				else
					lstrcpy(szEventType, TEXT(""));
				wsprintf(szBuf, TEXT("%s %s"), szEventType, lpszSubCategoryName);
			}
			else
				wsprintf(szBuf, TEXT("%s"), lpszSubCategoryName);

			SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
			AuditFree(lpszSubCategoryName);
		}

		AuditFree(pGuidSubCategory);

		return 0;
	}
	
	case WM_SIZE:
		MoveWindow(hwndComboBox, 0, 0, LOWORD(lParam) / 3, HIWORD(lParam), TRUE);
		MoveWindow(hwndListBox, LOWORD(lParam) / 3, 0, LOWORD(lParam) - LOWORD(lParam) / 3, HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		if (pGuidCategory != NULL)
			AuditFree(pGuidCategory);
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WM_CREATEでは、AuditEnumerateCategoriesでカテゴリのGUIDの配列を取得し、 この数だけAuditLookupCategoryNameを呼び出して文字列に変換します。 そして、この文字列をコンボボックスに追加することで、 任意のカテゴリを選択できるようになります。

コンボボックスが選択された場合は、WM_COMMANDが送られることになります。 この時には、選択されたカテゴリのサブカテゴリを列挙するため、 AuditEnumerateSubCategoriesを呼び出すようにしています。 カテゴリのGUIDは、静的変数のpGuidCategoryに選択されたコンボボックスのインデックスを指定することで参照できます。 その後の処理は、次のようになっています。

if (bConfirmSystemPolicy) {
	dwSuccessFailureOption = POLICY_AUDIT_EVENT_SUCCESS | POLICY_AUDIT_EVENT_FAILURE;
	dwSuccessOption = POLICY_AUDIT_EVENT_SUCCESS;
	dwFailureOption = POLICY_AUDIT_EVENT_FAILURE;

	bOpenAuditPolicy = AuditQuerySystemPolicy(pGuidSubCategory, uSubCategoryCount, &pAuditPolicy);
}
else {
	HANDLE      hToken;
	PTOKEN_USER pTokenUser;
	DWORD       dwLength;

	dwSuccessFailureOption = PER_USER_AUDIT_SUCCESS_INCLUDE | PER_USER_AUDIT_FAILURE_INCLUDE;
	dwSuccessOption = PER_USER_AUDIT_SUCCESS_INCLUDE;
	dwFailureOption = PER_USER_AUDIT_FAILURE_INCLUDE;
		
	OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);
	GetTokenInformation(hToken, TokenUser, NULL, 0, &dwLength);
	pTokenUser = (PTOKEN_USER)LocalAlloc(LPTR, dwLength);
	GetTokenInformation(hToken, TokenUser, pTokenUser, dwLength, &dwLength);

	bOpenAuditPolicy = AuditQueryPerUserPolicy(pTokenUser->User.Sid, pGuidSubCategory, uSubCategoryCount, &pAuditPolicy);

	CloseHandle(hToken);
	LocalFree(pTokenUser);			
}

サブカテゴリのポリシーは、システム単位で設定することもユーザー毎に設定することもできます。 bConfirmSystemPolicyがTRUEの場合は、AuditQuerySystemPolicyでシステム単位のポリシーを取得し、 FALSEの場合はAuditQueryPerUserPolicyでユーザー毎のポリシーを取得します。 この場合は、第1引数に対象ユーザーのSIDを指定することになります。 取得したAUDIT_POLICY_INFORMATION.AuditingInformationに格納される値は、 両者の関数で異なっているため、適切に初期化することになります。 bQueryAuditPolicyという変数で戻り値を取得しているのは、 Administratorsのメンバであるユーザーでなければ関数の呼び出しに失敗するためです。

取得したサブカテゴリのGUIDは、AuditLookupSubCategoryNameによって文字列に変換され、 リストボックスに追加されることになります。 このとき、bQueryAuditPolicyがTRUEである場合は、 サブカテゴリの設定を取得していることを意味しているため、 サブカテゴリの名前の先頭に記号を表示するようにしています。 ○の場合は成功の監査を表し、×の場合は失敗の監査、 ---はサブカテゴリを監査しないことを意味します。

監査セキュリティオブジェクトについて

各種Audit関数の呼び出しに成功するかどうかは、 監査セキュリティオブジェクトが呼び出し側にアクセスを許可している必要があります。 AuditQuerySecurityを呼び出せば、監査セキュリティオブジェクトのセキュリティ記述子を取得できるため、 これのDACLを走査することで、アクセスを許可しているアカウントを特定することができます。

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

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	PACL                 pDacl;
	BOOL                 bDaclPresent;
	BOOL                 bDaclDefaulted;
	TCHAR                szBuf[1024];
	TCHAR                szAccountName[256];
	DWORD                i, j; 
	PACCESS_ALLOWED_ACE  pAce;
	ACL_SIZE_INFORMATION aclInformation;
	PSECURITY_DESCRIPTOR pSecurityDescriptor;

	struct ACCESS_RIGHT {
		DWORD  dwAccessRight;
		LPTSTR lpszAccessRight;
	} accessRigtht [] = {
		AUDIT_SET_SYSTEM_POLICY, TEXT("AUDIT_SET_SYSTEM_POLICY"),
		AUDIT_QUERY_SYSTEM_POLICY, TEXT("AUDIT_QUERY_SYSTEM_POLICY"),
		AUDIT_SET_USER_POLICY, TEXT("AUDIT_SET_USER_POLICY"),
		AUDIT_QUERY_USER_POLICY, TEXT("AUDIT_QUERY_USER_POLICY"),
		AUDIT_ENUMERATE_USERS, TEXT("AUDIT_ENUMERATE_USERS"),
		AUDIT_SET_MISC_POLICY, TEXT("AUDIT_SET_MISC_POLICY"),
		AUDIT_QUERY_MISC_POLICY, TEXT("AUDIT_QUERY_MISC_POLICY")
	};
	DWORD dwAccessRigthtCount = sizeof(accessRigtht) / sizeof(accessRigtht[0]);
	
	if (!EnablePrivilege(SE_SECURITY_NAME, TRUE)) {
		MessageBox(NULL, TEXT("SE_SECURITY_NAME特権の有効に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}

	if (!AuditQuerySecurity(DACL_SECURITY_INFORMATION, &pSecurityDescriptor)) {
		MessageBox(NULL, TEXT("監査セキュリティオブジェクトのセキュリティ記述子の取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}

	GetSecurityDescriptorDacl(pSecurityDescriptor, &bDaclPresent, &pDacl, &bDaclDefaulted);
	GetAclInformation(pDacl, &aclInformation, sizeof(ACL_SIZE_INFORMATION), AclSizeInformation);
	
	for (i = 0; i < aclInformation.AceCount; i++) {
		GetAce(pDacl, i, (LPVOID *)&pAce);
		ConvertSidToName((PSID)&pAce->SidStart, szAccountName, sizeof(szAccountName) / sizeof(TCHAR));
		lstrcpy(szBuf, TEXT("\0"));
		for (j = 0; j < dwAccessRigthtCount; j++) {
			if (accessRigtht[j].dwAccessRight & pAce->Mask) {
				lstrcat(szBuf, accessRigtht[j].lpszAccessRight);
				lstrcat(szBuf, TEXT("\n"));
			}
		}
		
		MessageBox(NULL, szBuf, szAccountName, MB_OK);
	}

	return 0;
}

BOOL EnablePrivilege(LPTSTR lpszPrivilege, BOOL bEnable)
{
	BOOL             bResult;
	LUID             luid;
	HANDLE           hToken;
	TOKEN_PRIVILEGES tokenPrivileges;

	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
		return FALSE;
	
	if (!LookupPrivilegeValue(NULL, lpszPrivilege, &luid)) {
		CloseHandle(hToken);
		return FALSE;
	}

	tokenPrivileges.PrivilegeCount           = 1;
	tokenPrivileges.Privileges[0].Luid       = luid;
	tokenPrivileges.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
	
	bResult = AdjustTokenPrivileges(hToken, FALSE, &tokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
	
	CloseHandle(hToken);

	return bResult && GetLastError() == ERROR_SUCCESS;
}

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

AuditQuerySecurityの第1引数にDACL_SECURITY_INFORMATIONを指定すれば、 第2引数にDACLを格納したセキュリティ記述子が返ります。 ただし、AuditQuerySecurityの呼び出しを成功させるためには、 SE_SECURITY_NAME特権を有効にしておく必要があります。 独自のDACLを設定したい場合は、AuditSetSecurityを呼び出すことになります。



戻る