0

I have pretty much the same problem as discussed in WTSQueryUserToken always throws "An attempt was made to reference a token that does not exist" on Windows 7 in C#, but unlike the OP of that question, I'm using C++ on Windows 10 and read the docs properly before starting to code my solution. So my service is most-definitely running under the LocalSystem account.

Here's the relevant part of my SvcInit() function:

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

The docs also mention a need for your service process to have the SE_TCB_NAME privilege. The same paragraph that the other question references:

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.

But by reading https://learn.microsoft.com/en-us/windows/win32/services/localsystem-account, it seems to me as though any process running under the LocalSystem account would automatically have this privilege:

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)

So do I need to explicitly add this privilege to my process or not? And what else may be the cause of my issue? MTIA! :-)

Kenny83
  • 769
  • 12
  • 38
  • `ERROR_NO_TOKEN` - *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.* – RbMm Nov 05 '20 at 11:46
  • @RbMm Where are you getting that description from? According to https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--1000-1299-, the description of error 1008 is "An attempt was made to reference a token that does not exist", which is also what I get from `FormatMessage()` and what the linked question states. But that description doesn't really explain *why* the token doesn't exist, or where I'm supposedly referencing it wrongly. I'll add a code snippet to the question to hopefully provide some more insight. – Kenny83 Nov 05 '20 at 11:55
  • https://i.imgur.com/UcUOPpv.png - better show your relevant code – RbMm Nov 05 '20 at 11:58
  • or look - https://stackoverflow.com/questions/33069138/how-to-get-token-of-logged-in-user-from-within-windows-service-with-c#comment53997778_33069138 – RbMm Nov 05 '20 at 12:01
  • @RbMm Ahh nice! Would you mind sharing the source of that image (if it's a website)? It seems to have much better error descriptions than the standard MS docs page that I linked to in my previous comment. – Kenny83 Nov 05 '20 at 12:04
  • this is from very old (2008) offline msdn. in new you can view - *Among other errors, GetLastError can return one of the following errors.* - and than nothing.. part of old content is lost – RbMm Nov 05 '20 at 12:07
  • @RbMm LOL what a joke...but hey, we're talking about MicroS**t here, so it really doesn't surprise me. – Kenny83 Nov 05 '20 at 12:16
  • when you call `WTSQueryUserToken` ? based on what event ? – RbMm Nov 05 '20 at 12:27
  • @RbMm As I said just above the code snippet, it is taken from the `SvcInit()` function. This is where one generally initialises the service, sets up a stop event and performs the main functionality of the service until the stop event is triggered. So in answer to your second question: it is triggered when my system boots. – Kenny83 Nov 05 '20 at 15:36
  • *when my system boots* - this and your error of course. at this time any user not logged in, as result no user token. no user -> no token. error very logical at this place. you need rewrite logic. – RbMm Nov 05 '20 at 15:40
  • @RbMm OK that makes sense and it had occurred to me before, but could you please explain why I get a seemingly-valid session ID (1) from `WTSGetActiveConsoleSessionId()`? Would it help to use `WTSEnumerateSessions()` (I remember reading about that somewhere on SO when I was looking for a solution to this problem earlier)? – Kenny83 Nov 05 '20 at 15:50
  • 1
    the *csrss.exe* in session 1, *winlogon.exe* already started. so terminal 1 session already exist. but yet no any logged in users. and simply question - for what you need user token at this time ? if exist several users in system - which concrete user token you think must got ? only this question show error in design. the key question from which you need begin - for what you need some user token ? – RbMm Nov 05 '20 at 15:55
  • *This occurs, for example, when the session is in the idle state* - this is exactly your case – RbMm Nov 05 '20 at 15:56
  • Ahh OK, that makes perfect sense, thanks so much! The reason I need a user token is to run another process interactively, and this process needs to run from the moment the PC is turned on until the moment it's turned off...hence my thinking that I need a service. But I'm starting to realise from your comments and many others on SO that I should probably just run my interactive process as a scheduled task. – Kenny83 Nov 05 '20 at 16:07
  • 1
    you anyway need wait until some user logged in. in service, inside *HandlerEx* routine you got session notifications - `SERVICE_CONTROL_SESSIONCHANGE` - look say for `WTS_SESSION_LOGON` and in `PWTSSESSION_NOTIFICATION` you will be have `dwSessionId`. – RbMm Nov 05 '20 at 16:12
  • OK thanks. If you write up your comments as a proper answer I'll gladly accept it, since you very-much deserve the rep ;-) – Kenny83 Nov 05 '20 at 16:20

1 Answers1

1

in current WTSQueryUserToken exist incomplete phrase:

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

but in old msdn was list or errors which can return api:

enter image description here

so ERROR_NO_TOKEN mean can be 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.

if we try get user token at system startup time of course yet no any logged in user. user simply is unknown and as result not exist and user token (say token containing user sid - but user and unknown, sid is unknown, token can not exist at this time). however service if want can start process exactly when some (any)user logon to system. this need do from HandlerEx callback which registered via RegisterServiceCtrlHandlerEx

we need look for SERVICE_CONTROL_SESSIONCHANGE event. simply code for run process in user session can be like this

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

    // ...
}
RbMm
  • 31,280
  • 3
  • 35
  • 56
  • Awesome write up! Thanks again :D – Kenny83 Nov 05 '20 at 17:25
  • Hi again, I've updated my code to handle SERVICE_CONTROL_SESSIONCHANGE but it seems that code is never executed...any ideas why? My code is almost identical to [The Complete Service Sample](https://docs.microsoft.com/en-us/windows/win32/services/the-complete-service-sample) on MSDN, but I've changed `gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP` to `gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SESSIONCHANGE` in `ReportSvcStatus()`, updated `SvcCtrlHandler()` to match your code and added my own code that shouldn't effect anything to `SvcInit()`. What gives?! – Kenny83 Nov 06 '20 at 11:20
  • @Kenny83 - this code call `RegisterServiceCtrlHandler` for `SvcCtrlHandler` but you need use RegisterServiceCtrlHandler**Ex** use - read this [*This parameter can also be one of the following extended control codes. Note that these control codes are not supported by the Handler function.*](https://learn.microsoft.com/en-us/windows/win32/api/winsvc/nc-winsvc-lphandler_function_ex) – RbMm Nov 06 '20 at 11:40
  • 1
    @Kenny83 only Handler**Ex** received `SERVICE_CONTROL_SESSIONCHANGE` notification. – RbMm Nov 06 '20 at 11:41
  • Sorry I forgot to mention that I've changed that too (it was pretty much the first change I made to MSFT's code). – Kenny83 Nov 06 '20 at 12:01
  • @Kenny83 - i not have your code, not view it. as result can not say why. but can say code which i paste here - not simply code, but tested in service and it worked - receiver notification when user log in locally or via rdp – RbMm Nov 06 '20 at 12:27
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/224196/discussion-between-kenny83-and-rbmm). – Kenny83 Nov 06 '20 at 12:29