Сбой подмены LocalSystem в C++ в дочернем процессе

Пытаюсь решить ее, но все усилия пока тщетны. Рабочий процесс следующим образом

Служба Windows работает как локальная система создает дочерний с помощью CreateProcessAsUser(...) с токеном текущего зарегистрированного пользователя.

const auto session = WTSGetActiveConsoleSessionId();
auto result = WTSQueryUserToken(session, &token);

HANDLE primary;
result = DuplicateTokenEx(token,
    TOKEN_QUERY_SOURCE | TOKEN_ALL_ACCESS | TOKEN_IMPERSONATE |
    TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ADJUST_PRIVILEGES,
    nullptr, SecurityImpersonation, TokenPrimary, &primary);

const auto args = std::to_string(reinterpret_cast<long>(access));
CreateProcessAsUser(primary,
                      const_cast<LPSTR>(command.c_str()), // module name
                      const_cast<LPSTR>(args.c_str()),    // Command line
                      nullptr, // Process handle not inheritable
                      nullptr, // Thread handle not inheritable
                      TRUE,    // Set handle inheritance to TRUE
                      0,       // No creation flags
                      nullptr, // Use parent's environment block
                      nullptr, // Use parent's starting directory
                      &si,     // Pointer to STARTUPINFO structure
                      &pi);    // Pointer to PROCESS_INFORMATION structure

