LogonUserExExW関数を使ってみました。この関数のすごいところは、好きなグループを追加してアクセストークンを作成できるという点です。ただし、そういう使い方をするには特権SE_TCB_PRIVILEGEが必要です。
今までも同様のことはLsaLogonUserで可能だったはずですが、LogonUserExExWのほうが使い方が簡単です。LSAハンドルを開くなどの事前準備が不用だからです。
というわけで、これを使ってプロセスを起動するプログラムを書いてみました。本来ならUsersグループのLocalServiceアカウントに、Administratorsグループを追加しています。
// cl logon.cpp advapi32.lib userenv.lib #define UNICODE #define _UNICODE #define WIN32_LEAN_AND_MEAN #define _ATL_NO_AUTOMATIC_NAMESPACE #include <iostream> #include <locale> #include <memory> #include <windows.h> #include <userenv.h> #include <atlbase.h> #include <atlutil.h> extern "C" BOOL WINAPI LogonUserExExW( _In_ LPCWSTR lpszUsername, _In_opt_ LPCWSTR lpszDomain, _In_opt_ LPCWSTR lpszPassword, _In_ DWORD dwLogonType, _In_ DWORD dwLogonProvider, _In_opt_ PTOKEN_GROUPS pTokenGroups, _Outptr_opt_ PHANDLE phToken, _Outptr_opt_ PSID *ppLogonSid, _Outptr_opt_result_bytebuffer_all_(*pdwProfileLength) PVOID *ppProfileBuffer, _Out_opt_ LPDWORD pdwProfileLength, _Out_opt_ PQUOTA_LIMITS pQuotaLimits); struct env_deleter { void operator()(void* env) const throw() { DestroyEnvironmentBlock(env); } }; std::unique_ptr<void, env_deleter> create_environment_block( _In_ HANDLE hToken, BOOL inherit) { void* tmp; if (CreateEnvironmentBlock(&tmp, hToken, inherit)) { return std::unique_ptr<void, env_deleter>(tmp); } else { return nullptr; } } HRESULT OutputErrorMessgae(_In_ PCWSTR functionName, HRESULT hr) { std::wclog << functionName << '\n'; std::wclog << std::showbase << std::hex << hr << '\n'; std::wclog << ATL::AtlGetErrorDescription(hr).GetString() << std::endl; return hr; } HRESULT OutputLastError(_In_ PCWSTR functionName) { auto hr = ATL::AtlHresultFromLastError(); return OutputErrorMessgae(functionName, hr); } template<typename... Args> SE_SID MakeSid( BYTE revision, SID_IDENTIFIER_AUTHORITY identifierAuthority, Args&&... args) { SE_SID sid{ revision, sizeof...(args), identifierAuthority }; DWORD subAuthority[] = { static_cast<DWORD>(args)... }; std::copy( std::begin(subAuthority), std::end(subAuthority), sid.Sid.SubAuthority); return sid; } int main() { std::wclog.imbue(std::locale(std::locale::classic(), "", std::locale::ctype)); ATL::CHandle hTokenBase; if (!LogonUser( L"LocalService", L"NT AUTHORITY", nullptr, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, &hTokenBase.m_h)) { return OutputLastError(L"LogonUserExExW (hTokenBase)"); } DWORD tokenGroupsBufferSize; auto result = GetTokenInformation( hTokenBase, TokenGroups, nullptr, 0, &tokenGroupsBufferSize); auto e = GetLastError(); if (result == FALSE && e != ERROR_INSUFFICIENT_BUFFER) { return OutputErrorMessgae( L"GetTokenInformation (buffer size)", HRESULT_FROM_WIN32(e)); } auto tokenGroupsBuffer = std::make_unique<BYTE[]>(tokenGroupsBufferSize); if (!GetTokenInformation( hTokenBase, TokenGroups, tokenGroupsBuffer.get(), tokenGroupsBufferSize, &tokenGroupsBufferSize)) { return OutputLastError(L"GetTokenInformation"); } auto tgBase = reinterpret_cast<const TOKEN_GROUPS*>( tokenGroupsBuffer.get()); auto sidAdministrators = MakeSid( SID_REVISION, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); std::size_t bufferSize = sizeof(TOKEN_GROUPS) + (tgBase->GroupCount - 1 + 1) * sizeof tgBase->Groups[0]; auto buffer = std::make_unique<BYTE[]>(bufferSize); memcpy(buffer.get(), tgBase, bufferSize); auto tg = reinterpret_cast<TOKEN_GROUPS*>(buffer.get()); tg->GroupCount++; tg->Groups[tgBase->GroupCount].Sid = &sidAdministrators; tg->Groups[tgBase->GroupCount].Attributes = SE_GROUP_MANDATORY | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_ENABLED; ATL::CHandle hToken; PSID logonSid; void* profileBuffer; DWORD profileLength; QUOTA_LIMITS quotaLimits; if (!LogonUserExExW( L"LocalService", L"NT AUTHORITY", nullptr, LOGON32_LOGON_SERVICE, LOGON32_PROVIDER_DEFAULT, tg, &hToken.m_h, &logonSid, &profileBuffer, &profileLength, "aLimits)) { return OutputLastError(L"LogonUserExExW"); } STARTUPINFO si{ sizeof si }; PROCESS_INFORMATION pi{}; #if 1 if (!CreateProcessWithTokenW( hToken, LOGON_WITH_PROFILE, LR"(T:\hello.exe)", nullptr, 0, nullptr, nullptr, &si, &pi)) { return OutputLastError(L"CreateProcessWithTokenW"); } #else auto env = create_environment_block(hToken, FALSE); if (!CreateProcessAsUser( hToken, LR"(T:\hello.exe)", nullptr, nullptr, nullptr, FALSE, CREATE_UNICODE_ENVIRONMENT, env.get(), nullptr, &si, &pi)) { return OutputLastError(L"CreateProcessAsUser"); } #endif CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } |
CreateProcessWithTokenWとCreateProcessAsUser、どちらでもうまくいきました。次に、そこで指定しているhello.exeのソースコードを載せます。デスクトップにアクセスするための権限がないため、ファイルに出力するようにしています。
#include <fstream> #include <windows.h> int WINAPI WinMain(HINSTANCE, HINSTANCE, char*, int) { std::ofstream s(R"(T:\hello.txt)"); s << "hello world" << std::endl; Sleep(10000); // プロセスの存在を確認するため } |
これを以下のように起動します。LocalSystemアカウントで実行するため、PsExecを使います。
T:\>psexec -sid T:\logon.exe
このように、BUILTIN\Administrators
が追加されています。それに伴い、特権も増えているようです。
書いてみて気付いた注意事項です。
- MSDNライブラリには「GetProcAddressして使いなさい」と書いてありますが、その必要はありません。インポートライブラリには情報が入っているので、自分でプロトタイプ宣言すれば使えます。これに限らず、最近のWindows SDKはそんな感じです。
- 最初にLogonUserを呼び出している理由は、ログオンSIDを入手するためというのが最も大きいです。LogonUserExExWにTOKEN_GROUPSを渡す場合、ログオンSIDが含まれていないと、失敗になるようです。通常はOpenProcessTokenで自分自身のアクセストークンを取り出せば良いのでしょうが、LocalSystemアカウントのアクセストークンはログオンSIDが含まれないので、今回はLogonUserを使うことにしました。
もちろん使いどころは限られますが、(便利という意味で)面白いAPI関数です。
スポンサード リンク |