简体   繁体   English

子进程中的C ++ Windows LocalSystem模拟失败

[英]C++ windows LocalSystem impersonation in child process failing

Trying to resolve it but all efforts are in vain so far. 试图解决它,但到目前为止所有努力都是徒劳的。 The workflow as follows 工作流程如下

Windows service running as LocalSystem creates child using CreateProcessAsUser(...) with token of current logged user. 以LocalSystem运行的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

The child process is launched in user workstation\\desktop and main thread captures user I/O events. 子进程在用户工作站\\桌面中启动,主线程捕获用户I / O事件。 The child process impersonation is as follows 子进程模拟如下

 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;
  }
} 

Though current user is Administrator and added "Impersonate a Client After authentication" it still reporting "Security Identification". 尽管当前用户是管理员,并添加了“身份验证后模拟客户端”,但仍报告“安全性标识”。

Q: Is there anything else required to raise it to Security impersonate? 问:还有其他要求将其提升为安全模拟角色吗? Thanks, 谢谢,

how I understand you do next - you duplicate LocalSystem token, from service, to child process (via inherit handle) and pass it handle value in command line. 我接下来的工作方式-您将LocalSystem令牌从服务复制到子进程(通过继承句柄),然后在命令行中将其传递给句柄值。 then you call SetThreadToken . 然后调用SetThreadToken

but documentation of SetThreadToken is wrong and incomplete. 但是SetThreadToken文档错误SetThreadToken完整。

here only said that token must have TOKEN_IMPERSONATE access rights. 这里只说令牌必须具有TOKEN_IMPERSONATE访问权限。 nothing said about Thread handle access rights - it must have THREAD_SET_THREAD_TOKEN 关于线程句柄访问权限的任何说明-它必须具有THREAD_SET_THREAD_TOKEN

but main: 但主要:

When using the SetThreadToken function to impersonate, you must have the impersonate privileges and make sure that the SetThreadToken function succeeds 使用SetThreadToken函数进行模拟时,必须具有模拟特权 ,并确保SetThreadToken函数成功

what is mean under you must have ? 你必须拥有什么意思? usually this mean that calling thread (or process to which calling thread belong in case thread have no token) must have impersonate privileges in token. 通常,这意味着调用线程(或在线程没有令牌的情况下,调用线程所属的进程)必须在令牌中具有模拟特权。

