【C++】NT Authority\SYSTEM として外部プログラムを実行する

# 目的
Windows のプロセスは、特定のユーザーとして実行できる。それは、ユーザーアカウントだけではなく、__※サービスアカウントでも同じことだ。__

:::note
※ ここでいうサービスアカウントとは、__NT Authority\SYSTEM__ 等のアカウントを指します。
詳しくは、[こちら](https://learn.microsoft.com/ja-jp/windows-server/identity/ad-ds/manage/understand-service-accounts) をご覧ください。
:::

これが何に役立つかといえば、ユーザー次第では、保護されたシステムファイルを操作するなどの、__Administrator 以上の権限を必要とする場合だ。__ (SeBackupPrivilege と SeRestorePrivilege 特権が有効化された管理者権限を持つプロセスなら、ACL を無視して操作ができる。)

:::note alert
__NT Authority\SYSTEM はとても強力な権限を持ちます。__
下手に扱うとコンピュータのセキュリティリスクを高めるだけなので、__自己責任で行ってください。__
:::

# 使用環境
* IDE: [Visual Studio 2022](https://visualstudio.microsoft.com/ja/vs/)
* バージョン: [C++20](https://cpprefjp.github.io/lang/cpp20.html)

# ソース
<details><summary>ソースファイル</summary>

```c++:wWinMain.cpp
#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <TlHelp32.h>

#include <iostream>
#include <vector>
#include <memory>

namespace {
	constexpr LPCWSTR lpCommand = L"C:\\Windows\\System32\\cmd.exe";	// 実行したいファイル・コマンド

	constexpr LPCWSTR lpProcessImageName = L"Winlogon.exe";	// NT Authority\SYSTEM として実行されているプロセス ※注): 必ずプロセスが1つのみしか存在しないもの
}

::INT APIENTRY wWinMain(
	_In_		::HINSTANCE	hInst,
	_In_opt_	::HINSTANCE	/* hPrvInst = nullptr */,
	_In_		::LPWSTR	lpCmdLine,
	_In_		::INT		nCmdShow
) {
	::HANDLE hCurrentToken = nullptr;	// 自分のトークンハンドル
	
	/* プロセス(自分)のトークンハンドルを取得 */
	::OpenProcessToken(
		::OpenProcess(
			PROCESS_ALL_ACCESS,
			FALSE,
			::GetCurrentProcessId()
		),
		TOKEN_ALL_ACCESS,
		&hCurrentToken
	);

	if (!hCurrentToken) return -1;

	// 必要な特権
	std::vector<::LPCWSTR> PrivilegeNames{
		SE_DEBUG_NAME, SE_INCREASE_QUOTA_NAME, SE_ASSIGNPRIMARYTOKEN_NAME,
		SE_CREATE_TOKEN_NAME, SE_TCB_NAME, SE_DELEGATE_SESSION_USER_IMPERSONATE_NAME,
		SE_IMPERSONATE_NAME
	};

	::DWORD dwcbComputerNameLength = 256;
	auto szComputerName = std::make_unique<wchar_t[]>(dwcbComputerNameLength);

	// コンピュータの名前を取得
	if (!::GetComputerNameW(szComputerName.get(), &dwcbComputerNameLength)) {
		szComputerName = nullptr;
	}

	/* 必要な特権の有効化 */
	for (auto& lpPrivilegeName : PrivilegeNames) {
		::TOKEN_PRIVILEGES tp{};
		::LUID luId{};

		if (!::LookupPrivilegeValueW(
			szComputerName.get(),
			lpPrivilegeName,
			&luId
		)) return ::GetLastError();

		tp.PrivilegeCount = 1;
		tp.Privileges[0].Luid = luId;
		tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

		if (!::AdjustTokenPrivileges(
			hCurrentToken,
			FALSE,
			&tp,
			sizeof(tp),
			nullptr,
			nullptr
		)) return ::GetLastError();
	}

	// lpProcessImageName が示すプロセスの PID
	::DWORD dwProcessImagePID = -1;

	/* lpProcessImageName が示すプロセスの PID を取得 */
	::HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
	::PROCESSENTRY32W ProcessEntry{ sizeof(::PROCESSENTRY32W) };

	if (!::Process32FirstW(hSnapshot, &ProcessEntry)) return ::GetLastError();

	do {
		if (!::_wcsicmp(::lpProcessImageName, ProcessEntry.szExeFile)) {
			::CloseHandle(hSnapshot);
			
			dwProcessImagePID = ProcessEntry.th32ProcessID;

			break;
		}
	} while (::Process32NextW(hSnapshot, &ProcessEntry));

	if (dwProcessImagePID == -1) return -1;

	// lpProcessImageName が示すプロセスのトークンハンドル
	::HANDLE hSystemProcessToken = nullptr;

	// lpProcessImageName が示すプロセスのトークンハンドルを取得 (NT Authority\SYSTEM として実行されているため)
	if (!::OpenProcessToken(
		::OpenProcess(
			PROCESS_QUERY_INFORMATION,
			FALSE, 
			dwProcessImagePID
		),
		TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_IMPERSONATE,
		&hSystemProcessToken
	)) return ::GetLastError();

	// NT Authority\SYSTEM を示すトークンハンドル
	::HANDLE hToken = nullptr;
	
	// トークンハンドルを複製
	if (!::DuplicateTokenEx(
		hSystemProcessToken,
		TOKEN_ALL_ACCESS,
		nullptr,
		::SECURITY_IMPERSONATION_LEVEL::SecurityIdentification,
		::TOKEN_TYPE::TokenPrimary,
		&hToken
	)) return ::GetLastError();

	// プロセスの作成に必要な情報
	::STARTUPINFOW stInf{};
	::RtlSecureZeroMemory(&stInf, sizeof(STARTUPINFOW));

	stInf.cb = sizeof(stInf);

	// 既定のデスクトップ名を明示的に指定 (ウィンドウを表示されるため)
	wchar_t szDesktopName[] = L"Winsta0\\default";
	stInf.lpDesktop = szDesktopName;
	
	// プロセスに関する情報
	::PROCESS_INFORMATION pcInf{};
	::RtlSecureZeroMemory(&pcInf, sizeof(PROCESS_INFORMATION));

	// 取得したトークンハンドルをもとに、プログラムを実行
	auto bRet = ::CreateProcessWithTokenW(
		hToken,
		LOGON_WITH_PROFILE,
		::lpCommand,
		nullptr,
		NULL,
		nullptr,
		nullptr,
		&stInf,
		&pcInf
	);
	if (!bRet) {
		return ::GetLastError();
	}

	// 不要なハンドルをクローズ、開放
	::CloseHandle(pcInf.hProcess);
	::CloseHandle(pcInf.hThread);

	// 正常終了
	return 0;
}
```
</details>

# 仕組み
このプログラムは以下のような処理を主に行い、NT Authority\SYSTEM として実行する。

|関数|処理|
|:-:|:-:|
|OpenProcessToken|自分のプロセスのトークンハンドルを取得|
|AdjustTokenPrivileges|取得したトークンハンドルを用いて、必要な特権を有効化|
|Toolhelp32 系関数|プロセスを列挙し、NT Authority\SYSTEM として実行されているプロセスの PID を取得|
|OpenProcessToken|取得した PID から、トークンハンドルを取得|
|DuplicateTokenEx|取得したトークンハンドルを複製|
|CreateProcessWithToken|複製したトークンハンドルからプロセスを作成|

※ 処理の順番は上から下に

つまり、__NT Authority\SYSTEM として実行されているプロセスから実行しているユーザー情報を抜き取って、それをもとに新しいプロセスを作成する__ という処理を行っているのだ。