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.

Zaid Amir
  • 4,727
  • 6
  • 52
  • 101

1 Answers1

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

}

Michael Haephrati
  • 3,660
  • 1
  • 33
  • 56
Fotios Basagiannis
  • 1,432
  • 13
  • 14
  • 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. – Clay Feb 18 '19 at 17:44
  • 2
    Actually - just found one: https://fleexlab.blogspot.com/2015/04/remote-desktop-surprise.html – Clay Feb 18 '19 at 17:52
  • This answer is a life-saver! But why do you still call `::WTSQuerySessionInformation` when `pSession[i]` has a member called `State` already? – ncalmbeblpaicr0011 Apr 22 '19 at 15:34
  • 1
    @ncalmbeblpaicr0011 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 loop – Fotios Basagiannis Apr 23 '19 at 03:22