WTSQueryUserToken 抛出错误 1008,即使在 运行 下的 LocalSystem 下也是如此

WTSQueryUserToken throws error 1008, even when running under LocalSystem

我遇到的问题与 WTSQueryUserToken always throws "An attempt was made to reference a token that does not exist" on Windows 7 in C# 中讨论的问题几乎相同,但与该问题的 OP 不同的是,我在 Windows 10 上使用 C++ 并在开始编码之前正确阅读文档我的解决方案。所以我的服务绝对是 运行 在 LocalSystem 帐户下。

这是我的 SvcInit() 函数的相关部分:

    HANDLE hToken;

    // Returns 1, just like in the linked question
    DWORD sessionId = WTSGetActiveConsoleSessionId();

    if (!WTSQueryUserToken(sessionId, &hToken)) {
        // LogError() takes the name of an error-causing function and calls
        // GetLastError() and FormatMessage() to get the system-defined error
        // message, then logs all of that to a file
        LogError("WTSQueryUserToken");
        return;
    }

文档还提到您的服务进程需要拥有 SE_TCB_NAME 权限。另一个问题引用的同一段落:

Obtains the primary access token of the logged-on user specified by the session ID. To call this function successfully, the calling application must be running within the context of the LocalSystem account and have the SE_TCB_NAME privilege.

但是通过阅读 https://docs.microsoft.com/en-us/windows/win32/services/localsystem-account,在我看来,任何 进程 运行 在 LocalSystem 帐户下似乎 自动有这个特权:

The LocalSystem account has the following privileges:

  • SE_ASSIGNPRIMARYTOKEN_NAME (disabled)
  • SE_AUDIT_NAME (enabled)
  • SE_BACKUP_NAME (disabled)
  • ...many others
  • SE_TCB_NAME (enabled)

那么我是否需要明确地将此权限添加到我的进程中?还有什么可能是我的问题的原因? MTIA! :-)

当前WTSQueryUserToken存在不完整的词组:

Among other errors, GetLastError can return one of the following errors.

但在旧的 msdn 中是列表或错误可以 return api:

所以 ERROR_NO_TOKEN 意味着可以 returned if

The token query is for a session in which no user is logged-on. This occurs, for example, when the session is in the idle state or SessionId is zero.

如果我们当然在系统启动时尝试获取用户令牌,但没有任何登录用户。用户只是未知的,因此不存在和用户令牌(比如包含用户 sid 的令牌 - 但用户和未知,sid 未知,令牌此时不能存在)。但是,如果需要,服务可以在某些(任何)用户登录系统时准确启动进程。这需要从 HandlerEx 通过 RegisterServiceCtrlHandlerEx

注册的回调

我们需要寻找 SERVICE_CONTROL_SESSIONCHANGE 事件。 用户会话中 运行 进程的简单代码可以像这样

DWORD WINAPI HandlerEx(
                       DWORD dwControl,
                       DWORD dwEventType,
                       PVOID lpEventData,
                       PVOID lpContext
                       )
{

    switch (dwControl)
    {
    case SERVICE_CONTROL_SESSIONCHANGE:

        if (dwEventType == WTS_SESSION_LOGON)
        {
            HANDLE hToken;
            if (WTSQueryUserToken(reinterpret_cast<PWTSSESSION_NOTIFICATION>(lpEventData)->dwSessionId, &hToken))
            {
                PVOID lpEnvironment;
                if (CreateEnvironmentBlock(&lpEnvironment, hToken, FALSE))
                {
                    STARTUPINFOW si = { sizeof(si) };
                    PROCESS_INFORMATION pi;
                    if (CreateProcessAsUserW(hToken, 
                        L"c:\windows\notepad.exe", 0, 0, 0, 0, 
                        CREATE_UNICODE_ENVIRONMENT, lpEnvironment, 0, &si, &pi))
                    {
                        CloseHandle(pi.hThread);
                        CloseHandle(pi.hProcess);
                    }
                    DestroyEnvironmentBlock(lpEnvironment);
                }
                CloseHandle(hToken);
            }
        }
        break;
    }

    // ...
}