目的
Windows のプロセスは、特定のユーザーとして実行できる。それは、ユーザーアカウントだけではなく、※サービスアカウントでも同じことだ。
※ ここでいうサービスアカウントとは、NT Authority\SYSTEM 等のアカウントを指します。
詳しくは、こちら をご覧ください。
これが何に役立つかといえば、ユーザー次第では、保護されたシステムファイルを操作するなどの、Administrator 以上の権限を必要とする場合だ。 (SeBackupPrivilege と SeRestorePrivilege 特権が有効化された管理者権限を持つプロセスなら、ACL を無視して操作ができる。)
NT Authority\SYSTEM はとても強力な権限を持ちます。
下手に扱うとコンピュータのセキュリティリスクを高めるだけなので、自己責任で行ってください。
使用環境
- IDE: Visual Studio 2022
- バージョン: C++20
ソース
ソースファイル
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;
}
仕組み
このプログラムは以下のような処理を主に行い、NT Authority\SYSTEM として実行する。
関数 | 処理 |
---|---|
OpenProcessToken | 自分のプロセスのトークンハンドルを取得 |
AdjustTokenPrivileges | 取得したトークンハンドルを用いて、必要な特権を有効化 |
Toolhelp32 系関数 | プロセスを列挙し、NT Authority\SYSTEM として実行されているプロセスの PID を取得 |
OpenProcessToken | 取得した PID から、トークンハンドルを取得 |
DuplicateTokenEx | 取得したトークンハンドルを複製 |
CreateProcessWithToken | 複製したトークンハンドルからプロセスを作成 |
※ 処理の順番は上から下に
つまり、NT Authority\SYSTEM として実行されているプロセスから実行しているユーザー情報を抜き取って、それをもとに新しいプロセスを作成する という処理を行っているのだ。