but this is wrong and not true. 但这是错误的,而不是正确的。 which privilege you ( calling thread ) have - does not matter. 您(调用线程)具有哪种特权-没关系。 the process ( even if target thread have token ) to which target (not calling !) thread belong must have SeImpersonatePrivilege privilege or have the same logon session id as impersonation token, otherwise .. no, function not fail, and return succeeds, but it silently replace SECURITY_IMPERSONATION_LEVEL member in token to SecurityIdentification (look in WRK-v1.2\\base\\ntos\\ps\\security.c PsImpersonateClient function - begin from SeTokenCanImpersonate (implemented in WRK-v1.2\\base\\ntos\\se\\token.c - here and checked TOKEN_HAS_IMPERSONATE_PRIVILEGE and LogonSessionId) and if fail ( STATUS_PRIVILEGE_NOT_HELD ) returned by SeTokenCanImpersonate - the PsImpersonateClient function set ImpersonationLevel = SecurityIdentification ; 目标(未调用!)线程所属的进程( 即使目标线程具有令牌 )也必须具有SeImpersonatePrivilege特权或具有与模拟令牌相同的登录会话ID,否则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 ;

so even if you call SetThreadToken from service (which have impersonation privilege) for child process thread - call is "fail" if child process have not impersonation privilege. 因此,即使您从服务(具有模拟特权)为子进程线程调用SetThreadToken如果子进程没有模拟特权,则调用为“失败”。 and visa versa - if you say pass(duplicate) own thread handle (with THREAD_SET_THREAD_TOKEN access rights) to restricted process, which have not impersonation privilege - he can success call SetThreadToken for your thread - impersonation level will be not reset to SecurityIdentification 反之亦然-如果您说将自己的线程句柄(具有THREAD_SET_THREAD_TOKEN访问权限)传递给不具有模拟特权的受限进程,则他可以为您的线程成功调用SetThreadToken模拟级别不会重置为SecurityIdentification

in your case, because child process have no SeImpersonatePrivilege (usually it exist only in elevated processes, but if user enter to system with LOGON32_LOGON_INTERACTIVE - even "admins" have really restricted token (so they not really true admins)) and have different session id (compare local system token session id) - after SetThreadToken your thread have SecurityIdentification impersonation level. 在您的情况下,因为子进程没有SeImpersonatePrivilege (通常仅存在于提升的进程中,但是如果用户使用LOGON32_LOGON_INTERACTIVE进入系统-甚至“管理员”也确实限制了令牌(因此他们不是真正的管理员))并且具有不同的会话ID (比较本地系统令牌会话ID)-在SetThreadToken之后,您的线程具有SecurityIdentification模拟级别。 as result any system call, where security checked (say open file or registry key) will fail with error ERROR_BAD_IMPERSONATION_LEVEL . 结果,任何进行安全检查(例如打开文件或注册表项)的系统调用都将失败,并显示错误ERROR_BAD_IMPERSONATION_LEVEL

how about solution ? 解决方案怎么样? if user have admin privileges - you need create elevated child process in user session (like "run as admin" ). 如果用户具有管理员特权 -您需要在用户会话中创建提升的子进程(例如“以admin身份运行”)。 for this you need query elevation type of token returned by WTSQueryUserToken and if it is TokenElevationTypeLimited - we need get linked token by GetTokenInformation call with TokenLinkedToken . 为此,您需要查询WTSQueryUserToken返回的令牌的提升类型,如果它是TokenElevationTypeLimited我们需要通过GetTokenInformation调用使用TokenLinkedToken获得链接令牌

this is complete undocumented, but which token returned in TOKEN_LINKED_TOKEN structure depend from are calling thread (or process) have SE_TCB_PRIVILEGE - if yes - TokenPrimary is returned. 这是完全未记录的,但是TOKEN_LINKED_TOKEN结构中返回的令牌取决于调用线程(或进程)是否具有SE_TCB_PRIVILEGE如果是,则返回TokenPrimary otherwise TokenImpersonation is returned with SECURITY_IMPERSONATION_LEVEL set to SecurityIdentification (so this token can be used only for query). 否则,返回TokenImpersonation并将SECURITY_IMPERSONATION_LEVEL设置为SecurityIdentification (因此,此令牌只能用于查询)。 because service running under Local system account have SE_TCB_PRIVILEGE - you got the primary token, which you need use in CreateProcessAsUser call as is. 因为在本地系统帐户下运行的服务具有SE_TCB_PRIVILEGE您获得了主令牌,因此需要在CreateProcessAsUser调用中使用它。 so you need next function: 所以你需要下一个功能:

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;
}

and use next code for start child 并使用下一个代码作为开始子对象

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);
}

in this case you may be not need at all impersonate to LocalSystem in child process. 在这种情况下,您可能根本不需要在子进程中模拟LocalSystem however if still need LocalSystem - you can duplicate such token in child process and in this case SetThreadtoken will be full ok, because child process will be have impersonate privileges 但是,如果仍然需要LocalSystem ,则可以在子进程中复制此类令牌,​​在这种情况下, SetThreadtoken完全可以,因为子进程将具有模拟特权

Forgive me for asking what should be obvious but it needs to be asked: 请原谅我提出哪些显而易见的要求,但需要提出以下要求:

Are you checking the return values of these functions? 您是否正在检查这些函数的返回值? Calling GetLastError when they fail? 它们失败时调用GetLastError吗? What error codes are you getting back? 您返回什么错误代码?

If this is C++ are you setting an unhandled exception handler? 如果这是C ++,是否要设置未处理的异常处理程序?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM