2

I'm trying to execute a process from windows service that run under local administrator account. The machine is Windows 7 and its using the Remote Desktop/Terminal Service APIs.

The code failed on the WTSQueryUserToken with error code = 5.

First I tried to get the token from current thread, then call the SetPrivilege to enable both SE_DEBUG_NAME and SE_TCB_NAME privileges. then call the WTSQueryUserToken, but get error 5.

Just to clarify: When the service was under local system (localSystem), this code work perfect even without need to call the SetPrivilege. Now the problem is that I need to move the service to run under local administrator user!!!!

Any idea what I'm missing?


The code:

BOOL  T_Ex_RunProgram (DWORD sessionId, LPCWSTR targetPath)
{
#ifdef DEBUG
    if(g_pLog)
    {
         g_pLog->Format ("T_Ex_RunProgram: sessionId = %d, targetPath = \"%S\"\n", sessionId, targetPath);
    }
#endif
    HANDLE htoken;

    if(!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &htoken))
    {
        if (GetLastError() == ERROR_NO_TOKEN)
        {
            if (!ImpersonateSelf(SecurityImpersonation))
            {
                 DWORD dwErr = GetLastError();
#ifdef DEBUG
                    if(g_pLog)
                    {
                        g_pLog->Format ("ImpersonateSelf::RunProgram: dwErr = %d\n", dwErr);
                    }
#endif
                SetLastError(dwErr);
                return FALSE;
            }

            if(!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &htoken))
            {
                 DWORD dwErr = GetLastError();
#ifdef DEBUG
                    if(g_pLog)
                    {
                        g_pLog->Format ("OpenThreadToken::RunProgram: dwErr = %d\n", dwErr);
                    }
#endif
                SetLastError(dwErr);
                return FALSE;
            }
        }
        else{
                 DWORD dwErr = GetLastError();
#ifdef DEBUG
                    if(g_pLog)
                    {
                        g_pLog->Format ("OpenThreadToken::RunProgram: GetLastError return unexpected dwErr = %d\n", dwErr);
                    }
#endif
                SetLastError(dwErr);
                return FALSE;
            }
     }
#ifdef DEBUG
    if(g_pLog)
    {
        g_pLog->Format ("Before SetPrivilege(SE_DEBUG_NAME)::RunProgram\n");
    }
#endif
    // enable SeDebugPrivilege
    if(!SetPrivilege(htoken, SE_DEBUG_NAME, TRUE))
    {
        // close token handle
        CloseHandle(htoken);

        DWORD dwErr = GetLastError();
#ifdef DEBUG
        if(g_pLog)
        {
            g_pLog->Format ("SetPrivilege::RunProgram((SE_DEBUG_NAME): dwErr = %d\n", dwErr);
        }
#endif
        SetLastError(dwErr);
        return FALSE;
    }

#ifdef DEBUG
    if(g_pLog)
    {
        g_pLog->Format ("Before SetPrivilege(SE_TCB_NAME)::RunProgram\n");
    }
#endif
    // enable SeDebugPrivilege
    if(!SetPrivilege(htoken, SE_TCB_NAME, TRUE))
    {
        // close token handle
        CloseHandle(htoken);

        DWORD dwErr = GetLastError();
#ifdef DEBUG
        if(g_pLog)
        {
            g_pLog->Format ("SetPrivilege(SE_TCB_NAME)::RunProgram: dwErr = %d\n", dwErr);
        }
#endif
        SetLastError(dwErr);
        return FALSE;
    }

    BOOL b = WTSQueryUserToken (sessionId, &htoken);
    if (!b)
    {
        DWORD dwErr = GetLastError();
#ifdef DEBUG
    if(g_pLog)
    {
        g_pLog->Format ("T_Ex_RunProgram: WTSQueryUserToken failed. dwErr = %d\n", dwErr);
    }
#endif
        SetLastError(dwErr);
        return FALSE;
    }


    LPWSTR userName, userName1;
    DWORD userNameLength;
    b = WTSQuerySessionInformationW (WTS_CURRENT_SERVER_HANDLE, sessionId, WTSUserName, &userName, &userNameLength);
    if (b)
    {
        userName1 = _wcsdup (userName);
        WTSFreeMemory (userName);
    }
    else
    {
        DWORD dwErr = GetLastError();
    #ifdef DEBUG
        if(g_pLog)
        {
            g_pLog->Format ("T_Ex_RunProgram: WTSQuerySessionInformation failed: dwErr = %d\n", dwErr);
        }
    #endif
        SetLastError(dwErr);
        return FALSE;
    }
    b = RunProgramWithToken (htoken, userName1, targetPath,sessionId);
    DWORD dwreturnErr = GetLastError();


