EternalWindows
アクセスコントロール / オブジェクトの所有者

前節では、プロセスのセキュリティ記述子を独自に設定することで、 外部プロセスからの特定のアクセスを失敗させるようにしましたが、 実際にはこの制限は回避することができます。 1つは、SE_DEBUG_NAME特権を有効にする方法で、 この特権を有効にした状態でOpenProcessを呼び出せば、 オブジェクトに設定されたセキュリティ記述子は考慮されません。 つまり、どのようなアクセス権も指定してもハンドルを取得することができ、 プロセスに対して何らかのアクセスが失敗することはなくなります。

2つ目の方法は、オブジェクトに設定されたDACLの書き換えです。 DACLの書き換えにはWRITE_DACというアクセス権が必要ですが、 これが呼び出し側アカウントに許可されているならば、 SetSecurityInfoを通じて独自のDACLを設定することができます。 WRITE_DACを許可しなければよいようにも思えますが、 このアクセス権とREAD_CONTROLはオブジェクトの所有者であれば許可されることになっているため、 DACLの書き換えを防ぐことは基本的にできません。

オブジェクトの所有者になる方法についても触れておきます。 SetSecurityInfoには所有者SIDを受け取る引数が存在するため、 呼び出し側にWRITE_OWNERアクセス権が許可されていれば、 その所有者SIDで表されるアカウントをオブジェクトの所有者に設定できます。 また、WRITE_OWNERが許可されていない場合でも、 SE_TAKE_OWNERSHIP_NAM特権が有効であれば、所有者SIDの設定は許可されることになります。 なお、多くのオブジェクトの所有者は、現在とユーザーかそれがメンバであるグループになっているため、 明示的に所有者SIDを設定する機会はそれほど多くはないと思われます。

今回のプログラムは、特定のプロセスのDACLを独自のDACLに書き換えます。 対象とするプロセスは、前節のような独自のセキュリティ記述子が設定されたプロセスでも構いませんし、 winlogon.exeやlsass.exeのようなシステムプロセスでも構いません。 システムプロセスは、現在のユーザーにアクセスを許可するACEを含んでいませんが、 プロセスの所有者がAdministratorsとなっているので、 管理者としてログオンしていればDACLの書き換えが可能になります。

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

