如何正确地模拟用户的服务?

kam*_*mpi 4 c++ windows service impersonation

我正在使用一项服务,该服务应模拟已登录的用户.

我的代码到目前为止,基本的错误处理:

 // 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;
}
Run Code Online (Sandbox Code Playgroud)

根据MSDN:

当用户以交互方式登录时,系统会自动加载用户的配置文件.如果服务或应用程序模拟用户,则系统不会加载用户的配置文件.因此,服务或应用程序应使用LoadUserProfile加载用户的配置文件.

调用LoadUserProfile的服务和应用程序应检查用户是否具有漫游配置文件.如果用户具有漫游配置文件,请将其路径指定为PROFILEINFO的lpProfilePath成员.要检索用户的漫游配置文件路径,可以调用NetUserGetInfo函数,指定信息级别3或4.

成功返回后,PROFILEINFO的hProfile成员是一个向用户配置单元的根目录打开的注册表键句柄.它已以完全访问权限(KEY_ALL_ACCESS)打开.如果模拟用户的服务需要读取或写入用户的注册表文件,请使用此句柄而不是HKEY_CURRENT_USER.不要关闭hProfile句柄.而是将其传递给UnloadUserProfile函数.

如果我现在使用我的代码,那么它的工作原理.但是它有点奇怪,因为首先我必须冒充登录用户,然后结束模拟,加载用户配置文件.如果我没有结束模拟,那么LoadUserProfile将失败,错误5(访问被拒绝).在LoadUserProfile成功后,我应该再次冒充用户?

所以我的问题是,这意味着这样做,或者我做错了什么?另一个问题是,如果LoadUserProfile成功,我可以使用hProfile作为登录用户注册表的句柄.问题是怎么样的?因为要使用RegOpenKeyEy和RegSetValueEx,我需要传递HKEY,而不是HANDLE.那么我该如何使用这个Handle?

谢谢!

Rem*_*eau 7

您不需要调用,ImpersonateLoggedOnUser()因为您将用户的令牌传递给LoadUserProfile().打电话ImpersonateLoggedOnUser(),如果你需要调用的API,不要让你的用户令牌传递给他们而已.

如果您阅读其余LoadUserProfile()文档,它会说:

调用进程必须具有SE_RESTORE_NAME和SE_BACKUP_NAME权限.

通过冒充您尝试加载配置文件的用户,您可能会失去这些权限.所以不要冒充用户.

更新:尝试这样的事情:

// 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 );
Run Code Online (Sandbox Code Playgroud)

至于Registry,hProfile句柄是HKEY为用户的HKEY_CURRENT_USER树打开的.的simpy型铸造它HANDLEHKEY传递,当它到注册表API函数.它已经打开,因此您无需再次调用RegOpenKeyEx()再打开相同的密钥,但可以在创建/打开子密钥或在根密钥中读取/写入值时将其用作根密钥.

  • @ c00000fd:应该作为一个新问题发布.但是如果你仔细阅读我之前的评论评论,我说:"我会忽略`GetProfileType()`并且无条件地调用`NetGetUserInfo()`...用户信息要么具有漫游配置文件,要么不*".阅读[`NetGetUserInfo`文档](https://msdn.microsoft.com/en-us/library/windows/desktop/aa370654.aspx). (3认同)
  • 阅读[`LoadUserProfile()`文档](http://msdn.microsoft.com/en-us/library/windows/desktop/bb762281.aspx):"要检索用户的漫游配置文件路径,可以调用[ `NetUserGetInfo()`](http://msdn.microsoft.com/en-us/library/windows/desktop/aa370654.aspx)函数,指定信息级别3或4." 你不需要模仿.另请阅读[`PROFILEINFO`文档]底部有关漫游配置文件的注释(http://msdn.microsoft.com/en-us/library/windows/desktop/bb773378.aspx). (2认同)