Дочерний процесс запускается на пользовательской рабочей станции \ рабочем столе, и основной поток записывает события ввода-вывода пользователя. Процесс подражания дочернего процесса выглядит следующим образом

 void impersonate() {
  const auto args = GetCommandLine();
  const auto system_token = reinterpret_cast<HANDLE>(std::stol(args, nullptr));

  if (SetThreadToken(nullptr, system_token) == TRUE) {
   auto result = OpenThreadToken(GetCurrentThread(),
                            TOKEN_QUERY | TOKEN_QUERY_SOURCE, TRUE, &token);
  if (result == TRUE)
  {
    DWORD dwSize = 0;

   if (!GetTokenInformation(token, TokenStatistics, NULL, 0, &dwSize)) {
      const auto dwResult = GetLastError();

          if (dwResult != ERROR_INSUFFICIENT_BUFFER) {
            cout << "GetTokenInformation Error: " << dwResult;

          } else {
          // Allocate the buffer.
            PTOKEN_STATISTICS statistics =
                (PTOKEN_STATISTICS)GlobalAlloc(GPTR, dwSize);

            // Call GetTokenInformation again to get the group information.
            if (!GetTokenInformation(token, TokenStatistics, statistics, dwSize,
                           &dwSize)) {
              cout << "GetTokenInformation Error: " << error;
            } else {
              const auto level = statistics->ImpersonationLevel;
              std::string str;

              switch (level) {
              case SecurityAnonymous:
                str = R"(anonymous)";
                break;
              case SecurityIdentification:
                str = R"(identification)";
                break;
              case SecurityImpersonation:
                str = R"(impersonation)";
                break;
              case SecurityDelegation:
                str = R"(delegation)";
                break;
              }

              // This outputs identification.
              cout << "impersonation level : " << str;  
          }
      }
   }
 }

void thread_main() 
{
   impersonate();

   // if impersonation is successful, file opening fails otherwise not.
   const auto file = CreateFile(R"(C:\foo.txt)",                // name of the write
                   GENERIC_WRITE,          // open for writing
                   0,                      // do not share
                   NULL,                   // default security
                   CREATE_NEW,             // create new file only
                   FILE_ATTRIBUTE_NORMAL,  // normal file
                   NULL);                  // no attr. template

  if (file == INVALID_HANDLE_VALUE) {

  } else {
    // Rest of code;
  }
} 

Хотя текущий пользователь является администратором и добавил "Олицетворять клиента после аутентификации", он по-прежнему сообщает "Идентификация безопасности".

Q: Есть ли что-то еще, чтобы поднять это до безопасности подражать? Спасибо,

2 ответа

Решение

Насколько я понимаю, что вы делаете дальше - вы дублируете токен LocalSystem, от службы, до дочернего процесса (через наследующий дескриптор) и передаете его значение дескриптора в командной строке. тогда вы звоните SetThreadToken,

но документация SetThreadToken неправильно и неполно

вот только сказал, что токен должен иметь TOKEN_IMPERSONATE права доступа. ничего не сказано о правах доступа к дескриптору потока - он должен иметь THREAD_SET_THREAD_TOKEN

но главное:

При использовании функции SetThreadToken для олицетворения необходимо иметь привилегии олицетворения и убедиться, что функция SetThreadToken завершается успешно.

что значит под тобой должно быть? обычно это означает, что вызывающий поток (или процесс, которому принадлежит вызывающий поток в случае, если у потока нет токена) должен иметь права олицетворения в токене.

но это неправильно и не правда. какая привилегия у вас (вызывающего потока) - не имеет значения. процесс (даже если целевой поток имеет токен), которому должен принадлежать целевой (не вызывающий!) поток, должен иметь SeImpersonatePrivilege привилегия или иметь тот же идентификатор сеанса входа в систему, что и маркер олицетворения, в противном случае.. нет, функция не завершается ошибкой, и возврат завершается успешно, но он молча заменяет SECURITY_IMPERSONATION_LEVEL участник в токене SecurityIdentification (посмотрите в WRK-v1.2\base\ntos\ps\security.c PsImpersonateClient функция - начать с SeTokenCanImpersonate (реализовано в WRK-v1.2 \ base \ ntos \ se \ token.c - здесь и проверено TOKEN_HAS_IMPERSONATE_PRIVILEGE и LogonSessionId) и если не удается (STATUS_PRIVILEGE_NOT_HELD) вернулся SeTokenCanImpersonate - PsImpersonateClient набор функций ImpersonationLevel = SecurityIdentification ;

так что даже если вы позвоните SetThreadToken из службы (которая имеет привилегию олицетворения) для потока дочернего процесса - вызов "fail", если дочерний процесс не имеет привилегии олицетворения. и наоборот - если вы скажете передать (дублировать) собственный дескриптор потока (с THREAD_SET_THREAD_TOKEN права доступа) к ограниченному процессу, который не имеет права олицетворения - он может успешно вызвать SetThreadToken для вашей темы - уровень олицетворения не будет сброшен до SecurityIdentification

в вашем случае, потому что дочерний процесс не имеет SeImpersonatePrivilege (обычно это существует только в повышенных процессах, но если пользователь входит в систему с LOGON32_LOGON_INTERACTIVE - даже "администраторы" действительно ограничивают токен (поэтому они не являются настоящими администраторами) и имеют другой идентификатор сеанса (сравните идентификатор сеанса токена локальной системы) - после SetThreadToken ваша нить имеет SecurityIdentification уровень олицетворения. в результате любой системный вызов, в котором проверена безопасность (скажем, открытый файл или ключ реестра), завершится с ошибкой ERROR_BAD_IMPERSONATION_LEVEL,

как насчет решения? если у пользователя есть права администратора - вам нужно создать дочерний процесс с повышенными правами в сеансе пользователя (например, "запуск от имени администратора"). для этого вам нужен тип возвышения запроса токена, возвращаемого WTSQueryUserToken и если это TokenElevationTypeLimited - нам нужно получить токен GetTokenInformation позвонить с TokenLinkedToken,

это полностью недокументировано, но какой токен возвращен в TOKEN_LINKED_TOKEN структура зависит от вызывающего потока (или процесса) SE_TCB_PRIVILEGE - Если да - TokenPrimary возвращается иначе TokenImpersonation возвращается с SECURITY_IMPERSONATION_LEVEL установлен в SecurityIdentification (поэтому этот токен можно использовать только для запроса). потому что служба работает под учетной записью локальной системы имеют SE_TCB_PRIVILEGE - вы получили основной токен, который вам нужно использовать в CreateProcessAsUser называть как есть. поэтому вам нужна следующая функция:

ULONG GetElevatedUserToken(PHANDLE phToken)
{
    union {
        ULONG SessionId;
        TOKEN_ELEVATION_TYPE tet;
        TOKEN_LINKED_TOKEN tlt;
        TOKEN_TYPE tt;
    };

    SessionId = WTSGetActiveConsoleSessionId();

    if (SessionId == MAXDWORD)
    {
        return ERROR_NO_SUCH_LOGON_SESSION;
    }

    HANDLE hToken;

    if (!WTSQueryUserToken(SessionId, &hToken))
    {
        return GetLastError();
    }

    ULONG len;

    ULONG dwError = NOERROR;

    if (GetTokenInformation(hToken, TokenElevationType, &tet, sizeof(tet), &len))
    {
        if (tet == TokenElevationTypeLimited)
        {
            if (GetTokenInformation(hToken, TokenLinkedToken, &tlt, sizeof(tlt), &len))
            {
                CloseHandle(hToken);
                hToken = tlt.LinkedToken;
            }
            else
            {
                dwError = GetLastError();
            }
        }
    }
    else
    {
        dwError = GetLastError();
    }

    if (dwError == NOERROR)
    {
        if (GetTokenInformation(hToken, TokenType, &tt, sizeof(tt), &len))
        {
            if (tt != TokenPrimary)
            {
                dwError = ERROR_INVALID_HANDLE;
            }
        }
        else
        {
            dwError = GetLastError();
        }

        if (dwError == NOERROR)
        {
            *phToken = hToken;
            return NOERROR;
        }

        CloseHandle(hToken);
    }

    return dwError;
}

и использовать следующий код для запуска ребенка

HANDLE hToken;

ULONG dwError = GetElevatedUserToken(&hToken);

if (dwError == NOERROR)
{
    STARTUPINFO si = { sizeof(si) };
    PROCESS_INFORMATION pi;
    //***
    if (CreateProcessAsUser(hToken, ***, &si, &pi))
    {
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
    }

    CloseHandle(hToken);
}

в этом случае вам может не понадобиться выдавать себя за LocalSystem в дочернем процессе. однако, если все еще нужен LocalSystem - вы можете продублировать такой токен в дочернем процессе и в этом случае SetThreadtoken будет полностью нормально, потому что дочерний процесс будет иметь олицетворение привилегий

Простите за вопрос, что должно быть очевидным, но это нужно спросить:

Вы проверяете возвращаемые значения этих функций? Вызываете GetLastError, когда они терпят неудачу? Какие коды ошибок вы получаете обратно?

Если это C++, вы устанавливаете обработчик необработанного исключения?

Другие вопросы по тегам