0

试图解决它,但到目前为止所有的努力都是徒劳的。工作流程如下

CreateProcessAsUser(...)作为 LocalSystem 运行的 Windows 服务使用当前登录用户的令牌创建子级。

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

子进程在用户工作站\桌面启动,主线程捕获用户 I/O 事件。子进程模拟如下

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

尽管当前用户是管理员并添加了“身份验证后模拟客户端”,但它仍然报告“安全标识”。

问:将其提升为安全模拟还需要其他什么吗?谢谢,

4

2 回答 2

3

我如何理解你下一步做什么 - 你将LocalSystem令牌从服务复制到子进程(通过继承句柄)并在命令行中传递它的句柄值。然后你打电话SetThreadToken

但文档SetThreadToken是错误且不完整的。

这里只说token必须有TOKEN_IMPERSONATE访问权限。没有说线程句柄访问权限——它必须有THREAD_SET_THREAD_TOKEN

但主要:

使用SetThreadToken函数进行模拟时,必须具有模拟权限并确保 SetThreadToken函数成功

你必须有什么意思?通常这意味着调用线程(或调用线程所属的进程,以防线程没有令牌)必须在令牌中具有模拟权限。

但这是错误的,不是真的。您(调用线程)拥有的特权 - 无关紧要。目标(不调用!)线程所属的进程(即使目标线程具有令牌SeImpersonatePrivilege)必须具有特权或具有与模拟令牌相同的登录会话ID,否则..不,函数不会失败,并且返回成功,但它会默默地将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从服务(具有模拟特权)调用子进程线程 - 如果子进程没有模拟特权,调用也是“失败”。反之亦然-如果您说将自己的线程句柄(具有THREAD_SET_THREAD_TOKEN访问权限)传递给没有模拟特权的受限进程-他可以成功调用您的SetThreadToken线程-模拟级别将不会重置为SecurityIdentification

在您的情况下,因为子进程没有SeImpersonatePrivilege(通常它只存在于提升的进程中,但如果用户使用LOGON32_LOGON_INTERACTIVE- 即使“管理员”确实具有受限令牌(所以他们不是真正的管理员))并且具有不同的会话 ID(比较本地系统令牌会话 id) - 在SetThreadToken您的线程具有SecurityIdentification模拟级别之后。结果是任何系统调用,其中安全检查(例如打开文件或注册表项)将失败并出现错误ERROR_BAD_IMPERSONATION_LEVEL

解决方案如何?如果用户具有管理员权限- 您需要在用户会话中创建提升的子进程(例如“以管理员身份运行”)。为此,您需要查询返回的令牌的提升类型,WTSQueryUserToken如果是TokenElevationTypeLimited- 我们需要通过调用来获取链接令牌GetTokenInformationTokenLinkedToken

这是完全未记录的,但是在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完全可以,因为子进程将具有模拟权限

于 2017-08-10T22:38:53.763 回答
0

请原谅我问什么应该是显而易见的,但需要问:

你在检查这些函数的返回值吗?失败时调用 GetLastError?你得到什么错误代码?

如果这是 C++,您是否设置了未处理的异常处理程序?

于 2017-08-10T12:48:17.290 回答