WTSGetActiveConsoleSessionId returning system session
Asked Answered
G

1

7

I have this problem that happened once and I still don't know how to fix it. I have a windows service, when the service runs, it first need to impersonate the logged in user (active user) to load some paths and settings that are saved in the user's application data folder. The code that I'm using works perfectly every time a new user logs on to windows except once where the service got the impersonation wrong and impersonated the system session instead of the actie one. As I said this only happened once, but I can't really tell why.

This is how am checking whats the active session and how the impersonation is done:

first when the service detects a logon event it query's the active session ID by calling

WTSGetActiveConsoleSessionId();

then It checks if the session is active(connected) by calling WTSQuerySessionInformation as follows:

WTS_CONNECTSTATE_CLASS wts_connect_state = WTSDisconnected;
WTS_CONNECTSTATE_CLASS* ptr_wts_connect_state = NULL;

DWORD bytes_returned = 0;
if (::WTSQuerySessionInformation(
    WTS_CURRENT_SERVER_HANDLE,
    session_id,
    WTSConnectState,
    reinterpret_cast<LPTSTR*>(&ptr_wts_connect_state),
    &bytes_returned)) 
{
        ASSERT(bytes_returned == sizeof(*ptr_wts_connect_state));
        wts_connect_state = *ptr_wts_connect_state;
        ::WTSFreeMemory(ptr_wts_connect_state);
        return (WTSActive == wts_connect_state); 
}

where session_id is the session ID returned by WTSGetActiveConsoleSessionId().

Then I query for the user token using WTSQueryUserToken

Then if that succeeds the service calls GetTokenInformationas follows:

DWORD neededSize = 0;
    HANDLE *realToken = new HANDLE;
    if(GetTokenInformation(hImpersonationToken, (::TOKEN_INFORMATION_CLASS) TokenLinkedToken, realToken, sizeof(HANDLE), &neededSize))
    {
        CloseHandle(hImpersonationToken);
        hImpersonationToken = *realToken;
    }

where hImpersonationToken is the token retrieved from GetTokenInformation

And if all the above succeeds it then calls

DuplicateTokenEx( hImpersonationToken,
                                0,
                                NULL,
                                SecurityImpersonation,
                                TokenPrimary,
                                phUserToken );

        CloseHandle( hImpersonationToken );

and if it succeeds then it impersonates with the retrieved token

ImpersonateLoggedOnUser(phUserToken);

My service writes to a log file and according to the log all the previous calls where successful yet after the impersonation the service loaded the system profile instead of the user.

Now this issue happened once when I restarted my machine, yet I wasn't even reproduce it again and I've been trying for weeks.

I'm not sure how its possible for the system profile session to be an active session. I just want to know if I'm doing something wrong there, not sure if I'm using the wrong info class when I'm querying the session info or something.

Also want to know if its possible to determine if the queried session is actually the system session before impersonating with the returned token just so one can retry the impersonation again?

