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, &quotaLimits))
  {
    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

これで起動したhello.exeを

このように、BUILTIN\Administratorsが追加されています。それに伴い、特権も増えているようです。

書いてみて気付いた注意事項です。

  • MSDNライブラリには「GetProcAddressして使いなさい」と書いてありますが、その必要はありません。インポートライブラリには情報が入っているので、自分でプロトタイプ宣言すれば使えます。これに限らず、最近のWindows SDKはそんな感じです。
  • 最初にLogonUserを呼び出している理由は、ログオンSIDを入手するためというのが最も大きいです。LogonUserExExWにTOKEN_GROUPSを渡す場合、ログオンSIDが含まれていないと、失敗になるようです。通常はOpenProcessTokenで自分自身のアクセストークンを取り出せば良いのでしょうが、LocalSystemアカウントのアクセストークンはログオンSIDが含まれないので、今回はLogonUserを使うことにしました。

もちろん使いどころは限られますが、(便利という意味で)面白いAPI関数です。


スポンサード リンク

この記事のカテゴリ

  • ⇒ LogonUserExExWを使ってみた
  • ⇒ LogonUserExExWを使ってみた