How to impersonate a user from a service correctly?

11,229

You don't need to call ImpersonateLoggedOnUser() since you are passing the user's token to LoadUserProfile(). Call ImpersonateLoggedOnUser() only if you need to call APIs that do not allow you to pass a user token to them.

If you read the rest of the LoadUserProfile() documentation, it says:

The calling process must have the SE_RESTORE_NAME and SE_BACKUP_NAME privileges.

By impersonating the user you are trying to load a profile for, you are likely losing those privileges. So don't impersonate the user.

Update: Try something like this:

// get the active console session ID of the logged on user
DWORD dwSessionID = WTSGetActiveConsoleSessionId();
if ( dwSessionID == 0xFFFFFFFF )
{
    ShowErrorText( "WTSGetActiveConsoleSessionId failed.", GetLastError( ), true );
    return;
}

if ( !WTSQueryUserToken( dwSessionID, &hToken ) )
{
    ShowErrorText( "WTSQueryUserToken failed.", GetLastError( ), true );
    return;
}

// duplicate the token
HANDLE hDuplicated = NULL;
if ( !DuplicateToken( hToken, SecurityImpersonation, &hDuplicated ) )
{
    ShowErrorText( "DuplicateToken failed.", GetLastError( ), true );
    CloseHandle( hToken );
    return;
}

// retrieve the DC name 
if ( !GetPrimaryDC( DC ) )
{
    ShowErrorText( "GetPrimaryDC failed.", 0, true );
    CloseHandle( hDuplicated );
    CloseHandle( hToken );
    return;
}

PROFILEINFO lpProfileInfo;
ZeroMemory( &lpProfileInfo, sizeof( PROFILEINFO ) );
lpProfileInfo.dwSize = sizeof( PROFILEINFO );
lpProfileInfo.lpUserName = CurrentUser;

// get type of profile. roaming, mandatory or temporary
USER_INFO_4 *UserInfo = NULL;
int ret = GetTypeOfProfile();
if ( ret == 2 )
{
    // if roaming profile get the path of it
    if ( NetUserGetInfo( DC, CurrentUser, 4, (LPBYTE*)&UserInfo) != NERR_Success )
    {
        ShowErrorText( "NetUserGetInfo failed.", 0, true );
        CloseHandle( hDuplicated );
        CloseHandle( hToken );
        return;
    }

    lpProfileInfo.lpProfilePath = UserInfo->usri3_profile;
}

if ( !LoadUserProfile( hDuplicated, &lpProfileInfo ) )
{
    ShowErrorText( "LoadUserProfile failed.", GetLastError(), true );
    if ( UserInfo )
        NetApiBufferFree(UserInfo);
    CloseHandle( hDuplicated );
    CloseHandle( hToken );
    return;
}

if ( UserInfo )
    NetApiBufferFree(UserInfo);

ShowErrorText( "LoadUserProfile succeeded.", 0, true );

//do some stuff

if ( !UnloadUserProfile( hDuplicated, lpProfileInfo.hProfile ) )
{
    ShowErrorText( "UnloadUserProfile failed.", GetLastError( ), true );
}
else
{
    ShowErrorText( "UnloadUserProfile succeeded.", 0, true );
}

CloseHandle( hDuplicated );
CloseHandle( hToken );

As for the Registry, the hProfile handle is the opened HKEY for the user's HKEY_CURRENT_USER tree. Simpy type-cast it from HANDLE to HKEY when passing it to Registry API functions. It is already opened, so you do not need to call RegOpenKeyEx() to open that same key again, but you can use it as the root key when creating/opening subkeys, or reading/writing values in the root key.

Share:
11,229
kampi
Author by

kampi

-

Updated on July 25, 2022