As I said, all mentioned calls have their return objects and codes checked before moving to the next step so their weren't any errors from the calls as it shouldn't continue with the impersonation, yet it did :(

Would appreciate any help possible... thanks.

Gaal answered 29/11, 2011 at 10:13 Comment(0)
D
10

WTSGetActiveConsoleSessionId() may actually return session 0 when run from a service and depending on the Windows version it runs on.

The way to do what you want is enumerate all sessions find the one that is connected (session 0 is disconnected) and then impersonate the user of that session. Code below works well on my box.

//Function to run a process as active user from windows service
void ImpersonateActiveUserAndRun(WCHAR* path, WCHAR* args)
{
    DWORD session_id = -1;
    DWORD session_count = 0;

    WTS_SESSION_INFOW *pSession = NULL;


    if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSession, &session_count))
    {
        //log success
    }
    else
    {
        //log error
        return;
    }

    for (size_t i = 0; i < session_count; i++)
    {
        session_id = pSession[i].SessionId;

        WTS_CONNECTSTATE_CLASS wts_connect_state = WTSDisconnected;
        WTS_CONNECTSTATE_CLASS* ptr_wts_connect_state = NULL;

        DWORD bytes_returned = 0;
        if (::WTSQuerySessionInformation(
            WTS_CURRENT_SERVER_HANDLE,
            session_id,
            WTSConnectState,
            reinterpret_cast<LPTSTR*>(&ptr_wts_connect_state),
            &bytes_returned))
        {
            wts_connect_state = *ptr_wts_connect_state;
            ::WTSFreeMemory(ptr_wts_connect_state);
            if (wts_connect_state != WTSActive) continue;
        }
        else
        {
            //log error
            continue;
        }

        HANDLE hImpersonationToken;

        if (!WTSQueryUserToken(session_id, &hImpersonationToken))
        {
            //log error
            continue;
        }


        //Get real token from impersonation token
        DWORD neededSize1 = 0;
        HANDLE *realToken = new HANDLE;
        if (GetTokenInformation(hImpersonationToken, (::TOKEN_INFORMATION_CLASS) TokenLinkedToken, realToken, sizeof(HANDLE), &neededSize1))
        {
            CloseHandle(hImpersonationToken);
            hImpersonationToken = *realToken;
        }
        else
        {
            //log error
            continue;
        }


        HANDLE hUserToken;

        if (!DuplicateTokenEx(hImpersonationToken,
            //0,
            //MAXIMUM_ALLOWED,
            TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS | MAXIMUM_ALLOWED,
            NULL,
            SecurityImpersonation,
            TokenPrimary,
            &hUserToken))
        {
            //log error
            continue;
        }

        // Get user name of this process
        //LPTSTR pUserName = NULL;
        WCHAR* pUserName;
        DWORD user_name_len = 0;

        if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session_id, WTSUserName, &pUserName, &user_name_len))
        {
            //log username contained in pUserName WCHAR string
        }

        //Free memory                         
        if (pUserName) WTSFreeMemory(pUserName);

        ImpersonateLoggedOnUser(hUserToken);

        STARTUPINFOW StartupInfo;
        GetStartupInfoW(&StartupInfo);
        StartupInfo.cb = sizeof(STARTUPINFOW);
        //StartupInfo.lpDesktop = "winsta0\\default";

        PROCESS_INFORMATION processInfo;

        SECURITY_ATTRIBUTES Security1;
        Security1.nLength = sizeof SECURITY_ATTRIBUTES;

        SECURITY_ATTRIBUTES Security2;
        Security2.nLength = sizeof SECURITY_ATTRIBUTES;

        void* lpEnvironment = NULL;

        // Get all necessary environment variables of logged in user
        // to pass them to the new process
        BOOL resultEnv = CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE);
        if (!resultEnv)
        {
            //log error
            continue;
        }

        WCHAR PP[1024]; //path and parameters
        ZeroMemory(PP, 1024 * sizeof WCHAR);
        wcscpy_s(PP, path);
        wcscat_s(PP, L" ");
        wcscat_s(PP, args);

        // Start the process on behalf of the current user 
        BOOL result = CreateProcessAsUserW(hUserToken, 
            NULL,
            PP,
            //&Security1,
            //&Security2,
            NULL,
            NULL,
            FALSE, 
            NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
            //lpEnvironment,
            NULL,
            //"C:\\ProgramData\\some_dir",
            NULL,
            &StartupInfo,
            &processInfo);

        if (!result)
        {
            //log error
        }
        else
        {
            //log success
        }

        DestroyEnvironmentBlock(lpEnvironment);

        CloseHandle(hImpersonationToken);
        CloseHandle(hUserToken);
        CloseHandle(realToken);

        RevertToSelf();
    }

    WTSFreeMemory(pSession);
}
Dangelo answered 9/2, 2016 at 16:56 Comment(4)
Do you have any knowledge of or documentation that specifies under what circumstances you'd get a 0? Doc says you might get 0xFFFFFFFF if there's no session attached to the console.Ontina
Actually - just found one: fleexlab.blogspot.com/2015/04/remote-desktop-surprise.htmlOntina
This answer is a life-saver! But why do you still call ::WTSQuerySessionInformation when pSession[i] has a member called State already?Allerus
@Allerus It probably won't make a diff in most cases but you want the session state info to be as fresh as possible. pSession[] just holds a bunch of WTS_SESSION_INFO structs generated during the enumeration loopDangelo

© 2022 - 2024 — McMap. All rights reserved.