BOOL ModifyProcessSecurity(HANDLE hProcess, DWORD dwAccessMask);
DWORD GetTargetProcessId(LPTSTR lpszExeName);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HANDLE hProcess;
	DWORD  dwProcessId;
	DWORD  dwAccessMask = PROCESS_ALL_ACCESS;
	
	dwProcessId = GetTargetProcessId(TEXT("sample.exe"));
	
	hProcess = OpenProcess(dwAccessMask, FALSE, dwProcessId);
	if (hProcess == NULL) {
		hProcess = OpenProcess(READ_CONTROL | WRITE_DAC, FALSE, dwProcessId);
		if (hProcess == NULL) {
			MessageBox(NULL, TEXT("プロセスのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
			return 0;
		}

		if (!ModifyProcessSecurity(hProcess, dwAccessMask)) {
			MessageBox(NULL, TEXT("プロセスのセキュリティの修正に失敗しました。"), NULL, MB_ICONWARNING);
			CloseHandle(hProcess);
			return 0;
		}

		CloseHandle(hProcess);
		
		hProcess = OpenProcess(dwAccessMask, FALSE, dwProcessId);
		if (hProcess == NULL) {
			MessageBox(NULL, TEXT("プロセスのハンドルの再取得に失敗しました。"), NULL, MB_ICONWARNING);
			return 0;
		}
	}

	if (SetPriorityClass(hProcess, NORMAL_PRIORITY_CLASS))
		MessageBox(NULL, TEXT("優先順位を変更しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("優先順位の変更に失敗しました。"), NULL, MB_ICONWARNING);
	
	return 0;
}

BOOL ModifyProcessSecurity(HANDLE hProcess, DWORD dwAccessMask)
{
	TCHAR                szAccountName[256];
	DWORD                dwSize;
	DWORD                dwResult;
	PACL                 pDacl;
	PACL                 pDaclNew;
	EXPLICIT_ACCESS      explicitAccess;
	PSECURITY_DESCRIPTOR pSecurityDescriptor;
	
	if (GetSecurityInfo(hProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDacl, NULL, &pSecurityDescriptor) != ERROR_SUCCESS)
		return FALSE;

	dwSize = sizeof(szAccountName) / sizeof(TCHAR);
	GetUserName(szAccountName, &dwSize);
	BuildExplicitAccessWithName(&explicitAccess, szAccountName, dwAccessMask, GRANT_ACCESS, 0);
	SetEntriesInAcl(1, &explicitAccess, pDacl, &pDaclNew);

	dwResult = SetSecurityInfo(hProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pDaclNew, NULL);
	
	LocalFree(pDaclNew);
	LocalFree(pSecurityDescriptor);

	return dwResult == ERROR_SUCCESS;
}

DWORD GetTargetProcessId(LPTSTR lpszExeName)
{
	HANDLE         hSnapshot;
	DWORD          dwProcessId;
	PROCESSENTRY32 pe;
	
	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hSnapshot == INVALID_HANDLE_VALUE)
		return FALSE;

	pe.dwSize = sizeof(PROCESSENTRY32);
	Process32First(hSnapshot, &pe);

	dwProcessId = 0;
	do {
		if (lstrcmp(pe.szExeFile, lpszExeName) == 0) {
			dwProcessId = pe.th32ProcessID;
			break;
		}
	} while (Process32Next(hSnapshot, &pe));
	
	CloseHandle(hSnapshot);

	return dwProcessId;
}

このプログラムの目的は、PROCESS_ALL_ACCESSを指定してプロセスをオープンし、 プロセスに対してあらゆるアクセスを成功させることです。 しかし、独自にセキュリティ記述子が設定されたプロセスやシステムプロセスには、 こうしたアクセスを許可するACEが含まれていませんから、 独自のDACLを設定することで、PROCESS_ALL_ACCESSの指定を成立させるようにするのです。

hProcess = OpenProcess(dwAccessMask, FALSE, dwProcessId);
if (hProcess == NULL) {
	hProcess = OpenProcess(READ_CONTROL | WRITE_DAC, FALSE, dwProcessId);
	if (hProcess == NULL) {
		MessageBox(NULL, TEXT("プロセスのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}

	if (!ModifyProcessSecurity(hProcess, dwAccessMask)) {
		MessageBox(NULL, TEXT("プロセスのセキュリティの修正に失敗しました。"), NULL, MB_ICONWARNING);
		CloseHandle(hProcess);
		return 0;
	}

	CloseHandle(hProcess);
	
	hProcess = OpenProcess(dwAccessMask, FALSE, dwProcessId);
	if (hProcess == NULL) {
		MessageBox(NULL, TEXT("プロセスのハンドルの再取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
}

まず、PROCESS_ALL_ACCESS(dwAccessMask)を指定してOpenProcessを実行し、 これが失敗した場合はREAD_CONTROLとWRITE_DACを指定してOpenProcessを呼び出します。 既に述べたように、これらのアクセス権は、呼び出し側がオブジェクトの所有者であれば成功することになります。 ハンドルを取得したらModifyProcessSecurityという自作関数を呼び出し、 プロセスに独自のDACLを設定することになります。 これを終えたら、一度ハンドルをクローズし、 再度OpenProcessを呼び出します。 この時点では、プロセスにPROCESS_ALL_ACCESSを許可するACEが設定されていますから、 問題なくハンドルを取得することができるはずです。

ModifyProcessSecurityの実装は、次のようになっています。

BOOL ModifyProcessSecurity(HANDLE hProcess, DWORD dwAccessMask)
{
	TCHAR                szAccountName[256];
	DWORD                dwSize;
	DWORD                dwResult;
	PACL                 pDacl;
	PACL                 pDaclNew;
	EXPLICIT_ACCESS      explicitAccess;
	PSECURITY_DESCRIPTOR pSecurityDescriptor;
	
	if (GetSecurityInfo(hProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDacl, NULL, &pSecurityDescriptor) != ERROR_SUCCESS)
		return FALSE;

	dwSize = sizeof(szAccountName) / sizeof(TCHAR);
	GetUserName(szAccountName, &dwSize);
	BuildExplicitAccessWithName(&explicitAccess, szAccountName, dwAccessMask, GRANT_ACCESS, 0);
	SetEntriesInAcl(1, &explicitAccess, pDacl, &pDaclNew);

	dwResult = SetSecurityInfo(hProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pDaclNew, NULL);
	
	LocalFree(pDaclNew);
	LocalFree(pSecurityDescriptor);

	return dwResult == ERROR_SUCCESS;
}

まず、GetSecurityInfoで現在設定されているDACLを取得します。 第1引数にプロセスのハンドルを指定しているため、第2引数はSE_KERNEL_OBJECTを指定します。 次に、BuildExplicitAccessWithNameを呼び出して、 現在のユーザーにPROCESS_ALL_ACCESSを許可するACEの情報を作成します。 そして、これと先に取得した既存のDACLをSetEntriesInAclに指定することにより、 両者の内容が合わさった新しいDACLを取得することができます。 後はこれをSetSecurityInfoに指定すれば、プロセスのDACLは書き換えられたことになります。

整合性レベルについて

今回は、オブジェクトのDACLを書き換える方法について説明しましたが、 Windows Vista 以降のWindowsでは、整合性レベルの問題によって書き換えが失敗することがあります。 整合性レベルは、トークンまたはオブジェクトの強力さを表すレベルであり、 トークンに割り当てられた整合性レベルがオブジェクトの整合性レベルより低い場合は、 アクセスが失敗することになります。 たとえば、システムプロセスの整合性レベルはSystemであり、 管理者としてログオンしたユーザーのトークンはHighですが、 このHighはSystemよりもレベルが低いため、アクセスは失敗することになります。 整合性レベルによる検証は、DACLによる通常のアクセスチェックよりも先に行われるため、 たとえDACLによるアクセスチェックが成功する場合でも、 整合性レベルによる検証に失敗した場合はアクセス失敗となります。

整合性レベルが高いオブジェクトに対してアクセスを行うには、 そのオブジェクトの整合性レベルを自プロセスのトークンに合して低くするという方法があります。 このような整合性レベルの変更が、自分より高いオブジェクトに対して機能するはずがないのは明白ですが、 SE_RELABEL_NAME特権が有効になっている場合は変更が可能になります。

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

BOOL ModifyIntegrityLevel(HANDLE hProcess);
BOOL ModifyProcessSecurity(HANDLE hProcess, DWORD dwAccessMask);
DWORD GetTargetProcessId(LPTSTR lpszExeName);
BOOL EnablePrivilege(LPTSTR lpszPrivilege, BOOL bEnable);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HANDLE hProcess;
	DWORD  dwProcessId;
	DWORD  dwAccessMask = PROCESS_ALL_ACCESS;

	if (!EnablePrivilege(SE_RELABEL_NAME, TRUE)) {
		MessageBox(NULL, TEXT("SE_RELABEL_NAME特権の有効に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}

	dwProcessId = GetTargetProcessId(TEXT("sample.exe"));

	hProcess = OpenProcess(dwAccessMask, FALSE, dwProcessId);
	if (hProcess == NULL) {
		hProcess = OpenProcess(WRITE_OWNER, FALSE, dwProcessId);
		if (hProcess == NULL) {
			MessageBox(NULL, TEXT("プロセスのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
			return 0;
		}
			
		if (!ModifyIntegrityLevel(hProcess)) {
			MessageBox(NULL, TEXT("整合性レベルの修正に失敗しました。"), NULL, MB_ICONWARNING);
			CloseHandle(hProcess);
			return 0;
		}

		CloseHandle(hProcess);

		hProcess = OpenProcess(READ_CONTROL | WRITE_DAC, FALSE, dwProcessId);
		if (hProcess == NULL) {
			MessageBox(NULL, TEXT("プロセスのハンドルの再取得に失敗しました。"), NULL, MB_ICONWARNING);
			return 0;
		}

		if (!ModifyProcessSecurity(hProcess, dwAccessMask)) {
			MessageBox(NULL, TEXT("プロセスのDACLの修正に失敗しました。"), NULL, MB_ICONWARNING);
			CloseHandle(hProcess);
			return 0;
		}

		CloseHandle(hProcess);
		
		hProcess = OpenProcess(dwAccessMask, FALSE, dwProcessId);
		if (hProcess == NULL) {
			MessageBox(NULL, TEXT("プロセスのハンドルの再々取得に失敗しました。"), NULL, MB_ICONWARNING);
			return 0;
		}
	}

	if (SetPriorityClass(hProcess, HIGH_PRIORITY_CLASS))
		MessageBox(NULL, TEXT("優先順位を変更しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("優先順位の変更に失敗しました。"), NULL, MB_ICONWARNING);
	
	return 0;
}

BOOL ModifyIntegrityLevel(HANDLE hProcess)
{
	PACL  pSacl;
	PSID  pSid;
	DWORD dwSaclSize;
	DWORD dwSidSize;
	DWORD dwResult;

	dwSaclSize = 256;
	pSacl = (PACL)LocalAlloc(LPTR, dwSaclSize);
	InitializeAcl(pSacl, dwSaclSize, ACL_REVISION);

	dwSidSize = SECURITY_MAX_SID_SIZE;
	pSid = (PSID)LocalAlloc(LPTR, dwSidSize);
	CreateWellKnownSid(WinHighLabelSid, NULL, pSid, &dwSidSize);

	if (!AddMandatoryAce(pSacl, ACL_REVISION, 0, SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, pSid)) {
		MessageBox(NULL, TEXT("ACEの追加に失敗しました。"), NULL, MB_ICONWARNING);
		LocalFree(pSid);
		LocalFree(pSacl);
		return 0;
	}

	dwResult = SetSecurityInfo(hProcess, SE_KERNEL_OBJECT, LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, pSacl);

	LocalFree(pSid);
	LocalFree(pSacl);

	return dwResult == ERROR_SUCCESS;
}

BOOL ModifyProcessSecurity(HANDLE hProcess, DWORD dwAccessMask)
{
	TCHAR                szAccountName[256];
	DWORD                dwSize;
	DWORD                dwResult;
	PACL                 pDacl;
	PACL                 pDaclNew;
	EXPLICIT_ACCESS      explicitAccess;
	PSECURITY_DESCRIPTOR pSecurityDescriptor;
	
	if (GetSecurityInfo(hProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pDacl, NULL, &pSecurityDescriptor) != ERROR_SUCCESS)
		return FALSE;

	dwSize = sizeof(szAccountName) / sizeof(TCHAR);
	GetUserName(szAccountName, &dwSize);
	BuildExplicitAccessWithName(&explicitAccess, szAccountName, dwAccessMask, GRANT_ACCESS, 0);
	SetEntriesInAcl(1, &explicitAccess, pDacl, &pDaclNew);

	dwResult = SetSecurityInfo(hProcess, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pDaclNew, NULL);
	
	LocalFree(pDaclNew);
	LocalFree(pSecurityDescriptor);

	return dwResult == ERROR_SUCCESS;
}

DWORD GetTargetProcessId(LPTSTR lpszExeName)
{
	HANDLE         hSnapshot;
	DWORD          dwProcessId;
	PROCESSENTRY32 pe;
	
	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	if (hSnapshot == INVALID_HANDLE_VALUE)
		return FALSE;

	pe.dwSize = sizeof(PROCESSENTRY32);
	Process32First(hSnapshot, &pe);

	dwProcessId = 0;
	do {
		if (lstrcmp(pe.szExeFile, lpszExeName) == 0) {
			dwProcessId = pe.th32ProcessID;
			break;
		}
	} while (Process32Next(hSnapshot, &pe));
	
	CloseHandle(hSnapshot);

	return dwProcessId;
}

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

	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &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;
}

WRITE_OWNERは、オブジェクトの所有者を書き換える場合に指定するアクセス権ですが、 整合性レベルを書き換える場合にも指定することができます。 そして、このときにSE_RELABEL_NAME特権が有効になっていれば、 WRITE_OWNERは無条件に成功することになります。 ModifyIntegrityLevelは、独自の整合性レベルをプロセスに対して設定します。 まず、CreateWellKnownSidでHighを表すSIDを作成し、 これをAddMandatoryAceに指定することによって、 整合性レベルを格納したACEをSACLに追加します。 そして、SetSecurityInfoにLABEL_SECURITY_INFORMATIONを指定すれば、 オブジェクトに独自のSACLが設定されることになります。 これが成功すれば、整合性レベルが問題でアクセスが失敗することはなくなりますから、 READ_CONTROLとWRITE_DACを指定したOpenProcessは成功するはずです。

SE_RELABEL_NAME特権は、整合性レベルを書き換えるための特権であり、 整合性レベルによる検証を無効にする特権ではないことに注意してください。 この特権を有効にしても、WRITE_OWNER以外は整合性レベルによる検証が行われます。 SE_RELABEL_NAME特権は、既定でAdminidtratorsに割り当てられていないため、 先に示したコードを実行する場合は、LsaAddAccountRightsで明示的に特権を割り当てておく必要があります。 また、先のコードはオブジェクトの整合性レベルをHighにしていますから、 自プロセスのトークンの整合性レベルも当然ながらHighになっていなければなりません。 よって、管理者として実行する必要があります。



戻る