Comments

  • kampi
    kampi almost 2 years

    I'm working a service, which should impersonate the logged on user.

    My code so far, with basic error handling:

     // get the active console session ID of the logged on user
    if ( !WTSQueryUserToken( WTSGetActiveConsoleSessionId(), &hToken ) )
    {
        ShowErrorText( "WTSQueryUserToken failed.", GetLastError( ), true );
        return;
    }
    
    HANDLE hDuplicated;
    
    // duplicate the token
    if ( !DuplicateToken( hToken, SecurityImpersonation, &hDuplicated ) )
    {
        ShowErrorText( "DuplicateToken failed.", GetLastError( ), true );
    }
    else 
    {
        ShowErrorText( "DuplicateToken succeeded.", 0, true );
    }
    
    // impersonate the logged on user
    if ( !ImpersonateLoggedOnUser( hToken ) )
    {
        ShowErrorText( "ImpersonateLoggedOnUser failed.", GetLastError(), true );
        return;
    }
    
    // retrieve the DC name 
    if ( !GetPrimaryDC( DC ) )
    {
        ShowErrorText( "GetPrimaryDC failed.", 0, true );
    }
    PROFILEINFO lpProfileInfo;
    
    ZeroMemory( &lpProfileInfo, sizeof( PROFILEINFO ) );
    lpProfileInfo.dwSize = sizeof( PROFILEINFO );
    lpProfileInfo.lpUserName = CurrentUser;
    
    // get type of profile. roaming, mandatory or temporary
    int ret = GetTypeOfProfile();
    if ( ret == 2 )
    {
        // if roaming profile get the path of it
        if ( !GetRoamingProfilePath( DC, CurrentUser, RoamingProfilePath ) )
        {
            ShowErrorText( "Failed to retrieve roaming profile path.", GetLastError(), true );
        }
    }
    if ( RevertToSelf( ) )
    {
        ShowErrorText( "Impersonation ended successfully.", 0, true );
    }
    
     if ( !LoadUserProfile( hDuplicated, &lpProfileInfo ) )
    {
        ShowErrorText( "LoadUserProfile failed.", GetLastError(), true );
    }
    else
    {
        ShowErrorText( "LoadUserProfile succeeded.", 0, true );
    }
    
       //do some stuff
    
    
      if ( !UnloadUserProfile( hDuplicated, lpProfileInfo.hProfile ) )
    {
        ShowErrorText( "UnloadUserProfile failed.", GetLastError( ), true );
    }
    else
    {
        ShowErrorText( "UnloadUserProfile succeeded.", 0, true );
    }
    
     if ( !ImpersonateLoggedOnUser( hToken ) )
    {
        ShowErrorText( "ImpersonateLoggedOnUser failed.", GetLastError( ), true );
        return;
    }
    

    According to MSDN:

    When a user logs on interactively, the system automatically loads the user's profile. If a service or an application impersonates a user, the system does not load the user's profile. Therefore, the service or application should load the user's profile with LoadUserProfile.

    Services and applications that call LoadUserProfile should check to see if the user has a roaming profile. If the user has a roaming profile, specify its path as the lpProfilePath member of PROFILEINFO. To retrieve the user's roaming profile path, you can call the NetUserGetInfo function, specifying information level 3 or 4.

    Upon successful return, the hProfile member of PROFILEINFO is a registry key handle opened to the root of the user's hive. It has been opened with full access (KEY_ALL_ACCESS). If a service that is impersonating a user needs to read or write to the user's registry file, use this handle instead of HKEY_CURRENT_USER. Do not close the hProfile handle. Instead, pass it to the UnloadUserProfile function.

    If i use my code as it is now, then it works. However is it a little strange, because first i have to impersonate the logged on user, and then end the impersonation, to Load the users profile. If i don't end the impersonation then LoadUserProfile will fail with error 5 ( Access denied ). And after LoadUserProfile succeeded i should impersonate the user again?

    So my question is, this meant to be done this way, or i am doing something wrong? Another question is, that if LoadUserProfile succeeded i could use hProfile as a Handle to the logged on users registry. Question is how? Because to use RegOpenKeyEy and RegSetValueEx i need to pass a HKEY, not a HANDLE. So how can i use this Handle?

    Thank!

  • kampi
    kampi over 10 years
    I'm a little confused. I called ImpersonateLoggedonUser at the first time, because i need to get, if the user has a roaming profile. If yes then i need it's path, and of course the username. I need these thing to call LoadUserProfile. If i'm not using ImpersonateLoggedOnUser then how could i get them?
  • Remy Lebeau
    Remy Lebeau over 10 years
    Read the LoadUserProfile() documentation: "To retrieve the user's roaming profile path, you can call the NetUserGetInfo() function, specifying information level 3 or 4." You don't need impersonation for that. Also read the comments about Roaming Profiles at the bottom of the PROFILEINFO documentation.
  • kampi
    kampi over 10 years
    Thank you very much for your help! It does work, although i have on last problem. Untul now i used GetUsername, to retrieve the logged on users name. Because i was using ImpersonateLoggedOnUser it gave me back the correct username, which i have to pass to PROFILINFO. But how do i get it now?
  • Remy Lebeau
    Remy Lebeau over 10 years
    GetUserName() returns the username associated with the calling thread. That is why you have to use impersonation when calling GetUserName() from a service. Since you already have the Session ID that the user token comes from, you can use WTSQuerySessionInformation() to query the session's logged on username. Set the WTSInfoClass parameter to WTSUserName. You can also use WTSDomainName to query the session's logged on domain.
  • kampi
    kampi over 10 years
    Okay, thank you. It is working. However is discovered another Problem. Hopefully the last one. I use GetProfileType() to get the type of the profile. Which was until now working fine, because I used impersonation. Unfortunately, now it returns, that the user has local profile, not roaming. This is also because, it will get the profile type of the calling thread. Do you know some solution, how could I get the logged on users profile type? Thanks in advance!
  • Remy Lebeau
    Remy Lebeau over 10 years
    I would ignore GetProfileType() and just call NetGetUserInfo() unconditionally. Especially since the GetProfileType() documentation says: "If the user profile is not already loaded, the function fails", and you have not loaded the user's profile yet. The user info will either have a roaming profile or it will not.
  • kampi
    kampi over 10 years
    Thank you very much for your help. With you assistance I was able to correct my code.
  • c00000fd
    c00000fd over 7 years
    @RemyLebeau: Hey, I know you've asked it a while back. I'm dealing with a similar issue and I was wondering how do you determine the type of profile in GetTypeOfProfile function? Namely the roaming user profile. Thanks.
  • Remy Lebeau
    Remy Lebeau over 7 years
    @c00000fd: that should have been posted as a new question. But if you re-read my previous comment comment more carefully, I said: "I would ignore GetProfileType() and just call NetGetUserInfo() unconditionally... The user info will either have a roaming profile or it will not". Read the NetGetUserInfo documentation.