#ifdef DEBUG
    if(g_pLog)
    {
        g_pLog->Format ("Before SetPrivilege(SE_DEBUG_NAME,FALSE)::RunProgram\n");
    }
#endif
    // enable SeDebugPrivilege
    if(!SetPrivilege(htoken, SE_DEBUG_NAME, FALSE))
    {
        // close token handle
        CloseHandle(htoken);

        DWORD dwErr = GetLastError();
#ifdef DEBUG
        if(g_pLog)
        {
            g_pLog->Format ("SetPrivilege::RunProgram((SE_DEBUG_NAME,FALSE): dwErr = %d\n", dwErr);
        }
#endif
        SetLastError(dwErr);
    }

#ifdef DEBUG
    if(g_pLog)
    {
        g_pLog->Format ("Before SetPrivilege(SE_TCB_NAME,FALSE)::RunProgram\n");
    }
#endif
    // enable SeDebugPrivilege
    if(!SetPrivilege(htoken, SE_TCB_NAME, FALSE))
    {
        // close token handle
        CloseHandle(htoken);

        DWORD dwErr = GetLastError();
#ifdef DEBUG
        if(g_pLog)
        {
            g_pLog->Format ("SetPrivilege(SE_TCB_NAME,FALSE)::RunProgram: dwErr = %d\n", dwErr);
        }
#endif
        SetLastError(dwErr);
    }

    free (userName1);
    CloseHandle (htoken);
    if (!b)
        SetLastError (dwreturnErr);
    return b;
}
 BOOL SetPrivilege( HANDLE hToken,          // access token handle
    LPCTSTR lpszPrivilege,  // name of privilege to enable/disable
    BOOL bEnablePrivilege   // to enable or disable privilege
    ) 
{
    TOKEN_PRIVILEGES tp;
    LUID luid;

    if ( !LookupPrivilegeValue( 
            NULL,            // lookup privilege on local system
            lpszPrivilege,   // privilege to lookup 
            &luid ) )        // receives LUID of privilege
    {
        printf("LookupPrivilegeValue error: %u\n", GetLastError() ); 
        return FALSE; 
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    if (bEnablePrivilege)
        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    else
        tp.Privileges[0].Attributes = 0;

    // Enable the privilege or disable all privileges.

    if ( !AdjustTokenPrivileges(
           hToken, 
           FALSE, 
           &tp, 
           sizeof(TOKEN_PRIVILEGES), 
           (PTOKEN_PRIVILEGES) NULL, 
           (PDWORD) NULL) )
    { 
          printf("AdjustTokenPrivileges error: %u\n", GetLastError() ); 
          return FALSE; 
    } 

    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)

    {
          printf("The token does not have the specified privilege. \n");
          return FALSE;
    } 

    return TRUE;
}
Joseph
  • 1,716
  • 3
  • 24
  • 42
  • 2
    Normally, you only need one of `SE_DEBUG_NAME` or `SE_TCB_NAME`. But you may need to enable `SE_ASSIGNPRIMARYTOKEN_NAME`. That's the one that lets you replace the primary process token, and its the third in the holy trinity of "do anything you want with a process." See [Privilege Constants](http://msdn.microsoft.com/en-us/library/windows/desktop/bb530716%28v=vs.85%29.aspx) on MSDN. – jww Dec 26 '14 at 02:51
  • There are some Logon Rights that the target user account may need. The event log (`eventvwr`) should have more information for you with more details. Also, for starters, see the knowledge base article [How to set logon user rights by using the NTRights utility](http://support.microsoft.com/kb/315276). It should give you a toe-hold to find more information. – jww Dec 26 '14 at 02:55
  • Also, what's the version of Windows you are using. Is this Server 2008? Terminal Services behaves slightly differently in both XP and 2003 (and likely others), and it might help other in assisting you. – jww Dec 26 '14 at 03:06
  • I'm trying on win7, when I tried: "SE_ASSIGNPRIMARYTOKEN_NAME" I get error 6. – Joseph Dec 26 '14 at 03:29
  • That's [`ERROR_INVALID_HANDLE`](http://msdn.microsoft.com/en-us/library/windows/desktop/ms681382%28v=vs.85%29.aspx). Where is the failure occurring? Is it in `SetPrivilege`? – jww Dec 26 '14 at 03:35
  • @jww yes the SetPrivilege, seems like it failed in AdjustTokenPrivileges and return The token does not have the specified privilege. – Joseph Dec 26 '14 at 03:44
  • 1
    You said *"local administrator account"*. Is that account enabled (I thought it was disabled by default). Can you try with `LocalSystem` per the MSDN blog in an attempt to isolate the issue? Also [another blog](http://www.remkoweijnen.nl/blog/2007/10/20/how-to-launch-a-process-in-a-terminal-session/) states it needs `SE_TCB_NAME`. That seems a little heavy handed to me, but use it until you can test reducing privileges. – jww Dec 26 '14 at 03:58
  • Finally, Vista and above has Integrity Labels (you see it in the UAC prompt). See [What is the Windows Integrity Mechanism?](http://msdn.microsoft.com/en-us/library/bb625957.aspx). You might need to allow the token to use its administrator privileges *if* you are in fact using the local administrator account. – jww Dec 26 '14 at 04:05
  • it is a local administrator user. what mean allow user to use its administrator if i give it all access? – Joseph Dec 26 '14 at 04:09
  • See, for example, [How the Integrity Mechanism Is Implemented in Windows Vista](http://msdn.microsoft.com/en-us/library/bb625962.aspx). Unfortunately, I have never worked with the Terminal Service APIs for Vista and above. Only XP SP3 and below. – jww Dec 26 '14 at 04:21
  • I use the `LocalSystem` account in my service, and `WTSQueryUserToken()` works fine after enabling just `SE_TCB_NAME`. I also use `DuplicateTokenEx(SecurityIdentification, TokenPrimary)` before calling `CreateProcessAsUser()`. Works fine. – Remy Lebeau Dec 26 '14 at 18:34
  • @Remy Lebeau, this code was working as is without the SetPrivilege need, when the service was under local system. I know it work when it under localSystem. Now the problem is that I need to move the service to run under local administrator user!!!! – Joseph Dec 26 '14 at 19:47
  • 1
    @Joseph: `WTSQueryUserToken()` only works in the `LocalSystem` account. The [documentation](http://msdn.microsoft.com/en-us/library/aa383840.aspx) says as much: "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." Another way to get a user token for a session is to extract the token from a process that is running within the session, such as `explorer.exe`. – Remy Lebeau Dec 26 '14 at 20:28
  • I tried to obtain the winlogon pid for the sessionID i need, get the target token using OpenProcessToken, then obtain the token of the current thread as before to set SE_TCB_NAME privilege, tried to duplicate the winlogon token using DuplicateTokenEx but get error 6 again :(. Why the hell not working! – Joseph Dec 26 '14 at 21:51
  • Why not just run the service as local system? That's the correct solution in this situation. Running a service as Administrator is a bad idea in any case, because it means the service manager has to store the Administrator password in a reversible format. – Harry Johnston Dec 27 '14 at 02:35
  • because some requirements, but seems like i'll need to roll back to run it as localsystem, and split the part who need to run as localuser into another process, then communicate with each by using PIPE! – Joseph Dec 27 '14 at 20:09

1 Answers1

1

The code failed on the WTSQueryUserToken with error code = 5... Any idea what I'm missing?

I think this MSDN article might be useful to you: Launching an interactive process from Windows Service in Windows Vista and later. According to the article, you should be calling the following when using WTSQueryUserToken:

WTSQueryUserToken (WTSGetActiveConsoleSessionId (), &hToken);

Then use the token retrieved in a call to CreateProcessAsUser.


In your call to OpenThreadToken with TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, you might temporarily increase this to something like TOKEN_ALL_ACCESS until you have the wrinkles worked out (that's if you still need to make the call). See Access Rights for Access-Token Objects on MSDN.

jww
  • 97,681
  • 90
  • 411
  • 885
  • I tried this already, and tried now again, using WTSGetActiveConsoleSessionId () and TOKEN_ALL_ACCESS, still WTSQueryUserToken return error 5! – Joseph Dec 26 '14 at 03:35
  • 1
    You can use any session that has a user logged in, it does not have to be the "active" session (the one connected to the physical keyboard/mouse/monitor). For instance, the "active" session might be showing the `WinLogon` desktop when no user is logged in locally. But you can get the user token for a user who is logged in remotely. – Remy Lebeau Dec 26 '14 at 18:27
  • 1
    `WTSGetActiveConsoleSessionId()` gets the ID of the session that is active on the **local console**. This is fine for Fast User Switching, where users switch back and forth on the physical console, but this does not work for Terminal Services, where users are not attached to the console. So if you allow remote logins to the machine, you cannot rely on `WTSGetActiveConsoleSessionId()` by itself, you may have to use `WTSEnumerateSessions()` to find an active session with a user logged in. – Remy Lebeau Dec 26 '14 at 18:37
  • Guys, the sessionID id is NOT the issue, this code was working without the SetPrivilege, when the service was under local system. Now the problem is that I need to move the service to run under local administrator user. – Joseph Dec 26 '14 at 19:44